找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 2802|回復(fù): 0
打印 上一主題 下一主題
收起左側(cè)

總結(jié)-c語言與匯編

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:107189 發(fā)表于 2016-3-5 17:02 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
1. c語言的函數(shù)調(diào)用與對應(yīng)的匯編代碼 1.1 調(diào)用規(guī)則
比如調(diào)用函數(shù) function(parameter1, parameter2, parameter3)
Pascal調(diào)用規(guī)則
_cdecl調(diào)用規(guī)則
_stdcall調(diào)用規(guī)則
PUSH     parameter1
PUSH     parameter2
PUSH     parameter3
CALL      function
PUSH     parameter3
PUSH     parameter2
PUSH     parameter1
CALL      function
ADD       ESP, 0CH
PUSH     parameter3
PUSH     parameter2
PUSH     parameter1
CALL      function
參數(shù)從左到右傳遞壓棧,由被調(diào)用函數(shù)清理堆棧 參數(shù)從右到左傳遞壓棧,由調(diào)用函數(shù)負(fù)責(zé)清理堆棧 參數(shù)從右到左傳遞壓棧,由被調(diào)用函數(shù)負(fù)責(zé)清理堆棧
用于Win16平臺
C/C++調(diào)用標(biāo)準(zhǔn)
Windows API 使用


1.2 匯編代碼 對調(diào)用的方式,舉一個例子,對c語言常用的printf:

printf(“%d", a);
解析成匯編代碼如:
PUSH  a
PUSH  OFFSET String "%d"
CALL  printf
ADD ESP, 8

CALL指令和RET指令
段內(nèi)調(diào)用
對CALL指令來說,其執(zhí)行的步驟一般包括:
  • 將IP壓入棧(即寄存器EIP的值,指向CALL指令之后的第一條指令)
  • 將IP置為跳轉(zhuǎn)到的地址,開始執(zhí)行

對應(yīng) 的RET指令為:
  • IP出棧
  • 執(zhí)行下一條指令

段間調(diào)用
如果是段間函數(shù)調(diào)用,則CALL的執(zhí)行過程一般是:


  • 將CS(段地址)壓入棧
  • 將IP(即EIP的值)壓入棧
  • 將IP置為跳轉(zhuǎn)到的函數(shù)地址,開始執(zhí)行
對應(yīng)的RET指令執(zhí)行步驟:
  • IP出棧
  • CS出棧
  • 執(zhí)行下一條指令


被調(diào)用函數(shù)的執(zhí)行步驟
PUSH  EBP ; 保存當(dāng)前堆;積bp,以作返回用
MOVE EBP, ESP ; 將當(dāng)前esp的值賦給ebp,作為新的基址,即進入函數(shù)內(nèi)部
SUB ESP, 0CCH ; 將esp往下移動一個范圍,開辟一片新的堆?臻g給當(dāng)前函數(shù)使用
; 這是由于堆棧從高地址往低地址增長,所以,減一個值意味著開辟了
; 新的空間
................. ; 保存其他寄存器的值
.................
................. ; 恢復(fù)壓棧的其他寄存器的值
MOVE ESP, EBP ; 恢復(fù)esp的值為原來的堆棧棧頂值
POP EBP ; 恢復(fù)堆;窞樵肺恢
RET

一般來說,函數(shù)的返回值會放在EAX寄存器中返回。

2. c語言特殊語句塊的匯編代碼
1)For循環(huán)的匯編代碼模板
mov <循環(huán)變量>, <初始值> ; 循環(huán)變量賦初值
jmp B ; 直接跳轉(zhuǎn)到循環(huán)控制測試部分代碼
A: (改動循環(huán)變量) ; 修改循環(huán)變量值的部分代碼
......
B: cmp <循環(huán)變量>, <限制變量> ; 將循環(huán)變量的值進行測試、跳轉(zhuǎn)
jge 跳出循環(huán) ; 符合終止條件,則跳出循環(huán)體 ; (注意,這里的jl指令可以是其他的jge等,一具判斷條件而定)
(循環(huán)體代碼) ; 否則,進入循環(huán)體代碼執(zhí)行
...
jmp A ; 循環(huán)體結(jié)束的最后,是一個無條件跳轉(zhuǎn)語句,調(diào)回直接
; 修改循環(huán)變量的代碼
2)do循環(huán)的匯編代碼模板
A: (循環(huán)體) ; 直接是循環(huán)體代碼
....
cmp <循環(huán)變量>, <限制變量> ; 判斷是否需要終止循環(huán)
jl <循環(huán)開始處> ; 如果不符合終止條件,直接調(diào)回循環(huán)體開始處繼續(xù)執(zhí)行循環(huán)體
; 代碼(注意,這里的jl指令可以是其他的jge等,一具判斷條件而定)


