中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

成都蜀美網(wǎng)站建設(shè)徐州網(wǎng)站建設(shè)

成都蜀美網(wǎng)站建設(shè),徐州網(wǎng)站建設(shè),做企業(yè)網(wǎng)站用服務(wù)器,企業(yè)廣告平面設(shè)計(jì)多少錢關(guān)于Linux的編譯過(guò)程,其實(shí)只需要使用gcc這個(gè)功能,gcc并非一個(gè)編譯器,是一個(gè)驅(qū)動(dòng)程序。其編譯過(guò)程也很熟悉:預(yù)處理–編譯–匯編–鏈接。在接觸底層開發(fā)甚至操作系統(tǒng)開發(fā)時(shí),我們都需要了解這么一個(gè)知識(shí)點(diǎn),如何…

關(guān)于Linux的編譯過(guò)程,其實(shí)只需要使用gcc這個(gè)功能,gcc并非一個(gè)編譯器,是一個(gè)驅(qū)動(dòng)程序。其編譯過(guò)程也很熟悉:預(yù)處理–編譯–匯編–鏈接。在接觸底層開發(fā)甚至操作系統(tǒng)開發(fā)時(shí),我們都需要了解這么一個(gè)知識(shí)點(diǎn),如何從我們的代碼到機(jī)器碼。這段過(guò)程經(jīng)歷了什么,我們的函數(shù)變量又是在哪里?一個(gè)個(gè)好奇心驅(qū)使著我寫下這篇文章。于博客中有提及Linux的安裝以及gcc基本環(huán)境搭建、gcc編譯流程、常用gcc指令集:https://blog.csdn.net/Alkaid2000/article/details/128036290?spm=1001.2014.3001.5501

文章目錄

  • 0x01 編譯過(guò)程
  • 0x02 預(yù)編譯
  • 0x03 編譯
  • 0x04 匯編
    • 目標(biāo)文件ELF
    • 翻譯機(jī)器指令
      • 操作數(shù)地址通過(guò)ModR/M中的Mod+R/M指定
      • 操作數(shù)通過(guò)ModR/M中的Reg/Opcode指定
      • 操作數(shù)地址直接嵌入在機(jī)器指令中
      • 操作數(shù)直接嵌入在指令中
      • 操作數(shù)隱含在Opcode中
      • 回到代碼
    • 重定位表
    • 符號(hào)表
  • 0x05 鏈接
    • 合并目標(biāo)文件
    • 符號(hào)重定位
    • 鏈接靜態(tài)庫(kù)
    • 鏈接動(dòng)態(tài)庫(kù)

0x01 編譯過(guò)程

可以使用這么一句指令來(lái)觀察一個(gè).c文件所需要經(jīng)歷的編譯過(guò)程:gcc -v main.c。

根據(jù)gcc的輸出可見,對(duì)于一個(gè)C程序來(lái)說(shuō),從源代碼構(gòu)建出可執(zhí)行文件經(jīng)歷了三個(gè)階段:

  • 編譯

在這里插入圖片描述

gcc使用編譯器ccl.exe進(jìn)行編譯,產(chǎn)生的編譯代碼保存在目錄/temp下的文件ccelFAGc.s中。

  • 匯編

在這里插入圖片描述

gcc使用匯編器as.exe進(jìn)行匯編,匯編過(guò)程產(chǎn)生匯編文件ccZfpupi.o,將上面生成的ccelFAGc.s進(jìn)行匯編。

  • 鏈接

在這里插入圖片描述

調(diào)用collect2.exe進(jìn)行鏈接。實(shí)際上這個(gè)collect2只是一個(gè)輔助程序,最終他將調(diào)用鏈接器ld來(lái)完成真正的鏈接過(guò)程。包括框出來(lái)的crtend.o、以及啟動(dòng)文件等等,本質(zhì)上都是ld在進(jìn)行鏈接。

事實(shí)上,從gcc看到只有這三個(gè)過(guò)程,但是對(duì)于C程序來(lái)說(shuō),編譯過(guò)程也分為兩個(gè)階段:預(yù)編譯和編譯。所以軟件構(gòu)建過(guò)程通常分為四個(gè)階段:預(yù)編譯、編譯、匯編、鏈接。

在這里插入圖片描述

可以通過(guò)gcc手動(dòng)控制以上的編譯流程,從而留下中間文件以方便研究:

  • gcc HelloWorld.c -E -o HelloWorld.i 預(yù)處理:加入頭文件,替換宏。
  • gcc HelloWorld.c -S -c -o HelloWorld.s 編譯:包含預(yù)處理,將 C 程序轉(zhuǎn)換成匯編程序。
  • gcc HelloWorld.c -c -o HelloWorld.o 匯編:包含預(yù)處理和編譯,將匯編程序轉(zhuǎn)換成可鏈接的二進(jìn)制程序。
  • gcc HelloWorld.c -o HelloWorld 鏈接:包含以上所有操作,將可鏈接的二進(jìn)制程序和其它別的庫(kù)鏈接在一起,形成可執(zhí)行的程序文件。

那么接下來(lái)使用下面這段程序?qū)τ诰幾g過(guò)程來(lái)做個(gè)總結(jié):

hello.c:

#include <stdio.h>
#include "foo.h"extern int foo2;int main(int argc,char *argv[])
{int result;int r = 5;
#ifdef AREAresult = PI*r*r;
#elseresult = PI*r*2;
#endifreturn 0;
}

foo.h

#ifndef _FOO_H
#define _FOO_H#define PI 3.1415926
#define AREAstruct foo_struct{int a;
};#endif

fool2.c

int foo2 = 20;void foo2_func(int x)
{int ret = foo2;
}

fool1.c

int fool = 10;void fool_func()
{int ret = fool;
}

0x02 預(yù)編譯

