1. 程式人生 > >linux一些問題解決方法的收集

linux一些問題解決方法的收集

在Unix作業系統的動態連結庫的世界中,LD_PRELOAD就是這樣一個環境變數,它可以影響程式的執行時的連結(Runtime linker),它允許你定義在程式執行前優先載入的動態連結庫。

這個功能主要就是用來有選擇性的載入Unix作業系統不同動態連結庫中的相同函式。通過這個環境變數,我們可以在主程式和其動態連結庫的中間載入別的動態連結庫,甚至覆蓋正常的函式庫。一方面,我們可以以此功能來使用自己的或是更好的函式(無需別人的原始碼),而另一方面,我們也可以以向別人的程式注入惡意程式,從而達到那不可告人的罪惡的目的。

我們知道,Linux的用的都是glibc,有一個叫libc.so.6的檔案,這是幾乎所有Linux下命令的動態連結中,其中有標準C的各種函式。Unix作業系統中對於GCC而言,預設情況下,所編譯的程式中對標準C函式的連結,都是通過動態連結方式來連結libc.so.6這個函式庫的。

OK。還是讓我用一個例子來看一下用LD_PRELOAD來hack別人的程式。

Unix作業系統LD_PRELOAD示例一

我們寫下面一段例程:


 

  1. #include <stdio.h>
  2. #include <string.h>
  3. int main(int argc, char **argv)   
  4. {  
  5. char passwd[] = "password";  
  6. if (argc <2) {  
  7. printf("usage: %s <password>\n", argv[0]);  
  8. return;  
  9. }  
  10. if (!strcmp(passwd, argv[1])) {  
  11. printf("Correct Password!\n");  
  12. return;  
  13. }  
  14. printf("Invalid Password!\n");  

在上面這段Unix作業系統程式中,我們使用了strcmp函式來判斷兩個字串是否相等。下面,我們使用一個動態函式庫來過載strcmp函式:
 

  1. #include <stdio.h>
  2. #include <string.h>
  3. int strcmp(const char *s1, const char *s2)  
  4. {  
  5. printf("hack function invoked. s1=<%s>
    s2=<%s>\n", s1, s2);  
  6. return 0;  

編譯程式:
 

  1. $ gcc -o verifypasswd verifypasswd.c  
  2. $ gcc -shared -o hack.so hack.c 


測試一下程式:(得到正確結果)
 

  1. $ ./verifypasswd asdf  
  2. Invalid Password! 

設定LD_PRELOAD變數:(使我們重寫過的strcmp函式的hack.so成為優先載入連結庫)
 $ export LD_PRELOAD="./hack.so"

再次執行程式:
 

  1. $ ./verifypasswd  asdf  
  2. hack function invoked. s1=<password>s2=<asdf>
  3. Correct Password! 

1)我們的hack.so中的strcmp被呼叫了。
2)主程式中執行結果被影響了。

如果這是一個Unix作業系統登入程式,那麼這也就意味著我們用任意口令都可以進入Unix作業系統了。

(3) ldconfig命令詳解,linux動態連結庫
   http://hi.baidu.com/ostech/blog/item/d4a40b99f0846ab9c8eaf43b.html
動態連結庫管理命令

為了讓動態連結庫為系統所共享,還需執行動態連結庫的管理命令--ldconfig.此執行程式存放在/sbin目錄下.

ldconfig命令的用途,主要是在預設搜尋目錄(/lib和/usr/lib)以及動態庫配置檔案/etc/ld.so.conf內所列的目錄下,搜尋出可共享的動態連結庫(格式如前介紹,lib*.so*),進而創建出動態裝入程式(ld.so)所需的連線和快取檔案.快取檔案預設為/etc/ld.so.cache,此檔案儲存已排好序的動態連結庫名字列表.

ldconfig通常在系統啟動時執行,而當用戶安裝了一個新的動態連結庫時,就需要手工執行這個命令.

ldconfig命令列用法如下:

ldconfig [-v|--verbose] [-n] [-N] [-X] [-f CONF] [-C CACHE] [-r ROOT] [-l] [-p|--print-cache] [-c FORMAT] [--format=FORMAT] [-V] [-?|--help|--usage] path...

ldconfig可用的選項說明如下:

(1) -v或--verbose : 用此選項時,ldconfig將顯示正在掃描的目錄及搜尋到的動態連結庫,還有它所建立的連線的名字.

(2) -n : 用此選項時,ldconfig僅掃描命令列指定的目錄,不掃描預設目錄(/lib,/usr/lib),也不掃描配置檔案/etc/ld.so.conf所列的目錄.

(3) -N : 此選項指示ldconfig不重建快取檔案(/etc/ld.so.cache).若未用-X選項,ldconfig照常更新檔案的連線.

(4) -X : 此選項指示ldconfig不更新檔案的連線.若未用-N選項,則快取檔案正常更新.

(5) -f CONF : 此選項指定動態連結庫的配置檔案為CONF,系統預設為/etc/ld.so.conf.

(6) -C CACHE : 此選項指定生成的快取檔案為CACHE,系統預設的是/etc/ld.so.cache,此檔案存放已排好序的可共享的動態連結庫的列表.

(7) -r ROOT : 此選項改變應用程式的根目錄為ROOT(是呼叫chroot函式實現的).選擇此項時,系統預設的配置檔案/etc/ld.so.conf,實際對應的為ROOT/etc/ld.so.conf.如用-r /usr/zzz時,開啟配置檔案/etc/ld.so.conf時,實際開啟的是/usr/zzz/etc/ld.so.conf檔案.用此選項,可以大大增加動態連結庫管理的靈活性.

(8) -l : 通常情況下,ldconfig搜尋動態連結庫時將自動建立動態連結庫的連線.選擇此項時,將進入專家模式,需要手工設定連線.一般使用者不用此項.

(9) -p或--print-cache : 此選項指示ldconfig打印出當前快取檔案所儲存的所有共享庫的名字.

(10) -c FORMAT 或 --format=FORMAT : 此選項用於指定快取檔案所使用的格式,共有三種:old(老格式),new(新格式)和compat(相容格式,此為預設格式).

(11) -V : 此選項打印出ldconfig的版本資訊,而後退出.

(12) -? 或 --help 或 --usage : 這三個選項作用相同,都是讓ldconfig打印出其幫助資訊,而後退出.

舉三個例子:

例1:

# ldconfig -p
793 libs found in cache `/etc/ld.so.cache'
libzvt.so.2 (libc6) =>; /usr/lib/libzvt.so.2
libzvt.so (libc6) =>; /usr/lib/libzvt.so
libz.so.1.1.3 (libc6) =>; /usr/lib/libz.so.1.1.3
libz.so.1 (libc6) =>; /lib/libz.so.1
......
#

注: 有時候使用者想知道系統中有哪些動態連結庫,或者想知道系統中有沒有某個動態連結庫,這時,可用-p選項讓ldconfig輸出快取檔案中的動態連結庫列表,從而查詢得到.例子中,ldconfig命令的輸出結果第1行表明在快取檔案/etc/ld.so.cache中找到793個共享庫,第2行開始便是一系列共享庫的名字及其全名(絕對路徑).因為實際輸出結果太多,為節省篇幅,以......表示省略的部分.


例2:

# ldconfig -v
/lib:
liby.so.1 ->; liby.so.1
libnss_wins.so ->; libnss_wins.so
......
/usr/lib:
libjscript.so.2 ->; libjscript.so.2.0.0
libkspell.so.2 ->; libkspell.so.2.0.0
......
/usr/X11R6/lib:
libmej-0.8.10.so ->; libmej-0.8.10.so
libXaw3d.so.7 ->; libXaw3d.so.7.0
......
#

注: ldconfig命令在執行正常的情況下,預設不輸出什麼東西.本例中用了-v選項,以使ldconfig在執行時輸出正在掃描的目錄及搜尋到的共享庫,使用者可以清楚地看到執行的結果.執行結束後,ldconfig將重新整理快取檔案/etc/ld.so.cache.

例3:

# ldconfig /usr/zhsoft/lib
(4)ldd命令 http://www.xker.com/page/e2007/0927/35035.html

首先ldd不是一個可執行程式,而只是一個shell指令碼

2、ldd能夠顯示可執行模組的dependency,其原理是通過設定一系列的環境變數,如下:LD_TRACE_LOADED_OBJECTS、LD_WARN、LD_BIND_NOW、LD_LIBRARY_VERSION、LD_VERBOSE等。當LD_TRACE_LOADED_OBJECTS環境變數不為空時,任何可執行程式在執行時,它都會只顯示模組的dependency,而程式並不真正執行。要不你可以在shell終端測試一下,如下:

(1) export LD_TRACE_LOADED_OBJECTS=1

(2) 再執行任何的程式,如ls等,看看程式的執行結果

3、ldd顯示可執行模組的dependency的工作原理,其實質是通過ld-linux.so(elf動態庫的裝載

器)來實現的。我們知道,ld-linux.so模組會先於executable模組程式工作,並獲得控制權,因此當上述的那些環境變數被設定時,ld-linux.so選擇了顯示可執行模組的dependency。

4、實際上可以直接執行ld-linux.so模組,如:/lib/ld-linux.so.2 --list program(這相當於ldd program)

ldd命令使用方法(摘自ldd --help)

名稱 ldd - 列印共享庫的依賴關係

大綱 ldd [選項]... 檔案...

描述 ldd 輸出在命令列上指定的每個程式或共享庫需要的共享庫。

選項

--version

列印ldd的版本號

-v --verbose

列印所有資訊,例如包括符號的版本資訊

-d --data-relocs

執行符號重部署,並報告缺少的目標物件(只對ELF格式適用)

-r --function-relocs

對目標物件和函式執行重新部署,並報告缺少的目標物件和函式(只對ELF格式適用)

--help 用法資訊

注意:

ldd的標準版本與glibc2一起提供。Libc5與老版本以前提供,在一些系統中還存在。在libc5版本中長選項不支援。另一方面,glibc2版本不支援-V選項,只提供等價的--version選項。

如果命令列中給定的庫名字包含'/',這個程式的libc5版本將使用它作為庫名字;否則它將在標準位置搜尋庫。執行一個當前目錄下的共享庫,加字首"./"。

錯誤:

ldd不能工作在a.out格式的共享庫上。

ldd不能工作在一些非常老的a.out程式上,這些程式在支援ldd的編譯器發行前已經建立。如果你在這種型別的程式上使用ldd,程式將嘗試argc = 0的執行方式,其結果不可預知。

(5)LD_LIBRARY_PATH
  http://skatings.blogbus.com/logs/50437681.html 

Linux 執行的時候,是如何管理共享庫(*.so)的?在 Linux 下面,共享庫的尋找和載入是由 /lib/ld.so 實現的。 ld.so 在標準路經(/lib, /usr/lib) 中尋找應用程式用到的共享庫。

但是,如果需要用到的共享庫在非標準路經,ld.so 怎麼找到它呢?

目前,Linux 通用的做法是將非標準路經加入 /etc/ld.so.conf,然後執行 ldconfig 生成 /etc/ld.so.cache。 ld.so 載入共享庫的時候,會從 ld.so.cache 查詢。

傳統上,Linux 的先輩 Unix 還有一個環境變數:LD_LIBRARY_PATH 來處理非標準路經的共享庫。ld.so 載入共享庫的時候,也會查詢這個變數所設定的路經。

LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./lib

export LD_LIBRARY_PATH

但是,有不少聲音主張要避免使用 LD_LIBRARY_PATH 變數,尤其是作為全域性變數。這些聲音是:

* LD_LIBRARY_PATH is not the answer - http://prefetch.net/articles/linkers.badldlibrary.html

* Why LD_LIBRARY_PATH is bad - http://xahlee.org/UnixResource_dir/_/ldpath.html 

* LD_LIBRARY_PATH - just say no - http://blogs.sun.com/rie/date/20040710

解決這一問題的另一方法是在編譯的時候通過 -R<path> 選項指定 run-time path。

1. 往/lib和/usr/lib裡面加東西,是不用修改/etc/ld.so.conf的,但是完了之後要調一下ldconfig,不然這個library會找不到

2. 想往上面兩個目錄以外加東西的時候,一定要修改/etc/ld.so.conf,然後再呼叫ldconfig,不然也會找不到。

比如安裝了一個mysql到/usr/local/mysql,mysql有一大堆library在/usr/local/mysql/lib下面,這時就需要在/etc/ld.so.conf下面加一行/usr/local/mysql/lib,儲存過後ldconfig一下,新的library才能在程式執行時被找到。

3. 如果想在這兩個目錄以外放lib,但是又不想在/etc/ld.so.conf中加東西(或者是沒有許可權加東西)。那也可以,就是export一個全域性變數LD_LIBRARY_PATH,然後執行程式的時候就會去這個目錄中找library。一般來講這只是一種臨時的解決方案,在沒有許可權或臨時需要的時候使用。

4. ldconfig做的這些東西都與執行程式時有關,跟編譯時一點關係都沒有。編譯的時候還是該加-L就得加,不要混淆了。

5. 總之,就是不管做了什麼關於library的變動後,最好都ldconfig一下,不然會出現一些意想不到的結果。不會花太多的時間,但是會省很多的事。

LD_LIBRARY_PATH 這個環境變數是大家最為熟悉的,它告訴loader:在哪些目錄中可以找到共享庫。可以設定多個搜尋目錄,這些目錄之間用冒號分隔開。在linux下,還提供了另外一種方式來完成同樣的功能,你可以把這些目錄加到/etc/ld.so.conf中,然後呼叫ldconfig。當然,這是系統範圍內全域性有效的,而環境變數只對當前shell有效。按照慣例,除非你用上述方式指明,loader是不會在當前目錄下去找共享庫的,正如shell不會在當前目前找可執行檔案一樣。

================================================================================================

在shell下嘗試設定LD_LIBRARY_PATH,以下面這種形式設定,老是報錯bash: LD_LIBRARY_PATH: command not found,

LD_LIBRARY_PATH=/usr/local/lib

LD_LIBRARY_PATH = $ LD_LIBRARY_PATH:/usr/local/lib

可能是因為系統之前沒有設定過LD_LIBRARY_PATH,於是改成這樣:

export LD_LIBRARY_PATH=/usr/local/lib

然後用 echo $LD_LIBRARY_PATH檢查一下是否真的設定成功,發現可以。

接著在該shell下執行eclipse生成的可執行檔案,沒有錯誤。

另外,如果不想每次新啟一個shell都設定LD_LIBRARY_PATH,可以編輯~/.bash_profile檔案:

$ vi ~/.bash_profile 

新增:

LD_LIBRARY_PATH=/usr/local/lib

export LD_LIBRARY_PATH

這兩行,完成之後.bash_profile如下所示:

# .bash_profile

# Get the aliases and functions

if [ -f ~/.bashrc ]; then

        . ~/.bashrc

fi

# User specific environment and startup programs

PATH=$PATH:$HOME/bin

LD_LIBRARY_PATH=/usr/local/lib

export PATH

export LD_LIBRARY_PATH

然後執行 $ source ~/.bash_profile 就行了。

但是這種方法只能用在shell下,想在eclipse裡面執行,還是不行:

嘗試了eclipse專案properties裡面的各種設定都不起作用。

用“eclipse LD_LIBRARY_PATH”作為關鍵字(可見關鍵字多麼重要)才搜到這麼篇文章 《eclipse+cdt+gcc編譯選項控制》 http://hi.baidu.com/zsffei/blog/item/7b17c043ceb51e1772f05de1.html

才知道應該在eclipse的專案屬性-->C/C++ Build-->Settings-->Tool settings-->GCC C++ Linker-->Miscellaneous的Other options (-Xlinker [option])新增 -R/usr/local/lib

(6) 動態連結庫的搜尋優先順序

   http://hi.baidu.com/ilonng/blog/item/f851a0e9048d1231b80e2db8.html

   http://blog.csdn.net/gogdizzy/article/details/6591267

首先回答前面的問題,一共有多少種方法來指定告訴linux共享庫連結器ld.so已經編譯好的庫libbase.so的位置呢?答案是一共有五種,它們都可以通知ld.so去哪些地方找下已經編譯好的c語言函式動態庫,它們是:
1)ELF可執行檔案中動態段中DT_RPATH所指定的路徑。即在編譯目的碼時, 對gcc加入連結引數“-Wl,-rpath”指定動態庫搜尋路徑,eg:gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/lib test.c
2)環境變數LD_LIBRARY_PATH 指定的動態庫搜尋路徑
3)/etc/ld.so.cache中所快取的動態庫路徑,這個可以通過先修改配置檔案/etc/ld.so.conf中指定的動態庫搜尋路徑,然後執行ldconfig命令來改變。
4)預設的動態庫搜尋路徑/lib
5)預設的動態庫搜尋路徑/usr/lib
    另外:在嵌入式Linux系統的實際應用中,1和2被經常使用,也有一些相對簡單的的嵌入式系統會採用4或5的路徑來規範動態庫,3在嵌入式系統中使用的比較少, 因為有很多系統根本就不支援ld.so.cache。

    那麼,動態連結器ld.so在這五種路徑中,是按照什麼樣的順序來搜尋需要的動態共享庫呢?答案這裡先告知就是按照上面的順序來得,即優先順序是:1-->2-->3-->4-->5。我們可以寫簡單的程式來證明這個結論。

    首先,寫成5個函式,這5個函式名稱都叫pt,但是裡面的內容不一樣:
pt1.c
#include <stdio.h>
void pt(){
        printf("1 path on the gcc give \n");
}

pt2.c
#include <stdio.h>
void pt(){
        printf("2 path on the LD_LIBRARY_PATH \n");
}

pt3.c
#include <stdio.h>
void pt(){
        printf("3 path on the /etc/ld.so.conf \n");
}

pt4.c
#include <stdio.h>
void pt(){
        printf("4 path on the /lib \n");
}

pt5.c
#include <stdio.h>
void pt(){
        printf("5 path on the /usr/lib \n");
}

    然後,分別編譯這5個函式,然後將它們分別移到上面5種情況對應的5個不同目錄下:
gcc -fPIC -c pt1.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /tmp/st/1/

gcc -fPIC -c pt2.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /tmp/st/2/

gcc -fPIC -c pt3.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /tmp/st/3/

gcc -fPIC -c pt4.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /lib/

gcc -fPIC -c pt5.c -o pt.o
gcc -shared pt.o -o libpt.so
mv libpt.so /usr/lib/

    再次,編寫一個main函式m,讓它來呼叫函式pt:
m.c
#include <stdio.h>

int main(){
        printf("start....\n");
        pt();
        printf("......end\n");
        return 0;
}

    最後,準備環境,讓ld都知道這5個路徑:
(a) 往/etc/ld.so.conf總增加一行,內容:/tmp/st/3,然後執行 ldconfig 命令
(b) export LD_LIBRARY_PATH=/tmp/st/2
另外3中路徑,ld都可以得到,請接著看下面。

    之後測試:
gcc m.c -o m1 -L/tmp/st/1 -lpt -Wl,-rpath,/tmp/st/1
./m1
start....
1 path on the gcc give
......end
    這裡在可執行檔案中動態段中DT_RPATH所指定的路徑,因此需要在編譯m.c的時候就指定路徑,由於其他路徑都也告訴了ld,很明顯,此種方法優先順序最高了。

gcc m.c -o m -L/tmp/st/1 -lpt
./m
start....
2 path on the LD_LIBRARY_PATH
......end
    這裡很顯然呼叫了LD_LIBRARY_PATH指定了路徑中的共享庫,因此此種情況優先順序第二。

mv /tmp/st/2/libpt.so /tmp/st/2/libpt2.so
/m
start....
3 path on the /etc/ld.so.conf
......end
    這裡是呼叫了/etc/ld.so.cache中所快取的動態庫路徑中的共享庫,因此此種情況優先順序第三。

mv /tmp/st/3/libpt.so /tmp/st/3/libpt3.so
./m
start....
4 path on the /lib
......end
    這裡是呼叫/lib中的共享庫,優先順序第四。

rm /lib/libpt.so
./m
start....
5 path on the /usr/lib
......end
    這裡是呼叫/lib中的共享庫,優先順序第五。

    故證明這五種路徑指定方法的優先順序是1-->2-->3-->4-->5!