3.2 程序編碼

機(jī)器級(jí)代碼

兩種抽像:

  • (Instruction Set Architecture,ISA) 定義機(jī)器級(jí)田可以存入格式和行為,定義了處理器狀態(tài),指令的格式,以及每條指令對(duì)狀態(tài)的影響。
  • 機(jī)器機(jī)程序使用的內(nèi)存地址是虛擬地址,提供的內(nèi)存模型看上去是一個(gè)非常大的按字節(jié)尋址的數(shù)組。

一些C 語(yǔ)言中隱藏的處理器狀態(tài):

  • 程序計(jì)數(shù)器 (PC, %rip) 給出將要執(zhí)行的下一條指令在內(nèi)存中的地址。
  • 整數(shù)寄存器(包含16個(gè)命名位置),分別存儲(chǔ)64位的值,用來(lái)存儲(chǔ) 地址,整數(shù)數(shù)據(jù), 記錄狀態(tài), 臨時(shí)數(shù)據(jù), 局部變量.
  • 條件碼寄存器 保存最近執(zhí)行的算術(shù)或邏輯指令的狀態(tài)信息。用來(lái)實(shí)現(xiàn)控制或數(shù)據(jù)流中的條件變化,比如說(shuō)用來(lái)實(shí)現(xiàn)ifswitch 語(yǔ)句。
  • 一組向量寄存器 用來(lái)存放一個(gè)或多個(gè)整數(shù)或浮點(diǎn)數(shù)值。

目前一般 x86-64的虛擬地址是 由64位的字來(lái)表示,在目前的實(shí)現(xiàn)中這些地址的高16位必須設(shè)置為0,所以實(shí)現(xiàn)目前一般最高可以指定 64TB 內(nèi)存地址空間。

代碼示例

long mult2(long,long);

void multstore(long x, long y, long *dest){
    long t = mult2(x,y);
    *dest = t;
}

使用 gcc -Og -S mstore.c 編譯之后得到的文件的主要內(nèi)容如下:

.global _multstore
_multstore:
    pushq %rbp
    movq %rsp, %rbp
    pushq %rbx
    pushq %rax
    movq %rdx, %rbx
    callq _mult2
    movq %rax, (%rbx)
    addq $8, %rsp
    popq %rbx
    popq %rbp
    retq

上面編譯選項(xiàng)使用的是 -S 如果使用 -c 則可以得到匯編的目標(biāo)文件。

objdump 工具可以用來(lái)反匯編目標(biāo)文件。

?  ch3 objdump -d mstore.o

mstore.o:       file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
_multstore:
       0:       55      pushq   %rbp
       1:       48 89 e5        movq    %rsp, %rbp
       4:       53      pushq   %rbx
       5:       50      pushq   %rax
       6:       48 89 d3        movq    %rdx, %rbx
       9:       e8 00 00 00 00  callq   0 <_multstore+0xE>
       e:       48 89 03        movq    %rax, (%rbx)
      11:       48 83 c4 08     addq    $8, %rsp
      15:       5b      popq    %rbx
      16:       5d      popq    %rbp
      17:       c3      retq

將上面的輸出整理成下表:

Offset Bytes Equivalent assembly language
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 53 pushq %rbx
5: 50 pushq %rax
6: 48 89 d3 movq %rdx, %rbx
9: e8 00 00 00 00 callq 0 <_multstore+0xE>
e: 48 89 03 movq %rax, (%rbx)
11: 48 83 c4 08 addq $8, %rsp
15: 5b popq %rbx
16: 5d popq %rbp
17: c3 retq

上面一共 24個(gè)字節(jié)分成了 11 組,代表了11條指令。 右邊是等價(jià)的匯編語(yǔ)言。
比如 c3 字節(jié)表示的機(jī)器碼對(duì)應(yīng)了匯編指令就是 retq。

AT&T 語(yǔ)法與 Intel 語(yǔ)法

gcc 與 objdump 等工具默認(rèn)使用 AT&T 語(yǔ)法。也可以使用如下指令生成 Intel 語(yǔ)法格式的匯編。 gcc -Og -S -masm=intel mstore.c