C語(yǔ)言中的預(yù)編譯是以#開頭,常用的預(yù)編譯指令包括#include、#define#if等等。在工具鏈中,一般都提供單獨(dú)的編譯器,比如GCC中提供的編譯器為cpp。但是預(yù)編譯也可以看作編譯過(guò)程的第一遍,是為編譯做的一些工作,所以通常編譯器中也包含了預(yù)編譯的功能。比如前面的gcc并沒有單獨(dú)調(diào)用cpp,而是直接調(diào)用ccl進(jìn)行編譯,原因就是如上。

gcc -E hello.c -o hello.i

編譯之后可以查看文件hello.i

# 7 "foo.h"
struct foo_struct{int a;
};
# 3 "hello.c" 2extern int foo2;int main(int argc,char *argv[])
{int result;int r = 5;result = 3.1415926*r*r;return 0;
}

根據(jù)編譯后的結(jié)果可以總結(jié)出預(yù)編譯指令的處理步驟:

  • 文件包含:指示預(yù)編譯器將一個(gè)源文件的內(nèi)容全部復(fù)制到當(dāng)前源文件中。
  • 宏定義:預(yù)編譯器將宏名替換為具體的值。
  • 條件編譯:保留用戶希望編譯的代碼。(使用#if #else這種形式)

0x03 編譯

編譯程序?qū)︻A(yù)處理過(guò)的結(jié)果進(jìn)行詞法分析、語(yǔ)法分析、語(yǔ)義分析,然后生成中間代碼,對(duì)中間代碼進(jìn)行優(yōu)化,目標(biāo)是使最終生成的可執(zhí)行代碼時(shí)間更短、占用的空間更小,最后生成相應(yīng)的匯編代碼。

gcc -S fool2.c

其內(nèi)容如下:

int foo2 = 20;void foo2_func(int x)
{int ret = foo2;
}
	.file	"fool2.c".text.globl	foo2.data.align 4.type	foo2, @object.size	foo2, 4
foo2:.long	20.text.globl	foo2_func.type	foo2_func, @function
foo2_func:
.LFB0:.cfi_startprocendbr64pushq	%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq	%rsp, %rbp.cfi_def_cfa_register 6movl	%edi, -20(%rbp)movl	foo2(%rip), %eaxmovl	%eax, -4(%rbp)noppopq	%rbp.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE0:.size	foo2_func, .-foo2_func.ident	"GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0".section	.note.GNU-stack,"",@progbits.section	.note.gnu.property,"a".align 8.long	 1f - 0f.long	 4f - 1f.long	 5
0:.string	 "GNU"
1:.align 8.long	 0xc0000002.long	 3f - 2f
2:.long	 0x3
3:.align 8
4:

在此源文件中,定義了一個(gè)全局變量以及函數(shù),區(qū)區(qū)一行代碼卻出現(xiàn)了這么多匯編語(yǔ)言。其實(shí)里面相當(dāng)多的代碼是偽指令。偽指令不參與CPU運(yùn)行,只指導(dǎo)編譯鏈接過(guò)程。

就像上面所生成的cfi指令,這個(gè)指令主要的作用是輔助匯編器創(chuàng)建棧幀信息的。

這些偽指令也有其他的作用,比如說(shuō)中斷后會(huì)輸出回溯信息,比如在debug的時(shí)候,需要查找一些變量或者是查看函數(shù)調(diào)用信息。這個(gè)過(guò)程稱為棧的回卷

在上面的程序中注意到了有個(gè)寄存器rbp保存了frame pointerbase pointer均指向了棧的底部。對(duì)于main函數(shù)來(lái)說(shuō),他并非程序中第一個(gè)運(yùn)行的程序,所以main其實(shí)也是一個(gè)被調(diào)函數(shù),他也有自己的棧幀。在理論上可以使用這些指針來(lái)遍歷調(diào)用過(guò)程中各個(gè)函數(shù)的棧幀,但是由于gcc代碼的優(yōu)化,可能導(dǎo)致調(diào)試器或異常處理很難甚至不能正?;厮輻?#xff0c;所以這些偽指令的目的就是輔助編譯器創(chuàng)建棧幀信息,并且保存在目標(biāo)文件的段.eh_frame中,這樣就不會(huì)被編譯器優(yōu)化所影響。

去除偽指令后,可以看到代碼如下:

foo2_func:pushq	%rbpmovq	%rsp, %rbpmovl	%edi, -20(%rbp)movl	foo2(%rip), %eaxmovl	%eax, -4(%rbp)noppopq	%rbpret

在匯編代碼中,在函數(shù)的開頭和結(jié)尾處分別會(huì)插入一小段代碼,分別稱為PrologueEpilogue,比如上面1~3句是Prologue,最后兩句是Epilogue。

  • Prologue:保存主調(diào)函數(shù)的frame pointer,這是為了在子函數(shù)調(diào)用結(jié)束后,恢復(fù)主調(diào)函數(shù)的棧幀。同時(shí)為子函數(shù)準(zhǔn)備棧幀。

    	pushq	%rbpmovq	%rsp, %rbpmovl	%edi, -20(%rbp)
    

    上面這三句話起了一種構(gòu)造函數(shù)的作用,首先需要保存主調(diào)函數(shù)的frame pointer,之后保存在寄存器中金壓棧,在退出主函數(shù)時(shí)可以從棧中恢復(fù)主調(diào)函數(shù)frame pointer;將rsp賦值給rbp,即將子函數(shù)的frame pointer指向主調(diào)函數(shù)的棧頂,這行代碼記錄了子函數(shù)棧幀的底部,從這里就開始了主函數(shù)的棧幀。下面那句是為本地變量分配??臻g。

  • Epilogue功能是恰恰相反的,如果說(shuō)Prologue是構(gòu)造函數(shù),那么這個(gè)部分則是析構(gòu)函數(shù)。

    	popq	%rbpret
    

    當(dāng)前棧幀的棧底,是Prologue保存的主調(diào)函數(shù)的frame pointer,將其pop出回到了主調(diào)函數(shù)的main棧幀,之后,CPU就返回主調(diào)函數(shù)繼續(xù)執(zhí)行。

