靜態連結之深度解剖
靜態連結一般分為兩步,第一步是空間和地址的分配,第二步是符號解析和重定位。
本篇以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函式分配的真實虛擬地址。
總結:
重定位的過程中,每個重定位的人口都是對一個符號的引用,當聯結器對某個符號的引用進行重定位時,就會去查詢有所有輸入目標檔案的符號組成的全域性符號表,找到後就會進行重定位,即地址的修正。
相關文章
- 動態連結庫與靜態連結庫
- 【連結 1】與靜態連結庫連結
- 靜態連結動態連結的連結順序問題和makefile示例
- C#資料結構-靜態連結串列C#資料結構
- 《程式設計師的自我修養筆記之靜態連結》程式設計師筆記
- linux下靜態連結庫和動態連結庫的區別有哪些Linux
- 深度解剖dubbo原始碼原始碼
- Linux環境下:程式的連結, 裝載和庫[靜態連結]Linux
- 理解靜態繫結與動態繫結
- Nginx/Apache之偽靜態設定 - 運維小結NginxApache運維
- Java中靜態跟非靜態的區別總結Java
- 後期靜態繫結
- Java代理之靜態代理Java
- 在AndroidStudio下使用cmake編譯出靜態連結庫的方法Android編譯
- cmake 連結動態連結庫
- java中的靜態繫結與動態繫結Java
- C++的動態繫結和靜態繫結C++
- echarts之靜態與動態地圖Echarts地圖
- AOP之靜態代理VS動態代理
- php 後期靜態繫結PHP
- 延遲靜態繫結——static
- java 反射之操作靜態MethodJava反射
- Linux系統 g++ 連結 libopencv_world.a 靜態庫編譯程式LinuxOpenCV編譯
- 深入理解PHP物件導向之後期靜態繫結PHP物件
- 靜態庫封裝之ComDir類封裝
- 靜態庫封裝之ComFile類封裝
- 靜態庫封裝之ComStr類封裝
- Android廣播之靜態註冊Android
- 構建靜態頁面 之 [ 列表 ]
- 構建靜態頁面 之 [ 表格 ]
- 靜態域與靜態方法
- P/Invoke之C#呼叫動態連結庫DLLC#
- 特定深度節點連結串列
- 使用js動態新增連結隨機連結JS隨機
- SARIF:靜態分析結果交換格式
- Gazebo新增模型並控制模型運動作為動態障礙物(Ubuntu16.04, Gazebo7.16),附錄動態連結庫和靜態連結庫區別模型Ubuntu
- 實驗3.直連靜態路由實驗路由
- 解剖 RxJava 之變換操作符RxJava