3) while循環(huán)的匯編代碼模板
A: cmp <循環(huán)變量>, <限制變量> ; 先比較循環(huán)變量,是否需要進行循環(huán)
jge B ; 如果滿足停止循環(huán),則直接跳到B,即循環(huán)體后的第一個指令
(循環(huán)體)
……
jmp A ; 循環(huán)體的最后一條指令,是無條件跳轉(zhuǎn)到循環(huán)控制判斷指令A(yù)處
B: (循環(huán)結(jié)束了)

4)if-else的匯編代碼模板(待續(xù)...)
理解調(diào)用棧最重要的兩點是:棧的結(jié)構(gòu),EBP寄存器的作用。

首先要認(rèn)識到這樣兩個事實:
   1、一個函數(shù)調(diào)用動作可分解為:零到多個PUSH指令(用于參數(shù)入棧),一個CALL指令。CALL指令內(nèi)部其實還暗含了一個將返回地址(即CALL指令下一條指令的地址)壓棧的動作。
   2、幾乎任何本地編譯器都會在每個函數(shù)體之前插入類似如下指令:PUSH EBP; MOV EBP ESP;
即,在程序執(zhí)行到一個函數(shù)的真正函數(shù)體時,已有以下數(shù)據(jù)順序入棧:參數(shù),返回地址,EBP。
   由此得到類似如下的棧結(jié)構(gòu)(參數(shù)入棧順序跟調(diào)用方式有關(guān),這里以C語言默認(rèn)的CDECL為例):

+| (棧底方向,高位地址) |
| ....................|
| ....................|
| 參數(shù)3                |
| 參數(shù)2                |
| 參數(shù)1                |
| 返回地址             |
-| 上一層[EBP]         |
| 局部變量1            |
| 局部變量2            |
|.....................|

   補充:棧一直隨著函數(shù)調(diào)用的深入,一直想棧頂方向壓下去。每次調(diào)用函數(shù)時候,先壓函數(shù)參數(shù)(從右往左順序壓),再壓入函數(shù)調(diào)用下條指令的地址(由call完成)。接著進入調(diào)用函數(shù)體中先執(zhí)行PUSH EBP; MOV EBP ESP;(一般已經(jīng)由編譯器加入到函數(shù)頭中了),接著就是吧函數(shù)體中的局部變量壓入棧中。再遇到函數(shù)的調(diào)用的嵌套則依此類推。(added by smsong)

  “PUSH EBP”“MOV EBP ESP”這兩條指令實在大有深意:首先將EBP入棧,然后將棧頂指針ESP賦值給EBP!癕OV EBP ESP”這條指令表面上看是用ESP把EBP原來的值覆蓋了,其實不然——因為給EBP賦值之前,原EBP值已被壓棧(位于棧頂),而新的EBP又恰恰指向棧頂。
   此時EBP寄存器就已處于一個很重要的地位,該寄存器中存儲著棧中的一個地址(原EBP入棧后的棧頂),從該地址為基準(zhǔn),向上(棧底方向)能獲取返回地址、參數(shù)值,向下(棧頂方向)能獲取函數(shù)局部變量值,而該地址處又存儲著上一層函數(shù)調(diào)用時的EBP值!
   一般而言,ss:[ebp+4]處為返回地址,ss:[ebp+8]處為第一個參數(shù)值(最后一個入棧的參數(shù)值,此處假設(shè)其占用4字節(jié)內(nèi)存),ss:[ebp-4]處為第一個局部變量,ss:[ebp]處為上一層EBP值。
    由于EBP中的地址處總是“上一層函數(shù)調(diào)用時的EBP值”,而在每一層函數(shù)調(diào)用中,都能通過當(dāng)時的EBP值“向上(棧底方向)能獲取返回地址、參數(shù)值,向下(棧頂方向)能獲取函數(shù)局部變量值”。