中間程序的執(zhí)行部分,也就是int ret = foo2這段,從第四行開始,CPU從數(shù)據(jù)段中讀取了全局變量foo2的值將其放在寄存器eax中,之后在第五行代碼,將eax的內(nèi)容,賦值到棧中局部變量ret的位置。之后代碼根據(jù)局部變量相對(duì)于棧的frame pointer的偏移來(lái)訪問局部變量,如變量ret位于相對(duì)于棧底偏移為-4的內(nèi)存處。

0x04 匯編

匯編器將匯編代碼翻譯為機(jī)器指令,每一條匯編語(yǔ)句幾乎都對(duì)應(yīng)一條機(jī)器指令,所以匯編器的匯編過(guò)程相對(duì)于比較簡(jiǎn)單,只需要根據(jù)匯編指令和機(jī)器指令的對(duì)照表進(jìn)行翻譯即可。除了生成機(jī)器碼外,匯編器還要再目標(biāo)文件中創(chuàng)建輔助鏈接時(shí)需要的信息,包括符號(hào)表、重定位表等。

目標(biāo)文件ELF

目標(biāo)文件是匯編過(guò)程的產(chǎn)物。對(duì)于32位的ELF文件來(lái)說(shuō),其最前部是文件頭部信息,描述了整個(gè)文件的基本屬性,除了包括該文件運(yùn)行在什么操作系統(tǒng)中、運(yùn)行在什么硬件體系結(jié)構(gòu)上、程序入口地址是什么等基本信息外,最重要的是記錄了兩個(gè)表格的相關(guān)信息,如表格所在的位置、其中包括了條目數(shù)等。這兩個(gè)表格為:

  • Section Header Table:主要是供編譯時(shí)鏈接使用的,表格中定義了各個(gè)段的位置、長(zhǎng)度、屬性等信息。
  • Program Header Table:主要是供內(nèi)核和動(dòng)態(tài)加載器從磁盤加載ELF文件到內(nèi)存時(shí)使用的。

對(duì)于目標(biāo)文件,由于其只是編譯過(guò)程中的一個(gè)中間產(chǎn)物,不涉及裝載運(yùn)行,因此在目標(biāo)文件中不會(huì)創(chuàng)建Program Header Table。

如何列出目標(biāo)文件:

gcc -c hello.c fool.c fool2.c
readelf -h fool2.o

生成的目標(biāo)文件:

ELF Header:Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00Class:                             ELF64Data:                              2's complement, little endianVersion:                           1 (current)OS/ABI:                            UNIX - System VABI Version:                       0Type:                              REL (Relocatable file)Machine:                           Advanced Micro Devices X86-64Version:                           0x1Entry point address:               0x0Start of program headers:          0 (bytes into file)Start of section headers:          672 (bytes into file)Flags:                             0x0Size of this header:               64 (bytes)			Size of program headers:           0 (bytes)Number of program headers:         0Size of section headers:           64 (bytes)Number of section headers:         13Section header string table index: 12
  • 可以看到ELF占了64字節(jié),通過(guò)ELF頭可見該文件是64位的ELF文件。
  • 使用little endian字節(jié)序存儲(chǔ)字節(jié)。
  • ABI遵循UNIX - System V標(biāo)準(zhǔn),運(yùn)行在類UNIX系統(tǒng)上。
  • 該文件為REL類型文件:通??蓤?zhí)行文件的類型是EXEC;靜態(tài)庫(kù)和目標(biāo)文件的類型是REL;動(dòng)態(tài)共享庫(kù)的類型是DYN。
  • Entry point address為程序入口,由于是目標(biāo)文件,則不存在執(zhí)行的概念。
  • Start of section headers:在偏移264字節(jié)處。
  • Size of section headers:每個(gè)Section Header占用了40字節(jié),Section Header Table一共包含了12個(gè)Section Header。

看完頭信息后,就可以看到各個(gè)段的信息。ELF即各個(gè)段的組合。大體上,段可以分為如下幾種類型:一類是存儲(chǔ)指令的,通常稱為代碼段;第二類是存儲(chǔ)數(shù)據(jù)的,通常稱為數(shù)據(jù)段。數(shù)據(jù)段又細(xì)分為兩個(gè)段:

  • .bss:未初始化的全局?jǐn)?shù)據(jù)。
  • .data:已初始化的全局?jǐn)?shù)據(jù)。

這兩個(gè)段本質(zhì)并沒有什么不同,但是因?yàn)槲闯跏蓟淖兞坎话菙?shù)據(jù),所以在ELF文件中并不需要占用空間,在程序裝載時(shí)進(jìn)行分配即可。

使用命令:

readelf -S fool2.o

可以看到fool2.oSection Header Table中包含的12個(gè)Section Header:

