2015年12月17日 星期四

Shared Library 中 PLT 和 GOT 的使用機制

PLT (Procedure Linkage Table) 和 GOT (Global Offset Table) 是 GCC 中實作shared library的重要元素。至於為何一定要這兩個表格?



  • GOT的功用

以gcc 內建的libc.so為例,因為你不可能用到libc.so內的所有函式,所以其實不用知道所有函式在記憶體內的絕對位置。其中GOT只列出你會用到的 fuction 或是 global variable的絕對位置。這樣會節省許多解析時間。

以下面的圖為例,圖裡面是一個簡化的例子,這和實際編譯情況不同,但適合說明GOT。
當我要從main()內去呼叫shared library中的 foo(),編譯器會先產生binary檔案,在這裡檔名我設定為 a.out,原先 main.c中的 foo()被替換為 "b @GOT+0x14",功用是會跳到GOT內所記錄的位置去,地址就是GOT表格開始地址加上0x14。內容就是 0x76fc6578,和這個地址也就是 foo()在 shared library 的絕對位置。







  • PLT的功用

既然GOT已經列出需要的東西,那照理說工作就結束了,還需要PLT幹麻?
試想,當你的程式也大到跟 libc.so 一樣大時,你可能會呼叫上百個libc的函式,所以當你的程式載入記憶體時,linker 會解析你需要的函式,這也許會花上一些時間,並導致使用者認為反應很慢。為了解決這個問題,所以GCC 改為呼叫shared library的函式前,才去把絕對位置填入GOT內。而PLT的功用就是呼叫 linker去填入 GOT,這個機制就是延遲解析 (lazy binding)。

要注意 lazy binding和 lazy loading的差異。Lazy loading 是透過 dlopen()等函式將library動態載入記憶體內。GCC並沒有自動提供lazy loading的機制,所以的shared library都是一次載入到記憶體內,除非你使用dlopen()。

用下面的幾張圖來解釋:

Step 1: 呼叫 Linker

在解釋動作前,先看一下 GOT表格,其中 GOT+0x14的內容暫時填入 linker 的位置,這需要 linker 去解析然後回填到GOT+0x14。原先main()要呼叫的 foo()被替換成 "foo()@plt" 的函式,而這個函式又會轉跳到 GOT+0x14的地址去。請仔細看,這個地址是要跳去 linker,而非foo(),因為這時候 foo()的地址還沒有被解析。




Step 2: 解析 foo() 的地址

Linker "ld-2.so"會把 foo()在 shared library的絕對位址填入 GOT+0x14的記憶體內。請注意,ld代表的意思是 Linker/Loader。





Step 3: 轉跳到 foo()

接著 Linker 會轉跳到 foo(),大功告成。



  • 例子 — Overview
上面介紹了GOT和PLT的概念,底下用一個例子看看實際的結果。
例子是參考書本 <<程式設計師的自我修養 — 連結.載入.程式庫>> 中第 7.3.3節的例子。

範例可以從這邊下載


例子雖然很簡單,但是目的卻很有趣,一共有4個:

    Type 1: Inner-module call
    Type 2: Inner-module data access
    Type 3: Inter-module call
    Type 4: Inter-module data access


再檢視這四個目的前,先編譯並反組譯這些檔案 (實驗的環境是 arm cortex-a7 32bits、gcc 4.6.3)。首先產生 Lib_a.o 和 Lib_b.o

$ gcc -g -shared -fPIC Lib_a.c -o Lib_a.o
$ gcc -g -shared -fPIC Lib_b.c -o Lib_b.o

接著產生執行檔

$ gcc -g main.c ./Lib_a.o ./Lib_b.o

然後反組譯 Lib_a.o、Lib_b.o、a.out

$ objdump -sSdD a.out > objdump.txt
$ objdump -sSdD Lib_a.o > objdump.txt-Lib_a
$ objdump -sSdD Lib_b.o > objdump.txt-Lib_b

做完前置作業後,先看function call相關的 type 1 和 type 3的流程,也就是 inner-module call和 inter-module call。在開始檢視前,照直觀的想法,inter-module call一定會用到 GOT,而inner-module call因為不需要轉跳,所以應該不需要用到GOT。我們可以使用 "readelf  -r" 這個指令去看看 relocation section。這個section的功用就是標示 GOT每個欄位的定義。

先看 main.c的GOT

$ readelf -r a.out 

