日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C語言函數調用堆棧詳情分析_C 語言

更新時間: 2022-09-07 編程語言

?一、C函數棧幀開辟以及回退過程

__cdecl(C語言默認調用方式,函數參數8字節以內,使用push。本節采用此方式)

main函數的棧幀調用sum函數的棧幀,sum函數棧幀使用完了以后回退都是怎么進行的,要搞清楚這個問題必須得看匯編代碼,匯編代碼分為兩種:inter x86(windows)和AT&T(unix)。這兩種匯編非常相似,x86的匯編是從右向左看,unix的匯編是從左向右看的。

局部變量都是通過棧底指針ebp偏移訪問,不生成符號,不屬于數據,屬于指令。

形參壓棧在C/C++中是從右向左壓棧,因為要支持可變長參數,如果從左向右,編譯器就不知道用戶傳入了多少實參,形參內存是在調用函數棧幀中開辟,每壓棧一個實參,都會開辟一個形參的空間,棧頂指針esp都會減4字節。

實參壓棧完成后需要調用call指令來執行sum函數,執行完sum函數后,執行完sum函數后需要回到調用指令(call)的下一條指令繼續執行。

call指令做兩件事:

  • 1.把下一行指令的地址入棧
  • 2.jmp跳轉

棧空間圖:

執行call指令后,程序調到這里,不是sum函數的指令部分

在編譯階段,所有匯編指令代碼引用符號的地方全部不是合法的地址(因為我們當前文件可能引用外部的符號,而編譯階段是獨立編譯的,我們鏈接的時候才會進行符號解析、合并符號表等操作,之后再給符號分配內存地址),對于數據符號來說是零地址,對于函數符號來說是-4。那么當我們在鏈接階段符號解析完成以后,得到每一個符號的具體地址,給數據符號分配的地址是絕對地址,給函數符號分配的地址是與下一行指令地址的一個偏移量,這樣當程序需要跳轉到某個函數地址的時候,取出PC寄存器保存的地址與該偏移量相加就得到函數的入口地址。

計算相對偏移量后就進入了sum函數,先執行下面一段指令,才執行我們寫的sum函數

每次執行一個函數前要執行三個操作:

  • 把調用方的棧底地址入棧(push ebp),讓ebp指針指向當前函數的棧底(mov ebp,esp)
  • 移動棧頂指針esp,給被調用函數開辟棧幀(sub esp 44h)
  • 初始化新棧幀內存,把esp和ebp之間所有的棧內存全部初始化成0xCCCCCCCC(rep stos dword ptr [edi])無效值。

Linux為棧幀不分配初始值,windows會分配初始值,為0xCCCCCCCC(-858993460)

然后執行sum函數的指令:

局部變量通過棧底指針ebp負向偏移訪問,形參通過ebp正向偏移訪問,eax為a+b的計算結果,將計算結果賦值給temp,return的時候將temp的值賦值給eax,給調用方返回

棧幀清退:

可以看到,開辟棧幀的時候我們對占內存進行了初始化,但是棧幀清退的時候僅僅就是修改了esp和ebp,沒有做其他任何操作,如果我們此時通過一些手段去訪問已被清退棧幀的內存,還是可以訪問到的,因為數據還存在

參數清除:

sum函數執行完成后,從PC寄存器中取出地址繼續執行,這里PC寄存器存放的是call指令的下一行地址,這條指令做的操作是回退形參變量占的內存,形參內存由調用方開辟和釋放。sum函數返回值由eax寄存器帶回來。

在被調用方執行完成后,通過pop ebp就知道應該回到哪(恢復棧底),再通過ret指令就知道回到哪以后從哪一行指令開始運行(取出棧頂元素放到PC寄存器里面,而棧頂元素存的就是call的下一行地址)
sum函數棧底保存的是main的棧底地址,main函數棧底保存的是調用main的函數的棧底地址

二、C函數調用約定和返回值

函數調用約定和返回值

原文鏈接:https://blog.csdn.net/qq_41721746/article/details/124557537

欄目分類
最近更新