Section Headers:[Nr] Name              Type             Address           OffsetSize              EntSize          Flags  Link  Info  Align[ 0]                   NULL             0000000000000000  000000000000000000000000  0000000000000000           0     0     0[ 1] .text             PROGBITS         0000000000000000  000000400000000000000017  0000000000000000  AX       0     0     1[ 2] .rela.text        RELA             0000000000000000  000002000000000000000018  0000000000000018   I      10     1     8[ 3] .data             PROGBITS         0000000000000000  000000580000000000000004  0000000000000000  WA       0     0     4[ 4] .bss              NOBITS           0000000000000000  0000005c0000000000000000  0000000000000000  WA       0     0     1[ 5] .comment          PROGBITS         0000000000000000  0000005c000000000000002c  0000000000000001  MS       0     0     1[ 6] .note.GNU-stack   PROGBITS         0000000000000000  000000880000000000000000  0000000000000000           0     0     1[ 7] .note.gnu.propert NOTE             0000000000000000  000000880000000000000020  0000000000000000   A       0     0     8[ 8] .eh_frame         PROGBITS         0000000000000000  000000a80000000000000038  0000000000000000   A       0     0     8[ 9] .rela.eh_frame    RELA             0000000000000000  000002180000000000000018  0000000000000018   I      10     8     8[10] .symtab           SYMTAB           0000000000000000  000000e00000000000000108  0000000000000018          11     9     8[11] .strtab           STRTAB           0000000000000000  000001e80000000000000018  0000000000000000           0     0     1[12] .shstrtab         STRTAB           0000000000000000  00000230000000000000006c  0000000000000000           0     0     1
  • .text段存儲(chǔ)在文件中偏移0x40處,占據(jù)了0x17個(gè)字節(jié)。.text段并不全是代碼段,在鏈接時(shí),.init、.fini等存儲(chǔ)的代碼都屬于代碼段,這些都被映射到了Program Header Table中的一個(gè)段,在ELF加載時(shí),統(tǒng)一作為進(jìn)程的代碼段。

  • .data段存儲(chǔ)在文件中偏移0x58字節(jié)處,占據(jù)了0x04個(gè)字節(jié)的空間。

  • .bss段雖然包含著,但是他不必要記錄數(shù)據(jù),所以并沒有對(duì)應(yīng)的段。在加載程序時(shí),加載器將依據(jù).bss段的Section Header中的信息,在內(nèi)存中為其分配空間。所以占用著0x00字節(jié)空間。

  • .symtab段記錄的是符號(hào)表。因?yàn)榉?hào)的名字字串長(zhǎng)度可變,所以目標(biāo)文件將符號(hào)的名字字符串剝離出來(lái),記錄在另一個(gè)段.strtab中,符號(hào)表使用符號(hào)名字的索引在段.strtab中的偏移來(lái)確定符號(hào)名字。

  • .strtab段則是用于記錄段的名字

  • rel開頭的文件,如rel.text、rel.eh_frame,記錄的是段中需要重定位的符號(hào)。

  • .eh_frame段中記錄的是調(diào)試和異常處理時(shí)用到的信息。

  • .comment、.note.GNU-stack等都是在鏈接或者時(shí)裝載都不會(huì)用到的數(shù)據(jù),不需要關(guān)心。

那么綜上,ELF文件所有的內(nèi)容可以用如下的表所示:

在這里插入圖片描述

翻譯機(jī)器指令

機(jī)器指令由操作碼和操作數(shù)組成,操作碼指明該指令要完成的操作,即指令的功能;操作數(shù)是參與操作的參數(shù),主要以寄存器或存儲(chǔ)器地址的形式指明數(shù)據(jù)的來(lái)源或者計(jì)算存放的位置等。

匯編過(guò)程就是將操作碼翻譯為對(duì)應(yīng)的0和1的機(jī)器指令,這也是操作碼和操作數(shù)的編碼過(guò)程。這個(gè)過(guò)程也比較簡(jiǎn)單,對(duì)應(yīng)關(guān)系可以查看對(duì)應(yīng)的CPU指令手冊(cè)。但是對(duì)于操作數(shù)翻譯為機(jī)器碼復(fù)雜一些,操作數(shù)并沒有直接嵌入在指令編碼中,而是根據(jù)匯編指令使用的具體尋址方式,設(shè)置ModR/M、SIB、DisplacementImmediate各項(xiàng)的值,這個(gè)過(guò)程稱為操作數(shù)的解碼,CPU根據(jù)ModR/M、SIB、DisplacementImmediate各項(xiàng)的值解碼出操作數(shù)。

IA32機(jī)器指令的格式:

在這里插入圖片描述

下面是操作數(shù)的編碼方式:

操作數(shù)地址通過(guò)ModR/M中的Mod+R/M指定

ModR/M占用1字節(jié),包含三個(gè)域:Mod、Reg/OpcodeR/M,其中Mod占2位,R/M占3位,Reg/Opcode占3位。操作數(shù)可以使用ModR/M中的ModR/M字段聯(lián)合起來(lái)定義。

在這里插入圖片描述

在這里插入圖片描述

其中第二列表示尋址方式生成的有效地址;第三列和第四列表示對(duì)應(yīng)于某個(gè)尋址方式,ModR/M分別表示對(duì)應(yīng)的編碼。

在上面的表中,包含了直接尋址、寄存器尋址、寄存器間接尋址、基址尋址以及基址變址尋址等尋址方式下ModR/M對(duì)應(yīng)的編碼。如果匯編指令使用的是基址變址尋址,那么機(jī)器指令中也需要字段SIB。

以第七行的指令為例,假設(shè)匯編指令使用的尋址方式是[EAX]+disp8,那么Mod應(yīng)該取值01,R/M應(yīng)該取值位000。偏移disp8表示八位的Displacement,根據(jù)機(jī)器指令的格式,Displacement直接嵌入在指令中即可。Displacement取值可以為8位、16位、32位,選擇取決于尺寸方面,Displacement需要使用補(bǔ)碼的形式。當(dāng)CPU執(zhí)行指令時(shí),當(dāng)解析到ModR/M這個(gè)字節(jié)時(shí),一旦發(fā)現(xiàn)Mod的值是01,R/M的值是000,那么CPU就到寄存器EAX中取到其中的內(nèi)容,然后再取出嵌入在指令中的8位偏移Displacement,將這兩個(gè)值相加作為操作數(shù)的內(nèi)存地址,從而完成操作數(shù)的解碼過(guò)程。

操作數(shù)通過(guò)ModR/M中的Reg/Opcode指定

