讀 <<程序員的自我修改--鏈接,裝載與庫>> 和 <<深入理解java虛擬機>>
前陣子複習了一下 final , 然後發現 final 有一個知識點和 JMM 有關 ,然後又想起了 JVM 相關的知識有點模糊 ,然後我又想起了之前看過一部分的一本書 . <<程序員的自我修改>>
程序的生成過程
多個模塊鏈接形成一個可執行文件,纔可以執行
一個典型程序的轉換處理過程
可以看到我們即將學的鏈接
在最後的一個步驟 ,
- 前面 : 彙編生成的
可重定位目標程序
- 後面 : 鏈接生成的可執行目標程序(例如 window 下的
.exe文件
)
下面解釋預處理,編譯,彙編,鏈接的幾個重要過程。
預處理
編譯
彙編
鏈接
鏈接操作的步驟
其中步驟2中的合併可以說是歸類代碼的數據類型,代碼和數據分開,最後重寫寫入符號對應的物理地址。 也就是說鏈接完成後,物理地址
已經是固定了。
需要說明的是經過鏈接後的文件實際已經是“0101001”的二進制序列了,只是圖爲了讓我們更加好理解,依然用匯編語言來表示.
第1,2,3步可以統稱爲合併,第4步爲重定位,實際可以總結爲兩個操作。
需要注意的是第四步中的指令中填入新地址,這個地址指的是虛擬地址。
鏈接的本質
鏈接的本質就是合併相同的“節”,例如 : .text 節, .data節,.bss 節,這樣形成的一個文件,在liunx 中稱之爲 EIF 文件,可以說 EIF 文件就是打包好的文件,可以被引入其他的模塊中(有點像java 中的 jar ,當需要引入其他模塊時,就引入jar包就可以了)
目標文件
目標文件是編譯器和彙編器最終生成的產物 , 那麼我們先來看一下這種產物
到底是長什麼樣的
目標文件的格式
目標的格式分爲 :
我們鏈接這本書主要講的是 linux平臺下的 ELF
三類目標文件
- 可重定位目標文件
- 可執行目標文件
- 共享的目標文件
window中
- .exe 是可執行目標文件
- .dll 共享目標文件
ELF 文件表示的兩種視圖
兩種視圖代表的是一個東西的不同的表現形式 . (鏈接視圖和執行視圖)
我們先來看一下鏈接視圖
鏈接視圖表示的是**可被鏈接(合併)生成可執行文件或共享目標文件 **
可重定位目標文件格式
趁着上面講到的鏈接視圖 ,我們接着介紹該視圖所對應的目標文件--可重定位目標文件
可以說由三部分組成 :
- ELF header
- 各種節
- section header table
可重定位目標文件--節的信息
這裏簡單介紹
bss節,就像是java中的
private String str;
它沒有實際賦值,只是一個佔位符,沒有在這個節裏的(例子中的 str)分配磁盤空間 。
剩下的節含義
這些需要注意的是每個節都有重要的作用 ,可重定位目標文件中, 特有的重定向節 : .rel.txt , .rel.data ,這兩個節非常重要 ,後面我們在重定向的時候會學東西到
可執行目標文件格式
可執行目標文件是可以被載入到內存中去執行的文件,它主要與上面的可重定位文件有什麼不同呢?
可執行文件是需要載入到內存中某一個進程中去的,某個進程的虛擬空間有各個段,
程序頭表描述了該文件和虛擬地址的映射 (大白話就是我這個文件的哪個節應該映射到哪個虛擬地址)
關於一些重要節和table 的介紹我們留到下一篇文章
目標文件轉載到進程的虛擬空間
下圖描述的是可執行文件存儲映射到某個進程的虛擬空間中,該映射過程主要靠的是ELF頭(即程序頭表),簡單點表述就是各個節映射到進程的指定區域。需要注意的是虛存空間(虛擬地址)是每個進程自己編定的,不是內存的物理地址,這裏涉及到我們後面講到的頁表,每個進程它的虛擬地址與真實的內存物理地址有個映射表叫頁表,存儲在每個進程中。
然後操作系統只會去判斷 ,這個頁的內容有沒有被加載, 要是沒有被加載 ,那麼就將頁對應的虛擬地址的內容加載到頁中 (這不就是缺頁的處理過程嗎)
我們可以看到代碼段的地址總是從0x08048000 (虛擬地址)開始的,是隻讀代碼段的首地址。下面看一下一個例子。
上面一段代碼,地址從0開始的是沒鏈接前的,而0804*開頭則是已經鏈接過的。
通過例子查看目標文件內容
彙編生成文件
/* simpleSeciont.c */
int printf( const char* format, ...);
//#include <stdio.h>
// 這兩個變量是全局變量
int global_init_var = 84;
int global_uninit_var;
void func(int i)
{
printf("hello %d\n", i);
}
int main(void)
{
//這兩個是靜態變量
static int static_var = 85;
static int static_var2;
// 這兩個是局部變量
int a = 1;
int b;
func1(static_var + static_var2 + a + b);
return a;
}
gcc -c SimpleSection.c
然後我們得到了
[root@k8s-master c_compile_test]# ls
SimpleSection.c SimpleSection.o
我用了 winhex 這個工具看了 SimpleSection.o
這個文件的文件內容, 如圖所示 :
打印各個段的信息
[root@k8s-master c_compile_test]# objdump -h SimpleSection.o
SimpleSection.o: 文件格式 elf64-x86-64
節:
Idx Name Size VMA LMA File off Algn
0 .text 00000059 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 0000000000000000 0000000000000000 0000009c 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000004 0000000000000000 0000000000000000 000000a4 2**2
ALLOC
3 .rodata 0000000a 0000000000000000 0000000000000000 000000a4 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002e 0000000000000000 0000000000000000 000000ae 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000dc 2**0
CONTENTS, READONLY
6 .eh_frame 00000058 0000000000000000 0000000000000000 000000e0 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
VMA
虛擬內存區域是空的 ,因爲這是可重定向目標文件 ,所以是空的, 也就是說目前還是半成品, 是不會分配虛擬地址的, 只有經過鏈接以後纔會生成虛擬地址空間
查看每個段的大小
[root@k8s-master c_compile_test]# size SimpleSection.o
text data bss dec hex filename
187 8 4 199 c7 SimpleSection.o
把各段內容用 16進制打印出來 , -d
則是反彙編
[root@k8s-master c_compile_test]# objdump -s -d SimpleSection.o
SimpleSection.o: 文件格式 elf64-x86-64
Contents of section .text:
0000 554889e5 4883ec10 897dfc8b 45fc89c6 UH..H....}..E...
0010 bf000000 00b80000 0000e800 000000c9 ................
0020 c3554889 e54883ec 10c745fc 01000000 .UH..H....E.....
0030 8b150000 00008b05 00000000 01c28b45 ...............E
0040 fc01c28b 45f801d0 89c7b800 000000e8 ....E...........
0050 00000000 8b45fcc9 c3 .....E...
Contents of section .data:
0000 54000000 55000000 T...U...
Contents of section .rodata:
0000 68656c6c 6f202564 0a00 hello %d..
Contents of section .comment:
0000 00474343 3a202847 4e552920 342e382e .GCC: (GNU) 4.8.
0010 35203230 31353036 32332028 52656420 5 20150623 (Red
0020 48617420 342e382e 352d3434 2900 Hat 4.8.5-44).
Contents of section .eh_frame:
0000 14000000 00000000 017a5200 01781001 .........zR..x..
0010 1b0c0708 90010000 1c000000 1c000000 ................
0020 00000000 21000000 00410e10 8602430d ....!....A....C.
0030 065c0c07 08000000 1c000000 3c000000 .\..........<...
0040 00000000 38000000 00410e10 8602430d ....8....A....C.
0050 06730c07 08000000 .s......
Disassembly of section .text:
0000000000000000 <func>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 89 7d fc mov %edi,-0x4(%rbp)
b: 8b 45 fc mov -0x4(%rbp),%eax
e: 89 c6 mov %eax,%esi
10: bf 00 00 00 00 mov $0x0,%edi
15: b8 00 00 00 00 mov $0x0,%eax
1a: e8 00 00 00 00 callq 1f <func+0x1f>
1f: c9 leaveq
20: c3 retq
0000000000000021 <main>:
21: 55 push %rbp
22: 48 89 e5 mov %rsp,%rbp
25: 48 83 ec 10 sub $0x10,%rsp
29: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%rbp)
30: 8b 15 00 00 00 00 mov 0x0(%rip),%edx # 36 <main+0x15>
36: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 3c <main+0x1b>
3c: 01 c2 add %eax,%edx
3e: 8b 45 fc mov -0x4(%rbp),%eax
41: 01 c2 add %eax,%edx
43: 8b 45 f8 mov -0x8(%rbp),%eax
46: 01 d0 add %edx,%eax
48: 89 c7 mov %eax,%edi
4a: b8 00 00 00 00 mov $0x0,%eax
4f: e8 00 00 00 00 callq 54 <main+0x33>
54: 8b 45 fc mov -0x4(%rbp),%eax
57: c9 leaveq
58: c3 retq
其中 .data
段 8個字節,存放着是我們變量 static int static_var = 85
和 int global_init_var = 84
, 然後我們對比這前面的 , .data
段的 offset
是 9c
, 就是這裏
鏈接以後
鏈接上面例子的文件
使用打印,打印出關於ELF的消息
其他
每次生成可執行目標文件的虛擬地址會不會不同 ??
我們做一下實驗就知道了, 上面的地址是 :
然後我們刪除以後, 在鏈接生成一個新的可執行目標文件,並查看其虛擬地址
我們看這兩個需要加載進虛擬空間的內存的虛擬地址 , 前後兩次是一樣的
參考
- <<程序員的自我修改--鏈接,裝載與庫>> 書