網站首頁 編程語言 正文
函數棧幀
我們的代碼會被編譯成機器指令并寫入到可執行文件,當程序執行時,可執行文件被加載到內存,這些機器指令會被存儲到虛擬地址空間中的代碼段,在代碼段內部,指令是低地址向高地址堆積的。堆區存儲的是需要程序員手動alloc并free的空間,需要自己來控制。
虛擬內存空間是對存儲器的一層抽象,是為了更好的來管理存儲器,虛擬內存和存儲器之間存在映射關系。
如果在一個函數中調用了另外一個函數,編譯器就會對應生成一條call指令,當call指令被執行時,就會跳轉到被調用函數入口處開始執行,而每個函數的最后都有一條ret指令,負責在函數結束后跳回到調用處繼續執行。
call 指令做了兩件事,將下一條指令的地址入棧,這就是IP寄存器中存儲的值,第二,跳轉到被調用函數入口處執行。
函數執行時需要有足夠的內存空間用來存儲參數,局部變量,返回值,這塊空間對應的就是棧,棧區是從高地址向低地址生長的,且先進后出。分配給函數的棧空間被稱為函數棧幀。
C語言中,每個棧幀對應著一個未運行完的函數。棧幀中保存了該函數的返回地址和局部變量。
寄存器
ESP寄存器:ESP即 Extended stack pointer 的縮寫,直譯過來就是擴展的棧指針寄存器。SP是16位的,ESP是32位的,RSP是64位的,存放的都是棧頂地址。
EBP寄存器:EBP即 Extended base pointer 的縮寫,直譯過來就是擴展的基址指針寄存器。該指針總是指向當前棧幀的底部。
IP寄存器:指令指針,它指向代碼段中的地址,是一個16位專用寄存器,它指向當前需要取出的指令字節,也就是下一個將要執行的指令在代碼段中的地址。
eax:累加(Accumulator)寄存器,常用于函數返回值
ebx:基址(Base)寄存器,以它為基址訪問內存
ecx:計數器(Counter)寄存器,常用作字符串和循環操作中的計數器
edx:數據(Data)寄存器,常用于乘除法和I/O指針
esi:源地址寄存器
edi:目的地址寄存器
esp:堆棧指針
ebp:棧指針寄存器
當然,以上功能并未限制寄存器的使用,特殊情況為了效率也可作其他用途。
這八個寄存器低16位分別有一個引用別名 ax, bx, cx, dx, bp, si, di, sp,
其中 ax, bx, cx, dx, 的高8位又引用至 ah, bh, ch, dh,低八位引用至 al, bl, cl, dl
在 64-bit 模式下,有16個通用寄存器,但是這16個寄存器是兼容32位模式的,
32位方式下寄存器名分別為 eax, ebx, ecx, edx, edi, esi, ebp, esp, r8d – r15d.
在64位模式下,他們被擴展為 rax, rbx, rcx, rdx, rdi, rsi, rbp, rsp, r8 – r15.
其中 r8 – r15 這八個寄存器是64-bit模式下新加入的寄存器。
我們看到CPU在執行代碼段中的指令,而這當中又伴隨著內存的分配,于是在函數棧幀上就會有相應的變化。
int add(int a, int b) { int c = 4; c = a + b; return c; } int main() { int a = 1; int b = 2; int sum = 3; sum = add(a, b); return 0; }
生成的匯編代碼的方式
1、使用 gcc + objdump
gcc -save-temps -fverbose-asm -g -o b testasm.c
objdump -S --disassemble b > b.objdump
2、使用第三方網站來生成,進入 https://godbolt.org/,選擇語言為C
,編譯器為x86-64 gcc 12.2
,粘貼進你的代碼,就能看到匯編代碼,如下
add:
? ? ? ? push ? ?rbp
? ? ? ? mov ? ? rbp, rsp
? ? ? ? mov ? ? DWORD PTR [rbp-20], edi
? ? ? ? mov ? ? DWORD PTR [rbp-24], esi
? ? ? ? mov ? ? DWORD PTR [rbp-4], 4
? ? ? ? mov ? ? edx, DWORD PTR [rbp-20]
? ? ? ? mov ? ? eax, DWORD PTR [rbp-24]
? ? ? ? add ? ? eax, edx
? ? ? ? mov ? ? DWORD PTR [rbp-4], eax
? ? ? ? mov ? ? eax, DWORD PTR [rbp-4]
? ? ? ? pop ? ? rbp
? ? ? ? ret
main:
? ? ? ? push ? ?rbp
? ? ? ? mov ? ? rbp, rsp
? ? ? ? sub ? ? rsp, 16
? ? ? ? mov ? ? DWORD PTR [rbp-4], 1
? ? ? ? mov ? ? DWORD PTR [rbp-8], 2
? ? ? ? mov ? ? DWORD PTR [rbp-12], 3
? ? ? ? mov ? ? edx, DWORD PTR [rbp-8]
? ? ? ? mov ? ? eax, DWORD PTR [rbp-4]
? ? ? ? mov ? ? esi, edx
? ? ? ? mov ? ? edi, eax
? ? ? ? call ? ?add
? ? ? ? mov ? ? DWORD PTR [rbp-12], eax
? ? ? ? mov ? ? eax, 0
? ? ? ? leave
? ? ? ? ret
從main開始解讀
// 此時rbp存儲的還是上一層函數(調用者)的棧基地址,將rbp的值入棧保存起來,因為main函數也是被其他函 // 數調用的,運行完main之后還得回到那個函數體中去。這里的地址指的是指令的地址,是代碼段中的位置。 // push指令會使rsp下移。 push rbp // 此時rbp存儲的還是上一個函數的基地址,而rsp則已經游走到了main函數這里,mov指令將rsp中存儲的地址傳遞 // 給rbp,也就意味著執行完之后rbp和rsp都處于main函數的開始位置,稱為初始化操作。 mov rbp, rsp // rsp下移16,就是分配棧空間 sub rsp, 16
// DWORD 為雙字,即四個字節,PTR為指針的意思,此句意為在rbp向下偏移4個字節的這段棧內存中存儲0 // a mov DWORD PTR [rbp-4], 1 // b mov DWORD PTR [rbp-8], 2 // sum mov DWORD PTR [rbp-12], 3 // 將參數從右到左,依次存起來,此處存到了 edx和eax,并拷貝了一份到esi和edi。 mov edx, DWORD PTR [rbp-8]` mov eax, DWORD PTR [rbp-4]` mov esi, edx` mov edi, eax`
?// 執行call指令
// 注意,call會使CPU跳入到add的棧幀中去,那么執行完之后,我們需要跳回到被調用處繼續向下執行,由
// 最前面的push指令我們已經把調用者的棧基存了下來,可是我們還要精確到具體是回到哪個指令,這就是call
// 指令的額外工作,它會先將IP入棧(push ip),因為IP中存的就是下一條指令(mov DWORD PTR [rbp-12], eax)
// 的地址,然后再去跳轉(jmp),將add函數的第一條指令寫入IP,此后就進入add函數棧幀。
call ? ?add
// cpu執行完運算后會將結果存儲在寄存器中,至于它會把結果存儲在那個寄存器,這個由編譯器編譯出的指令 // 決定的,由add函數的指令來看,它選擇了eax // rbp-12 為sum的位置,這條指令將eax寄存器的值賦值給sum mov DWORD PTR [rbp-12], eax // 將eax置0,也就是main的返回值 mov eax, 0 // 意為 mov rsp, rbp 和 pop rbp 的組合 // 此時rbp為main函數的棧基,rsp為main函數的末尾了,將rbp賦值給rsp,于是它們都指向main函數的棧基,上 // 面解釋過,rbp寄存器存儲的地址指向的棧上的空間存儲的還是一個地址,此地址指向調用者的棧基, // pop rbp 將棧頂rsp的數據送入rbp,就意味著之后就回到了調用者的棧幀了,同時pop會伴隨著rsp的上移, // 于是rsp來到了EIP的位置。 leave // 相當于 pop ip // 此函數執行完需要跳回到調用者并繼續執行下一條指令,由于call的時候已經將下一條指令的地址入棧了,所以 // 此處值需要將其彈出即可。 ret
原文鏈接:https://blog.csdn.net/raoxiaoya/article/details/127746677
相關推薦
- 2022-12-24 ReactNative支付密碼輸入框實現詳解_React
- 2022-11-08 PostgreSQL?limit的神奇作用詳解_PostgreSQL
- 2022-04-20 Httprunner簡介、安裝及基本使用教程_python
- 2022-09-06 python?numpy中array與pandas的DataFrame轉換方式_python
- 2024-03-28 存儲過程整合springboot
- 2022-07-04 .NET性能優化之為結構體數組使用StructLinq的問題解析_實用技巧
- 2022-04-09 SpringBoot 項目打包成jar包,并執行Jar文件
- 2021-12-13 一次現網問題定位-Redis連接不斷增長
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支