ModR/M中的字段Reg/Opcode占據(jù)3位,如果在匯編指令中使用了寄存器作為操作數(shù),那么編碼時(shí)也可以使用Reg/Opcode指定操作數(shù)使用的寄存器。如果操作數(shù)不需要使用字段Reg/Opcode編碼,字段Reg/Opcode也可以作為操作碼的編碼,下面是32位寄存器與字段Reg/Opcode取值的對(duì)應(yīng)關(guān)系:

在這里插入圖片描述

操作數(shù)地址直接嵌入在機(jī)器指令中

這就是所謂的直接尋址方式,那么在翻譯為機(jī)器指令時(shí),直接使用機(jī)器指令中的Displacement字段表示操作數(shù)的地址。

操作數(shù)直接嵌入在指令中

如果在匯編指令中,操作數(shù)就是參與計(jì)算的數(shù)據(jù),即所謂的立即尋址,那么在翻譯為機(jī)器指令時(shí),直接使用機(jī)器指令中的Immediate表示操作數(shù)。

操作數(shù)隱含在Opcode中

這就是所謂的隱含尋址。其實(shí)就是通過(guò)一些其他子指令來(lái)區(qū)分功能相同,但是操作數(shù)類型不同的作用:

mov r/m16,r16
mov r/m32,r32

Intel并沒有為上述兩個(gè)分類操作分別定義兩個(gè)操作碼,而是使用了同一個(gè)操作碼。但是使用了Instruction Prefixes來(lái)區(qū)分指令中的操作數(shù)是16位的還是32為的,比如在32位環(huán)境下使用了16位的操作數(shù),那么需要在指令前使用0x66進(jìn)行標(biāo)識(shí)。

回到代碼

那么回到代碼,fool2中:

	movl	foo2(%rip), %eaxmovl	%eax, -4(%ebp)

這兩條使用的都是mov指令,IA32架構(gòu)的mov指令可以簡(jiǎn)單了解如下:

在這里插入圖片描述

需要關(guān)注Opcode以及Op/En(操作數(shù)的編碼方式)。

對(duì)于MOV指令,不僅僅只有一個(gè)操作碼,對(duì)于同一類操作,可能使用不同的操作數(shù),操作數(shù)可能是寄存器,也可能是內(nèi)存地址,同時(shí)操作數(shù)還會(huì)有長(zhǎng)度之分,比如8位、16位、32位。Intel采取的策略是為同一指令設(shè)計(jì)了多個(gè)操作碼來(lái)細(xì)分這些指令。

對(duì)于Op/En操作數(shù)的編碼方式,具有六種:

在這里插入圖片描述

需要注意的是,編譯器生成的匯編代碼使用的是AT&T的格式,其操作數(shù)的順序與Intel的匯編指令正好相反,所以指令movl foo2(%rip), %eax中,foo2是Intel語(yǔ)法中的第二個(gè)操作數(shù),%eax是第一個(gè)操作數(shù)。那么可以查表,第七行,mov eax,moffs32,根據(jù)該指令說(shuō)明,操作碼0xa1隱含地指出了指令中第一個(gè)操作數(shù)是寄存器EAX,也就是尋址方式中所謂地操作數(shù)隱含尋址。

該指令地操作數(shù)編碼方式是C,C類編碼方式不需要ModR/M,也不需要SIB,而且也沒有使用立即數(shù)作為操作數(shù),也不需要指令前綴進(jìn)行修飾,所以第一個(gè)操作數(shù)寄存器EAX是通過(guò)操作碼隱含指明,所以該條匯編代碼最后轉(zhuǎn)換為如下形式地機(jī)器指令:Opcode+Displacement。

第二個(gè)操作數(shù)是通過(guò)Displayment來(lái)進(jìn)行表示地,由于還沒有進(jìn)行鏈接,所以foo2的地址尚未確定,所以暫時(shí)填充0占位,在鏈接時(shí)根據(jù)實(shí)際地址修改。因?yàn)槭沁\(yùn)行在32位的環(huán)境下,所以地址是32位的,Displayment占用了4字節(jié),綜上所述,該指令的機(jī)器碼可以翻譯為:

movl	foo2(%rip), %eax||opcode + displayment||a1 00 00 00 00

下一條指令是movl %eax, -4(%ebp),這條指令也是有兩個(gè)操作數(shù),第一個(gè)操作數(shù)-4(%ebp)相當(dāng)于是[EBP]+dis8,用8位是因?yàn)楸硎?4使用1個(gè)字節(jié)就夠了。根據(jù)A類編碼的要求,第一個(gè)操作數(shù)需要使用的寄存器需要由ModR/M中的Mod和R/M共同指明,根據(jù)尋址模式可匹配表的第十行,mod為01,r/m為101.且第一個(gè)操作數(shù)中的偏移-4由displayment來(lái)表示,在機(jī)器指令中需要使用數(shù)的補(bǔ)碼來(lái)表示,-4補(bǔ)碼為fc。

根據(jù)A類編碼的方式要求,第二個(gè)操作數(shù)由ModR/M中的Reg/Opcode指明。匯編指令第二個(gè)操作數(shù)使用的寄存器位EAX,對(duì)照表位000,那么第二條指令:

movl	%eax, -4(%ebp)||
Opcode + ModR/M + displayment||
0x89  01 000 101  fc||89 45 fc

可以使用指令objdump -d fool2.o來(lái)分析機(jī)器碼翻譯過(guò)程:

在這里插入圖片描述

可以使用工具hexdump -e ' "%4_ax:" 16/1 " %02x" "\n"' fool2.o原汁原味的進(jìn)行分析,%4_ax表示使用4位十六進(jìn)制進(jìn)行偏移;16/1表示每行顯示16字節(jié),逐字解析,%02x表示以十六進(jìn)制顯示,每個(gè)字符占據(jù)兩位。

可以看到截取到的.text段以及.data段:

在這里插入圖片描述

