献给汇编初学者-函数调用堆栈变化分析
?獻給匯編初學者-函數調用堆棧變化分析
標 題: 獻給匯編初學者-函數調用堆棧變化分析
作 者: 墮落天才
時 間: 2007-01-19,19:20
鏈 接: http://bbs.pediy.com/showthread.php?threadid=38234
跟一個朋友談堆棧的時候?就寫下了這段文字,順便發到這里給需要的看看吧
匯編初學者比較頭痛的一個問題
比如?我們有這樣一個C函數
#include<stdio.h>
long?test(int?a,int?b)
{
????a?=?a?+?1;
????b?=?b?+?100;
????return?a?+?b;
}
void?main()
{??
??printf("%d",test(1000,2000));
}
寫成32位匯編就是這樣
;//
.386
.module?flat,stdcall???????????;這里我們用stdcall?就是函數參數?壓棧的時候從最后一個開始壓,和被調用函數負責清棧
option?casemap:none????????????;區分大小寫
includelib?msvcrt.lib??????????;這里是引入類庫?相當于?#include<stdio.h>了???????
printf??PROTO?C:DWORD,:VARARG??;這個就是聲明一下我們要用的函數頭,到時候?匯編程序會自動到msvcrt.lib里面找的了?
????????????????????????????????;:VARARG?表后面的參數不確定?因為C就是這樣的printf(const?char?*,?...);
???????????????????????????????;這樣的函數要注意?不是被調用函數負責清棧?因為它本身不知道有多少個參數
???????????????????????????????;而是有調用者負責清棧??下面會詳細說明
.data
szTextFmt??BYTE?'%d',0????????;這個是用來類型轉換的,跟C的一樣,字符用字節類型
a??????????dword?1000?????????;假設
b??????????dword?2000?????????;處理數值都用雙字?沒有int?跟long?的區別
;/
.code
_test?proc?A:DWORD,B:DWORD?
??????push?ebp
??????mov??ebp,esp
??????mov??eax,dword?ptr?ss:[ebp+8]
??????add??eax,1
??????mov??edx,dword?ptr?ss:[ebp+0Ch]
??????add??edx,100
??????add??eax,edx
??????pop??ebp??????
??????retn?8
_test?endp
_main?proc?
??????push?dword?ptr?ds:b???????;反匯編我們看到的b就不是b了而是一個[*****]數字?dword?ptr?就是我們在ds(數據段)把[*****]
????????????????????????????????;開始的一個雙字長數值取出來
??????push?dword?ptr?ds:a???????;跟她對應的還有?byte?ptr?****就是取一個字節出來?比如這樣?mov??al,byte?ptr?ds:szTextFmt?
????????????????????????????????;就把?%?取出來?而不包括?d
??????call?_test??????????????????
??????push?eax??????????????????;假設push?eax的地址是×××××
??????push?offset?szTextFmt
??????call?printf
??????add??esp,8
??????ret?????????????
_main?endp
end??_main
;//?下面介紹堆棧的變化
首先要明白的是?操作堆棧段?ss?只能用?esp或ebp寄存器?其他的寄存器eax?ebx?edx等都不能夠用?而?esp永遠指向堆棧棧頂?ebp用來?在堆棧段
里面尋址
push?指令是壓棧?ESP=ESP-4
pop??指令是出棧?ESP=ESP+4
我們假設main函數一開始堆棧定是?ESP=400
push?dword?ptr?ds:b?????????????????;ESP-4=396?->里面的值就是?2000?就是b的數值
push?dword?ptr?ds:a?????????????????;ESP-4=392?->里面的值就是?1000?就是a的數值
call?test???????????????????????????;ESP-4=388->里面的數值是什么?這個太重要了?就是我們用來找游戲函數的原理所在。
?????????????????????????????????????????????????里面的數值就是call?test?指令下一條指令的地址->即push?eax的地址×××××
到了test函數里面
push?ebp???????????????????????????;ESP-4=384->里面保存了當前ebp的值?而不是把ebp清零
mov??ebp,esp???????????????????????;這里ESP=384就沒變化了,但是?ebp=esp=384,為什么要這樣做呢?因為我們要用ebp到堆棧里面找參數
mov??eax,dword?ptr?ss:[ebp+8]??????;反匯編是這樣的?想想為什么a就是[ebp+8]呢
???????????????????????????????????;我們往上看看堆棧里地址392處就保存著a的值?這里ebp=384?加上8正好就是392了
???????????????????????????????????;這樣就把傳遞過來的1000拿了出來eax=1000
add??eax,1?????????????????????????;相當于?a+1了?eax=1001
mov??edx,dword?ptr?ss:[ebp+0Ch]????;?0Ch=12?一樣道理這里指向堆棧的地址是384+12=396?就是2000了?edx=2000
add??edx,100???????????????????????;相當于?b+100?edx=2100
add??eax,edx???????????????????????;eax=eax+edx=1001+2100=3101?這里eax已經保存了最終的結果了?
???????????????????????????????????;因為win32匯編一般用eax返回結果?所以如果最終結果不是在eax里面的話?還要把它放到eax
???????????????????????????????????;比如假設我的結果保存在變量nRet里面?最后還是要這樣?mov?eax,dword?ptr?nRet
pop??ebp???????????????????????????;ESP=384+4=388?而保存在棧頂384的值?保存到?ebp中?即恢復ebp原來的值???????????????????????
???????????????????????????????????;因為一開始我們就把ebp的值壓棧了,mov?ebp,esp已經改變了ebp的值,這里恢復就是保證了堆棧平衡
retn??8????????????????????????????;ESP+8->396?這里retn是由系統調用的?我們不用管?系統會自動把EIP指針指向?原來的call的下一條指令
???????????????????????????????????;由于是系統自動恢復了call那里的壓棧所以?真正返回到的時候ESP+4就是恢復了call壓棧的堆棧
???????????????????????????????????;到了這個時候?ESP=400?就是函數調用開始的堆棧,就是說函數調用前跟函數調用后的堆棧是一樣的
???????????????????????????????????;這就是堆棧平衡?
由于我們用stdcall上面retn?8就是被調用者負責恢復堆棧的意思了,函數test是被調用者,所以負責把堆棧加8,call?那里是系統自動恢復的
push?eax????????????????;ESP-4=396->里面保存了eax的值3101
????????????????????????;上面已經看到了eax保存著返回值,我們要把它傳給printf也是通過堆棧傳遞???????
push?offset?szTextFmt???;ESP-4=392->里面保存了szTextFmt的地址?也就是C里面的指針?實際上沒有什么把字符串傳遞的,我們傳的都是地址
????????????????????????;無論是在匯編或C?所以在匯編里沒有什么字符串類型?用最多的就是DWORD。嘿嘿游戲里面傳遞參數?簡單多了
call?printf?????????????;ESP-4=388->里面保存了下一條指令的地址
add??esp,8??????????????;ESP+8=400?恢復了調用printf前的堆棧狀態
????????????????????????;上面說了由于printf后面參數是:VARARG?這樣的類型是有調用者恢復堆棧的?所以printf里面沒有retn?8之類的指令
????????????????????????;這是由調用者負責清棧?main是調用者?所以下面一句就是?add?esp,8?把堆棧恢復到調用printf之前
????????????????????????;而call?printf那里的壓棧?是由系統做的?恢復的工作也是系統完成?我們不用理?只是知道里面保存是返回地址就夠??
??????????????????????;了
ret?????????????????????;main?函數返回?其他的事情是系統自動搞定?我們不用理?任務完成
總結
以上是生活随笔為你收集整理的献给汇编初学者-函数调用堆栈变化分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网卡故障会出现的错误代码_布袋除尘器压差
- 下一篇: python pandas csv时间聚