STDCALL 看到 EBP+幾 就得 RET 4*N 除非 偽指令定義好的 PROC USES 參數,參數
C 在 調用者 CALL ADDTEWO 後 ADD ESP,8
1 為什麼返回RET 有時候會出錯誤:STDCALL約定 子程式 ADDTWO 要清理參數
RET 8的含義(MOV ESP,EBP RET 之後 ADD ESP,8清除 參數)
C約定 是調用者 直接ADD ESP,8 直接清理然後找到了返回地址
C 約定 先(CALL已經返回)清理 在返回 別的調用者
STDCALL 先返回返回調用者後清理
-------------------------------------------------------------------------------
局順- 反+參
關鍵在於參數賦值(程序呼叫前 PUSH 和MOV)和參數的訪問
參數賦值 PUSH 5,push 6 這樣的情況 要注意RET 8
參數訪問 mov eax,[ebp+12] mov eax,[ebp+8] ;此時能正常返回 不用看RET
參數的順序:
反向 有4個參數的話 1 [EBP+20] 2 [EBP+16] 3 [EBP+12] 4 [EBP+8]
C++(4,3,2,1)
-------------------
2 調用子過程裡有局部變數 push ebp mov ebp,esp sub esp,8 ;倆變數 此時 ESP 有變動
結束之前要銷毀局部變數 mov esp,ebp ret 能正確返回
順序使用局部變數 第1個 EBP-4 第2個 EBP-8 第3個EBP-12 ESP在最後的局部變數位置上 所以要MOV ESP,EBP
--------------------------------------------------------------------------------------------------------------------------------
3:種過程形參數()
STDCALL: RET 4*N
C : ADD ESP,4*N
完整PROC 帶參數 就不考慮 RET +幾
4 局部變數 :
(1) 用LEA返回運行時變數地址 方便雙字對齊
(2)ENTER 和LEAVE為局部變數服務
(3)LOCAL聲明多變數 (自動開闢變數空間,自動leave)
一 .過程參數:2種基本類型的 寄存器參數(MOV)和堆棧參數(PUSH)。
Irvine32和Irine16庫使用寄存器參數(用的MOV 不是PUSH);缺點代碼混亂,堆棧的參數必須由調用過程壓棧。
(1)寄存器參數 速度快 亂
pushad<br />mov esi,OFFSET array ;起始位移地址<br />mov ecx,LENGTHOF array ;數目<br />mov ebx,TYPE array ;雙字格式<br />call DumpMem ;顯示記憶體內容<br />popad</p><p>
(2)堆棧參數 靈活(參數順序相反)
push TYPE array<br />push LENGEHOF array<br />push OFFSET array<br />call DumpMem
堆棧兩類參數: 值參數(變和常值),引用參數(變數地址)##傳遞數組也是地址##
(1)值傳遞 (變數和常量)
彙編:<br />.data<br />val1 DWORD 5<br />val2 DWORD 6<br />.code<br />push val2<br />push val1<br />call AddTwo</p><p>C++ :<br />int sum =AddTwo(val1,val2) //順序相反</p><p>
CALL前 堆棧
(2)傳遞引用
彙編:<br />push OFFSET val2<br />push OFFSET val1<br />call Swap</p><p>C++:<br />Swap(&val1,&val2);
調用Swap前 堆棧
傳遞數組:
.data<br />array DWORD 50 DUP(?) ;未初始化數組<br />.code<br />push OFFSET array<br />call ArrayFill<br />
二.堆棧參數的訪問(C++)
1準備:
訪問子過程參數 要準備2個步驟:
AddTwo PROC
push ebp ; 儲存EBP
mov ebp,esp ;用EBP代替ESP 訪問參數 ;ESP好做別的事情
2:訪問:
AddTwo PROC<br /> push ebp<br /> mov ebp,esp ;堆棧架構的基址</p><p> mov eax,[ebp+12] ;第2個參數<br /> add eax,[ebjp+8] ;第1個參數<br /> pop ebp ;esp 指向返回地址<br /> ret ;pop eip ;esp+4 正常返回<br /> ;因為ESP沒有改變 如果ESP變了就要<br /> ; MOV ESP,EBP</p><p>AddTwo PROC<br />
3:堆棧的清理:
3種解決:
(1)add esp,8
ret
(2) 如果有 push ebp
mov ebp,esp
mov esp,ebp
ret
(3) STDCALL 呼叫慣例 :
pop ebp
ret 8 ;1個參數是 ret 4 ,2個是8 3個是 12 ,。。。。
1) (PUSH的原因)MOVZX擴充8位和16位參數: 如果參數 是8位或者16位的 就不能 PUSH ‘X’ push word1 (字)
要用MOVZX變數來和1個寄存器間接 擴充
.data
charVal BYTE 'x'
.code
push charVal ;錯誤
call Uppercase
改:
movzx eax,charVal
push eax
call Uppercase
Uppercase PROC<br /> push ebp<br /> mov ebp,esp</p><p> mov al,[esp+8] ;al =字元<br /> cmp al,'a' ;小於'a'<br /> jb L1 ;是:跳出<br />cmp al,'z' ; 大於'z'<br /> ja L1 ;是 :跳出<br /> sub al,32 ;否 : 轉換大寫<br />L1: pop ebp ;清理堆棧<br /> ret 4<br />Uppercase ENDP
-----------------------------------------------------------------------------------------------------
2)16位
.data
word1 WORD 1234h
word2 WORD 4111h
.code
push word1
push word2
call AddTwo ;錯誤
改:
movzx eax,word1
push eax
movzx eax,word2
push eax
call AddTwo
--------------------------------------------------------------------------------------------------
3)長整數64位:
.data
logval DQ 1234567890ABCDEFh
.code
push DWORD PTR logval+4 ;高雙字 12345678
push DWORD PTR logval ;低雙字 90ABCDEF
call WriteHex 64
-----------------------------------------------------------------------
------------------------------------------------------------------------
三.局部變數
1。局部變數是運行時棧上建立的,在記憶體中起位置在基址指標(EBP)之下,彙編是不給定預設值,運行時候初始化。
C++ 局部變數X和局部變數Y:
void MySub()
{
int x=10;
int y =20;
}
2個變數8個位元組的空間
反組譯碼 C約定 :建立,賦值以及堆棧上移除變數
MySub PROC<br /> push ebp<br /> mov ebp,esp<br /> sub esp,8 ;建立局部變數<br /> mov DWORD PTR [ebp-4],10 ;X<br /> mov DWORD PTR [ebp-8],20 ;Y<br /> mov esp,ebp ;在堆棧上刪除局部變數<br /> pop ebp<br />ret<br />MySub ENDP<br />
如果沒有 mov esp,ebp
ESP 在20位置上 書上有錯誤 (是10的位置是錯的)圖上都寫了是ESP 在EBP-8第2個變數上
2 lea指令 動態運行時候 返位移地址 (ADDR和OFFSET在保護模式下返回32位位移值),因為Irvine32.inc 中的.MODEL偽指令指定了平坦記憶體模式
(1) 全域變數 擷取變數地址: mov 寄存器,offset 變數名(老羅的書73頁)
(2)局部變數 EBP操作的 ebp 40100h
變數1 ebp-4=400FCH
EBP隨著程式的執行環境不同而不同 在編譯的時候就不確定 所以offset擷取不到
擷取變數地址: lea,[ebp-4]
(3)實參賦值:invoke的參數用到一個局部變數地址: (invoke用 addr一起 但是不能和外EAX一起 invoke TEST eax,addr szH);就不能用LEA 和OFFSET 並且 如果是數組的話 指向第2個元素就要+4 INVOKE sWAP,ADDR [R+1],ADDR [R+4] 239頁
addr 全域變數名 (此時按照OFFSET方法用)
和局部變數名(自動用LEA指令把地址給EAX,EAX代替變數地址)
但是ADDR 裡不能是[ebp]也不能和MOV一起用
錯誤:
LEA的另一個用法
(4)ptr:以不同類型 訪問參數:(老羅71頁)
12
1234 5678h
裝入 記憶體 78 56 34 12
取: 12 34 56 78
1:12h
2:3412h 34
3:7812 3412h
和放到記憶體相反
-----------------------------------------------------
(5)ENTER和LEAVE指令 局部變數的用法
(6)LOCAL聲明變數(可以與PTR一起表示位元 和[]數組)
後面自動產生leava刪除局部變數空間 不用管RET
注意 位元組變數聲明會分配多餘空間
注意 位元組變數聲明會分配多餘空間
(6).stack 4096 保留額外的堆棧空間
總結
INVOKE ,ADDR ,PROC和PROTO 指令
PROC
參數名:類型 也可以是TYPEDEF和STRUCT 變數 和以下的指標
有引用參數就不能傳遞立即數; sub PROC da:ptr word mov esi,da INVOKE sub,1000H出現一個保護錯誤記憶體1000H不在資料區段內
PROTO
調用 注意前後順序
(1)把PROC改為PROTO
(2)如果有USES偽指令,去掉USES後面的寄存器
(3)類型與實現要匹配
建立多個模組OBJ 2種方法:
(1)EXTERN(移植有問題)
(2)INVOKE 和PROTO
1在一個過程 中變成私人 為了是避免變數名與其他模組使用發生衝突
2多過程 OPTION PROC:PRIVATE 所有過程都成私人
PUBLEC 匯出 PUBLIC SUB1,SUB2,SUB3
MIAN入口必須是PUBLIC否則找不到入口
調用 ;
EXTRN sub@0:PROC
call sub@0 0代表參數 如果2個參數 就是8
跨界使用變數和符號
變數和符號預設是私人的 要用PUBLIC 匯出指定名字
PUBLIC count ,SYM1
SYM1=10 ;符號
.DATA
count DWORD 0
訪問外部: EXTERN name :type
符號: 以EQU和=定義 type就是ABS
變數: type 是 BYTE,DOWRD PTR BYTE
ENTERN: one:WORD ,two:SDWORD, three:PTR BYTE, four:ABS
EXTERNDEF代替PUBLIC和EXTERN
;vars.inc 一個模組
EXTERNDEF count:DWORD,SYM:ABS
1 調用 :在sub.asm 調用 用到INCLUDE vars.inc
TITILE sub1.asm
.386
.model flat,STDCALL
INCLUDE vars.inc
SYM1=10
.data
count DWORD 0
END
END 後面省略程式入口標號 也無須聲明 運行時棧
在MIAN 必須 END main
2
天天