鏈接-從鏈接到目標文件

讀 <<程序員的自我修改--鏈接,裝載與庫>> 和 <<深入理解java虛擬機>>

前陣子複習了一下 final , 然後發現 final 有一個知識點和 JMM 有關 ,然後又想起了 JVM 相關的知識有點模糊 ,然後我又想起了之前看過一部分的一本書 . <<程序員的自我修改>>
img

這兩本書陷入了我思考 , 就 jvm 這一部分的內容和 <<程序員的自我修改>> 的東西是不是有什麼共通性 , 還是說這根本就是兩種東西 , 那麼是不同的東西 , 那麼 java 出現的動機又是什麼 ?

程序的生成過程

多個模塊鏈接形成一個可執行文件,纔可以執行
一個典型程序的轉換處理過程

1297993-20191125170650044-290363345.png

可以看到我們即將學的鏈接在最後的一個步驟 ,

  • 前面 : 彙編生成的 可重定位目標程序
  • 後面 : 鏈接生成的可執行目標程序(例如 window 下的 .exe文件)

下面解釋預處理,編譯,彙編,鏈接的幾個重要過程。

預處理

1297993-20191125171438711-311734481.png

1297993-20191125171652363-586854684.png

編譯

1297993-20191125171746850-721996807.png

彙編

1297993-20191125171829712-685084201.png

鏈接

1297993-20191125171923297-1211672660.png

鏈接操作的步驟

1297993-20191125172119139-840015843.png

其中步驟2中的合併可以說是歸類代碼的數據類型,代碼和數據分開,最後重寫寫入符號對應的物理地址。 也就是說鏈接完成後,物理地址已經是固定了。

需要說明的是經過鏈接後的文件實際已經是“0101001”的二進制序列了,只是圖爲了讓我們更加好理解,依然用匯編語言來表示.

第1,2,3步可以統稱爲合併,第4步爲重定位,實際可以總結爲兩個操作。
需要注意的是第四步中的指令中填入新地址,這個地址指的是虛擬地址

鏈接的本質

1297993-20191125173300747-1172364918.png

鏈接的本質就是合併相同的“節”,例如 : .text 節, .data節,.bss 節,這樣形成的一個文件,在liunx 中稱之爲 EIF 文件,可以說 EIF 文件就是打包好的文件,可以被引入其他的模塊中(有點像java 中的 jar ,當需要引入其他模塊時,就引入jar包就可以了)



目標文件

目標文件是編譯器和彙編器最終生成的產物 , 那麼我們先來看一下這種產物到底是長什麼樣的

目標文件的格式

目標的格式分爲 :
1297993-20191128132910725-1821987377.png

我們鏈接這本書主要講的是 linux平臺下的 ELF

三類目標文件

  • 可重定位目標文件
  • 可執行目標文件
  • 共享的目標文件

1297993-20191128122016418-1446256025.png

window中

  • .exe 是可執行目標文件
  • .dll 共享目標文件

ELF 文件表示的兩種視圖

兩種視圖代表的是一個東西的不同的表現形式 . (鏈接視圖和執行視圖)

1297993-20191128131927869-1050211866.png

我們先來看一下鏈接視圖

1297993-20191128133505905-1915023214.png

鏈接視圖表示的是**可被鏈接(合併)生成可執行文件或共享目標文件 **

可重定位目標文件格式

趁着上面講到的鏈接視圖 ,我們接着介紹該視圖所對應的目標文件--可重定位目標文件

可以說由三部分組成 :

  • ELF header
  • 各種節
  • section header table

1297993-20191128134412256-1572298592.png

可重定位目標文件--節的信息

這裏簡單介紹 

bss節,就像是java中的

	private	String str;

它沒有實際賦值,只是一個佔位符,沒有在這個節裏的(例子中的 str)分配磁盤空間 。

1297993-20191128134726006-1503349706.png

剩下的節含義

1297993-20191128135019185-297307043.png

這些需要注意的是每個節都有重要的作用 ,可重定位目標文件中, 特有的重定向節 : .rel.txt , .rel.data ,這兩個節非常重要 ,後面我們在重定向的時候會學東西到

可執行目標文件格式

可執行目標文件是可以被載入到內存中去執行的文件,它主要與上面的可重定位文件有什麼不同呢?

1297993-20191128144051965-887682799.png

可執行文件是需要載入到內存中某一個進程中去的,某個進程的虛擬空間有各個段,

1297993-20191128153629398-915316170.png

程序頭表描述了該文件和虛擬地址的映射 (大白話就是我這個文件的哪個節應該映射到哪個虛擬地址)

關於一些重要節和table 的介紹我們留到下一篇文章

目標文件轉載到進程的虛擬空間

下圖描述的是可執行文件存儲映射到某個進程的虛擬空間中,該映射過程主要靠的是ELF頭(即程序頭表),簡單點表述就是各個節映射到進程的指定區域。需要注意的是虛存空間(虛擬地址)是每個進程自己編定的,不是內存的物理地址,這裏涉及到我們後面講到的頁表,每個進程它的虛擬地址與真實的內存物理地址有個映射表叫頁表,存儲在每個進程中。

然後操作系統只會去判斷 ,這個頁的內容有沒有被加載, 要是沒有被加載 ,那麼就將頁對應的虛擬地址的內容加載到頁中 (這不就是缺頁的處理過程嗎)

1297993-20191128120636841-473906428.png

我們可以看到代碼段的地址總是從0x08048000 (虛擬地址)開始的,是隻讀代碼段的首地址。下面看一下一個例子。

1297993-20191128121603622-2013154924.png

上面一段代碼,地址從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 這個文件的文件內容, 如圖所示 :

img

圖片一

打印各個段的信息

[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 = 85int global_init_var = 84 , 然後我們對比這前面的 , .data段的 offset9c , 就是這裏

img

圖片二

鏈接以後

鏈接上面例子的文件
img

使用打印,打印出關於ELF的消息
img

其他

每次生成可執行目標文件的虛擬地址會不會不同 ??

我們做一下實驗就知道了, 上面的地址是 :

img

然後我們刪除以後, 在鏈接生成一個新的可執行目標文件,並查看其虛擬地址

img

我們看這兩個需要加載進虛擬空間的內存的虛擬地址 , 前後兩次是一樣的

參考

  • <<程序員的自我修改--鏈接,裝載與庫>> 書
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章