我的匯編學(xué)習(xí)之路(2):主要術(shù)語(yǔ)和概念

字號(hào):


    對(duì)于不折不扣的匯編新手來(lái)說(shuō),第一部分中出現(xiàn)的很多概念可能不是很明白,于是我決定寫更多有價(jià)值的文章。所以,讓我們開始《我的匯編學(xué)習(xí)之路》的第二部分的學(xué)習(xí)。
    術(shù)語(yǔ)和概念
    當(dāng)我寫了第一篇之后,我從不同的讀者那獲得很多反饋,第一篇中有些部分不明白,這就是本文以及接下來(lái)幾篇從一些術(shù)語(yǔ)的描述開始的原因。
    寄存器(Register):寄存器是處理器內(nèi)小容量的存儲(chǔ)結(jié)構(gòu),處理器的主要功能是數(shù)據(jù)處理,處理器可以從內(nèi)存中獲得數(shù)據(jù),但這是一種低速的操作,這就是為什么處理器為什么要有自己數(shù)據(jù)存儲(chǔ)結(jié)構(gòu),稱為“寄存器”。
    L小端(Little-endian):我們可以假設(shè)內(nèi)存是一個(gè)大的數(shù)組,它包含一個(gè)字節(jié)一個(gè)字節(jié)的數(shù)。每個(gè)地址存儲(chǔ)了內(nèi)存“數(shù)組”中的一個(gè)元素,每個(gè)元素一個(gè)字節(jié)。舉例來(lái)說(shuō),我們有 4 字節(jié)數(shù):AA 56 AB FF,在小端模式下最低位存放在低地址上:
    0 FF
    1 AB
    2 56
    3 AA
    這里,0、1、2、3 是內(nèi)存地址。
    大端(Big-endian):大端存儲(chǔ)數(shù)據(jù)與小端相反。所有上面的字節(jié)序在大端模式下是:
    0 AA
    1 56
    2 AB
    3 FF
    系統(tǒng)調(diào)用(Syscall):系統(tǒng)調(diào)用是用戶程序要求操作系統(tǒng)為其完成某些工作的一種方式。你可以在這里找到系統(tǒng)調(diào)用表。
    棧(Stack):處理器中寄存器的個(gè)數(shù)非常有限。所以棧是一塊連續(xù)的內(nèi)存空間,可以通過(guò)特殊寄存器如 RSP、SS、RIP 等來(lái)尋址。在接下來(lái)的文章我會(huì)專門深入介紹棧。
    段(Section): 每個(gè)匯編程序都是由段來(lái)組成的,有以下的段:
    data —— 用來(lái)聲明初始化的數(shù)據(jù)或常量
    bss —— 用來(lái)聲明未初始化的變量
    text —— 用來(lái)存放代碼
    通用寄存器(General-purpose register): 有 16 個(gè)通用寄存器:rax、rbx、rcx、rdx、rbp、rsp、rsi、rdi、r8、r9、r10、r11、r12、r13、r14、r15。
    當(dāng)然這不是與匯編語(yǔ)言有關(guān)的全部的術(shù)語(yǔ)和概念,如果在接下來(lái)的文章中遇到奇怪的不熟悉的詞匯,我們?cè)賮?lái)解釋這些詞的意思。
    數(shù)據(jù)類型
    基本的數(shù)據(jù)類型有:字節(jié)(bytes)、字(words)、雙字(doublewords)、四字(duadwords)以及雙四字(double dualwords),它們的長(zhǎng)度分別為:8位、2個(gè)字節(jié)、4個(gè)字節(jié)、8個(gè)字節(jié)、16個(gè)字節(jié)(128位)。
    現(xiàn)在我們只使用整數(shù),所以只看看它的表示。整型有兩種類型:無(wú)符號(hào)和有符號(hào)。無(wú)符號(hào)整型是一個(gè)字節(jié)、字、雙字、四字表示的無(wú)符號(hào)二進(jìn)制數(shù),它們能表示的范圍分別為:0~255、0~65,535、0~2^32-1、0~2^64-1。有符號(hào)整型是一個(gè)字節(jié)、字、雙字、四字表示的有符號(hào)的二進(jìn)制數(shù)。符號(hào)位在負(fù)數(shù)的時(shí)候是置位的,在正數(shù)和0的時(shí)候是清零的。整數(shù)能表示的范圍是:1個(gè)字節(jié) -128~127,1個(gè)字 -32,768~32,767,1個(gè)雙字 -2^31~2^31-1,1個(gè)四字 -2^63~2^63-1。
    段
    正如我上面提到的,每個(gè)匯編程序都是由段來(lái)組成的,它包含數(shù)據(jù)段、代碼段、bss 段。我們先來(lái)看看數(shù)據(jù)段,這是主要用來(lái)定義初始化的常量。例如:
    section .data
    num1: equ 100
    num2: equ 50
    msg: db "Sum is correct", 10
    好了,這兒差不多清楚了,三個(gè)常量名字分別為 num1、num2 和 msg,值分別是 100、50 和 “Sum is correct”,10 。但是 db 、equ 又是什么呢?實(shí)際上,NASM 支持大量偽指令:
    DB、DW、DD、DQ、DT、DO、DY 和 DZ —— 用來(lái)定義初始化數(shù)據(jù)的。例如:
    ;; Initialize 4 bytes 1h, 2h, 3h, 4h
    db 0x01,0x02,0x03,0x04
    ;; Initialize word to 0x12 0x34
    dw 0x1234
    RESB、RESW、RESD、RESQ、REST、RESO、RESY、RESZ —— 用來(lái)定義非初始化變量
    INCBIN —— 包含外部二進(jìn)制文件
    EQU —— 定義常量,例如:
    ;; now one is 1
    one equ 1
    TIMES —— 重復(fù)指令或數(shù)據(jù)(下一篇文章中描述)
    算術(shù)操作
    下面是算術(shù)操作指令的簡(jiǎn)單列表:
    ADD —— 整數(shù)加
    SUB —— 減
    MUL —— 無(wú)符號(hào)乘
    IMUL —— 有符號(hào)乘
    DIV —— 無(wú)符號(hào)除
    IDIV —— 有符號(hào)除
    INC —— 自增
    DEC —— 自減
    NEG —— 取反
    本文會(huì)用到一些,其它的在接下來(lái)的文章有所覆蓋。
    控制流
    通常編程語(yǔ)言使用 if、case、goto 等等來(lái)改變程序的運(yùn)行順序,當(dāng)然匯編也可以。這里我們提及到一些。有一個(gè)專門用來(lái)比較兩個(gè)數(shù)大小的 cmp 指令,它被用來(lái)接著條件判斷指令來(lái)決定是否跳轉(zhuǎn)。例如:
    ;; compare rax with 50
    cmp rax, 50
    cmp 指令僅僅比較兩個(gè)數(shù),但是對(duì)它們的值沒有影響,也不會(huì)根據(jù)比較的結(jié)果執(zhí)行任何東西。為了在比較之后執(zhí)行操作,有條件跳轉(zhuǎn)指令,可以是下面的一個(gè):
    JE —— 如果相等
    JZ —— 如果為零
    JNE —— 如果不相等
    JNZ —— 如果不為零
    JG —— 如果第一個(gè)操作數(shù)比第二個(gè)大
    JGE —— 如果第一個(gè)操作數(shù)比第二個(gè)大或者相等
    JA —— 與 JG 指令相同,只不過(guò)比較的是無(wú)符號(hào)數(shù)
    JAE —— 與 JGE 指令相同,只不過(guò)比較的是無(wú)符號(hào)數(shù)
    例如如果我們想寫 C 語(yǔ)言中類似于 if/else 的語(yǔ)句:
    if (rax != 50) {
    exit();
    } else {
    right();
    }
    在匯編中是這樣的:
    ;; compare rax with 50
    cmp rax, 50
    ;; perform .exit if rax is not equal 50
    jne .exit
    jmp .right
    也有一種無(wú)條件跳轉(zhuǎn)的指令語(yǔ)法:
    JMP LABEL
    例如:
    _start:
    ;; ....
    ;; do something and jump to .exit label
    ;; ....
    jmp .exit
    .exit:
    mov rax, 60
    mov rdi, 0
    syscall
    這里 _start 標(biāo)簽后有一些代碼,這些代碼會(huì)被執(zhí)行到,匯編最后控制轉(zhuǎn)向到 .exit 標(biāo)簽處,該標(biāo)簽后的代碼開始執(zhí)行。
    通常無(wú)條件跳轉(zhuǎn)用在循環(huán)中,例如我們有 label 標(biāo)簽,它后面有一些代碼,代碼執(zhí)行完之后進(jìn)行條件判斷,如果條件不成立將跳到該段代碼的起始處。循環(huán)將在后面文章中介紹。
    示例
    我們看個(gè)簡(jiǎn)單的例子:兩個(gè)數(shù)相加,得到它們的和,然后與預(yù)定義的一個(gè)數(shù)進(jìn)行比較,如果相等輸出一些東西到屏幕上;如果不等退出。下面是例子的源代碼:
    ;initialised data section
    section .data
    ; Define constants
    num1: equ 100
    num2: equ 50
    ; initialize message
    msg: db "Sum is correctn"
    section .text
    global _start
    ;; entry point
    _start:
    ; set num1's value to rax
    mov rax, num1
    ; set num2's value to rbx
    mov rbx, num2
    ; get sum of rax and rbx, and store it's value in rax
    add rax, rbx
    ; compare rax and 150
    cmp rax, 150
    ; go to .exit label if rax and 150 are not equal
    jne .exit
    ; go to .rightSum label if rax and 150 are equal
    jmp .rightSum
    ; Print message that sum is correct
    .rightSum:
    ;; write syscall
    mov rax, 1
    ;; file descritor, standard output
    mov rdi, 1
    ;; message address
    mov rsi, msg
    ;; length of message
    mov rdx, 15
    ;; call write syscall
    syscall
    ; exit from program
    jmp .exit
    ; exit procedure
    .exit:
    ; exit syscall
    mov rax, 60
    ; exit code
    mov rdi, 0
    ; call exit syscall
    syscall
    我們過(guò)一下這段代碼。首先在數(shù)據(jù)段定義了三個(gè)數(shù):num1、num2 和值為 “Sum is correctn” 的 msg?,F(xiàn)在看到第 14 行,這是程序的入口的地方。我們將 num1 和 num2 的值放到通用寄存器 rax 和 rbx 中,使用 add 指令相加,在 add 指令執(zhí)行完之后,rax 和 rbx 相加之和保存到 rax 中,即現(xiàn)在 num1 和 num2 的和存放在 rax 寄存器中。
    好了,我們讓 num1 是 100,num2 是 50,之和是 150,用 cmp 指令比較。在比較完 rax 和 150 之后,檢查比較的結(jié)果,如果 rax 和 150 不等,我們跳轉(zhuǎn)到 .exit 處,如果相等,跳到 .rightSum 標(biāo)簽處。
    接著有兩個(gè)標(biāo)簽:.exit 和 .rightSum。首先將 rax 設(shè)置為 60,這是 exit 系統(tǒng)調(diào)用號(hào),以及將 rdi 設(shè)為 0,這是退出碼。然后,.rightSUm 相當(dāng)簡(jiǎn)單,只是打印出 Sum is corretn,如果你不能理解怎么工作的,看看第一篇文章。
    總結(jié)
    這是 《我的匯編學(xué)習(xí)之路》 系列文章的第二篇,如果你有任何問(wèn)題或建議,給我留言。