本文整理自多材料源,感謝原址分享,請檢視末尾Url
I, 彙編語言分類:
彙編語言和CPU息息相關,但是不能把彙編語言完全等同于CPU的機器指令。不同架構的CPU指令并不相同,如x86,powerpc,arm各有各的指令系統;甚至同一種架構的CPU有幾套指令集,典型的如arm除了有32位的指令集外,還有一套16位的thumb指令集。但是作為開發語言的彙編,本質上是一套文法規則和助記符的集合,它可以包容不同的指令集。如果從CPU體系來劃分,常見的彙編有兩種:IBM PC彙編和ARM彙編。
IBM PC彙編也就是Intel的彙編,因為IBM 最早推出PC機,後來的體系很多都要和它相容,是以也使用了相同的彙編語言。ARM壓根沒考慮過相容,它的指令集和x86完全是兩個體系,是以彙編語言也獨立發展出一套。
CPU隻是限定了機器碼,作為開發語言的彙編,其實還和編譯器息息相關。彙編語言出現的早,沒有像C語言一樣定義出标準,是以編譯器的廠商各搞一套。到現在,最有名的也是兩家:MASM和GNU ASM。前者是微軟的,隻支援x86,用在DOS/Windows平台中;後者是開源産品,主要用在Linux中,基本上支援大部分的CPU架構。這兩者的差別在于僞指令的不同,僞指令是用來告訴編譯器如何工作的,和編譯器相關,和CPU無關。其實彙編的編譯相當簡單,這兩套僞指令隻是符号不相同,含義是大同小異,明白了一種,看另一種就很容易了。
從彙編格式分,還有Intel格式和AT&T格式的差別,前者是Intel的,windows平台常見,後者最早由貝爾實驗室推出,用于Unix中,GUN彙編器的預設格式就是AT&T。不過GNU的彙編器和調試器gdb對這兩種格式都支援,可以随便切換。MASM隻支援Intel格式。Intel格式和AT&T格式的差別隻是符号系統的差別,這與x86和arm的差別可不一樣,後者是CPU體系的差別。
所謂 内嵌彙編,它是用于C語言和彙編語言混合程式設計的,是以和編譯器也關系緊密,目前也是有兩種,GNU的内嵌彙編和MASM的内嵌彙編,它們的文法和普通彙編是有差別的,特别是GNU的内嵌彙編不是很容易看懂,需要專門學習才行。MASM的内嵌彙編和普通彙編的差別則不大。
關于彙編語言的種類,可以說有多少種不同核心的CPU,就有多少種彙編語言。彙編并不是隻有8086/8088彙編,還有8051,ARM,Alpha,MIPS彙編等等...
如你所知, 彙編是一種面向機器的程式設計語言,之是以說面向機器是指它的指令系統與具體的CPU晶片相關聯,通常不同CPU硬體有不同的彙編系統。8086&8088分别是Intel公司的16位和準16位的CPU,通常使用它作為教材講解微機機系統原理,是因為80x86系列CPU應用廣泛,具有代表性。
8051主要應用在單片機,ARM彙編用于ARM處理器...不需要解釋。
8086是INTEL公司推出的最早實際應用到微型個人計算機上CPU晶片型号;80x86是在8086基礎上的增強型,包括80286,80386,80486,其後就改稱奔騰了。大的差別上:8086和80286是16位的CPU,80386和80486是32位CPU;80486還多了數學輔助處理器,增強了複雜的數學運算能力。小的差別上就比較多了,如頻率越來越快,包括寄存器的增加等。
和C語言不同,彙編語言更多的針對特定CPU核心,是以,不同核心的CPU,必須有對應的彙編語言編譯器将彙編語言别寫的程式編譯成對應CPU的機器語言代碼,CPU才能正确識别和執行這些代碼。
II, 寄存器概念
寄存器是CPU裡的東西,記憶體是挂在CPU外面的資料總線上的,
通路記憶體時要在CPU的寄存器填上位址,再執行相應的彙編指令,這時CPU會在資料總線上生成讀取或寫入記憶體資料的時鐘信号,最終記憶體的内容會被CPU寄存器的内容更新(寫入)或被讀入CPU的寄存器(讀取)
不隻是PC上的CPU,所有的嵌入式CPU,單片機都一個樣
首先明确一點:
CPU <--- > 寄存器<--- > 緩存<--- >記憶體
寄存器的工作方式很簡單,隻有兩步:(1)找到相關的位,(2)讀取這些位。

