彙編命令

A、add:加法指令,第一個是目標操作數,第二個是源操作數,格式爲:目標操作數 = 目標操作數 + 源操作數。


B、sub:減法指令,格式同 add。


C、call:調用函數,一般函數的參數放在寄存器中。


D、ret:跳轉會調用函數的地方。對應於call,返回到對應的call調用的下一條指令,若有返回值,則放入eax中。


E、push:把一個32位的操作數壓入堆棧中,這個操作在32位機中會使得esp被減4(字節),esp通常是指向棧頂的(這裏要指出的是:學過單片機的同學請注意單片機種的堆棧與Windows下的堆棧是不同的,請參考相應資料),這裏頂部是地址小的區域,那麼,壓入堆棧的數據越多,esp也就越來越小。


F、pop:與push相反,esp每次加4(字節),一個數據出棧。pop的參數一般是一個寄存器,棧頂的數據被彈出到這個寄存器中。


一般不會把sub、add這樣的算術指令,以及call、ret這樣的跳轉指令歸入堆棧相關指令中。但是實際上在函數參數傳遞過程中,sub和add最常用來操作堆棧;call和ret對堆棧也有影響。


G、mov:數據傳送。第一個參數是目的操作數,第二個參數是源操作數,就是把源操作數拷貝到目的一份。


H、xor:異或指令,這本身是一個邏輯運算指令,但在彙編指令中通常會見到它被用來實現清零功能。用 xor eax,eax這種操作來實現 mov eax,0,可以使速度更快,佔用字節數更少。


I、lea:取得第二個參數地址後放入到前面的寄存器(第一個參數)中。


然而lea也同樣可以實現mov的操作,例如:


lea edi,[ebx-0ch]


方括號表示存儲單元,也就是提取方括號中的數據所指向的內容,然而lea提取內容的地址,這樣就實現了把(ebx-0ch)放入到了edi中,但是mov指令是不支持第二個操作數是一個寄存器減去一個數值的。


J、stos:串行存儲指令,它實現把eax中的數據放入到edi所指的地址中,同時edi後移4個字節,這裏的stos實際上對應的是stosd,其他的還有stosb,stosw分別對應1,2個字節。


K、jmp:無條件跳轉指令,對應於大量的條件跳轉指令。


L、jg:條件跳轉,大於時成立,進行跳轉,通常條件跳轉之前會有一條比較指令(用於設置標誌位)。


M、jl:小於時跳轉。


N、jge:大於等於時跳轉。


O、cmp:比較大小指令,結果用來設置標誌位。


4、函數參數傳遞方式


函數調用規則指的是調用者和被調用函數間傳遞參數及返回參數的方法,在Windows上,常用的有Pascal方式、WINAPI方式(_stdcall)、C方式(_cdecl)。


A、_cdecl C調用規則:


(a)參數從右到左進入堆棧;


(b)在函數返回後,調用者要負責清除堆棧,這種調用方式通常會生成較大的可執行程序。


B、_stdcall又稱爲WINAPI,調用規則如下:


(a)參數從右到左進入堆棧;


(b)被調用的函數在返回前自行清理堆棧,這種方式生成的代碼比cdecl小。


C、Pascal調用規則(主要用於Win16函數庫中,現在基本不用):


(a)參數從左到右進入堆棧;


(b)被調用的函數在返回前自行清理堆棧。


(c)不支持可變參數的函數調用。


5、VC中訪問無效變量出錯原因


我們看上面主函數反彙編後的其中一段代碼如下:


0041137C lea edi,[ebp-0C0h]
00411382 mov ecx,30h
00411387 mov eax,0CCCCCCCCh
0041138C rep stos dword ptr es:[edi]


從代碼的表面上看,它是實現把從ebp-0C0h開始的30h個字的空間寫入0CCCCCCCCh。其中eax爲四位的數據,這樣可以計算:


0C0h = 30h * 4


也就是把從ebp-0C0h 到ebp之間的空間初始化爲0CCCCCCCCh。大家在學習反彙編的過程中會發現,其實編譯器會根據情況把相應長度的這樣一段作爲局部變量的空間,而這裏把局部變量區域全都初始化成0CCCCCCCCh也是有其用意的,做VC編程的工作者,特別是初學者可能不會對0CCCCCCCCh這個常量陌生。0cch實際上是int 3指令的機器碼,這是一個斷點中斷指令(在反編譯出的信息中大家會看到int 3),因爲局部變量不可被執行,或者如果在沒有初始化的時候進行了訪問,則就會出現訪問失敗錯誤。這個在VC編譯Debug版本中才能看到提示這個錯誤,在Release版本中,會以另外一種錯誤形式體現。下面,我們修改主程序看下new與delete的反彙編的效果(註釋直接加到反彙編的代碼中了)。


VC生成工程,寫入源代碼如下:


(1)情況1


// ASM_Test.cpp : Defines the entry point for the console application. ( 源代碼1 )
//
#include "stdafx.h"
#include "stdlib.h"


int _tmain(int argc, _TCHAR* argv[])
{
int *pTest = new int(3); //定義一個整型指針,並初始化爲 3
printf( "*pTest = %d\r\n", *pTest ); //調用庫函數printf輸出數據
delete []pTest; //刪除這個指針


return 0;
}
這裏僅僅看下在new與delete進行空間管理時進行反彙編時可能出現的一些情況,我們把上面源代碼稱爲源代碼(1),我們按照前面講解的查看VS下反彙編的方法可以看到對應於上面代碼的反彙編代碼如下:
--- f:\mysource\asm_test\asm_test\asm_test.cpp --------------------------------- ( 反彙編代碼 1)
// ASM_Test.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "stdlib.h"