可以注意到起始于偏移0x40處,于我們ELF文件中看到的描述相同!!占據(jù)的字節(jié)數(shù)也是相同的,指令也是相同的!!

在這里插入圖片描述

對(duì)于數(shù)據(jù),0x58開始的數(shù)據(jù)段,正好是0x14對(duì)應(yīng)的十進(jìn)制數(shù)20,信息也可以對(duì)的上。

重定位表

在進(jìn)行匯編時(shí),在一個(gè)模塊內(nèi),如果引用了其他模塊或者時(shí)庫(kù)中的變量或者函數(shù),匯編器并不會(huì)解析引用的外部符號(hào)。匯編器基本上是留空引用的外部符號(hào)的地址;然后在鏈接時(shí),在符號(hào)地址確定后,鏈接器再來(lái)修訂這些位置,這個(gè)修訂的過(guò)程,被稱之為重定位(編譯時(shí)、加載/運(yùn)行時(shí),這里說(shuō)的是前者)。

這些需要修訂的位置并不是全都置為0,有時(shí)候這里填充的是一個(gè)Addend,這就是之所以使用引號(hào)將空引用起來(lái)的原因。

但是鏈接器并不能自動(dòng)找到目標(biāo)文件中引用外部符號(hào)的地方,所以在目標(biāo)文件中需要建立一個(gè)表格,這個(gè)表格中的每一條記錄對(duì)應(yīng)的就是一共需要重定位的符號(hào),這個(gè)表格通常稱為重定位表,匯編器將為可重定位文件中每個(gè)包含需要重定位符號(hào)的段都建立一個(gè)重定位表。

ELF標(biāo)準(zhǔn)規(guī)定,重定位表中的表項(xiàng)可以使用如下兩種格式:

在這里插入圖片描述

唯一不同的成員即r_addend,這個(gè)成員一般是個(gè)常量,用來(lái)輔助計(jì)算修訂值;若使用了第一種格式,那么r_addend將被填充在引用外部符號(hào)的地址處,也就是留空處。

  • r_offset為需要重定位的符號(hào)在目標(biāo)文件中的偏移;對(duì)于目標(biāo)文件,r_offset是相對(duì)于段的,是段內(nèi)偏移;對(duì)于執(zhí)行文件或者動(dòng)態(tài)庫(kù),r_offset是虛擬地址。
  • r_info中包含重定位類型和此處引用的外部符號(hào)在符號(hào)表中的索引。根據(jù)符號(hào)在符號(hào)表中的索引,鏈接器就可以從符號(hào)表中解析出符號(hào)的地址。

在這里插入圖片描述

可以使用命令readelf -r hello.o查看文件的重定位表。

在這里插入圖片描述

可以看到段.text以及.eh_frame段中都有符號(hào)需要重定位,所以建立了兩重定位表。在.text段的重定位表中,引用了兩個(gè)外部符號(hào),并且可以在第一列得到他們的偏移為0x15以及0x23。

在這里插入圖片描述

根據(jù)objdump的輸出可見,在偏移0x15處,則是變量foo2的地址,匯編器填充的addend是0;在偏移0x23處,foo2_func填充addend的也是0。

符號(hào)表

在鏈接時(shí)需要重定位目標(biāo)文件中引用的外部符號(hào),顯然鏈接器也需要指定這些符號(hào)的定義是在哪里,所以匯編器在每個(gè)目標(biāo)文件中創(chuàng)建了一個(gè)符號(hào)表,符號(hào)表中記錄了這個(gè)模塊定義的可以提供給其他模塊引用的全局符號(hào)。

查看符號(hào)表readelf -s fool2.o

在這里插入圖片描述

根據(jù)輸出可見,fool2.o符號(hào)表包含了10個(gè)符號(hào)。

  • value列表示的時(shí)符號(hào)的地址,由于鏈接時(shí)鏈接器才會(huì)分配地址,所以現(xiàn)在看到的符號(hào)地址全都是0。
  • Size列代表的時(shí)申請(qǐng)內(nèi)存的大小,可以看到變量foo2占據(jù)了4個(gè)字節(jié),foo2_func占據(jù)了23個(gè)字節(jié)。
  • Type列表示符號(hào)的類型。如foo2類型為OBJECT表示的是變量;FUNC表示的是函數(shù)。
  • Bind列表示符號(hào)綁定的相關(guān)信息,LOCAL表示模塊內(nèi)部符號(hào),對(duì)外不可見;GLOBAL表示全局符號(hào),屬于全局變量。
  • Ndx列表示該符號(hào)在哪個(gè)段,3為.data段,1為.text段。

那么對(duì)于引用外部符號(hào)的符號(hào)表,可以看看hello.o:

在這里插入圖片描述

由于符號(hào)foo2以及foo2_func都在模塊foo2中定義,對(duì)于模塊hello來(lái)說(shuō)是外部符號(hào),沒有在任何一個(gè)段中,所以在列Ndx中,他們的值都是UNDUNDUndefined的縮寫,表示其是未定義的。

在鏈接時(shí),對(duì)于模塊中引用的外部符號(hào),鏈接器將根據(jù)符號(hào)表進(jìn)行符號(hào)的重定位。如果將符號(hào)表刪除,那么鏈接器在鏈接時(shí)將找不到符號(hào)的定義,從而不能進(jìn)行正確的符號(hào)解析??梢钥吹较旅娴牟僮?#xff1a;

在這里插入圖片描述

0x05 鏈接

鏈接時(shí)編譯過(guò)程的最后一個(gè)階段,鏈接將一個(gè)或者多個(gè)目標(biāo)文件和庫(kù),包括動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù),鏈接為一個(gè)單獨(dú)的文件(通常為可執(zhí)行文件、動(dòng)態(tài)庫(kù)或者靜態(tài)庫(kù))。