記憶體的工作方式就要複雜得多:
(1)找到資料的指針。(指針可能存放在寄存器内,是以這一步就已經包括寄存器的全部工作了。)
(2)将指針送往記憶體管理單元(MMU),由MMU将虛拟的記憶體位址翻譯成實際的實體位址。
(3)将實體位址送往記憶體控制器(memory controller),由記憶體控制器找出該位址在哪一根記憶體插槽(bank)上。
(4)确定資料在哪一個記憶體塊(chunk)上,從該塊讀取資料。
(5)資料先送回記憶體控制器,再送回CPU,然後開始使用。
記憶體的工作流程比寄存器多出許多步。每一步都會産生延遲,累積起來就使得記憶體比寄存器慢得多。
為了緩解寄存器與記憶體之間的巨大速度差異,硬體設計師做出了許多努力,包括在CPU内部設定緩存、優化CPU工作方式,盡量一次性從記憶體讀取指令所要用到的全部資料等等。
寄存器、存儲器、記憶體之間的關系:
存儲器 涵蓋了所有關于存儲的範疇,寄存器和記憶體都屬于該範疇。
寄存器是中央處理器内的組成部份。它跟CPU有關。寄存器是有限存貯容量的高速存貯部件,它們可用來暫存指令、資料和位址。在中央處理器的控制部件中,包含的寄存器有指令寄存器(IR)和程式計數器(PC)。在中央處理器的算術及邏輯部件中,包含的寄存器有累加器(ACC)。
記憶體,即 内部存儲器 ,一般分為隻讀存儲器和随即存儲器,以及最強悍的高速緩沖存儲器(CACHE),隻讀存儲器應用廣泛,它通常是一塊在硬體上內建的可讀晶片,作用是識别與控制硬體,它的特點是隻可讀取,不能寫入。随機存儲器的特點是可讀可寫,斷電後一切資料都消失,我們所說的記憶體條就是指它了。
CACHE是在CPU中速度非常塊,而容量卻很小的一種存儲器,它是計算機存儲器中最強悍的存儲器。由于技術限制,容量很難提升,一般都不過兆。
是以,堆棧概念不應與寄存器混淆,堆Heap 棧 Stack 概念存在于程式的記憶體配置設定環節
主要寄存器如下圖所示:
X86處理器中有8個32位的通用寄存器。由于曆史的原因,EAX通常用于計算,ECX通常用于循環變量計數。ESP和EBP有專門用途,ESP訓示棧指針(用于訓示棧頂位置),而EBP則是基址指針(用于訓示子程式或函數調用的基址指針)。如圖中所示,EAX、EBX、ECX和EDX的前兩個高位位元組和後兩個低位位元組可以獨立使用,其中兩位低位元組又被獨立分為H和L部分,這樣做的原因主要是考慮相容16位的程式,具體相容比對細節請查閱相關文獻。
應用寄存器時,其名稱大小寫是不敏感的,如EAX和eax沒有差別。
更詳細一些的介紹圖:
下面通過一個具體的C代碼反彙編的彙編代碼分析加深對這些常用代碼的了解,實驗環境是實驗樓32位Linux虛拟機。
具體C代碼如下:
通過 gcc –S –o main.s main.c -m32 指令将代碼編譯成彙編代碼,精簡後的彙編代碼如下:
下面将着重分析上面這段代碼。首先,彙編代碼也是從main開始執行,首先将ebp寄存器值入棧,然後ebp指向esp位置,esp值減4之後将數字10存在esp指向的位置,最後調将eip入棧,同時eip指向函數f的起始位置。
f函數首先也是ebp入棧,然後ebp指向esp位置,esp值減4之後将ebp位置加8位置的值,也就是數字10儲存到eax寄存器中,然後将eax中的值也就是10儲存到esp中,最後将eip入棧,調用函數g。
g函數也是相同的操作,ebp入棧,ebp指向esp位置,ebp位址減8處的值10放進eax,然後eax中的數值增加5,然後出棧到ebp,ebp隻想24位址處。然後ret,也就是esp處值出棧到eip,eip=15。
然後又回到f函數的15指令處執行,eax寄存器的值增加4,變成19,然後執行leave指令,也就是esp指向ebp處,然後esp處值出棧到ebp,然後esp處值出棧到eip,程式下面跳轉至24行指令。
指令又回到main函數執行,首先eax值加8,變成27,然後執行leave指令,也就是esp指向ebp處,然後esp處值出棧到ebp,然後esp處值出棧到eip,程式下面跳轉至main函數開始前的地方繼續執行。
III, 記憶體和尋址模式
III.1聲明靜态資料區
可以在X86彙編語言中用彙編指令.DATA聲明靜态資料區(類似于全局變量),資料以單位元組、雙位元組、或雙字(4位元組)的方式存放,分别用DB,DW, DD指令表示聲明記憶體的長度。在彙編語言中,相鄰定義的标簽在記憶體中是連續存放的。
.DATA | ||
var | DB 64 | ;聲明一個位元組,并将數值64放入此位元組中 |
var2 | DB ? | ; 聲明一個為初始化的位元組. |
DB 10 | ; 聲明一個沒有label的位元組,其值為10. | |
X | DW ? | ; 聲明一個雙位元組,未初始化. |
Y | DD 30000 | ; 聲明一個4位元組,其值為30000. |
還可以聲明連續的資料和數組,聲明數組時使用DUP關鍵字
Z | DD 1, 2, 3 | ; Declare three 4-byte values, initialized to 1, 2, and 3. The value of location Z + 8 will be 3. |
bytes | DB 10 DUP(?) | ; Declare 10 uninitialized bytes starting at location bytes. |
arr | DD 100 DUP(0) | ; Declare 100 4-byte words starting at location arr, all initialized to 0 |
str | DB 'hello',0 | ; Declare 6 bytes starting at the address str, initialized to the ASCII character values for hello and the null (0) byte. |
III,2 尋址模式
現代X86處理器具有232位元組的尋址空間。在上面的例子中,我們用标簽(label)表示記憶體區域,這些标簽在實際彙編時,均被32位的實際位址代替。除了支援這種直接的記憶體區域描述,X86還提供了一種靈活的記憶體尋址方式,即利用最多兩個32位的寄存器和一個32位的有符号常數相加計算一個記憶體位址,其中一個寄存器可以左移1、2或3位以表述更大的空間。下面例子是彙程式設計式中常見的方式
mov eax, [ebx] ; 将ebx值訓示的記憶體位址中的4個位元組傳送到eax中 mov [var], ebx ; 将ebx的内容傳送到var的值訓示的記憶體位址中. mov eax, [esi-4] ; 将esi-4值訓示的記憶體位址中的4個位元組傳送到eax中 mov [esi+eax], cl ; 将cl的值傳送到esi+eax的值訓示的記憶體位址中 mov edx, [esi+4*ebx] ; 将esi+4*ebx值訓示的記憶體中的4個位元組傳送到edx
下面是違反規則的例子:
mov eax, [ebx-ecx] | ; 隻能用加法 |
mov [eax+esi+edi], ebx | ; 最多隻能有兩個寄存器參與運算 |
III,3 長度規定
在聲明記憶體大小時,在彙編語言中,一般用DB,DW,DD均可聲明的記憶體空間大小,這種現實聲明能夠很好地指導彙編器配置設定記憶體空間,但是,對于
mov [ebx], 2
如果沒有特殊的辨別,則不确定常數2是單位元組、雙位元組,還是雙字。對于這種情況,X86提供了三個訓示規則标記,分别為BYTE PTR, WORD PTR, and DWORD PTR,如上面例子寫成:mov BYTE PTR [ebx], 2, mov WORD PTR [ebx], 2, mov DWORD PTR [ebx], 2,則意思非常清晰。
IV. 彙編指令
彙編指令通常可以分為資料傳送指令、邏輯計算指令和控制流指令。本節将講述其中最重要的指令,以下标記分别表示寄存器、記憶體和常數。
<reg32> | 32位寄存器 (EAX, EBX, ECX, EDX, ESI, EDI, ESP, or EBP) |
<reg16> | 16位寄存器 (AX, BX, CX, or DX) |
<reg8> | 8位寄存器(AH, BH, CH, DH, AL, BL, CL, or DL) |
<reg> | 任何寄存器 |
<mem> | 記憶體位址 (e.g., [eax], [var + 4], or dword ptr [eax+ebx]) |
<con32> | 32為常數 |
<con16> | 16位常數 |
<con8> | 8位常數 |
<con> | 任何8位、16位或32位常數 |
IV. 1 資料傳送指令
mov — Move (Opcodes: 88, 89, 8A, 8B, 8C, 8E, ...)
mov指令将第二個操作數(可以是寄存器的内容、記憶體中的内容或值)複制到第一個操作數(寄存器或記憶體)。mov不能用于直接從記憶體複制到記憶體,其文法如下所示:
mov <reg>,<reg>
mov <reg>,<mem>
mov <mem>,<reg>
mov <reg>,<const>
mov <mem>,<const>
Examples
mov eax, ebx — 将ebx的值拷貝到eax
mov byte ptr [var], 5 — 将5儲存找var訓示記憶體中的一個位元組中
push— Push stack (Opcodes: FF, 89, 8A, 8B, 8C, 8E, ...)
push指令将操作數壓入記憶體的棧中,棧是程式設計中一種非常重要的資料結構,其主要用于函數調用過程中,其中ESP隻是棧頂。在壓棧前,首先将ESP值減4(X86棧增長方向與記憶體位址編号增長方向相反),然後将操作數内容壓入ESP訓示的位置。其文法如下所示:
push <reg32>
push <mem>
push <con32>
push eax — 将eax内容壓棧
push [var] — 将var訓示的4直接内容壓棧
pop— Pop stack
pop指令與push指令相反,它執行的是出棧的工作。它首先将ESP訓示的位址中的内容出棧,然後将ESP值加4. 其文法如下所示:
pop <reg32>
pop <mem>
Examples
pop edi — pop the top element of the stack into EDI.
pop [ebx] — pop the top element of the
stack into memory at the four bytes starting at location EBX.
lea— Load effective address
lea實際上是一個載入有效位址指令,将第二個操作數表示的位址載入到第一個操作數(寄存器)中。其文法如下所示:
Syntax
lea <reg32>,<mem>
lea eax, [var] — var訓示的位址載入eax中.
lea edi, [ebx+4*esi] — ebx+4*esi表示的位址載入到edi中,這實際是上面所說的尋址模式的一種表示方式.
IV. 2 算術和邏輯指令
add— Integer Addition
add指令将兩個操作數相加,且将相加後的結果儲存到第一個操作數中。其文法如下所示:
add <reg>,<reg>
add <reg>,<mem>
add <mem>,<reg>
add <reg>,<con>
add <mem>,<con>
add eax, 10 — EAX ← EAX + 10
add BYTE PTR [var], 10 — 10與var訓示的記憶體中的一個byte的值相加,并将結果儲存在var訓示的記憶體中
sub— Integer Subtraction
sub指令訓示第一個操作數減去第二個操作數,并将相減後的值儲存在第一個操作數,其文法如下所示:
sub <reg>,<reg>
sub <reg>,<mem>
sub <mem>,<reg>
sub <reg>,<con>
sub <mem>,<con>
sub al, ah — AL ← AL - AH
sub eax, 216 — eax中的值減26,并将計算值儲存在eax中
inc, dec— Increment, Decrement
inc,dec分别表示将操作數自加1,自減1,其文法如下所示:
inc <reg>
inc <mem>
dec <reg>
dec <mem>
dec eax — eax中的值自減1.
inc DWORD PTR [var] — var訓示記憶體中的一個4-byte值自加1
imul— Integer Multiplication
整數相乘指令,它有兩種指令格式,一種為兩個操作數,将兩個操作數的值相乘,并将結果儲存在第一個操作數中,第一個操作數必須為寄存器;第二種格式為三個操作數,其語義為:将第二個和第三個操作數相乘,并将結果儲存在第一個操作數中,第一個操作數必須為寄存器。其文法如下所示:
imul <reg32>,<reg32>
imul <reg32>,<mem>
imul <reg32>,<reg32>,<con>
imul <reg32>,<mem>,<con>
imul eax, [var] — eax→ eax * [var]
imul esi, edi, 25 — ESI → EDI * 25
idiv— Integer Division
idiv指令完成整數除法操作,idiv隻有一個操作數,此操作數為除數,而被除數則為EDX:EAX中的内容(一個64位的整數),操作的結果有兩部分:商和餘數,其中商放在eax寄存器中,而餘數則放在edx寄存器中。其文法如下所示:
Syntax
idiv <reg32>
idiv <mem>
idiv ebx
idiv DWORD PTR [var]
and, or, xor— Bitwise logical and, or and exclusive or
邏輯與、邏輯或、邏輯異或操作指令,用于操作數的位操作,操作結果放在第一個操作數中。其文法如下所示:
and <reg>,<reg>
and <reg>,<mem>
and <mem>,<reg>
and <reg>,<con>
and <mem>,<con>
or <reg>,<reg>
or <reg>,<mem>
or <mem>,<reg>
or <reg>,<con>
or <mem>,<con>
xor <reg>,<reg>
xor <reg>,<mem>
xor <mem>,<reg>
xor <reg>,<con>
xor <mem>,<con>
and eax, 0fH — 将eax中的錢28位全部置為0,最後4位保持不變.
xor edx, edx — 設定edx中的内容為0.
not— Bitwise Logical Not
位翻轉指令,将操作數中的每一位翻轉,即0->1, 1->0。其文法如下所示:
not <reg>
not <mem>
Example
not BYTE PTR [var] — 将var訓示的一個位元組中的所有位翻轉.
neg— Negate
取負指令。文法為:
neg <reg>
neg <mem>
neg eax — EAX → - EAX
shl, shr— Shift Left, Shift Right
位移指令,有兩個操作數,第一個操作數表示被操作數,第二個操作數訓示位移的數量。其文法如下所示:
shl <reg>,<con8>
shl <mem>,<con8>
shl <reg>,<cl>
shl <mem>,<cl>
shr <reg>,<con8>
shr <mem>,<con8>
shr <reg>,<cl>
shr <mem>,<cl>
shl eax, 1 — Multiply the value of EAX by 2 (if the most significant bit is 0),左移1位,相當于乘以2
shr ebx, cl — Store in EBX the floor of result of dividing the value of EBX by 2n where n is the value in CL.
IV. 3 控制轉移指令
X86處理器維持着一個訓示目前執行指令的指令指針(IP),當一條指令執行後,此指針自動指向下一條指令。IP寄存器不能直接操作,但是可以用控制流指令更新。
一般用标簽(label)訓示程式中的指令位址,在X86彙編代碼中,可以在任何指令前加入标簽。如:
mov esi, [ebp+8]
begin: xor ecx, ecx
mov eax, [esi]
如第二條指令用begin訓示,這種标簽的方法在某種程度上簡化了彙程式設計式設計,控制流指令通過标簽實作程式指令跳轉。
jmp — Jump
控制轉移到label所訓示的位址,(從label中取出執行執行),如下所示:
jmp <label>
jmp begin — Jump to the instruction labeled begin.
jcondition— Conditional Jump
條件轉移指令,條件轉移指令依據機器狀态字中的一些列條件狀态轉移。機器狀态字中包括訓示最後一個算數運算結果是否為0,運算結果是否為負數等。機器狀态字具體解釋請見微機原理、計算機組成等課程。文法如下所示:
je <label> (jump when equal)
jne <label> (jump when not equal)
jz <label> (jump when last result was zero)
jg <label> (jump when greater than)
jge <label> (jump when greater than or equal to)
jl <label> (jump when less than)
jle <label>(jump when less than or equal to)
cmp eax, ebx
jle done , 如果eax中的值小于ebx中的值,跳轉到done訓示的區域執行,否則,執行下一條指令。
cmp— Compare
cmp指令比較兩個操作數的值,并根據比較結果設定機器狀态字中的條件碼。此指令與sub指令類似,但是cmp不用将計算結果儲存在操作數中。其文法如下所示:
cmp <reg>,<reg>
cmp <reg>,<mem>
cmp <mem>,<reg>
cmp <reg>,<con>
Example
cmp DWORD PTR [var], 10
jeq loop,
比較var訓示的4位元組内容是否為10,如果不是,則繼續執行下一條指令,否則,跳轉到loop訓示的指令開始執行
call, ret— Subroutine call and return
這兩條指令實作子程式(過程、函數等意思)的調用及傳回。call指令首先将目前執行指令位址入棧,然後無條件轉移到由标簽訓示的指令。與其它簡單的跳轉指令不同,call指令儲存調用之前的位址資訊(當call指令結束後,傳回到調用之前的位址)。
ret指令實作子程式的傳回機制,ret指令彈出棧中儲存的指令位址,然後無條件轉移到儲存的指令位址執行。
call,ret是函數調用中最關鍵的兩條指令。具體細節見下面一部分的講解。文法為:
call <label>
ret
V, 調用規則
為了加強程式員之間的協作及簡化程式開發程序,設定一個函數調用規則非常必要,函數調用規則規定函數調用及傳回的規則,隻要遵照這種規則寫的程式均可以正确執行,進而程式員不必關心諸如參數如何傳遞等問題;另一方面,在彙編語言中可以調用符合這種規則的進階語言所寫的函數,進而将彙編語言程式與進階語言程式有機結合在一起。
調用規則分為兩個方面,及調用者規則和被調用者規則,如一個函數A調用一個函數B,則A被稱為調用者(Caller),B被稱為被調用者(Callee)。
下圖顯示一個調用過程中的記憶體中的棧布局:
在X86中,棧增長方向與記憶體編号增長方向相反。
Caller Rules
調用者規則包括一系列操作,描述如下:
1)在調用子程式之前,調用者應該儲存一系列被設計為調用者儲存的寄存器的值。調用者儲存寄存器有eax,ecx,edx。由于被調用的子程式會修改這些寄存器,是以為了在調用子程式完成之後能正确執行,調用者必須在調用子程式之前将這些寄存器的值入棧。
2)在調用子程式之前,将參數入棧。參數入棧的順序應該是從最後一個參數開始,如上圖中parameter3先入棧。
3)利用call指令調用子程式。這條指令将傳回位址放置在參數的上面,并進入子程式的指令執行。(子程式的執行将按照被調用者的規則執行)
當子程式傳回時,調用者期望找到子程式儲存在eax中的傳回位址。為了恢複調用子程式執行之前的狀态,調用者應該執行以下操作:
1)清除棧中的參數;
2)将棧中儲存的eax值、ecx值以及edx值出棧,恢複eax、ecx、edx的值(當然,如果其它寄存器在調用之前需要儲存,也需要完成類似入棧和出棧操作)
Example
如下代碼展示了一個調用子程式的調用者應該執行的操作。此彙程式設計式調用一個具有三個參數的函數_myFunc,其中第一個參數為eax,第二個參數為常數216,第三個參數為var訓示的記憶體中的值。
push [var] ; Push last parameter first
push 216 ; Push the second parameter
push eax ; Push first parameter last
call _myFunc ; Call the function (assume C naming)
add esp, 12
在調用傳回時,調用者必須清除棧中的相應内容,在上例中,參數占有12個位元組,為了消除這些參數,隻需将ESP加12即可。
_myFunc的值儲存在eax中,ecx和edx中的值也許已經被改變,調用者還必須在調用之前儲存在棧中,并在調用結束之後,出棧恢複ecx和edx的值。
VI, 被調用者規則
被調用者應該遵循如下規則:
1)将ebp入棧,并将esp中的值拷貝到ebp中,其彙編代碼如下:
push ebp
mov ebp, esp
上述代碼的目的是儲存調用子程式之前的基址指針,基址指針用于尋找棧上的參數和局部變量。當一個子程式開始執行時,基址指針儲存棧指針訓示子程式的執行。為了在子程式完成之後調用者能正确定位調用者的參數和局部變量,ebp的值需要傳回。
2)在棧上為局部變量配置設定空間。
3)儲存callee-saved寄存器的值,callee-saved寄存器包括ebx,edi和esi,将ebx,edi和esi壓棧。
4)在上述三個步驟完成之後,子程式開始執行,當子程式傳回時,必須完成如下工作:
4.1)将傳回的執行結果儲存在eax中
4.2)彈出棧中儲存的callee-saved寄存器值,恢複callee-saved寄存器的值(ESI和EDI)
4.3)收回局部變量的記憶體空間。實際處理時,通過改變EBP的值即可:mov esp, ebp。
4.4)通過彈出棧中儲存的ebp值恢複調用者的基址寄存器值。
4.5)執行ret指令傳回到調用者程式。
After these three actions are performed, the body of the subroutine may proceed. When the subroutine is returns, it must follow these steps:
- Leave the return value in EAX.
.486
.MODEL FLAT
.CODE
PUBLIC _myFunc
_myFunc PROC
; Subroutine Prologue
push ebp ; Save the old base pointer value.
mov ebp, esp ; Set the new base pointer value.
sub esp, 4 ; Make room for one 4-byte local variable.
push edi ; Save the values of registers that the function
push esi ; will modify. This function uses EDI and ESI.
; (no need to save EBX, EBP, or ESP)
; Subroutine Body
mov eax, [ebp+8] ; Move value of parameter 1 into EAX
mov esi, [ebp+12] ; Move value of parameter 2 into ESI
mov edi, [ebp+16] ; Move value of parameter 3 into EDI
mov [ebp-4], edi ; Move EDI into the local variable
add [ebp-4], esi ; Add ESI into the local variable
add eax, [ebp-4] ; Add the contents of the local variable
; into EAX (final result)
; Subroutine Epilogue
pop esi ; Recover register values
pop edi
mov esp, ebp ; Deallocate local variables
pop ebp ; Restore the caller's base pointer value
ret
_myFunc ENDP
END
子程式首先通過入棧的手段儲存ebp,配置設定局部變量,儲存寄存器的值。
在子程式體中,參數和局部變量均是通過ebp進行計算。由于參數傳遞在子程式被調用之前,是以參數總是在ebp訓示的位址的下方(在棧中),是以,上例中的第一個參數的位址是ebp+8,第二個參數的位址是ebp+12,第三個參數的位址是ebp+16;而局部變量在ebp訓示的位址的上方,所有第一個局部變量的位址是ebp-4,而第二個這是ebp-8.
具體的CPU-Register-Cache關系請看連結
參考文獻: 1. MIPS程式設計入門 https://www.cnblogs.com/thoupin/p/4018455.html
2.Linux系統分析入門--簡單彙編代碼分析 https://blog.csdn.net/ven_kon/article/details/57080849
3 .X86彙編快速入門 https://www.cnblogs.com/YukiJohnson/archive/2012/10/27/2741836.html
4. 記憶體、棧、堆的一點小總結 https://blog.csdn.net/hust_sheng/article/details/47947037
5.寄存器、存儲器、記憶體的差別 https://blog.csdn.net/u012137644/article/details/21864955?locationNum=3
6. Linux 2.x 核心對記憶體的管理 https://blog.csdn.net/yang_yulei/article/details/24385573
7.計算機中記憶體、cache和寄存器之間的關系及差別 https://blog.csdn.net/hellojoy/article/details/54744231
8. 堆,棧差別了解 https://www.cnblogs.com/pomp/archive/2007/10/19/930145.html