Relocation section '.rel.dyn' at offset 0x41c contains 1 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00010708  00000115 R_ARM_GLOB_DAT    00000000   __gmon_start__

Relocation section '.rel.plt' at offset 0x424 contains 4 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
000106f8  00000d16 R_ARM_JUMP_SLOT   00000000   __libc_start_main
000106fc  00000116 R_ARM_JUMP_SLOT   00000000   __gmon_start__
00010700  00000516 R_ARM_JUMP_SLOT   00000000   foo

00010704  00000916 R_ARM_JUMP_SLOT   00000000   abort

其中的"Relocation section '.rel.dyn'" 表示的是資料欄位,而"Relocation section '.rel.plt'"表示的則是function欄位。這也可以從"R_ARM_GLOB_DAT" 和 "R_ARM_JUMP_SLOT" 看出來。

進一步去看各個symbol,

  1. __gmon_start__ : 查看效能用的,如果編譯時加上 -pg選項,這個symbol就會有作用,譬如 "gcc -pg main.c" (解說)
  2. __libc_start_main : 這是c程式啟動前一定會跑的程式,為的是載入需要的library。(解說)
  3. foo: 這是 Lib_a.o的程式
  4. abort: 這是 C90標準定義的預設function (參考來源)


雖然有許多沒看過的function,但是 foo()還是預期般的出現。

接著看 Lib_a.o的relocation section

$ readelf -r Lib_a.o

Relocation section '.rel.dyn' at offset 0x3bc contains 7 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00008598  00000017 R_ARM_RELATIVE   
0000859c  00000017 R_ARM_RELATIVE   
000086b8  00000017 R_ARM_RELATIVE   
000086a8  00000315 R_ARM_GLOB_DAT    00000000   __cxa_finalize
000086ac  00000415 R_ARM_GLOB_DAT    00000000   b
000086b0  00000515 R_ARM_GLOB_DAT    00000000   __gmon_start__
000086b4  00000715 R_ARM_GLOB_DAT    00000000   _Jv_RegisterClasses

Relocation section '.rel.plt' at offset 0x3f4 contains 4 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00008698  00000316 R_ARM_JUMP_SLOT   00000000   __cxa_finalize
0000869c  00000a16 R_ARM_JUMP_SLOT   00000530   bar
000086a0  00000516 R_ARM_JUMP_SLOT   00000000   __gmon_start__

000086a4  00000616 R_ARM_JUMP_SLOT   00000000   ext


這個檔案少了abort()但是出現一些新東西

  1. __cxa_finalize : 當shared library unload時,會呼叫他。(參考資料)
  2. b : 這是Lib_b.o內的全域變數
  3. _Jv_RegisterClasses: 為了使Java能呼叫c library的stub function。記住,gcc內部有java相關的tool (參考資料)
  4. bar : 這是Lib_a.o內的function
  5. ext : 這是Lib_b.o內的function  

有趣的事情可以看出來,即便 bar() 在 Lib_a.o內,也需要GOT,和之前的猜想不一樣,所以"Type 2: Inner-module data access"是需要GOT。另外,變數 "static int a" 並沒有在GOT內,非常合理。


繼續看最後一個 shared library  "Lib_b.o"的relocation section:

$ readelf -r Lib_b.o

Relocation section '.rel.dyn' at offset 0x3a4 contains 7 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00008594  00000017 R_ARM_RELATIVE   
00008598  00000017 R_ARM_RELATIVE   
000086b0  00000017 R_ARM_RELATIVE   
000086a0  00000315 R_ARM_GLOB_DAT    00000000   __cxa_finalize
000086a4  00000e15 R_ARM_GLOB_DAT    000086b8   b
000086a8  00000515 R_ARM_GLOB_DAT    00000000   __gmon_start__
000086ac  00000615 R_ARM_GLOB_DAT    00000000   _Jv_RegisterClasses

Relocation section '.rel.plt' at offset 0x3dc contains 3 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00008694  00000316 R_ARM_JUMP_SLOT   00000000   __cxa_finalize
00008698  00000416 R_ARM_JUMP_SLOT   00000000   printf

0000869c  00000516 R_ARM_JUMP_SLOT   00000000   __gmon_start__


裡面只有兩個有興趣的symbol

  1. b : Lib_b.o本身的全域變數
  2. printf : libc提供的function
