C語言函數(shù)調(diào)用堆棧詳情分析
一、C函數(shù)棧幀開辟以及回退過程
__cdecl(C語言默認調(diào)用方式,函數(shù)參數(shù)8字節(jié)以內(nèi),使用push。本節(jié)采用此方式)
main函數(shù)的棧幀調(diào)用sum函數(shù)的棧幀,sum函數(shù)棧幀使用完了以后回退都是怎么進行的,要搞清楚這個問題必須得看匯編代碼,匯編代碼分為兩種:inter x86(windows)和AT&T(unix)。這兩種匯編非常相似,x86的匯編是從右向左看,unix的匯編是從左向右看的。
局部變量都是通過棧底指針ebp偏移訪問,不生成符號,不屬于數(shù)據(jù),屬于指令。
形參壓棧在C/C++中是從右向左壓棧,因為要支持可變長參數(shù),如果從左向右,編譯器就不知道用戶傳入了多少實參,形參內(nèi)存是在調(diào)用函數(shù)棧幀中開辟,每壓棧一個實參,都會開辟一個形參的空間,棧頂指針esp都會減4字節(jié)。
實參壓棧完成后需要調(diào)用call指令來執(zhí)行sum函數(shù),執(zhí)行完sum函數(shù)后,執(zhí)行完sum函數(shù)后需要回到調(diào)用指令(call)的下一條指令繼續(xù)執(zhí)行。
call指令做兩件事:
- 1.把下一行指令的地址入棧
- 2.jmp跳轉(zhuǎn)
棧空間圖:
執(zhí)行call指令后,程序調(diào)到這里,不是sum函數(shù)的指令部分
在編譯階段,所有匯編指令代碼引用符號的地方全部不是合法的地址(因為我們當前文件可能引用外部的符號,而編譯階段是獨立編譯的,我們鏈接的時候才會進行符號解析、合并符號表等操作,之后再給符號分配內(nèi)存地址),對于數(shù)據(jù)符號來說是零地址,對于函數(shù)符號來說是-4。那么當我們在鏈接階段符號解析完成以后,得到每一個符號的具體地址,給數(shù)據(jù)符號分配的地址是絕對地址,給函數(shù)符號分配的地址是與下一行指令地址的一個偏移量,這樣當程序需要跳轉(zhuǎn)到某個函數(shù)地址的時候,取出PC寄存器保存的地址與該偏移量相加就得到函數(shù)的入口地址。
計算相對偏移量后就進入了sum函數(shù),先執(zhí)行下面一段指令,才執(zhí)行我們寫的sum函數(shù)
每次執(zhí)行一個函數(shù)前要執(zhí)行三個操作:
- 把調(diào)用方的棧底地址入棧(push ebp),讓ebp指針指向當前函數(shù)的棧底(mov ebp,esp)
- 移動棧頂指針esp,給被調(diào)用函數(shù)開辟棧幀(sub esp 44h)
- 初始化新棧幀內(nèi)存,把esp和ebp之間所有的棧內(nèi)存全部初始化成0xCCCCCCCC(rep stos dword ptr [edi])無效值。
Linux為棧幀不分配初始值,windows會分配初始值,為0xCCCCCCCC(-858993460)
然后執(zhí)行sum函數(shù)的指令:
局部變量通過棧底指針ebp負向偏移訪問,形參通過ebp正向偏移訪問,eax為a+b的計算結(jié)果,將計算結(jié)果賦值給temp,return的時候?qū)emp的值賦值給eax,給調(diào)用方返回
棧幀清退:
可以看到,開辟棧幀的時候我們對占內(nèi)存進行了初始化,但是棧幀清退的時候僅僅就是修改了esp和ebp,沒有做其他任何操作,如果我們此時通過一些手段去訪問已被清退棧幀的內(nèi)存,還是可以訪問到的,因為數(shù)據(jù)還存在
參數(shù)清除:
sum函數(shù)執(zhí)行完成后,從PC寄存器中取出地址繼續(xù)執(zhí)行,這里PC寄存器存放的是call指令的下一行地址,這條指令做的操作是回退形參變量占的內(nèi)存,形參內(nèi)存由調(diào)用方開辟和釋放。sum函數(shù)返回值由eax寄存器帶回來。
在被調(diào)用方執(zhí)行完成后,通過pop ebp就知道應該回到哪(恢復棧底),再通過ret指令就知道回到哪以后從哪一行指令開始運行(取出棧頂元素放到PC寄存器里面,而棧頂元素存的就是call的下一行地址)
sum函數(shù)棧底保存的是main的棧底地址,main函數(shù)棧底保存的是調(diào)用main的函數(shù)的棧底地址
二、C函數(shù)調(diào)用約定和返回值
到此這篇關于C語言函數(shù)調(diào)用堆棧詳情分析的文章就介紹到這了,更多相關C函數(shù)調(diào)用堆棧內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++實現(xiàn)LeetCode(82.移除有序鏈表中的重復項之二)
這篇文章主要介紹了C++實現(xiàn)LeetCode(82.移除有序鏈表中的重復項之二),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07C++連接數(shù)據(jù)庫SqlServer、MySql、Oracle、Access、SQLite、PostgreSQL、Mong
C++是一種通用的編程語言,可以使用不同的庫和驅(qū)動程序來連接各種數(shù)據(jù)庫,以下是一些示例代碼,演示如何使用?C++?連接?SQL?Server、MySQL、Oracle、ACCESS、SQLite?、?PostgreSQL、MongoDB、Redis數(shù)據(jù)庫2024-08-08C++?Protobuf實現(xiàn)接口參數(shù)自動校驗詳解
用C++做業(yè)務發(fā)開的同學是否還在不厭其煩的編寫大量if-else模塊來做接口參數(shù)校驗呢?今天,我們就模擬Java里面通過注解實現(xiàn)參數(shù)校驗的方式來針對C++?protobuf接口實現(xiàn)一個更加方便、快捷的參數(shù)校驗自動工具,希望對大家有所幫助2023-04-04C++ min/max_element 函數(shù)用法詳解
這篇文章主要介紹了C++ min/max_element 函數(shù)用法,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02