靜態連結之深度解剖

陳(程)序員發表於2020-10-16

靜態連結一般分為兩步,第一步是空間和地址的分配,第二步是符號解析和重定位。
本篇以arm-himix200-linux為工具鏈,add_m.c和main.c程式碼作為例子介紹相關內容。
add_m.c的程式碼

#include<stdio.h>
int sun=9;
int add(int a,int b)
{
	sun+=1;
	printf("sun = %d\n",sun);
	return a+b;
}
int sub(int a,int b)
{
	add(a,b);
	return a-b;
}

int sub1(int a,int b)
{
	sub(a,b);
	return a-b;
}

main.c的程式碼

#include<stdio.h>
int mun = 10;
extern sun;
int main(int argc , char**argv)
{
	printf("chen_test\n");
	int ad = 40,su =20;
	printf("add = %d\n",add(ad,su));
	printf("mun = %d\n",mun);
	printf("sun = %d\n",sun);
	return 0;
}

編譯、連結

arm-himix200-linux-gcc -c -o add_m.o add_m.c    編譯
arm-himix200-linux-gcc -c -o main.o main.c      編譯
arm-himix200-linux-gcc  -o main main.o add_m.o  連結

1、空間和地址的分配
靜態連結的過程,其實就是把幾個輸入目標檔案加工合併成一個輸出檔案,連結器會計算所有輸入目標檔案的各個段(資料段、程式碼段等)的長度、位置及其屬性,然後把相似段合併,合併過程中,各個目標檔案的各個段的內容的位置是相對不變的,只是把每個相似段拼湊在一塊,這樣可以保持每一塊段裡的內容是可以相對定址的,知道段的起始位置,就可以通過相對定址,即偏移地址來確認符號的地址。連結器合併各個相似段後,計算合併段總的長度和位置。再根據符號在對應段起始位置的相對偏移地址,即可確定符號的地址。同時連結器會收集輸入目標檔案中的符號表中的所有的符號定義和符號引用,並做成一個全域性符號表。

arm-himix200-linux-objdump -h main

objdump -h可以檢視目標檔案的各個段的大小、分配的虛擬地址、檔案偏移等。

root@chen:/home/chenjg/share/test/static# arm-himix200-linux-objdump -h main

main:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .interp       00000013  00010154  00010154  00000154  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  00010168  00010168  00000168  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .hash         00000024  00010188  00010188  00000188  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .dynsym       00000040  000101ac  000101ac  000001ac  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynstr       0000003c  000101ec  000101ec  000001ec  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .gnu.version  00000008  00010228  00010228  00000228  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version_r 00000020  00010230  00010230  00000230  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .rel.dyn      00000008  00010250  00010250  00000250  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rel.plt      00000018  00010258  00010258  00000258  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .init         0000000c  00010270  00010270  00000270  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 10 .plt          00000038  0001027c  0001027c  0000027c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .text         0000027c  000102b4  000102b4  000002b4  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 ...

2、符號解析和重定位
(1)重定位表
對於靜態連結來說,重定位表存在於可重定位檔案裡,重定位表主要包含兩個部分,一個是重定位入口的偏移,表示重定位要修正的位置,該位置是相對於可重定位檔案的段起始的偏移。另一個是重定位入口的型別和符號,每種處理器都有自己的一套重定位入口的型別,各種處理器的指令格式不一樣,重定位所修正的指令格式也不一樣。 重定位表的作用就是用來記錄對外引用的符號在目標檔案的哪些位置,用什麼指令修正方式去修正引用符號的地址。

下面介紹變數和函式是如何重定位的。
變數
在add_m.c定義一個全域性變數sun,在main.c檔案裡引用它。通過檢視main.o的重重定位表和反彙編程式碼,如下,發現,由於main.c檔案引用的sun變數是在add_m.c檔案裡定義的,所以編譯的時候,sun變數地址是無法確認的,只能當做0來處理。重定位表中sun的偏移地址為00000090,檢視main.o的反彙編程式碼,實際對應的是

90:   00000000        .word   0x00000000

這裡說明一下,在彙編裡,點一個機器長的全域性變數,是通過.word指令來表示的。.word指令後面是跟著變數的地址。一開始引用符號sun的地址是0的。包括·在本檔案內定義的mun變數的地址一開始也是0。

main.o的重定位表:

root@chen:/home/chenjg/share/test/static# arm-himix200-linux-objdump -r main.o
main.o:     file format elf32-littlearm
RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE 
00000018 R_ARM_CALL        puts
00000034 R_ARM_CALL        add
00000044 R_ARM_CALL        printf
00000058 R_ARM_CALL        printf
0000006c R_ARM_CALL        printf
00000080 R_ARM_ABS32       .rodata
00000084 R_ARM_ABS32       .rodata
00000088 R_ARM_ABS32       mun
0000008c R_ARM_ABS32       .rodata
00000090 R_ARM_ABS32       sun
00000094 R_ARM_ABS32       .rodata

main.o的反彙編程式碼:


root@chen:/home/chenjg/share/test/static# arm-himix200-linux-objdump -S main.o
main.o:     file format elf32-littlearm
Disassembly of section .text:

