我們在linux平臺(tái)下建立一個(gè)a.c文件,程序很簡單,顯示輸出Please input your name:,然后讓我們輸入名字,最后調(diào)用了一個(gè)子函數(shù)輸出hello,我們的名字
成都創(chuàng)新互聯(lián)公司作為成都網(wǎng)站建設(shè)公司,專注成都網(wǎng)站建設(shè)公司、網(wǎng)站設(shè)計(jì),有關(guān)成都定制網(wǎng)站方案、改版、費(fèi)用等問題,行業(yè)涉及不銹鋼雕塑等多個(gè)領(lǐng)域,已為上千家企業(yè)服務(wù),得到了客戶的尊重與認(rèn)可。#includevoid hello(char * name);
int main()
{char name[16]={0};
printf("Please input your name:");
gets(name);
hello(name);
return 0;
}
void hello(char * name)
{printf("hello,%s\n",name);
}
從.c文件到可執(zhí)行文件
gcc的編譯過程預(yù)處理(Preprocessing),
編譯(Compilation),
匯編(Assemble),
鏈接(Linking)
gcc編譯C語言主要用到以下幾個(gè)程序:C編譯器gcc、匯編器as、鏈接器ld和二進(jìn)制轉(zhuǎn)換工具objcopy
gcc選項(xiàng)總結(jié):-E 只激活預(yù)處理,這個(gè)不生成文件,你需要把它重定向到一個(gè)輸出文件里面
-S 編譯到匯編語言不進(jìn)行匯編和鏈接
-c 編譯到目標(biāo)代碼
-o 文件輸出到 文件
-static 此選項(xiàng)對生成的文件采用靜態(tài)鏈接
-g 生成調(diào)試信息。GNU 調(diào)試器可利用該信息
-shared 此選項(xiàng)將盡量使用動(dòng)態(tài)庫,所以生成文件比較小,但是需要系統(tǒng)由動(dòng)態(tài)庫
-O0
-O1
-O2
-O3 編譯器的優(yōu)化選項(xiàng)的4個(gè)級別,-O0表示沒有優(yōu)化,-O1為缺省值,-O3優(yōu)化級別最高
-w 不生成任何警告信息
-Wall 生成所有警告信息(默認(rèn)生成)
1.預(yù)處理(Preprocessing)由編譯器完成,將所有的#include頭文件以及宏定義替換成其真正的內(nèi)容
gcc的預(yù)處理是預(yù)處理器cpp來完成的
我們可以用以下指令對.c文件進(jìn)行預(yù)處理:
gcc -E a.c -o a.i
或者
cpp a.c -o a.i
-o:代表輸出到指定文件
可以看到文件預(yù)處理后變大了很多
打開a.i文件發(fā)現(xiàn)是把我們用的頭文件stdio.h所涉及的其他頭文件以及宏定義,變量都引入了進(jìn)來
可以看到文件最后才是我們寫的c代碼,實(shí)際上我們能直接使用printf等函數(shù)是因?yàn)槲覀円昧艘粋€(gè)頭文件,使得我們可以少寫很多代碼,就相當(dāng)于自己事先把一個(gè)自定義的函數(shù)寫到一個(gè)文件里,下次再使用直接引用這個(gè)文件就可以了,而不用自己再把以前定義好的函數(shù)再抄一遍了,我們用的gcc編譯器的預(yù)處理就幫助我們省略了抄定義好的函數(shù),變量等東西這一步
由編譯器完成,將經(jīng)過預(yù)處理之后的程序轉(zhuǎn)換成特定匯編代碼的過程, 編譯的命令如下:
gcc -S a.i -o a.s
執(zhí)行這一步程序會(huì)出現(xiàn)一個(gè)warning,警告:函數(shù)gets的隱式聲明;你是說“fgets”嗎?
為什么會(huì)出現(xiàn)這個(gè)呢,這是因?yàn)間cc編譯器太強(qiáng)了,檢測出我們使用了gets函數(shù),然后說gets函數(shù)很危險(xiǎn),建議用fgets函數(shù),這個(gè)問題在后面會(huì)講
我們先來查看一下編譯好的匯編代碼
.file "a.c"
.text
.section .rodata
.LC0:
.string "Please input your name:"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
movq $0, -32(%rbp)
movq $0, -24(%rbp)
leaq .LC0(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
leaq -32(%rbp), %rax
movq %rax, %rdi
movl $0, %eax
call gets@PLT
leaq -32(%rbp), %rax
movq %rax, %rdi
call hello
movl $0, %eax
movq -8(%rbp), %rdx
subq %fs:40, %rdx
je .L3
call __stack_chk_fail@PLT
.L3:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.section .rodata
.LC1:
.string "hello,%s\n"
.text
.globl hello
.type hello, @function
hello:
.LFB1:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rsi
leaq .LC1(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
nop
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size hello, .-hello
.ident "GCC: (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.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:
這是AT&T匯編語言,初學(xué)者可能看上去很復(fù)雜,暫且不看
接著往下走
由匯編器完成,匯編過程將上一步的匯編代碼轉(zhuǎn)換成機(jī)器碼(machine code),這一步產(chǎn)生了二進(jìn)制的目標(biāo)文件(.o文件)(ELF文件), gcc匯編過程通過as命令完成
gcc -c a.s -o a.o
或者
as a.s -o a.o
查看一下a.o
這已經(jīng)把我們的匯編語言變成二進(jìn)制目標(biāo)代碼(.o文件)了
.o文件又稱對象文件,是可執(zhí)行文件,是可重定向文件的一種,通常以ELF格式保存,里面包含了對各個(gè)函數(shù)的入口標(biāo)記,描述,當(dāng)程序要執(zhí)行時(shí)還需要鏈接(link).鏈接就是把多個(gè).o文件鏈成一個(gè)可執(zhí)行文件。
中間插一段elf文件的內(nèi)容
ELF文件格式是linux下可執(zhí)行文件的一種格式
幾種類型的ELF文件:ELF文件參與程序的連接(建立一個(gè)程序)和程序的執(zhí)行(運(yùn)行一個(gè)程序),所以可以從不同的角度來看待elf格式的文件:
elf文件頭描述elf文件的總體信息。包括:系統(tǒng)相關(guān),類型相關(guān),加載相關(guān),鏈接相關(guān)。
可以用readelf命令來查看elf文件信息
如查看elf文件頭信息
readelf -h a.o
readelf命令解析
-a
--all 顯示全部信息,等價(jià)于 -h -l -S -s -r -d -V -A -I.
-h
--file-header 顯示elf文件開始的文件頭信息.
-l
--program-headers
--segments 顯示程序頭(段頭)信息(如果有的話)。
-S
--section-headers
--sections 顯示節(jié)頭信息(如果有的話)。
-g
--section-groups 顯示節(jié)組信息(如果有的話)。
-t
--section-details 顯示節(jié)的詳細(xì)信息(-S的)。
-s
--syms
--symbols 顯示符號表段中的項(xiàng)(如果有的話)。
-e
--headers 顯示全部頭信息,等價(jià)于: -h -l -S
-n
--notes 顯示note段(內(nèi)核注釋)的信息。
-r
--relocs 顯示可重定位段的信息。
-u
--unwind 顯示unwind段信息。當(dāng)前只支持IA64 ELF的unwind段信息。
-d
--dynamic 顯示動(dòng)態(tài)段的信息。
-V
--version-info 顯示版本段的信息。
-A
--arch-specific 顯示CPU構(gòu)架信息。
-D
--use-dynamic 使用動(dòng)態(tài)段中的符號表顯示符號,而不是使用符號段。
-x--hex-dump=以16進(jìn)制方式顯示指定段內(nèi)內(nèi)容。number指定段表中段的索引,或字符串指定文件中的段名。
-w[liaprmfFsoR] or
--debug-dump[=line,=info,=abbrev,=pubnames,=aranges,=macro,=frames,=frames-interp,=str,=loc,=Ranges] 顯示調(diào)試段中指定的內(nèi)容。
-I
--histogram 顯示符號的時(shí)候,顯示bucket list長度的柱狀圖。
-v
--version 顯示readelf的版本信息。
-H
--help 顯示readelf所支持的命令行選項(xiàng)。
-W
--wide 寬行輸出。
elf文件格式先講這些,接著往下看
4.鏈接(Linking)鏈接過程實(shí)際上是把多個(gè)可重定位文件合并成一個(gè)可執(zhí)行文件的過程。這個(gè)過程中最重要的兩個(gè)步驟是符號解析和重定位。所謂的符號解析,就是將符號的定義和引用關(guān)聯(lián)起來,而重定位就是給所有符號和指令添加運(yùn)行時(shí)的地址的過程。
過程
我們的程序最終是要裝載到運(yùn)行內(nèi)存中才可以執(zhí)行的,所以需要規(guī)定一種文件格式來確定裝載到內(nèi)存后對應(yīng)的地址
由鏈接器完成,鏈接分為兩種:動(dòng)態(tài)鏈接和靜態(tài)鏈接
GCC默認(rèn)情況下以動(dòng)態(tài)庫方式link
命令
gcc -static a.o -o a
實(shí)際是gcc調(diào)用了ld鏈接器,因?yàn)槌绦蛐枰溄雍芏嘞到y(tǒng)文件,所以這里不推薦使用ld命令鏈接
這里出現(xiàn)gets函數(shù)比較危險(xiǎn),跟編譯成匯編時(shí)一樣的意思,先不管這里,后面再講
對于靜態(tài)鏈接而言,程序鏈接后的地址其實(shí)就是在裝載進(jìn)內(nèi)存后,程序運(yùn)行的地址
可以看到符號表已經(jīng)有了地址,符號表在鏈接時(shí)已經(jīng)重定位,當(dāng)程序被裝載進(jìn)內(nèi)存時(shí),運(yùn)行時(shí)所用的虛擬地址就是這個(gè)地址
GCC默認(rèn)情況下以動(dòng)態(tài)庫方式link
gcc a.o -o a
用readelf命令查看一下生成的ELF文件的信息
這里符號表的地址只是偏移地址(相對于ELF文件首地址的偏移),而ELF文件的首地址在程序裝載進(jìn)內(nèi)存時(shí)才會(huì)分配,而動(dòng)態(tài)符號表(.dynsym)的地址也沒有分配,只有在程序運(yùn)行時(shí)才會(huì)分配地址,這是動(dòng)態(tài)鏈接的一個(gè)延遲綁定機(jī)制
查看程序節(jié)頭信息會(huì)發(fā)現(xiàn)動(dòng)態(tài)鏈接比靜態(tài)鏈接多出幾個(gè)符號表
readelf -S a
動(dòng)態(tài)鏈接
There are 31 section headers, starting at offset 0x3710:
節(jié)頭:
[號] 名稱 類型 地址 偏移量
大小 全體大小 旗標(biāo) 鏈接 信息 對齊
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000318 00000318
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.pr[…] NOTE 0000000000000338 00000338
0000000000000030 0000000000000000 A 0 0 8
[ 3] .note.gnu.bu[…] NOTE 0000000000000368 00000368
0000000000000024 0000000000000000 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000000038c 0000038c
0000000000000020 0000000000000000 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000000003b0 000003b0
0000000000000024 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 00000000000003d8 000003d8
00000000000000d8 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 00000000000004b0 000004b0
00000000000000af 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 0000000000000560 00000560
0000000000000012 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 0000000000000578 00000578
0000000000000040 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 00000000000005b8 000005b8
00000000000000c0 0000000000000018 A 6 0 8
[11] .rela.plt RELA 0000000000000678 00000678
0000000000000048 0000000000000018 AI 6 24 8
[12] .init PROGBITS 0000000000001000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[13] .plt PROGBITS 0000000000001020 00001020
0000000000000040 0000000000000010 AX 0 0 16
[14] .plt.got PROGBITS 0000000000001060 00001060
0000000000000010 0000000000000010 AX 0 0 16
[15] .plt.sec PROGBITS 0000000000001070 00001070
0000000000000030 0000000000000010 AX 0 0 16
[16] .text PROGBITS 00000000000010a0 000010a0
000000000000018e 0000000000000000 AX 0 0 16
[17] .fini PROGBITS 0000000000001230 00001230
000000000000000d 0000000000000000 AX 0 0 4
[18] .rodata PROGBITS 0000000000002000 00002000
0000000000000026 0000000000000000 A 0 0 4
[19] .eh_frame_hdr PROGBITS 0000000000002028 00002028
000000000000003c 0000000000000000 A 0 0 4
[20] .eh_frame PROGBITS 0000000000002068 00002068
00000000000000cc 0000000000000000 A 0 0 8
[21] .init_array INIT_ARRAY 0000000000003da8 00002da8
0000000000000008 0000000000000008 WA 0 0 8
[22] .fini_array FINI_ARRAY 0000000000003db0 00002db0
0000000000000008 0000000000000008 WA 0 0 8
[23] .dynamic DYNAMIC 0000000000003db8 00002db8
00000000000001f0 0000000000000010 WA 7 0 8
[24] .got PROGBITS 0000000000003fa8 00002fa8
0000000000000058 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000004000 00003000
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000004010 00003010
0000000000000008 0000000000000000 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 00003010
000000000000002b 0000000000000001 MS 0 0 1
[28] .symtab SYMTAB 0000000000000000 00003040
00000000000003a8 0000000000000018 29 18 8
[29] .strtab STRTAB 0000000000000000 000033e8
000000000000020b 0000000000000000 0 0 1
[30] .shstrtab STRTAB 0000000000000000 000035f3
000000000000011a 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)
靜態(tài)鏈接
There are 32 section headers, starting at offset 0xdb548:
節(jié)頭:
[號] 名稱 類型 地址 偏移量
大小 全體大小 旗標(biāo) 鏈接 信息 對齊
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.pr[…] NOTE 0000000000400270 00000270
0000000000000030 0000000000000000 A 0 0 8
[ 2] .note.gnu.bu[…] NOTE 00000000004002a0 000002a0
0000000000000024 0000000000000000 A 0 0 4
[ 3] .note.ABI-tag NOTE 00000000004002c4 000002c4
0000000000000020 0000000000000000 A 0 0 4
[ 4] .rela.plt RELA 00000000004002e8 000002e8
0000000000000240 0000000000000018 AI 29 20 8
[ 5] .init PROGBITS 0000000000401000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[ 6] .plt PROGBITS 0000000000401020 00001020
0000000000000180 0000000000000000 AX 0 0 16
[ 7] .text PROGBITS 00000000004011c0 000011c0
0000000000095138 0000000000000000 AX 0 0 64
[ 8] __libc_freeres_fn PROGBITS 0000000000496300 00096300
00000000000014cd 0000000000000000 AX 0 0 16
[ 9] .fini PROGBITS 00000000004977d0 000977d0
000000000000000d 0000000000000000 AX 0 0 4
[10] .rodata PROGBITS 0000000000498000 00098000
000000000001cb2c 0000000000000000 A 0 0 32
[11] .stapsdt.base PROGBITS 00000000004b4b2c 000b4b2c
0000000000000001 0000000000000000 A 0 0 1
[12] .eh_frame PROGBITS 00000000004b4b30 000b4b30
000000000000b928 0000000000000000 A 0 0 8
[13] .gcc_except_table PROGBITS 00000000004c0458 000c0458
0000000000000122 0000000000000000 A 0 0 1
[14] .tdata PROGBITS 00000000004c17b0 000c07b0
0000000000000020 0000000000000000 WAT 0 0 8
[15] .tbss NOBITS 00000000004c17d0 000c07d0
0000000000000048 0000000000000000 WAT 0 0 8
[16] .init_array INIT_ARRAY 00000000004c17d0 000c07d0
0000000000000008 0000000000000008 WA 0 0 8
[17] .fini_array FINI_ARRAY 00000000004c17d8 000c07d8
0000000000000008 0000000000000008 WA 0 0 8
[18] .data.rel.ro PROGBITS 00000000004c17e0 000c07e0
0000000000003788 0000000000000000 WA 0 0 32
[19] .got PROGBITS 00000000004c4f68 000c3f68
0000000000000098 0000000000000000 WA 0 0 8
[20] .got.plt PROGBITS 00000000004c5000 000c4000
00000000000000d8 0000000000000008 WA 0 0 8
[21] .data PROGBITS 00000000004c50e0 000c40e0
00000000000019e0 0000000000000000 WA 0 0 32
[22] __libc_subfreeres PROGBITS 00000000004c6ac0 000c5ac0
0000000000000048 0000000000000000 WAR 0 0 8
[23] __libc_IO_vtables PROGBITS 00000000004c6b20 000c5b20
0000000000000768 0000000000000000 WA 0 0 32
[24] __libc_atexit PROGBITS 00000000004c7288 000c6288
0000000000000008 0000000000000000 WAR 0 0 8
[25] .bss NOBITS 00000000004c72a0 000c6290
0000000000005980 0000000000000000 WA 0 0 32
[26] __libc_freer[…] NOBITS 00000000004ccc20 000c6290
0000000000000020 0000000000000000 WA 0 0 8
[27] .comment PROGBITS 0000000000000000 000c6290
000000000000002b 0000000000000001 MS 0 0 1
[28] .note.stapsdt NOTE 0000000000000000 000c62bc
0000000000001648 0000000000000000 0 0 4
[29] .symtab SYMTAB 0000000000000000 000c7908
000000000000c480 0000000000000018 30 770 8
[30] .strtab STRTAB 0000000000000000 000d3d88
0000000000007668 0000000000000000 0 0 1
[31] .shstrtab STRTAB 0000000000000000 000db3f0
0000000000000157 0000000000000000 0 0 1
這是因?yàn)樗鼈兊闹囟ㄏ驒C(jī)制不同
先來說一下兩個(gè)符號表
1.全局偏移表(GOT):存放外部函數(shù)地址的數(shù)據(jù)段。
2.程序連接表(PLT):用來獲取數(shù)據(jù)段記錄的外部函數(shù)地址的代碼。
print_banner: printf@plt: printf@got: 0xf7e835f0 :
… jmp *printf@got 0xf7e835f0 …
call printf@plt ret
… …
可執(zhí)行文件 PLT表 GOT表 glibc中的printf函數(shù)
GOT是一個(gè)存儲(chǔ)外部庫函數(shù)的表
PLT則是由代碼片段組成的,每個(gè)代碼片段都跳轉(zhuǎn)到GOT表中的一個(gè)具體的函數(shù)調(diào)用
這里講一下重定位
番外篇2–重定位 鏈接時(shí)重定位–靜態(tài)鏈接鏈接階段是將一個(gè)或多個(gè)中間文件(.o文件)通過鏈接器將它們鏈接成一個(gè)可執(zhí)行文件,主要做的事情有
對各個(gè)中間文件的同名section進(jìn)行合并
對代碼段,數(shù)據(jù)段等進(jìn)行地址分配
進(jìn)行鏈接時(shí)重定位
兩種情況:
如果是在其他中間文件中已經(jīng)定義了的函數(shù),鏈接階段可以直接重定位到函數(shù)地址
如果是在動(dòng)態(tài)庫中定義了的函數(shù),鏈接階段無法直接重定位到函數(shù)地址,只能生成額外的小片段代碼,也就是PLT表,然后重定位到該代碼片段
運(yùn)行時(shí)重定位–動(dòng)態(tài)鏈接運(yùn)行后加載動(dòng)態(tài)庫,把動(dòng)態(tài)庫中的相應(yīng)函數(shù)地址填入GOT表,由于PLT表是跳轉(zhuǎn)到GOT表的,這就構(gòu)成了運(yùn)行時(shí)重定位
延遲重定位–動(dòng)態(tài)鏈接只有動(dòng)態(tài)庫函數(shù)在被調(diào)用時(shí),才會(huì)進(jìn)行地址解析和重定位工作,這時(shí)候動(dòng)態(tài)庫函數(shù)的地址才會(huì)被寫入到GOT表項(xiàng)中
第一步由函數(shù)調(diào)用跳入到PLT表中,然后第二步PLT表跳到GOT表中,可以看到第三步由GOT表回跳到PLT表中,這時(shí)候進(jìn)行壓棧,把代表函數(shù)的ID壓棧,接著第四步跳轉(zhuǎn)到公共的PLT表項(xiàng)中,第5步進(jìn)入到GOT表中,然后_dl_runtime_resolve對動(dòng)態(tài)函數(shù)進(jìn)行地址解析和重定位,第七步把動(dòng)態(tài)函數(shù)真實(shí)的地址寫入到GOT表項(xiàng)中,然后執(zhí)行函數(shù)并返回。
解釋下dynamic段,link_map和_dl_runtime_resolve
dynamic段:提供動(dòng)態(tài)鏈接的信息,例如動(dòng)態(tài)鏈接中各個(gè)表的位置
link_map:已加載庫的鏈表,由動(dòng)態(tài)庫函數(shù)的地址構(gòu)成的鏈表
_dl_runtime_resolve:在第一次運(yùn)行時(shí)進(jìn)行地址解析和重定位工作
可以看到,第一步還是由函數(shù)調(diào)用跳入到PLT表,但是第二步跳入到GOT表中時(shí),由于這個(gè)時(shí)候該表項(xiàng)已經(jīng)是動(dòng)態(tài)函數(shù)的真實(shí)地址了,所以可以直接執(zhí)行然后返回。
對于動(dòng)態(tài)函數(shù)的調(diào)用,第一次要經(jīng)過地址解析和回寫到GOT表項(xiàng)中,第二次直接調(diào)用即可
運(yùn)行可執(zhí)行文件 裝載進(jìn)內(nèi)存程序編譯鏈接完成后是保存在硬盤中的,當(dāng)用戶執(zhí)行該程序的時(shí)候,該程序(ELF可執(zhí)行文件)會(huì)被加載器按照program header table(可執(zhí)行文件頭)的描述將程序的代碼段和數(shù)據(jù)段從硬盤加載到內(nèi)存中。
如果是靜態(tài)鏈接就是直接將各個(gè)段的地址寫入內(nèi)存,如果是動(dòng)態(tài)鏈接就先隨機(jī)分配一個(gè)地址給ELF文件頭,作為首地址,然后ELF文件其他數(shù)據(jù)根據(jù)偏移量確定地址。
這里的地址都是虛擬地址,在運(yùn)行內(nèi)存中有著許多進(jìn)程,每個(gè)進(jìn)程又包含至少一個(gè)線程
進(jìn)程
這里講一下進(jìn)程與線程
gcc編譯好的ELF可執(zhí)行文件叫程序
那么運(yùn)行時(shí)的程序就叫做進(jìn)程,進(jìn)程之間通過 TCP/IP 端口實(shí)現(xiàn)交互
進(jìn)程是申請一塊內(nèi)存空間,將數(shù)據(jù)放到內(nèi)存空間中去, 是申請數(shù)據(jù)的過程
是最小的資源管理單元
而線程就是進(jìn)程的子集,多個(gè)線程共享同一塊內(nèi)存(由進(jìn)程向操作系統(tǒng)申請),通過共享的內(nèi)存空間來進(jìn)行交互
是進(jìn)程的一條流水線, 只用來執(zhí)行程序,而不涉及到申請資源, 是程序的實(shí)際執(zhí)行者
最小的執(zhí)行單元
總的來說,程序要運(yùn)行,需要先向操作系統(tǒng)申請一段內(nèi)存作為進(jìn)程
進(jìn)程空間的內(nèi)存分配
對于linux來說,從 Linux 內(nèi)核的角度來看,進(jìn)程和線程都是一樣的。
系統(tǒng)調(diào)用fork()可以新建一個(gè)子進(jìn)程,函數(shù)pthread()可以新建一個(gè)線程。但無論線程還是進(jìn)程,都是用task_struct結(jié)構(gòu)表示的,,唯一的區(qū)別就是共享的數(shù)據(jù)區(qū)域不同。
Linux 系統(tǒng)將線程看做共享數(shù)據(jù)的進(jìn)程
函數(shù)調(diào)用經(jīng)常是嵌套的,在同一時(shí)刻,堆棧中會(huì)有多個(gè)函數(shù)的信息。每個(gè)未完成運(yùn)行的函數(shù)占用一個(gè)獨(dú)立的連續(xù)區(qū)域,稱作棧幀(Stack Frame)。棧幀是堆棧的邏輯片段,當(dāng)調(diào)用函數(shù)時(shí)邏輯棧幀被壓入堆棧, 當(dāng)函數(shù)返回時(shí)邏輯棧幀被從堆棧中彈出。棧幀存放著函數(shù)參數(shù),局部變量及恢復(fù)前一棧幀所需要的數(shù)據(jù)等。
棧幀的邊界由棧幀基地址指針EBP和堆棧指針ESP界定(指針存放在相應(yīng)寄存器中)。EBP指向當(dāng)前棧幀底部(高地址),在當(dāng)前棧幀內(nèi)位置固定;ESP指向當(dāng)前棧幀頂部(低地址),當(dāng)程序執(zhí)行時(shí)ESP會(huì)隨著數(shù)據(jù)的入棧和出棧而移動(dòng)。因此函數(shù)中對大部分?jǐn)?shù)據(jù)的訪問都基于EBP進(jìn)行。
函數(shù)調(diào)用時(shí)入棧順序?yàn)?/p>
實(shí)參N~1 → 主調(diào)函數(shù)返回地址→主調(diào)函數(shù)幀基指針EBP → 被調(diào)函數(shù)局部變量1~N
對于我們剛寫的的hello.c來說,大概是這樣的
上面說進(jìn)程向系統(tǒng)申請了一段內(nèi)存,gcc編譯生成的ELF可執(zhí)行文件會(huì)將自己數(shù)據(jù)映射到這段內(nèi)存上,而線程作為進(jìn)程的執(zhí)行單位擁有自己的堆棧,棧上有一個(gè)個(gè)棧幀來臨時(shí)存儲(chǔ)數(shù)據(jù)。
CPU運(yùn)行程序的過程就是執(zhí)行一條條指令的過程,這些指令就是gcc編譯鏈接成的ELF可執(zhí)行文件里的指令(也就是.text段里的代碼段的機(jī)器代碼)。
番外篇5–虛擬內(nèi)存與內(nèi)存尋址棧幀的創(chuàng)建和銷毀也是通過CPU執(zhí)行ELF文件里的代碼段中的指令實(shí)現(xiàn)的。
至此,c程序從編譯開始到運(yùn)行結(jié)束的過程就結(jié)束了。
到這里就可以解釋gcc進(jìn)行第二步操作編譯時(shí)和靜態(tài)鏈接時(shí)的警告了。
我們再看一下我們的a.c文件
#includevoid hello(char * name);
int main()
{char name[16]={0};
printf("Please input your name:");
gets(name);
hello(name);
return 0;
}
void hello(char * name)
{printf("hello,%s\n",name);
}
我們的name數(shù)組只有16字節(jié)大小,gets函數(shù)可以輸入超過16字節(jié)的數(shù)據(jù),根據(jù)函數(shù)調(diào)用棧的原理,name數(shù)組存放的位置與main返回地址是挨著的,如果輸入過長的數(shù)據(jù)可能會(huì)破壞返回地址的數(shù)據(jù),致使程序返回到一個(gè)錯(cuò)誤的地址,如果這個(gè)地址無意義,程序不能正常退出,就會(huì)報(bào)錯(cuò),如果這個(gè)地址被我們設(shè)置成一個(gè)特定的有惡意代碼的地址,程序就會(huì)去執(zhí)行我們的惡意代碼,造成危險(xiǎn)。所以gcc向我們發(fā)送警告,讓我們使用fgets函數(shù)來限制輸入字節(jié)為16字節(jié)以內(nèi)。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧
網(wǎng)站題目:CTF--PWN必備技能--理解c程序從編譯開始到運(yùn)行結(jié)束的過程-創(chuàng)新互聯(lián)
文章轉(zhuǎn)載:http://jinyejixie.com/article46/egieg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化、定制網(wǎng)站、營銷型網(wǎng)站建設(shè)、面包屑導(dǎo)航、網(wǎng)站設(shè)計(jì)公司、動(dòng)態(tài)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容