對(duì)應(yīng)的輸出的代碼如下:

.intel_syntax noprefix
global _multstore
_multstore:
    push rbp
    mov rbp, rsp
    push rbx
    push rax
    mov rbx,rdx
    call _mult2
    mov qword ptr [rbx], rax
    add rsp, 8
    pop rbx
    pop rbp
    ret

這兩種語(yǔ)法的對(duì)比如下:

語(yǔ)法 AT&T Intel 說(shuō)明
指令名稱 pushq push Intel 語(yǔ)法省略了指示大小的后綴
寄存器引用 %rbp rbp Intel 語(yǔ)法省略了寄存器前面的 % 符號(hào)
地址引用 (%rbp) qword ptr [rbp] 內(nèi)存位置
字面量 $8 8 Intel 語(yǔ)法省略了字面量前面的 $ 符號(hào)
多操作數(shù)順序 addq $8, %rsp add rsp, 8 兩種語(yǔ)法的操作數(shù)順序相反

關(guān)于格式的注解

gcc -Og -S mstore.c 實(shí)際的完整如下:

    .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 13
    .globl  _multstore              ## -- Begin function multstore
    .p2align    4, 0x90
_multstore:                             ## @multstore
    .cfi_startproc
## BB#0:
    pushq   %rbp
Lcfi0:
    .cfi_def_cfa_offset 16
Lcfi1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Lcfi2:
    .cfi_def_cfa_register %rbp
    pushq   %rbx
    pushq   %rax
Lcfi3:
    .cfi_offset %rbx, -24
    movq    %rdx, %rbx
    callq   _mult2
    movq    %rax, (%rbx)
    addq    $8, %rsp
    popq    %rbx
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function

.subsections_via_symbols

其中以 . 開(kāi)頭的行都是指導(dǎo)匯編器和鏈接器工作的偽指令。
匯編代碼沒(méi)有注釋是很難看的。
基本要求是對(duì)于指令在右給出注釋或?qū)?yīng)的可能 C 語(yǔ)言方法。
函數(shù)的參數(shù)需要給出對(duì)應(yīng) C 語(yǔ)言的簽名,參數(shù)的說(shuō)明。

把 C 程序與匯編代碼結(jié)合起來(lái)

  • 用匯編編寫(xiě)完整的匯編代碼(主要是函數(shù)),放進(jìn)一個(gè)獨(dú)立的匯編文件中,讓匯編器和鏈接器反它和用 C 語(yǔ)言書(shū)寫(xiě)的代碼合并起來(lái)。
  • 用 GCC 內(nèi)聯(lián)匯編的特性,用 __asm__ 偽指令可以在 C 程序中包含簡(jiǎn)短的匯編代碼。

C 代碼調(diào)用外部匯編函數(shù)代碼

編寫(xiě) add.s 文件,內(nèi)容如下:

# 兩整數(shù)想加,返回和
# @signature: int (int a,int b)
# @body: return a + b
# @param a - %edi 
# @param b - %esi
# @return 返回兩數(shù)想加和 - %eax
.global _add
_add:
    pushq %rbp  # 保存老的基址指針(即將 rbp 寄存器的值壓入當(dāng)前棧頂)
    movq %rsp, %rbp # 將當(dāng)前棧指針作為新的基址指針
    movl %edi, -4(%rbp)
    movl %esi, -8(%rbp)
    movl -4(%rbp), %esi
    addl -8(%rbp), %esi
    movl %esi, %eax
    popq %rbp # 將棧頂保存的老的基址指針恢復(fù)到 rbp 寄存器
    retq

編寫(xiě) add_main.c 內(nèi)容如下:

#include<stdio.h>

extern int add(int a,int b); // 在 add.s 中實(shí)現(xiàn)


int main(int argc, char const *argv[]) {
    int sum = add(3,4);
    printf("3 + 4 = %d\n", sum); 
    return 0;
}

編譯與運(yùn)行:

?  ch3 cc -o add_main add_main.c add.s
?  ch3 ./add_main
3 + 4 = 7
?  ch3
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容