如此形成遞歸,直至到達棧底。這就是函數(shù)調(diào)用棧。
編譯器對EBP的使用實在太精妙了。
從當(dāng)前EBP出發(fā),逐層向上找到任何的EBP是很容易的:
unsigned int _ebp;
__asm _ebp, ebp;
while (not stack bottom)
{
    //...
    _ebp = *(unsigned int*)_ebp;
}
假如要寫一個簡單的調(diào)試器的話,注意需在被調(diào)試進程(而非當(dāng)前進程——調(diào)試器進程)中讀取內(nèi)存數(shù)據(jù)
8個通用寄存器:
      數(shù)據(jù)寄存器:AX,BX,CX,DX
      指針寄存器:SP(堆棧指針),BP(基址指針)
      變址寄存器:SI(原地址),DI(目的地址)
1、通用寄存器
數(shù)據(jù)寄存器,指針寄存器和變址寄存器統(tǒng)稱為通用寄存器。這些寄存器除了各自專門用途外,它們均可用于傳送和暫存數(shù)據(jù),可以保存算術(shù)邏輯運算中的操作數(shù)和運算結(jié)果。
(1)數(shù)據(jù)寄存器
數(shù)據(jù)寄存器主要用來保存操作數(shù)或運算結(jié)果等信息,它們的存在節(jié)省了為存取操作數(shù)所需占用總線和訪問存儲器的時間。
(2)變址和指針寄存器
變址和指針寄存器主要用于存放某個存儲單元地址的偏移,或某組存儲單元地址的偏移,即作為存儲器(短)指針使用。作為通用寄存器,它們可以保存16位算術(shù)邏輯運算中的操作數(shù)和運算結(jié)果,有時運算結(jié)果就是需要的存儲單元地址的偏移。
2、控制寄存器(2個)
(1)指令指針寄存器
8086/8088CPU中的指令指針I(yè)P也是16位的。
指令指針I(yè)P給出接著要執(zhí)行的指令在代碼段中的偏移。
(2)標(biāo)志寄存器
8086/8088CPU中有一個16位的標(biāo)志寄存器,包含了9個標(biāo)志,主要用于反映處理器的狀態(tài)和運算結(jié)果的某些特征。6個條件標(biāo)志+3個方向標(biāo)志
3、段寄存器(4個)
8086/8088CPU依賴其內(nèi)部的四個段寄存器實現(xiàn)尋址1M字節(jié)物理地址空間。
8086/8088把1M字節(jié)地址空間分成若干邏輯段,當(dāng)前使用的段值存放在段寄存器中。
由于8086/8088有這四個段寄存器,所以有四個當(dāng)前使用段可以直接存取,這四個當(dāng)前段分別稱為代碼段,數(shù)據(jù)段,堆棧段和附加段。
(1)代碼段
(2)數(shù)據(jù)段
(3)堆棧段
(4)附加段


分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩

相關(guān)帖子

回復(fù)

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機教程網(wǎng)

快速回復(fù) 返回頂部 返回列表