鏈接器的工作可以分為兩個(gè)階段:

  • 第一階段是將多個(gè)文件合并為一個(gè)單獨(dú)的文件。對(duì)于可執(zhí)行文件,還需要為指令以及符號(hào)分配運(yùn)行時(shí)的地址。
  • 第二階段進(jìn)行符號(hào)重定位。

合并目標(biāo)文件

合并多個(gè)目標(biāo)文件其實(shí)就是將多個(gè)目標(biāo)文件的相同類型的段合并到一個(gè)段中:

在這里插入圖片描述

可以試著查看所有文件的目標(biāo)文件以及鏈接后可執(zhí)行文件的.text段:

hello.o:

在這里插入圖片描述

fool1.o:

在這里插入圖片描述

foo2.o:

在這里插入圖片描述

hello:

在這里插入圖片描述

根據(jù)上面輸出的結(jié)果可見,對(duì)于目標(biāo)文件,并沒有為目標(biāo)文件的機(jī)器指令及符號(hào)分配運(yùn)行時(shí)的地址,而對(duì)于可執(zhí)行文件hello,鏈接器已經(jīng)為其機(jī)器指令及符號(hào)分配了運(yùn)行時(shí)地址,并且申請(qǐng)了對(duì)應(yīng)的內(nèi)存空間。

理論上,三個(gè)目標(biāo)文件的.text段加起來(lái)應(yīng)該與可執(zhí)行文件的hello的.text段的尺寸大小是相等的。三個(gè)可執(zhí)行文件加起來(lái)的大小是0x70,但是遠(yuǎn)小于可執(zhí)行文件0x1a5。

可以注意到在編譯時(shí)會(huì)向gcc傳遞了參數(shù)-v,細(xì)心可以發(fā)現(xiàn),實(shí)際上鏈接時(shí)鏈接器自作主張地鏈接了一些特別的文件,包括crtl.o\crti.0\crtn.o\crtbegin.o\ctrend.o,其實(shí)就是我們前面提到的啟動(dòng)文件。所以會(huì)增加了.text段的大小。

也可以手動(dòng)調(diào)用ld,不鏈接這些啟動(dòng)文件,再來(lái)對(duì)比一下.text段的尺寸。在默認(rèn)情況下,鏈接器將使用函數(shù)_start作為可執(zhí)行文件的入口,但是這個(gè)函數(shù)的實(shí)現(xiàn)在啟動(dòng)文件ctrl.o中,因此,在這里我們通過(guò)給鏈接器ld傳遞參數(shù)-e main,明確告訴鏈接器不適用默認(rèn)的啟動(dòng)函數(shù)_start了,否則鏈接器會(huì)找不到符號(hào)_start,直接使用函數(shù)main作為可執(zhí)行文件的入口。當(dāng)然main函數(shù)中并沒有實(shí)現(xiàn)啟動(dòng)代碼的功能,在這里這是為了方便查看.text段,尺寸是所有目標(biāo)文件size的總和。如果不是等于總和,差別有幾個(gè)字節(jié)的話,是由內(nèi)存對(duì)齊所引起的。

符號(hào)重定位

上面為鏈接的第一階段,目標(biāo)文件已經(jīng)合并完成了,并且已經(jīng)為符號(hào)分配了運(yùn)行時(shí)的地址,鏈接器將符號(hào)進(jìn)行重定位。

在這里插入圖片描述

可以看到匯編器已經(jīng)將這兩處需要重定位的符號(hào)記錄在了重定位表中。

  • R_386_32,ELF標(biāo)準(zhǔn)規(guī)定的計(jì)算修訂值得公式是:S+A;其中,S表示符號(hào)的運(yùn)行地址,A就是匯編器填充在引用外部符號(hào)處的Addend。
  • R_386_PC32,ELF標(biāo)準(zhǔn)規(guī)定的計(jì)算修訂值的公式是:S+A-P;其中,S,A與前面的意義完全相同,P為修訂處的運(yùn)行地址或者偏移。對(duì)于可執(zhí)行文件和動(dòng)態(tài)庫(kù),P為修訂處的運(yùn)行時(shí)地址。

首先確定S,運(yùn)行時(shí)地址在鏈接時(shí)才分配:

在這里插入圖片描述

可以看到foo2、foo2_func的運(yùn)行時(shí)的地址。

之后再捋捋匯編器為這兩個(gè)符號(hào)填充的Addend是多少,可以使用objdump反匯編hello.o,也可以看到上面圖中的-8以及-4。

需要注意的是,對(duì)于函數(shù)占據(jù)的運(yùn)行時(shí)地址小于main函數(shù),那么這里的函數(shù)地址與PC相對(duì)地址將是負(fù)數(shù),其實(shí)就是將PC跳回去執(zhí)行。在機(jī)器指令中,使用的是數(shù)的補(bǔ)碼形式。

對(duì)于R_386_32這種重定位類型,是絕對(duì)地址重定位,鏈接器只要解析符號(hào)運(yùn)行時(shí)地址替換修訂處即可。而對(duì)于R_386_PC32,這是一個(gè)PC相對(duì)地址重定位,當(dāng) 執(zhí)行當(dāng)前指令時(shí),PC中已經(jīng)加載了下一條指令的地址,并不是當(dāng)前指令的地址。

在鏈接時(shí),鏈接器在需要重定位的符號(hào)所在的偏移處直接進(jìn)行了編輯修訂,所以鏈接器也被形象地稱為link editor。

鏈接靜態(tài)庫(kù)

靜態(tài)庫(kù)其實(shí)就是多個(gè)目標(biāo)文件的打包,因此與合并多個(gè)目標(biāo)文件并沒有什么區(qū)別。但是在鏈接靜態(tài)庫(kù)時(shí),并不是將整個(gè)靜態(tài)庫(kù)中包含的目標(biāo)文件全部復(fù)制一份到最終的可執(zhí)行文件中,而是僅僅鏈接庫(kù)中使用的目標(biāo)文件。