int _tmain(int argc, _TCHAR* argv[])
{


;(1)函數預處理部分
004113C0 push ebp
004113C1 mov ebp,esp ;保存堆棧的棧頂位置
004113C3 sub esp,0E8h ;要置爲0CCCCCCCCh 保留變量空間長度
004113C9 push ebx ;保存寄存器ebx、esi、edi
004113CA push esi
004113CB push edi
004113CC lea edi,[ebp-0E8h] ;提出要置爲0CCCCCCCCh 的空間起始地址
004113D2 mov ecx,3Ah ;要置爲0CCCCCCCCh 的個數,每個佔4個字節
004113D7 mov eax,0CCCCCCCCh ;於是3Ah * 4 = 0E8h
004113DC rep stos dword ptr es:[edi] ;進行置爲0CCCCCCCCh操作


;(2)定義一個int 型指針,分配空間後,並初始化爲 3 ,
int *pTest = new int(3); //定義一個整型指針,並初始化爲 3
004113DE push 4 ;要分配的空間長度,會根據定義的數據類型而不同
004113E0 call operator new (411186h) ;分配空間,並把分配空間的起始地址放入eax中
004113E5 add esp,4 ;由於new與delete函數本身沒有對棧進行彈出操作,所以,要編寫者自己處理
004113E8 mov dword ptr [ebp-0E0h],eax ;比較分配的空間是否爲0,如果爲0
004113EE cmp dword ptr [ebp-0E0h],0
004113F5 je wmain+51h (411411h)
004113F7 mov eax,dword ptr [ebp-0E0h] ;對於分配的地址分配空間進行賦值爲:3
004113FD mov dword ptr [eax],3
00411403 mov ecx,dword ptr [ebp-0E0h]
00411409 mov dword ptr [ebp-0E8h],ecx ;似乎用[ebp - 0E0h]和[ebp - 0E8h]作爲了中間存儲單元
0041140F jmp wmain+5Bh (41141Bh)
00411411 mov dword ptr [ebp-0E8h],0 ;上面分配空間失敗是的操作
0041141B mov edx,dword ptr [ebp-0E8h]
00411421 mov dword ptr [pTest],edx ;數據最後送入pTest變量中


;調用printf函數進行數據輸出
printf( "*pTest = %d\r\n", *pTest ); //調用庫函數printf輸出數據
00411424 mov esi,esp ;用於調用printf後的Esp檢測,不明白編譯器爲什麼這樣做
00411426 mov eax,dword ptr [pTest] ;提取要打印的數據,先是地址,下面一條是提取具體數據
00411429 mov ecx,dword ptr [eax]
0041142B push ecx ;兩個參數入棧
0041142C push offset string "*pTest = %d\r\n" (41573Ch)
00411431 call dword ptr [__imp__printf (4182C4h)] ;調用函數
00411437 add esp,8 ;由於庫函數無出棧管理操作,同new與delete,所以要加 8,進行堆棧處理
0041143A cmp esi,esp ;對堆棧的棧頂進行測試
0041143C call @ILT+325(__RTC_CheckEsp) (41114Ah)


;進行指針變量的清理工作
delete []pTest; //刪除這個指針
00411441 mov eax,dword ptr [pTest] ;[pTest] 中放入的是分配的地址,下面幾條指令轉悠一圈
00411444 mov dword ptr [ebp-0D4h],eax ;就是要把要清理的地址送入堆棧,然後調用delete函數
0041144A mov ecx,dword ptr [ebp-0D4h]
00411450 push ecx
00411451 call operator delete (411091h)
00411456 add esp,4 ;對堆棧進行處理,同new與printf函數


;函數結束後,進行最終的清理工作
return 0;
00411459 xor eax,eax ;做相應的清理工作,堆棧中保存的變量送回原寄存器
}
0041145B pop edi
0041145C pop esi
0041145D pop ebx
0041145E add esp,0E8h ;進行堆棧的棧頂判斷
00411464 cmp ebp,esp
00411466 call @ILT+325(__RTC_CheckEsp) (41114Ah)
0041146B mov esp,ebp
0041146D pop ebp
0041146E ret


--- No source file -------------------------------------------------------------;後面不再是源代碼


在列出反彙編程序時把反彙編代碼的上下的分解註釋也列了出來,親手去查看的朋友可能會發現在這段代碼的之外的其他部分會有大量的int 3彙編中的中斷指令,這個是與上面的所說的0CCCCCCCCh具有一致性,這些區域是無效區域,但代碼訪問這些區域時就會出現非法訪問提示。當然,你應該可以想到,那個提示是可以被屏蔽掉的,你可以把這部分區域填充上數據或者修改 iint 3 調用的中斷程序。


從以上反彙編程序,我們可以發現幾點:


A、一些內部的庫函數是不會對堆棧進行出棧管理的,所以若要對反彙編程序進行操作時,一點要注意這一點


B、編譯器會自動的加上一些對棧頂的檢查工作,這個是我們在做VC調試時經常遇到的一個問題,就是堆棧錯誤


當然以上只是對debug版本下的程序進行反彙編,如果爲release 版本,代碼就會進行大量的優化,在理解時會有一定的難度,有興趣朋友可以試着反彙編一下,推薦大家有IDA返回工具,感覺挺好用的。
發佈了31 篇原創文章 · 獲贊 1 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章