00000000 <main>:
   0:   e92d4800        push    {fp, lr}
   4:   e28db004        add     fp, sp, #4
   8:   e24dd010        sub     sp, sp, #16
   c:   e50b0010        str     r0, [fp, #-16]
  10:   e50b1014        str     r1, [fp, #-20]  ; 0xffffffec
  14:   e59f0064        ldr     r0, [pc, #100]  ; 80 <main+0x80>
  18:   ebfffffe        bl      0 <puts>
  1c:   e3a03028        mov     r3, #40 ; 0x28
  20:   e50b3008        str     r3, [fp, #-8]
  24:   e3a03014        mov     r3, #20
  28:   e50b300c        str     r3, [fp, #-12]
  2c:   e51b100c        ldr     r1, [fp, #-12]
  30:   e51b0008        ldr     r0, [fp, #-8]
  34:   ebfffffe        bl      0 <add>
  38:   e1a03000        mov     r3, r0
  3c:   e1a01003        mov     r1, r3
  40:   e59f003c        ldr     r0, [pc, #60]   ; 84 <main+0x84>
  44:   ebfffffe        bl      0 <printf>
  48:   e59f3038        ldr     r3, [pc, #56]   ; 88 <main+0x88>
  4c:   e5933000        ldr     r3, [r3]
  50:   e1a01003        mov     r1, r3
  54:   e59f0030        ldr     r0, [pc, #48]   ; 8c <main+0x8c>
  58:   ebfffffe        bl      0 <printf>
  5c:   e59f302c        ldr     r3, [pc, #44]   ; 90 <main+0x90>
  60:   e5933000        ldr     r3, [r3]
  64:   e1a01003        mov     r1, r3
  68:   e59f0024        ldr     r0, [pc, #36]   ; 94 <main+0x94>
  6c:   ebfffffe        bl      0 <printf>
  70:   e3a03000        mov     r3, #0
  74:   e1a00003        mov     r0, r3
  78:   e24bd004        sub     sp, fp, #4
  7c:   e8bd8800        pop     {fp, pc}
  80:   00000000        .word   0x00000000
  84:   0000000c        .word   0x0000000c
  88:   00000000        .word   0x00000000
  8c:   00000018        .word   0x00000018
  90:   00000000        .word   0x00000000
  94:   00000024        .word   0x00000024

可執行檔案main的反彙編程式碼:

0001043c <main>:
   1043c:       e92d4800        push    {fp, lr}
   10440:       e28db004        add     fp, sp, #4
   10444:       e24dd010        sub     sp, sp, #16
   10448:       e50b0010        str     r0, [fp, #-16]
   1044c:       e50b1014        str     r1, [fp, #-20]  ; 0xffffffec
   10450:       e59f0064        ldr     r0, [pc, #100]  ; 104bc <main+0x80>
   10454:       ebffffa2        bl      102e4 <puts@plt>
   10458:       e3a03028        mov     r3, #40 ; 0x28
   1045c:       e50b3008        str     r3, [fp, #-8]
   10460:       e3a03014        mov     r3, #20
   10464:       e50b300c        str     r3, [fp, #-12]
   10468:       e51b100c        ldr     r1, [fp, #-12]
   1046c:       e51b0008        ldr     r0, [fp, #-8]
   10470:       eb000017        bl      104d4 <add>
   10474:       e1a03000        mov     r3, r0
   10478:       e1a01003        mov     r1, r3
   1047c:       e59f003c        ldr     r0, [pc, #60]   ; 104c0 <main+0x84>
   10480:       ebffff94        bl      102d8 <printf@plt>
   10484:       e59f3038        ldr     r3, [pc, #56]   ; 104c4 <main+0x88>
   10488:       e5933000        ldr     r3, [r3]
   1048c:       e1a01003        mov     r1, r3
   10490:       e59f0030        ldr     r0, [pc, #48]   ; 104c8 <main+0x8c>
   10494:       ebffff8f        bl      102d8 <printf@plt>
   10498:       e59f302c        ldr     r3, [pc, #44]   ; 104cc <main+0x90>
   1049c:       e5933000        ldr     r3, [r3]
   104a0:       e1a01003        mov     r1, r3
   104a4:       e59f0024        ldr     r0, [pc, #36]   ; 104d0 <main+0x94>
   104a8:       ebffff8a        bl      102d8 <printf@plt>
   104ac:       e3a03000        mov     r3, #0
   104b0:       e1a00003        mov     r0, r3
   104b4:       e24bd004        sub     sp, fp, #4
   104b8:       e8bd8800        pop     {fp, pc}
   104bc:       00010610        .word   0x00010610
   104c0:       0001061c        .word   0x0001061c
   104c4:       0002102c        .word   0x0002102c
   104c8:       00010628        .word   0x00010628
   104cc:       00021030        .word   0x00021030
   104d0:       00010634        .word   0x00010634

再看可執行檔案main的反彙編程式碼,經過連結,符號解析、重定位後,變數符號sun和mun的地址也被修正了。例如sun變數,一開始符號sun是相對於main函式地址的偏移00000090,所以連結後,main函式的地址確認後,在main函式地址的基礎上,加上偏移00000090,即main+0x94 得到104d0的地址,即

104d0:       00010634        .word   0x00010634

從而得到變數sun的地址0x00010634。
函式
在main.c檔案裡呼叫add函式,是在add_m.c檔案裡定義的符號,所以會出現在重定位表裡,從重定位表裡看,相對於main函式的偏移地址為00000034,但在連結之前,add函式的地址是不確定的,即為0.

34:   ebfffffe        bl      0 <add>

連結後,其地址被修正為104d4 ,即連結後,add函式分配的真實虛擬地址。

總結:
重定位的過程中,每個重定位的人口都是對一個符號的引用,當聯結器對某個符號的引用進行重定位時,就會去查詢有所有輸入目標檔案的符號組成的全域性符號表,找到後就會進行重定位,即地址的修正。

相關文章