彙編

sgqmax發表於2024-12-05

彙編

儲存程式計算機
圖靈機
馮諾依曼機
運算器、控制器、儲存器、輸入和輸出裝置

CPU由運算器(算術邏輯單元ALU),控制器和暫存器組成
暫存器程式計數器PC,在IA32(x86-32)中是EIP,指示要執行的下一條指令在儲存器中的地址

x86-32彙編

  • 通用暫存器
    4個資料暫存器EAX,EBX,ECX,EDX
    2個指標暫存器ESP,EBP
    2個變址和指標暫存器ESI,EDI
  • 控制暫存器
    1個指令指標暫存器EIP
    1個標誌暫存器EFlags
  • 段暫存器
    6個段暫存器CS,ES,SS,DS,FS,GS

32位資料暫存器更具通用性,可以暫存資料,如ALU的運算結果,還可以作為指標暫存器
程式碼段暫存器CS,指令儲存在程式碼段中,使用CS:EIP指明指令地址
堆疊段暫存器SS,每個程序都有核心態堆疊和使用者態堆疊

定址方式和彙編指令
彙編指令包括操作碼和運算元

操作碼,即彙編指令
movb, movw, movl, movq

運算元

  • 立即數
    即常數,如$8
  • 暫存器數
    %eax
  • 儲存器引用

定址方式

  • 暫存器定址register
    操作的是暫存器,不與記憶體互動,如movl %eax, %edx
  • 立即定址immediate
    $符號後跟一個數值,如movl $0x123, %edx
  • 直接定址direct
    直接用數值表示記憶體地址,如movl 0x123, %edx
  • 間接定址indirect
    暫存器加個小括號,表示暫存器中儲存的是記憶體地址,如move (%ebx), %edx
  • 變址定址displaced
    在原地址上加一個立即數,如movl 4(%ebx), %edx

AT&T格式彙編,也是Linux核心使用的彙編格式
一般全是大寫字母的為Intel彙編,全是小寫字母的是AT&T彙編
pushl/popl
call/ret

pushl %eax
將EAX暫存器值壓棧,實際上做了兩個動作
subl $4, %esp
movl %eax, (%esp)
堆疊是向低地址增長的,所以用subl指令

popl %eax
從堆疊棧頂取一個儲存單元放入EAX暫存器,實際上做了兩個動作
movl (%esp), %eax
addl $4, %esp

call 0x12345
實際上做了兩個動作,將EIP壓棧,並將地址0x12345傳入EIP暫存器,由硬體一次性完成

ret
將堆疊棧頂的一個儲存單元放入EIP暫存器,該動作由硬體一次性完成

儲存程式
函式呼叫堆疊
中斷

ESP堆疊暫存器
EBP基址暫存器
x86體系結構,堆疊空間是從高地址向低地址增長的,即向上增長
堆疊操作push/pop

在函式傳遞返回值時,透過EAX暫存器來儲存返回值,若有多個返回值,EAX返回記憶體地址
堆疊提供區域性變數的空間,編譯器會預留足夠的棧空間以儲存區域性變數,但是早期編譯器並沒有這麼做,而是要求區域性變數宣告在函式體前面
透過堆疊傳遞引數的方法是從右向左以此壓棧,如形參的壓棧順序

C語言內嵌彙編

__asm__ __volatile__(
    彙編語句模板:
    輸出部分:
    輸入部分:
    破壞描述部分:
);

示例程式碼

#include<stdio.h>
int main(){
    /* val1 + val2 = val3 */
    unsigned int val1= 1;
    unsigned int val2= 2;
    unsigned int val3= 0;
    printf("val1:%d, val2:%d, val3:%d\n", val1, val2, val3);
    asm volatile(
        "movl $0, %%eax\n\t" // clear %eax
        "addl %1, %%eax\n\t" // %eax+= val1
        "addl %2, %%eax\n\t" // %eax+= val2
        "movl %%eax, %0\n\t" // val2= %eax
        : "=m" (val3)        // =m mean only write output memory variable
        : "c" (val1), "d" (val2) // input c or d mean %ecx/%edx
    );
    printf("val1:%d + val2:%d = val3:%d\n", val1, val2, val3);

    return 0;
}

__asm__是gcc關鍵字asm的宏定義,是內嵌彙編關鍵字
__volatile__是gcc關鍵字volatile的宏定義,告訴編譯器不要最佳化程式碼

內嵌彙編的暫存器前有兩個%,一個%加一個數字表示第二部分輸出,第三部分輸入或第四部分破壞描述
%1指的是val1,用c修飾,即存入ECX暫存器
%2指的是val2,用d修飾,即存入EDX暫存器
%0指的是val3,用=m修飾,即寫入記憶體變數

內嵌彙編語法

內嵌彙編常用的修飾限定符

通用暫存器

限定符 說明
a EAX暫存器
b EBX暫存器
c ECX暫存器
d EDX暫存器
s ESI暫存器
D EDI暫存器
q 將輸入變數放入eax,ebx,ecx,edx中的一個
r 將輸入變數放入eax,ebx,ecx,esi,edi中的一個
A 將eax和edx合成為一個64位暫存器

記憶體

限定符 說明
m 記憶體變數
o 運算元為記憶體變數,定址方式是偏移量定址
v 運算元為記憶體變數,定址方式不是偏移量定址
. 運算元為記憶體變數,定址方式是自動增量
p 運算元是一個合法的記憶體地址

運算元型別
| = | 運算元在指令中是隻寫的 |
| + | 運算元在指令中是讀寫的 |

暫存器或記憶體

限定符 說明
g 將輸入變數放入eax,ebx,ecx,edx中的一個或作為記憶體變數
x 運算元可以是任何型別

立即數

限定符 說明
i 0~31之間的立即數,用於32位移位指令
J 0~63之間的立即數,用於64位移位指令
N 0~255之間的立即數,用於out指令
l 立即數
n 立即數,有些系統不支援除字以外的立即數,這些系統應該使用n

浮點數

限定符 說明
f 浮點數
t 第一個浮點暫存器
u 第二個浮點暫存器
G 標準的80387浮點常數
% 該運算元可以和下一個運算元交換位置

示例

int main(){
    int input, output, temp;
    intput= 1;
    __asm__ __volatile__(
        "movl $0, %%eax;\n\t"
        "movl %%eax, %1;\n\t"
        "movl %2, %%eax;\n\t"
        "movl %%eax, %0;\n\t"
        : "=m" (output), "=m" (temp)
        : "r" (input)
        : "eax"
    );
    printf("%d, %d\n", temp, output);

    return 0;
}

破壞描述部分"eax",表示EAX暫存器會被改動

相關文章