可以將兩個(gè)源文件編譯為靜態(tài)庫(kù)libfoo.a,然后將其鏈接到hello:

在這里插入圖片描述

可以看到靜態(tài)庫(kù)的符號(hào)表:

在這里插入圖片描述

在這里插入圖片描述

可以看到就是兩個(gè)目標(biāo)文件的合體,但是在hello中可不是什么都有:

在這里插入圖片描述

鏈接動(dòng)態(tài)庫(kù)

與靜態(tài)庫(kù)不同,動(dòng)態(tài)庫(kù)不會(huì)在可執(zhí)行文件中有任何副本,那么為什么編譯鏈接依然需要指定動(dòng)態(tài)庫(kù)?

  • 動(dòng)態(tài)加載器需要知道可執(zhí)行程序依賴的動(dòng)態(tài)庫(kù),這樣在加載可執(zhí)行程序時(shí)才能加載其依賴的動(dòng)態(tài)庫(kù)。

在鏈接時(shí)會(huì)根據(jù)可執(zhí)行程序引用的動(dòng)態(tài)庫(kù)中的符號(hào)的情況,在dynamic段中記錄可執(zhí)行程序依賴的動(dòng)態(tài)庫(kù)。

gcc -c -fPIC fool1.c fool2.c		#產(chǎn)生與地址無(wú)關(guān)的目標(biāo)文件
gcc -shared -o libfoo.so fool1.o fool2.o
gcc hello.c -o hello -L./ -lfoo
readelf -d hello | grep Shared
  • 鏈接器需要在重定位表中創(chuàng)建重定位記錄,這樣當(dāng)動(dòng)態(tài)鏈接器加載hello時(shí),將依據(jù)重定位記錄重定位hello引用的這些外部符號(hào)。

重定位記錄存儲(chǔ)在ELF文件的重定位段中,ELF文件中可能有多個(gè)段包含需要重定位的符號(hào),所以可能會(huì)包含多個(gè)重定位段。

rel.dyn段中記錄的是加載時(shí)需要重定位的變量。

rel.plt段中記錄的是需要重定位的函數(shù)。

雖然編譯時(shí)不需要鏈接共享庫(kù),但是可執(zhí)行文件中需要記錄其依賴的共享庫(kù)以及加載/運(yùn)行時(shí)需要重定位的條目,在加載程序時(shí),動(dòng)態(tài)加載器需要這些信息來(lái)完成加載時(shí)的重定位。

http://www.risenshineclean.com/news/54817.html

相關(guān)文章:

  • 美容院網(wǎng)站源碼seo搜索引擎優(yōu)化名詞解釋
  • 遼寧建設(shè)廳新網(wǎng)站個(gè)人免費(fèi)開發(fā)網(wǎng)站
  • 完善旅游網(wǎng)站的建設(shè)網(wǎng)站制作建設(shè)公司
  • 想做網(wǎng)站去哪里做百度灰色關(guān)鍵詞排名技術(shù)
  • 白云區(qū)建網(wǎng)站常用的網(wǎng)絡(luò)推廣手段有哪些
  • 企業(yè)網(wǎng)站建設(shè)費(fèi)用深圳免費(fèi)外鏈生成器
  • 網(wǎng)站開發(fā)兼職接單平臺(tái)長(zhǎng)沙關(guān)鍵詞優(yōu)化公司電話
  • 邯鄲外貿(mào)網(wǎng)站建設(shè)公司成都網(wǎng)站建設(shè)公司
  • 政府網(wǎng)站建設(shè)的存在問題網(wǎng)址查詢工具
  • 如何實(shí)現(xiàn)網(wǎng)站開發(fā)太原網(wǎng)站建設(shè)制作
  • 廣州建筑集團(tuán)股份有限公司杭州seo排名優(yōu)化外包
  • 如何提取網(wǎng)頁(yè)中的視頻seo主要做什么
  • 龍崗區(qū)住房建設(shè)局網(wǎng)站品牌營(yíng)銷方案
  • 云盤做網(wǎng)站關(guān)鍵詞推廣營(yíng)銷
  • 廈門網(wǎng)站建設(shè)哪家好優(yōu)化網(wǎng)絡(luò)的軟件
  • 企業(yè)網(wǎng)站建設(shè)webbj免費(fèi)網(wǎng)站優(yōu)化排名
  • 湛江疫情最新通報(bào)五年級(jí)上冊(cè)語(yǔ)文優(yōu)化設(shè)計(jì)答案
  • 什么是網(wǎng)站解決方案武漢網(wǎng)絡(luò)推廣有哪些公司
  • 網(wǎng)站留言評(píng)論功能深圳百度seo代理
  • 網(wǎng)站建設(shè)合同印花稅稅目外鏈?zhǔn)珍浘W(wǎng)站
  • vs2015網(wǎng)站開發(fā)教程seo搜索優(yōu)化待遇
  • 青島做網(wǎng)站的公司深圳市前十的互聯(lián)網(wǎng)推廣公司
  • asp網(wǎng)站后臺(tái)安全退出購(gòu)物網(wǎng)站
  • 做網(wǎng)站哪家南京做網(wǎng)站網(wǎng)站關(guān)鍵詞挖掘
  • 網(wǎng)站建設(shè)市場(chǎng)前景體育新聞最新消息
  • 網(wǎng)站沒有被搜索引擎收錄東莞seo排名公司
  • 愛情動(dòng)做網(wǎng)站推薦收錄批量查詢
  • 國(guó)內(nèi)做賭博網(wǎng)站代理怎么樣加快百度收錄的方法
  • 分布式移動(dòng)網(wǎng)站開發(fā)技術(shù)一個(gè)品牌的策劃方案
  • 南昌哪里可以做電商網(wǎng)站seo收索引擎優(yōu)化