• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            興海北路

            ---男兒仗劍自橫行
            <2008年3月>
            2425262728291
            2345678
            9101112131415
            16171819202122
            23242526272829
            303112345

            統(tǒng)計(jì)

            • 隨筆 - 85
            • 文章 - 0
            • 評(píng)論 - 17
            • 引用 - 0

            常用鏈接

            留言簿(6)

            隨筆分類

            隨筆檔案

            收藏夾

            全是知識(shí)啊

            搜索

            •  

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            動(dòng)態(tài)符號(hào)鏈接的細(xì)節(jié)
            by falcon<zhangjinw@gmail.com>
            2008-02-26
               
                Linux支持動(dòng)態(tài)連接庫,不僅節(jié)省了磁盤、內(nèi)存空間,而且可以提高程序運(yùn)行效率[1]。不過引入動(dòng)態(tài)連接庫也可能會(huì)帶來很多問題,例如動(dòng)態(tài)連接庫的調(diào)試 [4]、升級(jí)更新[5]和潛在的安全威脅[6][7]。這里主要討論符號(hào)的動(dòng)態(tài)鏈接過程,即程序在執(zhí)行過程中,對(duì)其中包含的一些未確定地址的符號(hào)進(jìn)行重定 位的過程[3][8]。
                本篇主要參考資料[3]和[8],前者側(cè)重實(shí)踐,后者側(cè)重原理,把兩者結(jié)合起來就方便理解程序的動(dòng)態(tài)鏈接過程了。另外,動(dòng)態(tài)連接庫的創(chuàng)建、使用以及調(diào)用動(dòng)態(tài)連接庫的部分參考了資料[1][2]。
                下面先來看看幾個(gè)基本概念,接著就介紹動(dòng)態(tài)連接庫的創(chuàng)建、隱式和顯示調(diào)用,最后介紹符號(hào)的動(dòng)態(tài)鏈接細(xì)節(jié)。

            1、基本概念

            1.1 ELF

                ELF是Linux支持的一種程序文件格式,本身包含重定位、執(zhí)行、共享(動(dòng)態(tài)連接庫)三種類型。(man elf)

            代碼:


            Code:

            [Ctrl+A Select All]


            演示:
            Quote:

            $ gcc -c test.c    #通過-c生成可重定位文件test.o,這里不會(huì)進(jìn)行鏈接
            $ file test.o
            test.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
            $ gcc -o test test.o  #鏈接后才可以執(zhí)行
            $ file test      
            test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), not stripped
            //也可鏈接成動(dòng)態(tài)連接庫,不過一般不會(huì)把main函數(shù)鏈接成動(dòng)態(tài)連接庫,后面再介紹
            $ gcc -fpic -shared -W1,-soname,libtest.so.0 -o libtest.so.0.0 test.o
            $ file libtest.so.0.0
            libtest.so.0.0: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped


                雖然ELF文件本身就支持三種不同的類型,不過它有一個(gè)統(tǒng)一的結(jié)構(gòu)。這個(gè)結(jié)構(gòu)是:

                文件頭部(ELF Header)
                程序頭部表(Program Header Table)
                節(jié)區(qū)1(Section1)
                節(jié)區(qū)2(Section2)
                節(jié)區(qū)3(Section3)
                ...
                節(jié)區(qū)頭部表(Section Header Table)

               
                無論是文件頭部、程序頭部表、節(jié)區(qū)頭部表,還是節(jié)區(qū),它們都對(duì)應(yīng)著C語言里頭的一些結(jié)構(gòu)體(elf.h中定義)。文件頭部主要描述ELF文件的類型,大 小,運(yùn)行平臺(tái),以及和程序頭部表和節(jié)區(qū)頭部表相關(guān)的信息。節(jié)區(qū)頭部表則用于可重定位文件,以便描述各個(gè)節(jié)區(qū)的信息,這些信息包括節(jié)區(qū)的名字、類型、大小 等。程序頭部表則用于描述可執(zhí)行文件或者動(dòng)態(tài)連接庫,以便系統(tǒng)加載和執(zhí)行它們。而節(jié)區(qū)主要存放各種特定類型的信息,比如程序的正文區(qū)(代碼)、數(shù)據(jù)區(qū)(初 始化和未初始化的數(shù)據(jù))、調(diào)試信息、以及用于動(dòng)態(tài)鏈接的一些節(jié)區(qū),比如解釋器(.interp)節(jié)區(qū)將指定程序動(dòng)態(tài)裝載/連接器ld-linux.so的 位置,而過程鏈接表(plt)、全局偏移表(got)、重定位表則用于輔助動(dòng)態(tài)鏈接過程。

            1.2  符號(hào)

                對(duì)于可執(zhí)行文件除了編譯器引入的一些符號(hào)外,主要就是用戶自定義的全局變量,函數(shù)等,而對(duì)于可重定位文件僅僅包含用戶自定義的一些符號(hào)。
            Quote:

            $ gcc -c test.c  #生成可重定位文件test.o
            //包含全局變量、自定義函數(shù)以及動(dòng)態(tài)連接庫中的函數(shù),但不包含局部變量;發(fā)現(xiàn)這個(gè)三個(gè)符號(hào)的地址都沒有確定
            $ nm test.o      #nm可以用來查看ELF文件的符號(hào)表信息        
            00000000 B global
            00000000 T main
                     U printf
            $ gcc -o test test.o  #生成可執(zhí)行文件
            //經(jīng)過鏈接后,global和main的地址都已經(jīng)確定了,但是printf卻還沒有,因?yàn)樗莿?dòng)態(tài)連接庫glibc中定義函數(shù),需要?jiǎng)討B(tài)鏈接,而不是這里的“靜態(tài)”鏈接
            $ nm test | egrep "main$| printf|global$"
            080495a0 B global
            08048354 T main
                     U printf@@GLIBC_2.0

              

            1.3 重定位:"是將符號(hào)引用與符號(hào)定義進(jìn)行連接的過程"[8]

                從上面的演示可以看出,重定位文件test.o中的符號(hào)地址都是沒有確定的,而經(jīng)過“靜態(tài)"鏈接(gcc默認(rèn)調(diào)用ld進(jìn)行鏈接)以后有兩個(gè)符號(hào)地址已經(jīng)確 定了,這樣一個(gè)確定符號(hào)地址的過程實(shí)際上就是鏈接的實(shí)質(zhì)。鏈接過后,對(duì)符號(hào)的引用變成了對(duì)地址(定義符號(hào)時(shí)確定該地址)的引用,這樣程序運(yùn)行時(shí)就可通過訪 問內(nèi)存地址而訪問特定的數(shù)據(jù)。
                我們也注意到符號(hào)printf在可重定位文件和可執(zhí)行文件中的地址都沒有確定,這意味著該符號(hào)是一個(gè)外部符號(hào),可能定義在動(dòng)態(tài)連接庫中,在程序運(yùn)行時(shí)需要通過動(dòng)態(tài)鏈接器(ld-linux.so)進(jìn)行重定位,即動(dòng)態(tài)鏈接。
                通過這個(gè)演示可以看出printf確實(shí)在glibc中有定義。
            Quote:

            $ nm /lib/libc.so.6 | grep  "\ printf$"
            000457b0 T printf



            1.4 動(dòng)態(tài)鏈接
               
                動(dòng)態(tài)鏈接就是在程序運(yùn)行時(shí)對(duì)符號(hào)進(jìn)行重定位,確定符號(hào)對(duì)應(yīng)的內(nèi)存地址的過程。
                Linux下符號(hào)的動(dòng)態(tài)鏈接默認(rèn)采用Lazy Mode方式[3],也就是說在程序運(yùn)行過程中用到該符號(hào)時(shí)才去解析它的地址。這樣一種符號(hào)解析方式有一個(gè)好處:只解析那些用到的符號(hào),而對(duì)那些不用的符號(hào)則永遠(yuǎn)不用解析,從而提高程序的執(zhí)行效率。
                不過這種默認(rèn)是可以通過設(shè)置LD_BIND_NOW為非空來打破的(下面會(huì)通過實(shí)例來分析這個(gè)變量的作用),也就是說如果設(shè)置了這個(gè)變量,動(dòng)態(tài)鏈接器將在程序加載后和符號(hào)被使用之前就對(duì)這些符號(hào)的地址進(jìn)行解析。

            1.5 動(dòng)態(tài)連接庫
               
                上面提到重定位的過程就是對(duì)符號(hào)引用和符號(hào)地址進(jìn)行鏈接的過程,而動(dòng)態(tài)鏈接過程涉及到的符號(hào)引用和符號(hào)定義分別對(duì)應(yīng)可執(zhí)行文件和動(dòng)態(tài)連接庫,在可執(zhí)行文件中可能引用了某些動(dòng)態(tài)連接庫中定義的符號(hào),這類符號(hào)通常是函數(shù)。
                為了讓動(dòng)態(tài)鏈接器能夠進(jìn)行符號(hào)的重定位,必須把動(dòng)態(tài)連接庫的相關(guān)信息寫入到可執(zhí)行文件當(dāng)中,這些信息是什么呢?
            Quote:

            $ readelf -d test | grep NEEDED
             0x00000001 (NEEDED)                     Shared library: [libc.so.6]


                ELF文件有一個(gè)特別的節(jié)區(qū),.dynamic,它存放了和動(dòng)態(tài)鏈接相關(guān)的很多信息,例如動(dòng)態(tài)鏈接器通過它找到該文件使用的動(dòng)態(tài)連接庫。不過,該信息并未包含動(dòng)態(tài)連接庫libc.so.6的絕對(duì)路徑,那動(dòng)態(tài)鏈接器去哪里查找相應(yīng)的庫呢?
                通過LD_LIBRARY_PATH參數(shù),它類似shell解釋器中用于查找可執(zhí)行文件的PATH環(huán)境變量,也是通過冒號(hào)分開指定了各個(gè)存放庫函數(shù)的路 徑。該變量實(shí)際上也可以通過/etc/ld.so.conf文件來指定,一行對(duì)應(yīng)一個(gè)路徑名。為了提高查找和加載動(dòng)態(tài)連接庫的效率,系統(tǒng)啟動(dòng)后會(huì)通過 ldconfig工具創(chuàng)建一個(gè)庫的緩存/etc/ld.so.cache。如果用戶通過/etc/ld.so.conf加入了新的庫搜索路徑或者是把新庫 加到某個(gè)原有的庫目錄下,最好是執(zhí)行一下ldconf以便刷新緩存。

                需要補(bǔ)充的是,因?yàn)閯?dòng)態(tài)連接庫本身還可能引用其他的庫,那么一個(gè)可執(zhí)行文件的動(dòng)態(tài)符號(hào)鏈接過程可能涉及到多個(gè)庫,通過read -d可以打印出該文件直接依賴的庫,而通過ldd命令則可以打印出所有依賴或者間接依賴的庫。
            Quote:

            $ ldd test
                    linux-gate.so.1 =>  (0xffffe000)
                    libc.so.6 => /lib/libc.so.6 (0xb7da2000)
                    /lib/ld-linux.so.2 (0xb7efc000)


                lib.so.6通過read -d就可以看到的,是直接依賴的庫;而linux-gate.so.1在文件系統(tǒng)中并沒有對(duì)應(yīng)的庫文件,它是一個(gè)虛擬的動(dòng)態(tài)連接庫,對(duì)應(yīng)進(jìn)程內(nèi)存映像的內(nèi) 核部分,更多細(xì)節(jié)請(qǐng)參考資料[11];而/lib/ld-linux.so.2正好是動(dòng)態(tài)鏈接器,系統(tǒng)需要用它來進(jìn)行符號(hào)重定位。那ldd是怎么知道 /lib/ld-linux.so就是該文件的動(dòng)態(tài)鏈接器呢?
                那是因?yàn)镋LF文件通過專門的節(jié)區(qū)指定了動(dòng)態(tài)鏈接器,這個(gè)節(jié)區(qū)就是.interp。
            Quote:

            $ readelf -x .interp test

            Hex dump of section '.interp':
              0x08048114 2f6c6962 2f6c642d 6c696e75 782e736f /lib/ld-linux.so
              0x08048124 2e3200                              .2.


                可以看到這個(gè)節(jié)區(qū)剛好有字符串/lib/ld-linux.so.2,即ld-linux.so的絕對(duì)路徑。
                我們發(fā)現(xiàn),與libc.so不同的是,ld-linux.so的路徑是絕對(duì)路徑,而libc.so僅僅包含了文件名。原因是:程序被執(zhí)行時(shí),ld- linux.so將最先被裝載到內(nèi)存中,沒有其他程序知道去哪里查找ld-linux.so,所以它的路徑必須是絕對(duì)的;當(dāng)ld-linux.so被裝載 以后,由它來去裝載可執(zhí)行文件和相關(guān)的共享庫,它將根據(jù)PATH變量和LD_LIBRARY_PATH變量去磁盤上查找它們,因此可執(zhí)行文件和共享庫都可 以不指定絕對(duì)路徑。
                下面著重介紹動(dòng)態(tài)連接器本身。

            1.6 動(dòng)態(tài)連接器(dynamic linker/loader)

                Linux下elf文件的動(dòng)態(tài)鏈接器是ld-linux.so,即/lib/ld-linux.so.2。從名字來看和靜態(tài)連接器ld(gcc默認(rèn)使用的 連接器,見參考資料[10])類似。通過man ld-linux可以獲取與動(dòng)態(tài)鏈接器相關(guān)的資料,包括各種相關(guān)的環(huán)境變量和文件都有詳細(xì)的說明。
                對(duì)于環(huán)境變量,除了上面提到過的LD_LIBRARY_PATH和LD_BIND_NOW變量外,還有其他幾個(gè)重要參數(shù),比如LD_PRELOAD用于指 定預(yù)裝載一些庫,以便替換其他庫中的函數(shù),從而做一些安全方面的處理[6][9][12],而環(huán)境變量LD_DEBUG可以用來進(jìn)行動(dòng)態(tài)鏈接的相關(guān)調(diào)試。
                對(duì)于文件,除了上面提到的ld.so.conf和ld.so.cache外,還有一個(gè)文件/etc/ld.so.preload用于指定需要預(yù)裝載的庫。
                從上一小節(jié)中發(fā)現(xiàn)有一個(gè)專門的節(jié)區(qū).interp存放有動(dòng)態(tài)鏈接器,但是這個(gè)節(jié)區(qū)為什么叫做.interp(interpeter)呢?因?yàn)楫?dāng)shell 解釋器或者其他父進(jìn)程通過exec啟動(dòng)我們的程序時(shí),系統(tǒng)會(huì)先為ld-linux創(chuàng)建內(nèi)存映像,然后把控制權(quán)交給ld-linux,之后ld-linux 負(fù)責(zé)為可執(zhí)行程序提供運(yùn)行環(huán)境,負(fù)責(zé)解釋程序的運(yùn)行,因此ld-linux也叫做dynamic loader(或intepreter)(關(guān)于程序的加載過程請(qǐng)參考資料[13])
                那么在exec()之后和程序指令運(yùn)行之前的過程是怎樣的呢?ld-linux.so主要為程序本身創(chuàng)建了內(nèi)存映像(以下內(nèi)容摘自資料[8]),大體過程如下:
                1) 將可執(zhí)行文件的內(nèi)存段添加到進(jìn)程映像中;
                2) 把共享目標(biāo)內(nèi)存段添加到進(jìn)程映像中;
                3) 為可執(zhí)行文件和它的共享目標(biāo)(動(dòng)態(tài)連接庫)執(zhí)行重定位操作;
                4) 關(guān)閉用來讀入可執(zhí)行文件的文件描述符,如果動(dòng)態(tài)鏈接程序收到過這樣的文件描述符的話;
                5) 將控制轉(zhuǎn)交給程序,使得程序好像從exec()直接得到控制
                關(guān)于第1)步,在ELF文件的文件頭中就指定了該文件的入口地址,程序的代碼和數(shù)據(jù)部分會(huì)相繼map到對(duì)應(yīng)的內(nèi)存中。而關(guān)于可執(zhí)行文件本身的路徑,如果指定了PATH環(huán)境變量,ld-linux會(huì)到PATH指定的相關(guān)目錄下查找。
            Quote:

            $ readelf -h test | grep Entry
              Entry point address:               0x80482b0


                對(duì)于第2)步,上一節(jié)提到的.dynamic節(jié)區(qū)指定了可執(zhí)行文件依賴的庫名,ld-linux(在這里叫做動(dòng)態(tài)裝載器或程序解釋器比較合適)再?gòu)? LD_LIBRARY_PATH指定的路徑中找到相關(guān)的庫文件或者直接從/etc/ld.so.cache庫緩沖中加載相關(guān)庫到內(nèi)存中。(關(guān)于進(jìn)程的內(nèi)存 映像,推薦參考資料[14])
                對(duì)于第3)步,在前面已提到,如果設(shè)置了LD_BIND_NOW環(huán)境變量,這個(gè)動(dòng)作就會(huì)在此時(shí)發(fā)生,否則將會(huì)采用lazy mode方式,即當(dāng)某個(gè)符號(hào)被使用時(shí)才會(huì)進(jìn)行符號(hào)的重定位。不過無論在什么時(shí)候發(fā)生這個(gè)動(dòng)作,重定位的過程大體是一樣的(在后面將主要介紹該過程)。
                對(duì)于第4)步,這個(gè)主要是釋放文件描述符。
                對(duì)于第5)步,動(dòng)態(tài)鏈接器把程序控制權(quán)交還給程序。

                現(xiàn)在關(guān)心的主要是第3步,即如何進(jìn)行符號(hào)的重定位?下面來探求這個(gè)過程。期間會(huì)逐步討論到和動(dòng)態(tài)鏈接密切相關(guān)的三個(gè)數(shù)據(jù)結(jié)構(gòu),它們分別是ELF文件的過程鏈接表、全局偏移表和重定位表,這三個(gè)表都是ELF文件的節(jié)區(qū)。

            1.7 過程鏈接表(plt)
               
                從上面的演示發(fā)現(xiàn),還有一個(gè)printf符號(hào)的地址沒有確定,它應(yīng)該在動(dòng)態(tài)連接庫libc.so中定義,需要進(jìn)行動(dòng)態(tài)鏈接。這里假設(shè)采用lazy mode方式,即執(zhí)行到printf所在位置時(shí)才去解析該符號(hào)的地址。
                假設(shè)當(dāng)前已經(jīng)執(zhí)行到了printf所在位置,即call printf,我們通過objdump反編譯test程序的正文段看看。

            Quote:

            $ objdump -d -s -j .text test | grep printf
             804837c:       e8 1f ff ff ff          call   80482a0 <printf@plt>


                發(fā)現(xiàn),該地址指向了plt(即過程鏈接表)即地址80482a0處。下面查看該地址處的內(nèi)容。
            Quote:

            $ objdump -D test | grep "80482a0" | grep -v call
            080482a0 <printf@plt>:
             80482a0:       ff 25 8c 95 04 08       jmp    *0x804958c


                發(fā)現(xiàn)80482a0地址對(duì)應(yīng)的是一條跳轉(zhuǎn)指令,跳轉(zhuǎn)到0x804958c地址指向的地址。到底0x804958c地址本身在什么地方呢?我們能否從.dynamic節(jié)區(qū)(該節(jié)區(qū)存放了和動(dòng)態(tài)鏈接相關(guān)的數(shù)據(jù))獲取相關(guān)的信息呢?
            Quote:

            $ readelf -d test

            Dynamic section at offset 0x4ac contains 20 entries:
              Tag        Type                         Name/Value
             0x00000001 (NEEDED)                     Shared library: [libc.so.6]
             0x0000000c (INIT)                       0x8048258
             0x0000000d (FINI)                       0x8048454
             0x00000004 (HASH)                       0x8048148
             0x00000005 (STRTAB)                     0x80481c0
             0x00000006 (SYMTAB)                     0x8048170
             0x0000000a (STRSZ)                      76 (bytes)
             0x0000000b (SYMENT)                     16 (bytes)
             0x00000015 (DEBUG)                      0x0
             0x00000003 (PLTGOT)                     0x8049578
             0x00000002 (PLTRELSZ)                   24 (bytes)
             0x00000014 (PLTREL)                     REL
             0x00000017 (JMPREL)                     0x8048240
             0x00000011 (REL)                        0x8048238
             0x00000012 (RELSZ)                      8 (bytes)
             0x00000013 (RELENT)                     8 (bytes)
             0x6ffffffe (VERNEED)                    0x8048218
             0x6fffffff (VERNEEDNUM)                 1
             0x6ffffff0 (VERSYM)                     0x804820c
             0x00000000 (NULL)                       0x0


                發(fā)現(xiàn)0x8049578地址和0x804958c地址比較近,通過資料[8]查到前者正好是.got.plt(即過程鏈接表)對(duì)應(yīng)的全局偏移表的入口地址。難道0x804958c正好位于.got.plt節(jié)區(qū)中?

            1.8 全局偏移表(got)

                現(xiàn)在進(jìn)入全局偏移表看看,
            Quote:

            $  readelf -x .got.plt test

            Hex dump of section '.got.plt':
              0x08049578 ac940408 00000000 00000000 86820408 ................
              0x08049588 96820408 a6820408                   ........


                從上述結(jié)果可以看出0x804958c地址(即0x08049588+4)處存放的是a6820408,考慮到我的實(shí)驗(yàn)平臺(tái)是i386,字節(jié)順序是 little-endian的,所以實(shí)際數(shù)值應(yīng)該是080482a6,也就是說*(0x804958c)的值是080482a6,這個(gè)地址剛好是過程鏈接 表的最后一項(xiàng)call 80482a0<printf@plt>中80482a0地址往后偏移6個(gè)字節(jié),容易猜到該地址應(yīng)該就是jmp指令的后一條地址。
            Quote:

            $ objdump -d -d -s -j .plt test |  grep "080482a0 <printf@plt>:" -A 3
            080482a0 <printf@plt>:
             80482a0:       ff 25 8c 95 04 08       jmp    *0x804958c
             80482a6:       68 10 00 00 00          push   $0x10
             80482ab:       e9 c0 ff ff ff          jmp    8048270 <_init+0x18>


                80482a6地址恰巧是一條push指令,隨后是一條jmp指令(暫且不管push指令入棧的內(nèi)容有什么意義),執(zhí)行完push指令之后,就會(huì)跳轉(zhuǎn)到8048270地址處,下面看看8048270地址處到底有哪些指令。
            Quote:

            $ objdump -d -d -s -j .plt test | grep -v "jmp    8048270 <_init+0x18>" | grep "08048270" -A 2
            08048270 <__gmon_start__@plt-0x10>:
             8048270:       ff 35 7c 95 04 08       pushl  0x804957c
             8048276:       ff 25 80 95 04 08       jmp    *0x8049580


                同樣是一條入棧指令跟著一條跳轉(zhuǎn)指令。不過這兩個(gè)地址0x804957c和0x8049580是連續(xù)的,而且都很熟悉,剛好都在.got.plt表里頭 (從上面我們已經(jīng)知道.got.plt的入口是0x08049578)。這樣的話,我們得確認(rèn)這兩個(gè)地址到底有什么內(nèi)容。
            Quote:

            $ readelf -x .got.plt test

            Hex dump of section '.got.plt':
              0x08049578 ac940408 00000000 00000000 86820408 ................
              0x08049588 96820408 a6820408                   ........


                不過,遺憾的是通過readelf查看到的這兩個(gè)地址信息都是0,它們到底是什么呢?
                現(xiàn)在只能求助參考資料[8],該資料的“3.8.5 過程鏈接表”部分在介紹過程鏈接表和全局偏移表相互合作解析符號(hào)的過程中的三步涉及到了這兩個(gè)地址和前面沒有說明的push $0x10指令。
                1) 在程序第一次創(chuàng)建內(nèi)存映像時(shí),動(dòng)態(tài)鏈接器為全局偏移表的第二(0x804957c)和第三項(xiàng)(0x8049580)設(shè)置特殊值。
                2) 原步驟5。在跳轉(zhuǎn)到08048270 <__gmon_start__@plt-0x10>,即過程鏈接表的第一項(xiàng)之前,有一條壓入棧指令,即push $0x10,0x10是相對(duì)于重定位表起始地址的一個(gè)偏移地址,這個(gè)偏移地址到底有什么用呢?它應(yīng)該是提供給動(dòng)態(tài)鏈接器的什么信息吧?后面再說明。
                3) 原步驟6。跳轉(zhuǎn)到過程鏈接表的第一項(xiàng)之后,壓入了全局偏移表中的第二項(xiàng)(即0x804957c處),“為動(dòng)態(tài)鏈接器提供了識(shí)別信息的機(jī)會(huì)”(具體是什么 呢?后面會(huì)簡(jiǎn)單提到,但這個(gè)并不是很重要),然后跳轉(zhuǎn)到全局偏移表的第三項(xiàng)(0x8049580,這一項(xiàng)比較重要),把控制權(quán)交給動(dòng)態(tài)連接器。
                從這三步發(fā)現(xiàn)程序運(yùn)行時(shí)地址0x8049580處存放的應(yīng)該是動(dòng)態(tài)連接器的入口地址,而重定位表0x10位置處和0x804957c處應(yīng)該為動(dòng)態(tài)連接器提供了解析符號(hào)需要的某些信息。
                在繼續(xù)之前先總結(jié)一下過程鏈接表和全局偏移表。上面的操作過程僅僅從“局部”看過了這兩個(gè)表,但是并沒有宏觀地看里頭的內(nèi)容。下面將宏觀的分析一下, 對(duì)于過程鏈接表:
            Quote:

            $ objdump -d -d -s -j .plt test
            08048270 <__gmon_start__@plt-0x10>:
             8048270:       ff 35 7c 95 04 08       pushl  0x804957c
             8048276:       ff 25 80 95 04 08       jmp    *0x8049580
             804827c:       00 00                   add    %al,(%eax)
                    ...

            08048280 <__gmon_start__@plt>:
             8048280:       ff 25 84 95 04 08       jmp    *0x8049584
             8048286:       68 00 00 00 00          push   $0x0
             804828b:       e9 e0 ff ff ff          jmp    8048270 <_init+0x18>

            08048290 <__libc_start_main@plt>:
             8048290:       ff 25 88 95 04 08       jmp    *0x8049588
             8048296:       68 08 00 00 00          push   $0x8
             804829b:       e9 d0 ff ff ff          jmp    8048270 <_init+0x18>

            080482a0 <printf@plt>:
             80482a0:       ff 25 8c 95 04 08       jmp    *0x804958c
             80482a6:       68 10 00 00 00          push   $0x10
             80482ab:       e9 c0 ff ff ff          jmp    8048270 <_init+0x18>


                除了該表中的第一項(xiàng)外,其他各項(xiàng)實(shí)際上是類似的。而最后一項(xiàng)080482a0 <printf@plt>和第一項(xiàng)我們都分析過,因此不難理解其他幾項(xiàng)的作用。過程鏈接表沒有辦法單獨(dú)工作,因?yàn)樗腿制票硎顷P(guān)聯(lián)的,所 以在說明它的作用之前,先從總體上來看一下全局偏移表。
            Quote:

            $ readelf -x .got.plt test

            Hex dump of section '.got.plt':
              0x08049578 ac940408 00000000 00000000 86820408 ................
              0x08049588 96820408 a6820408                   ........


                比較全局偏移表中0x08049584處開始的數(shù)據(jù)和過程鏈接表第二項(xiàng)開始的連續(xù)三項(xiàng)中push指定所在的地址,不難發(fā)現(xiàn),它們是對(duì)應(yīng)的。而 0x0804958c即push 0x10對(duì)應(yīng)的地址我們剛才提到過(下一節(jié)會(huì)進(jìn)一步分析),其他幾項(xiàng)的作用類似,都是跳回到過程鏈接表的push指令處,隨后就跳轉(zhuǎn)到過程鏈接表的第一 項(xiàng),以便解析相應(yīng)的符號(hào)(實(shí)際上過程鏈接表的第一個(gè)表項(xiàng)是進(jìn)入動(dòng)態(tài)鏈接器,而之前的連續(xù)兩個(gè)指令則傳送了需要解析的符號(hào)等信息)。另外 0x08049578和0x08049580處分別存放有傳遞給動(dòng)態(tài)連接庫的相關(guān)信息和動(dòng)態(tài)鏈接器本身的入口地址。但是還有一個(gè)地址 0x08049578,這個(gè)地址剛好是.dynamic的入口地址,該節(jié)區(qū)存放了和動(dòng)態(tài)鏈接過程相關(guān)的信息,資料[8]提到這個(gè)表項(xiàng)實(shí)際上保留給動(dòng)態(tài)鏈接 器自己使用的,以便在不依賴其他程序的情況下對(duì)自己進(jìn)行初始化,所以下面將不再關(guān)注該表項(xiàng)。
            Quote:

            $ objdump -D test | grep 080494ac
            080494ac <_DYNAMIC>:



            1.9 重定位表

                這里主要接著上面的push 0x10指令來分析。通過資料[8]發(fā)現(xiàn)重定位表包含如何修改其他節(jié)區(qū)的信息,以便動(dòng)態(tài)鏈接器對(duì)某些節(jié)區(qū)內(nèi)的符號(hào)地址進(jìn)行重定位(修改為新的地址)。那到底重定位表項(xiàng)提供了什么樣的信息呢?
                每一個(gè)重定位項(xiàng)有三部分內(nèi)容,我們重點(diǎn)關(guān)注前兩部分。
                第一部分是r_offset,這里考慮的是可執(zhí)行文件,因此根據(jù)資料發(fā)現(xiàn),它的取值是被重定位影響(可以說改變或修改)到的存儲(chǔ)單元的虛擬地址。
                第二部分是r_info,此成員給出要進(jìn)行重定位的符號(hào)表索引(重定位表項(xiàng)引用到的符號(hào)表),以及將實(shí)施的重定位類型(如何進(jìn)行符號(hào)的重定位)。(Type)。
                先來看看重定位表的具體內(nèi)容,
            Quote:

            $ readelf -r test

            Relocation section '.rel.dyn' at offset 0x238 contains 1 entries:
             Offset     Info    Type            Sym.Value  Sym. Name
            08049574  00000106 R_386_GLOB_DAT    00000000   __gmon_start__

            Relocation section '.rel.plt' at offset 0x240 contains 3 entries:
             Offset     Info    Type            Sym.Value  Sym. Name
            08049584  00000107 R_386_JUMP_SLOT   00000000   __gmon_start__
            08049588  00000207 R_386_JUMP_SLOT   00000000   __libc_start_main
            0804958c  00000407 R_386_JUMP_SLOT   00000000   printf


                僅僅關(guān)注和過程鏈接表相關(guān)的.rel.plt部分,0x10剛好是1*16+0*1,即16字節(jié),作為重定位表的偏移,剛好對(duì)應(yīng)該表的第三行。發(fā)現(xiàn)這個(gè)結(jié) 果中竟然包含了和printf符號(hào)相關(guān)的各種信息。不過重定位表中沒有直接指定符號(hào)printf,而是根據(jù)r_info部分從動(dòng)態(tài)符號(hào)表中計(jì)算出來的,注 意觀察上述結(jié)果中的Info一列的1,2,4和下面結(jié)果的Num列的對(duì)應(yīng)關(guān)系。
            Quote:

            $ readelf -s test | grep ".dynsym" -A 6
            Symbol table '.dynsym' contains 5 entries:
               Num:    Value  Size Type    Bind   Vis      Ndx Name
                 0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
                 1: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
                 2: 00000000   410 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
                 3: 08048474     4 OBJECT  GLOBAL DEFAULT   14 _IO_stdin_used
                 4: 00000000    57 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.0 (2)


                也就是說在執(zhí)行過程鏈接表中的第一項(xiàng)的跳轉(zhuǎn)指令("jmp    *0x8049580")調(diào)用動(dòng)態(tài)鏈接器以后,動(dòng)態(tài)連接器因?yàn)橛辛藀ush 0x10,從而可以通過該重定位表項(xiàng)中的r_info找到對(duì)應(yīng)符號(hào)(printf)在符號(hào)表(.dynsym)中的相關(guān)信息。
                除此之外,符號(hào)表中還有Offset(r_offset)以及Type這兩個(gè)重要信息,前者表示該重定位操作后可能影響的地址0804958c,這個(gè)地址 剛好是got表項(xiàng)的最后一項(xiàng),原來存放的是push 0x10指令的地址。這意味著,該地址處的內(nèi)容將被修改,而如何修改呢?根據(jù)Type類型R_386_JUMP_SLOT,通過資料[8]查找到該類型對(duì) 應(yīng)的說明如下(原資料有誤,下面做了修改):
            Quote:

            鏈接編輯器創(chuàng)建這種重定位類型主要是為了支持動(dòng)態(tài)鏈接。其偏移地址成員給出過程鏈接表項(xiàng)的位置。動(dòng)態(tài)鏈接器修改全局偏移表項(xiàng)的內(nèi)容,把控制傳輸給指定符號(hào)的地址。


                這說明,動(dòng)態(tài)連接器將根據(jù)該類型對(duì)全局偏移表中的最有一項(xiàng),即0804958c地址處的內(nèi)容進(jìn)行修改,修改為符號(hào)的實(shí)際地址,即printf函數(shù)在動(dòng)態(tài)連接庫的內(nèi)存映像中的地址。
                到這里,動(dòng)態(tài)鏈接的宏觀過程似乎已經(jīng)了然于心,不過一些細(xì)節(jié)還是不太清楚。
                下面先介紹動(dòng)態(tài)連接庫的創(chuàng)建,隱式調(diào)用和顯示調(diào)用,接著進(jìn)一步澄清上面還不太清楚的細(xì)節(jié),即全局偏移表中第二項(xiàng)到底傳遞給了動(dòng)態(tài)連接器什么信息?第三項(xiàng)是 否就是動(dòng)態(tài)連接器的地址?并討論通過設(shè)置LD_BIND_NOW而不采用默認(rèn)的lazy mode進(jìn)行動(dòng)態(tài)鏈接和采用lazy mode動(dòng)態(tài)鏈接的區(qū)別?

            2、動(dòng)態(tài)連接庫的創(chuàng)建和調(diào)用

                在介紹動(dòng)態(tài)符號(hào)鏈接的更多細(xì)節(jié)之前,先來了解一下動(dòng)態(tài)連接庫的創(chuàng)建和兩種使用方法,進(jìn)而引出符號(hào)解析的后臺(tái)細(xì)節(jié)。
                首先來創(chuàng)建一個(gè)簡(jiǎn)單動(dòng)態(tài)連接庫。
            代碼:



            Code:

            [Ctrl+A Select All]





            Code:

            [Ctrl+A Select All]



            演示:
            Quote:

            $ gcc -c myprintf.c
            $ gcc -shared -W1,-soname,libmyprintf.so.0 -o libmyprintf.so.0.0 myprintf.o
            $ ln -sf libmyprintf.so.0.0 libmyprintf.so.0
            $ ln -fs libmyprintf.so.0 libmyprintf.so
            $ ls
            libmyprintf.so  libmyprintf.so.0  libmyprintf.so.0.0  myprintf.c  myprintf.h  myprintf.o


                得到三個(gè)文件libmyprintf.so,libmyprintf.so.0,libmyprintf.so.0.0,這些庫暫且存放在當(dāng)前目錄下。這里有一個(gè)問題值得關(guān)注,那就是為什么要?jiǎng)?chuàng)建兩個(gè)符號(hào)鏈接呢?為了在不影響兼容性的前提下升級(jí)庫[5]。
                現(xiàn)在寫一段代碼來使用該庫,調(diào)用其中的myprintf函數(shù),這里是隱式使用該庫:在代碼中并沒有直接使用該庫,而是通過調(diào)用myprintf隱式地使用了該庫,在編譯引用該庫的可執(zhí)行文件時(shí)需要通過-l參數(shù)指定該庫的名字。


            Code:

            [Ctrl+A Select All]


                演示:
            Quote:

            $ gcc -o test test.c -lmyprintf -L./ -I./
            $ ./test       #直接運(yùn)行test,提示找不到該庫,因?yàn)閹斓哪J(rèn)搜索路徑里頭沒有包含當(dāng)前目錄
            ./test: error while loading shared libraries: libmyprintf.so: cannot open shared object file: No such file or directory
            $ LD_LIBRARY_PATH=$PWD ./test  #如果指定庫的搜索路徑,則可以運(yùn)行
            Hello World


                LD_LIBRARY_PATH環(huán)境變量使得庫可以放到某些指定的路徑下面,而無須在調(diào)用程序中顯式的指定該庫的絕對(duì)路徑,這樣避免了把程序限制在某些絕對(duì)路徑下,方便庫的移動(dòng)。
                雖然顯式調(diào)用有不便,但是能夠避免隱式調(diào)用搜索路徑的時(shí)間消耗,提高效率,除此之外,顯式調(diào)用為我們提供了一組函數(shù)調(diào)用,讓符號(hào)的重定位過程一覽無遺。


            Code:

            [Ctrl+A Select All]


                演示:
            Quote:

            $ gcc -o test1 test1.c -ldl


                這種情況下,無須包含頭文件。從這個(gè)代碼中很容易看出符號(hào)重定位的過程:
                1、首先通過dlopen找到依賴庫,并加載到內(nèi)存中,再返回該庫的handle,通過dlopen我們可以指定RTLD_LAZY采用lazy mode動(dòng)態(tài)鏈接模式,如果采用RTLD_NOW則和隱式調(diào)用時(shí)設(shè)置LD_BIN_NOW類似。
                2、找到該庫以后就是對(duì)某個(gè)符號(hào)進(jìn)行重定位,這里是確定myprintf函數(shù)的地址。
                3、找到函數(shù)地址以后就可以直接調(diào)用該函數(shù)了。
                關(guān)于dlopen,dlsym等后臺(tái)工作細(xì)節(jié)建議參考資料[15]。
                隱式調(diào)用的動(dòng)態(tài)符號(hào)鏈接過程和上面類似。下面通過一些實(shí)例來確定之前沒有明確的兩個(gè)內(nèi)容:即全局偏移表中的第二項(xiàng)和第三項(xiàng),并進(jìn)一步討論lazy mode和非lazy mode的區(qū)別。

            3、動(dòng)態(tài)鏈接過程

                因?yàn)橥ㄟ^ELF文件,我們就可以確定全局偏移表的位置,因此為了確定全局偏移表位置的第三項(xiàng)和第四項(xiàng)的內(nèi)容,有兩種辦法:
                1、通過gdb調(diào)試。
                2、直接在函數(shù)內(nèi)部打印。
                因?yàn)橘Y料[3]詳細(xì)介紹了第一種方法,這里現(xiàn)試著通過第二種方法來確定這兩個(gè)地址的值。


            Code:

            [Ctrl+A Select All]


                在寫好上面的代碼后就需要確定全局偏移表的地址,然后把該地址設(shè)置為代碼中的宏GOT。
            Quote:

            $ make got
            $ readelf -d got | grep PLTGOT
             0x00000003 (PLTGOT)                     0x8049614

             
                把地址0x8049614替換到上述代碼中,然后重新編譯運(yùn)行,查看結(jié)果。
            Quote:

            $ make got
            $ Hello World
            got2: 0xb7f376d8, got3: 0xb7f2ef10, old_addr: 0x80482da, new_addr: 0xb7e19a20
            $ ./got
            Hello World
            got2: 0xb7f1e6d8, got3: 0xb7f15f10, old_addr: 0x80482da, new_addr: 0xb7e00a20


                通過兩次運(yùn)行,發(fā)現(xiàn)全局偏移表中的這兩項(xiàng)是變化的,并且printf的地址對(duì)應(yīng)的new_addr也是變化的,說明libc和ld-linux這兩個(gè)庫啟動(dòng)以后對(duì)應(yīng)的虛擬地址并不確定。因此,無法直接跟蹤到那個(gè)地址處的內(nèi)容,還得借助調(diào)試工具,以便確認(rèn)它們。
                下面重新編譯got,加上-g參數(shù)以便調(diào)試,并通過調(diào)試確認(rèn)got2,got3,以及調(diào)用printf前后printf地址的重定位情況。
            Quote:

            $ gcc -g -o got got.c
            $ gdb ./got
            (gdb) l
            5       #include <stdio.h>
            6
            7       #define GOT 0x8049614
            8
            9       int main(int argc, char *argv[])
            10      {
            11              long got2, got3;
            12              long old_addr, new_addr;
            13
            14              got2=*(long *)(GOT+4);
            (gdb) l
            15              got3=*(long *)(GOT+8);
            16              old_addr=*(long *)(GOT+24);
            17
            18              printf("Hello World\n");
            19
            20              new_addr=*(long *)(GOT+24);
            21
            22              printf("got2: 0x%0x, got3: 0x%0x, old_addr: 0x%0x, new_addr: 0x%0x\n",
            23                                              got2, got3, old_addr, new_addr);
            24
            (gdb) break 18      #在第一個(gè)printf處設(shè)置一個(gè)斷點(diǎn)
            Breakpoint 1 at 0x80483c3: file got.c, line 18.
            (gdb) break 22      #在第二個(gè)printf處設(shè)置一個(gè)斷點(diǎn)
            Breakpoint 2 at 0x80483dd: file got.c, line 22.
            (gdb) r               #運(yùn)行到第一個(gè)printf之前會(huì)停止
            Starting program: /mnt/hda8/Temp/c/program/got

            Breakpoint 1, main () at got.c:18
            18              printf("Hello World\n");
            (gdb) x/8x 0x8049614    #查看執(zhí)行printf之前的全局偏移表內(nèi)容
            0x8049614 <_GLOBAL_OFFSET_TABLE_>:      0x08049548      0xb7f3c6d8      0xb7f33f10      0x080482aa
            0x8049624 <_GLOBAL_OFFSET_TABLE_+16>:   0xb7ddbd20      0x080482ca      0x080482da      0x00000000
            (gdb) disassemble 0x080482da  #查看GOT表項(xiàng)的最有一項(xiàng),發(fā)現(xiàn)剛好是PLT表中push指令的地址,說明此時(shí)還沒有進(jìn)行進(jìn)行符號(hào)的重定位,不過發(fā)現(xiàn)并非printf,而是puts(1)
            Dump of assembler code for function puts@plt:
            0x080482d4 <puts@plt+0>:        jmp    *0x804962c
            0x080482da <puts@plt+6>:        push   $0x18
            0x080482df <puts@plt+11>:       jmp    0x8048294 <_init+24>
            (gdb) disassemble 0xb7f33f10   #查看GOT第三項(xiàng)的內(nèi)容,剛好是dl-linux對(duì)應(yīng)的代碼,
                          #可通過nm /lib/ld-linux.so.2 | grep _dl_runtime_resolve進(jìn)行確認(rèn)
            Dump of assembler code for function _dl_runtime_resolve:
            0xb7f33f10 <_dl_runtime_resolve+0>:     push   %eax
            0xb7f33f11 <_dl_runtime_resolve+1>:     push   %ecx
            0xb7f33f12 <_dl_runtime_resolve+2>:     push   %edx
            (gdb) x/8x 0xb7f3c6d8   #查看GOT表第二項(xiàng)處的內(nèi)容,看不出什么特別的信息,反編譯時(shí)提示無法反編譯
            0xb7f3c6d8:     0x00000000      0xb7f39c3d      0x08049548      0xb7f3c9b8
            0xb7f3c6e8:     0x00000000      0xb7f3c6d8      0x00000000      0xb7f3c9a4
            (gdb) break *(0xb7f33f10)  #在*(0xb7f33f10)指向的代碼處設(shè)置一個(gè)斷點(diǎn),確認(rèn)它是否被執(zhí)行
            break *(0xb7f33f10)
            Breakpoint 3 at 0xb7f3cf10
            (gdb) c
            Continuing.

            Breakpoint 3, 0xb7f3cf10 in _dl_runtime_resolve () from /lib/ld-linux.so.2
            (gdb)  c            #繼續(xù)運(yùn)行,直到第二次調(diào)用printf
            Continuing.
            Hello World

            Breakpoint 2, main () at got.c:22
            22              printf("got2: 0x%0x, got3: 0x%0x, old_addr: 0x%0x, new_addr: 0x%0x\n",
            (gdb) x/8x 0x8049614   #再次查看GOT表項(xiàng),發(fā)現(xiàn)GOT表的最后一項(xiàng)的值應(yīng)該被修改
            0x8049614 <_GLOBAL_OFFSET_TABLE_>:      0x08049548      0xb7f3c6d8      0xb7f33f10      0x080482aa
            0x8049624 <_GLOBAL_OFFSET_TABLE_+16>:   0xb7ddbd20      0x080482ca      0xb7e1ea20      0x00000000
            (gdb) disassemble 0xb7e1ea20   #查看GOT表最后一項(xiàng),發(fā)現(xiàn)變成了puts函數(shù)的代碼,說明進(jìn)行了符號(hào)puts的重定位(2)
            Dump of assembler code for function puts:
            0xb7e1ea20 <puts+0>:    push   %ebp
            0xb7e1ea21 <puts+1>:    mov    %esp,%ebp
            0xb7e1ea23 <puts+3>:    sub    $0x1c,%esp


                通過演示發(fā)現(xiàn)一個(gè)問題(1)(2),即本來調(diào)用的是printf,為什么會(huì)進(jìn)行puts的重定位呢?通過gcc -S參數(shù)編譯生成匯編代碼后發(fā)現(xiàn),gcc把printf替換成了puts,因此不難理解程序運(yùn)行過程為什么對(duì)puts進(jìn)行了重定位。
                從演示中不難發(fā)現(xiàn),當(dāng)符號(hào)被使用到時(shí)才進(jìn)行重定位。因?yàn)橥ㄟ^調(diào)試發(fā)現(xiàn)在執(zhí)行printf之后,GOT表項(xiàng)的最后一項(xiàng)才被修改為printf(確切的說是puts)的地址。這就是所謂的lazy mode動(dòng)態(tài)符號(hào)鏈接方式。
                除此之外,我們?nèi)菀装l(fā)現(xiàn)GOT表第三項(xiàng)確實(shí)是ld-linux.so中的某個(gè)函數(shù)地址,并且發(fā)現(xiàn)在執(zhí)行printf語句之前,先進(jìn)入了ld- linux.so的_dl_runtime_resolve函數(shù),而且在它返回之后,GOT表的最后一項(xiàng)才變?yōu)閜rintf(puts)的地址。
                本來打算通過第一個(gè)斷點(diǎn)確認(rèn)第二次調(diào)用printf時(shí)不再需要進(jìn)行動(dòng)態(tài)符號(hào)鏈接的,不過因?yàn)間cc把第一個(gè)替換成了puts,所以這里沒有辦法繼續(xù)調(diào)試。 如果想確認(rèn)這個(gè),你可以通過寫兩個(gè)一樣的printf語句看看。實(shí)際上第一次鏈接以后,GOT表的第三項(xiàng)已經(jīng)修改了,當(dāng)下次再進(jìn)入過程鏈接表,并執(zhí)行 “jmp *(全局偏移表中某一個(gè)地址)”指令時(shí),*(全局偏移表中某一個(gè)地址)已經(jīng)被修改為了對(duì)應(yīng)符號(hào)的實(shí)際地址,這樣jmp語句會(huì)自動(dòng)跳轉(zhuǎn)到符號(hào)的地址處運(yùn)行, 執(zhí)行具體的函數(shù)代碼,因此無須再進(jìn)行重定位。
                到現(xiàn)在GOT表中只剩下第二項(xiàng)還沒有被確認(rèn),通過資料[3]我們發(fā)現(xiàn),該項(xiàng)指向一個(gè)link_map類型的數(shù)據(jù),是一個(gè)鑒別信息,具體作用對(duì)我們來說并不是很重要,如果想了解,請(qǐng)參考資料[16]。

                下面通過設(shè)置LD_BIND_NOW再運(yùn)行一下got程序并查看結(jié)果,比較它與默認(rèn)的動(dòng)態(tài)鏈接方式(lazy mode)的異同。
            Quote:

            $ LD_BIND_NOW=1 ./got  #設(shè)置LD_BIND_NOW環(huán)境變量的運(yùn)行結(jié)果
            Hello World
            got2: 0x0, got3: 0x0, old_addr: 0xb7e61a20, new_addr: 0xb7e61a20
            $ ./got               #默認(rèn)情況下的運(yùn)行結(jié)果
            Hello World
            got2: 0xb7f806d8, got3: 0xb7f77f10, old_addr: 0x80482da, new_addr: 0xb7e62a20


                通過比較容易發(fā)現(xiàn),在非lazy mode(設(shè)置LD_BIND_NOW后)下,程序運(yùn)行之前符號(hào)的地址就已經(jīng)被確定,即調(diào)用printf之前GOT表的最后一項(xiàng)已經(jīng)被確定為了 printf函數(shù)對(duì)應(yīng)的地址,即0xb7e61a20,因此在程序運(yùn)行之后,GOT表的第二項(xiàng)和第三項(xiàng)就保持為0,因?yàn)榇藭r(shí)不再需要它們進(jìn)行符號(hào)的重定位 了。通過這樣一個(gè)比較,就更容易理解lazy mode的特點(diǎn)了:在用到的時(shí)候才解析。

                到這里,符號(hào)動(dòng)態(tài)鏈接的細(xì)節(jié)基本上就已經(jīng)清楚了。

            參考資料:

            [1] LINUX系統(tǒng)中動(dòng)態(tài)鏈接庫的創(chuàng)建與使用
            http://www.ccw.com.cn/htm/app/linux/develop/01_8_6_2.asp
            [2] LINUX動(dòng)態(tài)鏈接庫高級(jí)應(yīng)用
            http://www.vchome.net/tech/dll/dll9.htm
            [3] ELF動(dòng)態(tài)解析符號(hào)過程(修訂版)
            http://elfhack.whitecell.org/mydocs/ELF_symbol_resolve_process1.txt
            [4] 如何在 Linux 下調(diào)試動(dòng)態(tài)鏈接庫
            http://unix-cd.com/unixcd12/article_5065.html
            [5] Dissecting shared libraries
            http://www.ibm.com/developerworks/linux/library/l-shlibs.html
            [6] 關(guān)于Linux和Unix動(dòng)態(tài)連接庫的安全
            http://fanqiang.chinaunix.net/safe/system/2007-02-01/4870.shtml
            [7] Linux系統(tǒng)下解析Elf文件DT_RPATH后門
            http://article.pchome.net/content-323084.html
            [8] ELF 文件格式分析
            http://162.105.203.48/web/gaikuang/submission/TN05.ELF.Format.Summary.pdf
            [9] C語言程序緩沖區(qū)注入分析(第二部分:緩沖區(qū)溢出和注入實(shí)例)
            http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1540.html
            [10] GCC編譯的背后(第二部分:匯編和鏈接)
            http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1546.html
            [11] What is Linux-gate.so.1
            http://www.trilithium.com/johan/2005/08/linux-gate/
            http://isomerica.net/archives/2007/05/28/what-is-linux-gateso1-and-why-is-it-missing-on-x86-64/
            http://www.linux010.cn/program/Linux-gateso1-DeHanYi-pcee6103.htm
            [12] Linux下緩沖區(qū)溢出攻擊的原理及對(duì)策
            http://www.ibm.com/developerworks/cn/linux/l-overflow/index.html
            [13] Linux命令行上程序執(zhí)行的那一剎那
            http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1543.html
            [14] C語言程序緩沖區(qū)注入分析(第一部分:進(jìn)程的內(nèi)存映像)
            http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1539.html
            [15] Intel平臺(tái)下Linux中ELF文件動(dòng)態(tài)鏈接的加載、解析及實(shí)例分析
            http://www.ibm.com/developerworks/cn/linux/l-elf/part1/index.html
            http://www.ibm.com/developerworks/cn/linux/l-elf/part2/index.html
            [16] ELF file format and ABI
            http://www.x86.org/ftp/manuals/tools/elf.pdf
            http://www.muppetlabs.com/~breadbox/software/ELF.txt

            posted on 2008-03-14 15:30 隨意門 閱讀(815) 評(píng)論(0)  編輯 收藏 引用


            只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            色综合久久久久无码专区| 欧洲国产伦久久久久久久| 国产欧美久久久精品| 精品久久久无码中文字幕| 久久无码国产专区精品| 国内精品久久久久| 青青青青久久精品国产h久久精品五福影院1421 | 欧美日韩成人精品久久久免费看 | 狠狠色狠狠色综合久久| 99久久精品午夜一区二区| 色诱久久av| 99久久99久久精品国产片果冻 | 国产亚洲色婷婷久久99精品| 亚洲国产另类久久久精品小说| 久久婷婷综合中文字幕| 亚洲国产另类久久久精品黑人| 久久99精品久久久久久野外 | 国产精品xxxx国产喷水亚洲国产精品无码久久一区 | 亚洲精品国产美女久久久| 国产精品无码久久综合网| 国产精品久久久久影院色| 无码久久精品国产亚洲Av影片| 色欲综合久久躁天天躁| 2021国产成人精品久久| 亚洲国产精品婷婷久久| 久久91精品国产91久久户| 久久AV高清无码| 久久人人爽人人爽人人片av高请| 久久久久久精品免费免费自慰| 久久强奷乱码老熟女网站| 久久久久人妻一区精品果冻| 国内精品久久久久久久亚洲| 国产精品成人99久久久久| 久久久久久国产a免费观看不卡| 久久综合狠狠色综合伊人| 欧美精品一区二区精品久久| 99久久免费国产精精品| 久久精品国产精品青草app| 国产农村妇女毛片精品久久| 久久免费视频一区| 久久人妻AV中文字幕|