即便 int b就在Lib_b.o內,也需要GOT來存取。


  • 例子 — Trace Code  ( Main.c )

實際Trace Code來看看 GOT + PLT的用途

先看 main.c的反組譯結果


513 00008540 <main>:
514 #include <stdio.h>
515 #include "Lib_a.h"
516 
517 int main(int argc, char* argv[])
518 {
519     8540:   e92d4800    push    {fp, lr}
520     8544:   e28db004    add fp, sp, #4
521     8548:   e24dd008    sub sp, sp, #8
522     854c:   e50b0008    str r0, [fp, #-8]
523     8550:   e50b100c    str r1, [fp, #-12]
524     foo();  
525     8554:   ebffffc8    bl  847c <foo@plt>
526 }
527     8558:   e1a00003    mov r0, r3
528     855c:   e24bd004    sub sp, fp, #4
529     8560:   e8bd8800    pop {fp, pc}


Line 525可以看到為了呼叫foo()直接跳到0x847c的位置,但是註解寫的function名稱是foo@plt,有點奇怪。不過直接去看0x847c

450 0000847c <foo@plt>:
451     847c:   e28fc600    add ip, pc, #0, 12
452     8480:   e28cca08    add ip, ip, #8, 20  ; 0x8000
453     8484:   e5bcf27c    ldr pc, [ip, #636]! ; 0x27c



這個arm的程式碼有點煩,不過一行行解讀就行了,
Line 451:  add ip, pc, #0, 12
其中pc指的是下兩行指令的位址,也就是 Line 453標注的位置 0x8484。整個指令的作用為 "ip = pc + 0x0 << 12",所以 ip = 0x8484 + 0x0 = 0x8484。

接著往下一行看
Line 452: add ip, ip, #8, 20
指令等校於 "ip = ip + 0x8 << 20",因為我使用的機器是 32bit arm cortex-a7,所以向右做circular bit shift等於是向右位移 (32-20 = 12) bit,所以指令變為 "ip = ip + 0x8 << 12 = ip + 0x8000 = 0x8484 + 0x8000 = 0x10484"

再往下一行看
Line 453: ldr pc, [ip, #636]!
pc = [ip + d'636] = [0x10484 + d'636]  =[0x10700]

看一下0x10700內存的值事什麼:
126 Contents of section .got:
127  106ec ec050100 00000000 00000000 50840000  ............P...
128  106fc 50840000 50840000 50840000 00000000  P...P...P.......

所以 [0x10700]是0x8450,注意這是little endian的排列方式。所以pc會載入0x8450嗎??
記得這只是反組譯的內容,而非 linker載入程式後的結果,有可能linker會去修改GOT內的值,保險起見,還是透過 gdb去看看這個值。


$ gdb ./a.out
(gdb) list
1 #include <stdio.h>
2 #include "Lib_a.h"
3
4 int main(int argc, char* argv[])
5 {
6 foo();
7 }


反組譯 main()

(gdb) disassemble main
Dump of assembler code for function main:
   0x00008540 <+0>: push {r11, lr}
   0x00008544 <+4>: add r11, sp, #4
   0x00008548 <+8>: sub sp, sp, #8
   0x0000854c <+12>: str r0, [r11, #-8]
   0x00008550 <+16>: str r1, [r11, #-12]
   0x00008554 <+20>: bl 0x847c
   0x00008558 <+24>: mov r0, r3
   0x0000855c <+28>: sub sp, r11, #4
   0x00008560 <+32>: pop {r11, pc}
End of assembler dump.

查看要執行的程式碼
(gdb) x/10wi 0x847c
   0x847c: add r12, pc, #0, 12
   0x8480: add r12, r12, #8, 20 ; 0x8000
   0x8484: ldr pc, [r12, #636]! ; 0x27c
   0x8488: add r12, pc, #0, 12
   0x848c: add r12, r12, #8, 20 ; 0x8000
   0x8490: ldr pc, [r12, #628]! ; 0x274
   0x8494 <_start>: mov r11, #0
   0x8498 <_start+4>: mov lr, #0
   0x849c <_start+8>: pop {r1} ; (ldr r1, [sp], #4)
   0x84a0 <_start+12>: mov r2, sp

將break point設在 0x8484
(gdb) b *0x8484
Breakpoint 1 at 0x8484

開始 run
(gdb) r
Starting program: /home/pi/tmp/c_language/linkage_loader_library/ch7_dynamic_linkage/ch7.3.3-fPIC/a.out 

Breakpoint 1, 0x00008484 in ?? ()

重新印出組與確認是否有 break在指定的位置
(gdb) x/10wi 0x847c
   0x847c: add r12, pc, #0, 12
   0x8480: add r12, r12, #8, 20 ; 0x8000
=> 0x8484: ldr pc, [r12, #636]! ; 0x27c
   0x8488: add r12, pc, #0, 12
   0x848c: add r12, r12, #8, 20 ; 0x8000
   0x8490: ldr pc, [r12, #628]! ; 0x274
   0x8494 <_start>: mov r11, #0
   0x8498 <_start+4>: mov lr, #0
   0x849c <_start+8>: pop {r1} ; (ldr r1, [sp], #4)
   0x84a0 <_start+12>: mov r2, sp

列印出 GOT 內容
(gdb) x/8wx 0x106ec
0x106ec <_GLOBAL_OFFSET_TABLE_>: 0x000105ec 0x76fff958 0x76fedbe4 0x76e9470c
0x106fc <_GLOBAL_OFFSET_TABLE_+16>: 0x00008450 0x00008450 0x00008450 0x00000000

所以確認0x10700的內容還是 0x8450,回頭去看 0x8450在反組譯的內容是什麼

433 00008450 <__libc_start_main@plt-0x14>:
434     8450:   e52de004    push    {lr}        ; (str lr, [sp, #-4]!)
435     8454:   e59fe004    ldr lr, [pc, #4]    ; 8460 <_init+0x1c>
436     8458:   e08fe00e    add lr, pc, lr
437     845c:   e5bef008    ldr pc, [lr, #8]!
438     8460:   0000828c    andeq   r8, r0, ip, lsl #5

如果單步執行到 Line 437後可以發現會馬上轉跳到[0x106f4] = [GOT+0x8] = 0x76fedbe4,這就是Linker (ld-linux-armhf.so.3)。因為GCC Linker相關的code很多,還沒有能力去看,但可以看看foo()執行後的GOT的內容。

(gdb) list main
1 #include <stdio.h>
2 #include "Lib_a.h"
3
4 int main(int argc, char* argv[])
5 {
6 foo();
7 }
(gdb) b 7
Breakpoint 1 at 0x8558: file main.c, line 7.
(gdb) r
Starting program: /home/pi/tmp/c_language/linkage_loader_library/ch7_dynamic_linkage/ch7.3.3-fPIC/a.out 
Calling from Lib_b.c: ext

Breakpoint 1, main (argc=1, argv=0x7efff794) at main.c:7
7 }
(gdb) x/8wx 0x106ec
0x106ec <_GLOBAL_OFFSET_TABLE_>: 0x000105ec 0x76fff958 0x76fedbe4 0x76e9470c
0x106fc <_GLOBAL_OFFSET_TABLE_+16>: 0x00008450 0x76fc6578 0x00008450 0x00000000


可以發現0x10700的內容從0x8450變成0x76fc6578,先看看這個記憶體附近的內容
(gdb) x/8wi 0x76fc6578
   0x76fc6578 <foo>: push {r11, lr}
   0x76fc657c <foo+4>: add r11, sp, #4
   0x76fc6580 <foo+8>: bl 0x76fc6440
   0x76fc6584 <foo+12>: bl 0x76fc6458
   0x76fc6588 <foo+16>: pop {r11, pc}
   0x76fc658c <_fini>: push {r3, lr}
   0x76fc6590 <_fini+4>: pop {r3, pc}
   0x76fc6594 <__FRAME_END__>: andeq r0, r0, r0

看起來很像一段程式碼,試著去看看是否是一個symbol
(gdb) info symbol 0x76fc6578
foo in section .text of ./Lib_a.o

的確是 foo(),這樣就算解決"Type 3: Inter-module call"的追蹤。

  • 例子 — Type 1: Inner-module call  ( Lib_a.c )
如果用同樣的方式去追蹤呼叫 foo()內呼叫bar()的流程,也可以發現"Type 1: Inner-module call"也是用同樣的方式。

先看 foo()的反組譯
551 void foo()
552 {
553  578:   e92d4800    push    {fp, lr}
554  57c:   e28db004    add fp, sp, #4
555     bar();
556  580:   ebffffae    bl  440 <bar@plt>
557     ext();
558  584:   ebffffb3    bl  458 <ext@plt>
559 }
560  588:   e8bd8800    pop {fp, pc}

可以看出呼叫 inner-module call時,是透過PLT,轉跳到 0x440的位置。

馬上反組譯 bar@plt
446 00000440 <bar@plt>:
447  440:   e28fc600    add ip, pc, #0, 12
448  444:   e28cca08    add ip, ip, #8, 20  ; 0x8000
449  448:   e5bcf254    ldr pc, [ip, #596]! ; 0x254

可以看出馬上轉跳到 [0x448 + 0x8000 + 0x254] 的位置,也就是 [0x869c] = [GOT+0x10]

先看反組譯.got的內容
121 Contents of section .got:
122  868c a4850000 00000000 00000000 20040000  ............ ...
123  869c 20040000 20040000 20040000 00000000   ... ... .......
124  86ac 00000000 00000000 00000000           ............

再比照程式跑起後.got的內容發現除了加上0x76fc6000的offset外,指到的function一樣是
(gdb) x/11wx 0x76fce68c
0x76fce68c: 0x000085a4 0x76ffa000 0x76fedbe4 0x76fc6420
0x76fce69c: 0x76fc6420 0x76fc6420 0x76fc6420 0x76eaf104
0x76fce6ac: 0x76fc56b8 0x00000000 0x00000000

反組譯看 0x420的位置
434 00000420 <__cxa_finalize@plt-0x14>:
435  420:   e52de004    push    {lr}        ; (str lr, [sp, #-4]!)
436  424:   e59fe004    ldr lr, [pc, #4]    ; 430 <_init+0x1c>
437  428:   e08fe00e    add lr, pc, lr
438  42c:   e5bef008    ldr pc, [lr, #8]!
439  430:   0000825c    andeq   r8, r0, ip, asr r2

程式會跳到 [0x825c + 0x430 + 0x8] 的位置,也就是 [0x8694] = [GOT+0x8],實際跑起時的地址就是0x76fedbe4 ,查看這個位置發現沒有找到symbol name !!

(gdb) info symbol 0x76fedbe4
No symbol matches 0x76fedbe4.

單步執行程式,直到pc載入這個地址,發現會跳到 linker
(gdb) si
Cannot access memory at address 0x0
0x76fedbe4 in ?? () from /lib/ld-linux-armhf.so.3

因為追不下去了,所以直接break在 bar()開始的位置,並且看看 .got的內容
0x76fce68c: 0x000085a4 0x76ffa000 0x76fedbe4 0x76fc6420
0x76fce69c: 0x76fc6530 0x76fc6420 0x76fc6420 0x76eaf104
0x76fce6ac: 0x76fc56b8 0x00000000 0x00000000

發覺到 [GOT+0x10] 的值變了,查看symbol可以發現,變成 bar()的地址
(gdb) info symbol 0x76fc6530
bar in section .text of ./Lib_a.o

這個流程和 main.c中呼叫 foo()的流程一樣。

  • 例子 — Type 2: Inner-module data access  ( Lib_a.c )
直接看 bar()的反組譯,看他如何存取data

525 void bar(void)
526 {
......................................
531     a = 1;
532  540:   e59f3028    ldr r3, [pc, #40]   ; 570 <bar+0x40>
533  544:   e08f3003    add r3, pc, r3
534  548:   e3a01001    mov r1, #1
535  54c:   e5831000    str r1, [r3]
......................................
541 }
542  560:   e28bd000    add sp, fp, #0
543  564:   e8bd0800    ldmfd   sp!, {fp}
544  568:   e12fff1e    bx  lr
545  56c:   00008148    andeq   r8, r0, r8, asr #2
546  570:   00008174    andeq   r8, r0, r4, ror r1
547  574:   00000020    andeq   r0, r0, r0, lsr #32

可以看出來存取的記憶體是 0x8174 + 0x54c = 0x86c0,而這個地址在 .bss section內,所以不需要GOT
657 Disassembly of section .bss:
.....
662 000086c0 <a>:
663     86c0:   00000000    andeq   r0, r0, r0


  • 例子 — Type 4: Inter-module data access  ( Lib_a.c )
同樣的去反組譯bar()

526 {
527  530:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)
528  534:   e28db000    add fp, sp, #0
529  538:   e59f202c    ldr r2, [pc, #44]   ; 56c <bar+0x3c>
530  53c:   e08f2002    add r2, pc, r2
...........................................
536     b = 2;
537  550:   e59f301c    ldr r3, [pc, #28]   ; 574 <bar+0x44>
538  554:   e7923003    ldr r3, [r2, r3]
539  558:   e3a02002    mov r2, #2
540  55c:   e5832000    str r2, [r3]
541 }
542  560:   e28bd000    add sp, fp, #0
543  564:   e8bd0800    ldmfd   sp!, {fp}
544  568:   e12fff1e    bx  lr
545  56c:   00008148    andeq   r8, r0, r8, asr #2
546  570:   00008174    andeq   r8, r0, r4, ror r1
547  574:   00000020    andeq   r0, r0, r0, lsr #32
在Line 530時,r2 = 0x8148 + 0x544 = 0x868c。
接著可以看出來 b的地址在 [0x868c + 0x20] = [0x86ac] = [GOT+0x20]。而實際跑起來時,發現在這不像function一樣需要lazy binding,在main.c內的.text開始跑之前,就已經知道這個地址了。

可以break在 _start去查看。
(gdb) b *0x8494
Breakpoint 1 at 0x8494
(gdb) r
Starting program: /home/pi/tmp/c_language/linkage_loader_library/ch7_dynamic_linkage/ch7.3.3-fPIC/a.out 

Breakpoint 1, 0x00008494 in _start ()
(gdb) x/11wx 0x76fce68c
0x76fce68c: 0x000085a4 0x76ffa000 0x76fedbe4 0x76fc6420
0x76fce69c: 0x76fc6420 0x76fc6420 0x76fc6420 0x76eaf104
0x76fce6ac: 0x76fc56b8 0x00000000 0x00000000
(gdb) info symbol 0x76fc56b8
b in section .bss of ./Lib_b.o


  • 例子 — Type 3: Inter-module call  ( Lib_a.c )
反組譯 foo()
551 void foo()
552 {
553  578:   e92d4800    push    {fp, lr}
554  57c:   e28db004    add fp, sp, #4
555     bar();
556  580:   ebffffae    bl  440 <bar@plt>
557     ext();
558  584:   ebffffb3    bl  458 <ext@plt>
559 }

接著查看ext@plt
456 00000458 <ext@plt>:
457  458:   e28fc600    add ip, pc, #0, 12
458  45c:   e28cca08    add ip, ip, #8, 20  ; 0x8000
459  460:   e5bcf244    ldr pc, [ip, #580]! ; 0x244
程式會跳到 [0x460 + 0x8000 + 0x244] = [0x86a4] = [GOT+0x18]

121 Contents of section .got:
122  868c a4850000 00000000 00000000 20040000  ............ ...
123  869c 20040000 20040000 20040000 00000000   ... ... .......
124  86ac 00000000 00000000 00000000           ............

很熟悉的 0x420位置,確認實際run起來是否是一樣
(gdb) x/11wx 0x76fce68c
0x76fce68c: 0x000085a4 0x76ffa000 0x76fedbe4 0x76fc6420
0x76fce69c: 0x76fc6530 0x76fc6420 0x76fc6420 0x76eaf104
0x76fce6ac: 0x76fc56b8 0x00000000 0x00000000
和呼叫 bar()時候是一樣的

馬上看跳到ext()時,.got section是否一樣。
(gdb) b Lib_b.c:6
No source file named Lib_b.c.
Make breakpoint pending on future shared library load? (y or [n]) y

Breakpoint 1 (Lib_b.c:6) pending.
(gdb) r
Starting program: /home/pi/tmp/c_language/linkage_loader_library/ch7_dynamic_linkage/ch7.3.3-fPIC/a.out 

Breakpoint 1, ext () at Lib_b.c:6
6 {
(gdb) x/11wx 0x76fce68c
0x76fce68c: 0x000085a4 0x76ffa000 0x76fedbe4 0x76fc6420
0x76fce69c: 0x76fc6530 0x76fc6420 0x76fbd504 0x76eaf104
0x76fce6ac: 0x76fc56b8 0x00000000 0x00000000
(gdb) info symbol 0x76fbd504
ext in section .text of ./Lib_b.o

看來等到叫起 linker後,就會把實際的值回填到GOT+0x18內。所以Type3: inter-module call 和 Type 1: inner-module call的實作方式一樣。

沒有留言:

張貼留言