歡迎您光臨本站 註冊首頁

Linux 彙編器:對比 GAS 和 NASM

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
本文解釋兩種最流行的 Linux® 彙編器 —— GNU Assembler(GAS)和 Netwide Assembler(NASM) —— 之間一些比較重要的語法差異和語義差異,包括基本語法、變數和內存訪問、宏處理、函數和外部常式、堆棧處理以及重複執行代碼塊的技術方面的差異。

與其他語言不同,彙編語言要求開發人員了解編程所用機器的處理器體系結構。彙編程序不可移植,維護和理解常常比較麻煩,通常包含大量代碼行。但是,在機器上執行的運行時二進位代碼在速度和大小方面有優勢。

對於在 Linux 上進行彙編級編程已經有許多參考資料,本文主要講解語法之間的差異,幫助您更輕鬆地在彙編形式之間進行轉換。本文源於我自己試圖改進這種轉換的嘗試。

本文使用一系列程序示例。每個程序演示一些特性,然後是對語法的討論和對比。儘管不可能討論 NASM 和 GAS 之間存在的每個差異,但是我試圖討論主要方面,給進一步研究提供一個基礎。那些已經熟悉 NASM 和 GAS 的讀者也可以在這裡找到有用的內容,比如宏。

本文假設您至少基本了解彙編的術語,曾經用符合 Intel® 語法的彙編器編寫過程序,可能在 Linux 或 Windows 上使用過 NASM。本文並不講解如何在編輯器中輸入代碼,或者如何進行彙編和鏈接(但是下面的邊欄可以幫助您 快速回憶一下)。您應該熟悉 Linux 操作系統(任何 Linux 發行版都可以;我使用的是 Red Hat 和 Slackware)和基本的 GNU 工具,比如 gcc 和 ld,還應該在 x86 機器上進行編程。

現在,我描述一下本文討論的範圍。

構建示例


彙編:
GAS:
as –o program.o program.s

NASM:
nasm –f elf –o program.o program.asm

鏈接(對於兩種彙編器通用):
ld –o program program.o

在使用外部 C 庫時的鏈接方法:
ld –-dynamic-linker /lib/ld-linux.so.2 –lc –o program program.o

本文討論:

  • NASM 和 GAS 之間的基本語法差異
  • 常用的彙編級結構,比如變數、循環、標籤和宏
  • 關於調用外部 C 常式和使用函數的信息
  • 彙編助記符差異和使用方法
  • 內存定址方法

 

本文不討論:

  • 處理器指令集
  • 一種彙編器特有的各種宏形式和其他結構
  • NASM 或 GAS 特有的彙編器指令
  • 不常用的特性,或者只在一種彙編器中出現的特性

 

更多信息請參考彙編器的官方手冊(參見 參考資料 中的鏈接),因為這些手冊是最完整的信息源。

基本結構

清單 1 給出一個非常簡單的程序,它的作用僅僅是使用退出碼 2 退出。這個小程序展示了 NASM 和 GAS 的彙編程序的基本結構。

清單 1. 一個使用退出碼 2 退出的程序
行號 NASM GAS
001  002  003  004  005  006  007  008  009  010  011  012  013  014  015  016  

; Text segment begins  section .text       global _start    ; Program entry point     _start:    ; Put the code number for system call        mov   eax, 1     ; Return value         mov   ebx, 2    ; Call the OS        int   80h  

# Text segment begins  .section .text       .globl _start    # Program entry point     _start:    # Put the code number for system call        movl  $1, %eax    /* Return value */        movl  $2, %ebx    # Call the OS        int   $0x80  

現在解釋一下。

NASM 和 GAS 之間最大的差異之一是語法。GAS 使用 AT&T 語法,這是一種相當老的語法,由 GAS 和一些老式彙編器使用;NASM 使用 Intel 語法,大多數彙編器都支持它,包括 TASM 和 MASM。(GAS 的現代版本支持 .intel_syntax 指令,因此允許在 GAS 中使用 Intel 語法。)

下面是從 GAS 手冊總結出的一些主要差異:

  • AT&T 和 Intel 語法採用相反的源和目標操作數次序。例如:
    • Intel:mov eax, 4
    • AT&T:movl $4, %eax
  • 在 AT&T 語法中,中間操作數前面加 $;在 Intel 語法中,中間操作數不加前綴。例如:
    • Intel:push 4
    • AT&T:pushl $4
  • 在 AT&T 語法中,寄存器操作數前面加 %。在 Intel 語法中,它們不加前綴。
  • 在 AT&T 語法中,內存操作數的大小由操作碼名稱的最後一個字元決定。操作碼後綴 b、w 和 l 分別指定位元組(8 位)、字(16 位)和長(32 位)內存引用。Intel 語法通過在內存操作數(而不是操作碼本身)前面加 byte ptr、word ptr 和 dword ptr 來指定大小。所以:
    • Intel:mov al, byte ptr foo
    • AT&T:movb foo, %al
  • 在 AT&T 語法中,中間形式長跳轉和調用是 lcall/ljmp $section, $offset;Intel 語法是 call/jmp far section:offset。在 AT&T 語法中,遠返回指令是 lret $stack-adjust,而 Intel 使用 ret far stack-adjust。

 

在這兩種彙編器中,寄存器的名稱是一樣的,但是因為定址模式不同,使用它們的語法是不同的。另外,GAS 中的彙編器指令以 “.” 開頭,但是在 NASM 中不是。

.text 部分是處理器開始執行代碼的地方。global(或者 GAS 中的 .globl 或 .global)關鍵字用來讓一個符號對鏈接器可見,可以供其他鏈接對象模塊使用。在清單 1 的 NASM 部分中,global _start 讓 _start 符號成為可見的標識符,這樣鏈接器就知道跳轉到程序中的什麼地方並開始執行。與 NASM 一樣,GAS 尋找這個 _start 標籤作為程序的默認進入點。在 GAS 和 NASM 中標籤都以冒號結尾。

中斷是一種通知操作系統需要它的服務的一種方法。第 16 行中的 int 指令執行這個工作。GAS 和 NASM 對中斷使用同樣的助記符。GAS 使用 0x 前綴指定十六進位數字,NASM 使用 h 後綴。因為在 GAS 中中間操作數帶 $ 前綴,所以 80 hex 是 $0x80。

int $0x80(或 NASM 中的 80h)用來向 Linux 請求一個服務。服務編碼放在 EAX 寄存器中。EAX 中存儲的值是 1(代表 Linux exit 系統調用),這請求程序退出。EBX 寄存器包含退出碼(在這個示例中是 2),也就是返回給操作系統的一個數字。(可以在命令提示下輸入 echo $? 來檢查這個數字。)

最後討論一下註釋。GAS 支持 C 風格(/* */)、C++ 風格(//)和 shell 風格(#)的註釋。NASM 支持以 “;” 字元開頭的單行註釋。





變數和內存訪問

本節首先給出一個示常式序,它尋找三個數字中的最大者。

清單 2. 尋找三個數字中最大者的程序
行號 NASM GAS
001  002  003  004  005  006  007  008  009  010  011  012  013  014  015  016  017  018  019  020  021  022  023  024  025  026  027  028  029  030  031  

; Data section begins  section .data       var1 dd 40       var2 dd 20       var3 dd 30      section .text       global _start       _start:    ; Move the contents of variables        mov   ecx, [var1]        cmp   ecx, [var2]        jg    check_third_var        mov   ecx, [var2]       check_third_var:        cmp   ecx, [var3]        jg    _exit        mov   ecx, [var3]       _exit:        mov   eax, 1        mov   ebx, ecx        int   80h  

// Data section begins  .section .data          var1:        .int 40     var2:        .int 20     var3:        .int 30    .section .text       .globl _start       _start:    # move the contents of variables        movl  (var1), %ecx        cmpl  (var2), %ecx        jg    check_third_var        movl  (var2), %ecx       check_third_var:        cmpl  (var3), %ecx        jg    _exit        movl  (var3), %ecx          _exit:        movl  $1, %eax        movl  %ecx, %ebx        int   $0x80  

在上面的內存變數聲明中可以看到幾點差異。NASM 分別使用 dd、dw 和 db 指令聲明 32 位、16 位和 8 位數字,而 GAS 分別使用 .long、.int 和 .byte。GAS 還有其他指令,比如 .ascii、.asciz 和 .string。在 GAS 中,像聲明其他標籤一樣聲明變數(使用冒號),但是在 NASM 中,只需在內存分配指令(dd、dw 等等)前面輸入變數名,後面加上變數的值。

清單 2 中的第 18 行演示內存直接定址模式。NASM 使用方括弧間接引用一個內存位置指向的地址值:[var1]。GAS 使用圓括弧間接引用同樣的值:(var1)。本文後面討論其他定址模式的使用方法。





使用宏

清單 3 演示本節討論的概念;它接受用戶名作為輸入並返回一句問候語。

清單 3. 讀取字元串並向用戶顯示問候語的程序
行號 NASM GAS
001  002  003  004  005  006  007  008  009  010  011  012  013  014  015  016  017  018  019  020  021  022  023  024  025  026  027  028  029  030  031  032  033  034  035  036  037  038  039  040  041  042  043  044  045  046  047  048  049  050  051  052  053  054  055  056  057  058  059  060  061  062  

section .data       prompt_str  db   'Enter your name: '    ; $ is the location counter     STR_SIZE  equ  $ - prompt_str       greet_str  db  'Hello '         GSTR_SIZE  equ  $ - greet_str      section .bss    ; Reserve 32 bytes of memory     buff  resb  32    ; A macro with two parameters  ; Implements the write system call     %macro write 2         mov   eax, 4        mov   ebx, 1        mov   ecx, %1        mov   edx, %2        int   80h     %endmacro      ; Implements the read system call     %macro read 2        mov   eax, 3        mov   ebx, 0        mov   ecx, %1        mov   edx, %2        int   80h     %endmacro      section .text       global _start       _start:        write prompt_str, STR_SIZE        read  buff, 32    ; Read returns the length in eax        push  eax    ; Print the hello text        write greet_str, GSTR_SIZE          pop   edx    ; edx  = length returned by read        write buff, edx       _exit:        mov   eax, 1        mov   ebx, 0        int   80h  

.section .data       prompt_str:        .ascii "Enter Your Name: "     pstr_end:        .set STR_SIZE, pstr_end - prompt_str       greet_str:        .ascii "Hello "       gstr_end:        .set GSTR_SIZE, gstr_end - greet_str    .section .bss    // Reserve 32 bytes of memory     .lcomm  buff, 32    // A macro with two parameters  //  implements the write system call     .macro write str, str_size         movl  $4, %eax        movl  $1, %ebx        movl  \str, %ecx        movl  \str_size, %edx        int   $0x80     .endm      // Implements the read system call     .macro read buff, buff_size        movl  $3, %eax        movl  $0, %ebx        movl  \buff, %ecx        movl  \buff_size, %edx        int   $0x80     .endm      .section .text       .globl _start       _start:        write $prompt_str, $STR_SIZE        read  $buff, $32    // Read returns the length in eax        pushl %eax    // Print the hello text        write $greet_str, $GSTR_SIZE          popl  %edx    // edx = length returned by read     write $buff, %edx       _exit:        movl  $1, %eax        movl  $0, %ebx        int   $0x80  

本節要討論宏以及 NASM 和 GAS 對它們的支持。但是,在討論宏之前,先與其他幾個特性做一下比較。

清單 3 演示了未初始化內存的概念,這是用 .bss 部分指令(第 14 行)定義的。BSS 代表 “block storage segment” (原來是以一個符號開頭的塊),BSS 部分中保留的內存在程序啟動時初始化為零。BSS 部分中的對象只有一個名稱和大小,沒有值。與數據部分中不同,BSS 部分中聲明的變數並不實際佔用空間。

NASM 使用 resb、resw 和 resd 關鍵字在 BSS 部分中分配位元組、字和雙字空間。GAS 使用 .lcomm 關鍵字分配位元組級空間。請注意在這個程序的兩個版本中聲明變數名的方式。在 NASM 中,變數名前面加 resb(或 resw 或 resd)關鍵字,後面是要保留的空間量;在 GAS 中,變數名放在 .lcomm 關鍵字的後面,然後是一個逗號和要保留的空間量。

NASM:varname resb size

GAS:.lcomm varname, size

清單 3 還演示了位置計數器的概念(第 6 行)。 NASM 提供特殊的變數($ 和 $$ 變數)來操作位置計數器。在 GAS 中,無法操作位置計數器,必須使用標籤計算下一個存儲位置(數據、指令等等)。

例如,為了計算一個字元串的長度,在 NASM 中會使用以下指令:

prompt_str db 'Enter your name: '
STR_SIZE equ $ - prompt_str     ; $ is the location counter

$ 提供位置計數器的當前值,從這個位置計數器中減去標籤的值(所有變數名都是標籤),就會得出標籤的聲明和當前位置之間的位元組數。equ 用來將變數 STR_SIZE 的值設置為後面的表達式。GAS 中使用的相似指令如下:

prompt_str:
     .ascii "Enter Your Name: "

pstr_end:
     .set STR_SIZE, pstr_end - prompt_str

末尾標籤(pstr_end)給出下一個位置地址,減去啟始標籤地址就得出大小。還要注意,這裡使用 .set 將變數 STR_SIZE 的值設置為逗號後面的表達式。也可以使用對應的 .equ。在 NASM 中,沒有與 GAS 的 set 指令對應的指令。

正如前面提到的,清單 3 使用了宏(第 21 行)。在 NASM 和 GAS 中存在不同的宏技術,包括單行宏和宏重載,但是這裡只關注基本類型。宏在彙編程序中的一個常見用途是提高代碼的清晰度。通過創建可重用的宏,可以避免重複輸入相同的代碼段;這不但可以避免重複,而且可以減少代碼量,從而提高代碼的可讀性。

NASM 使用 %beginmacro 指令聲明宏,用 %endmacro 指令結束聲明。%beginmacro 指令後面是宏的名稱。宏名稱後面是一個數字,這是這個宏需要的宏參數數量。在 NASM 中,宏參數是從 1 開始連續編號的。也就是說,宏的第一個參數是 %1,第二個是 %2,第三個是 %3,以此類推。例如:

%beginmacro macroname 2
     mov eax, %1
     mov ebx, %2
%endmacro

這創建一個有兩個參數的宏,第一個參數是 %1,第二個參數是 %2。因此,對上面的宏的調用如下所示:

macroname 5, 6

還可以創建沒有參數的宏,在這種情況下不指定任何數字。

現在看看 GAS 如何使用宏。GAS 提供 .macro 和 .endm 指令來創建宏。.macro 指令後面跟著宏名稱,後面可以有參數,也可以沒有參數。在 GAS 中,宏參數是按名稱指定的。例如:

.macro macroname arg1, arg2
     movl \arg1, %eax
     movl \arg2, %ebx
.endm

當在宏中使用宏參數名稱時,在名稱前面加上一個反斜線。如果不這麼做,鏈接器會把名稱當作標籤而不是參數,因此會報告錯誤。





函數、外部常式和堆棧

本節的示常式序在一個整數數組上實現選擇排序。

清單 4. 在整數數組上實現選擇排序
行號 NASM GAS
001  002  003  004  005  006  007  008  009  010  011  012  013  014  015  016  017  018  019  020  021  022  023  024  025  026  027  028  029  030  031  032  033  034  035  036  037  038  039  040  041  042  043  044  045  046  047  048  049  050  051  052  053  054  055  056  057  058  059  060  061  062  063  064  065  066  067  068  069  070  071  072  073  074  075  076  077  078  079  080  081  082  083  084  085  086  087  088  089  090  091  092  093  094  095  096  097  098  099  100  101  102  103  104  105  106  107  108  109  110  111  112  113  114  115  116  117  118  119  120  121  122  123  124  125  126  127  128  129  130  131  132  133  134  135  136  137  138  139  140  141  142  143  144  145  

section .data       array db        89, 10, 67, 1, 4, 27, 12, 34,           86, 3       ARRAY_SIZE equ $ - array         array_fmt db "  %d", 0         usort_str db "unsorted array:", 0         sort_str db "sorted array:", 0         newline db 10, 0        section .text     extern puts       global _start       _start:          push  usort_str        call  puts        add   esp, 4             push  ARRAY_SIZE        push  array        push  array_fmt        call  print_array10        add   esp, 12          push  ARRAY_SIZE         push  array        call  sort_routine20    ; Adjust the stack pointer        add   esp, 8          push  sort_str        call  puts        add   esp, 4          push  ARRAY_SIZE         push  array        push  array_fmt        call  print_array10        add   esp, 12        jmp   _exit          extern printf       print_array10:        push  ebp        mov   ebp, esp        sub   esp, 4        mov   edx, [ebp + 8]        mov   ebx, [ebp + 12]        mov   ecx, [ebp + 16]          mov   esi, 0       push_loop:        mov   [ebp - 4], ecx        mov   edx, [ebp + 8]        xor   eax, eax        mov   al, byte [ebx + esi]        push  eax        push  edx          call  printf        add   esp, 8        mov   ecx, [ebp - 4]        inc   esi        loop  push_loop          push  newline        call  printf        add   esp, 4        mov   esp, ebp        pop   ebp        ret       sort_routine20:        push  ebp        mov   ebp, esp    ; Allocate a word of space in stack        sub   esp, 4     ; Get the address of the array        mov   ebx, [ebp + 8]     ; Store array size        mov   ecx, [ebp + 12]        dec   ecx    ; Prepare for outer loop here        xor   esi, esi       outer_loop:  ; This stores the min index        mov   [ebp - 4], esi         mov   edi, esi        inc   edi       inner_loop:        cmp   edi, ARRAY_SIZE        jge   swap_vars        xor   al, al        mov   edx, [ebp - 4]        mov   al, byte [ebx + edx]        cmp   byte [ebx + edi], al        jge   check_next        mov   [ebp - 4], edi       check_next:        inc   edi        jmp   inner_loop       swap_vars:        mov   edi, [ebp - 4]        mov   dl, byte [ebx + edi]        mov   al, byte [ebx + esi]        mov   byte [ebx + esi], dl        mov   byte [ebx + edi], al          inc   esi        loop  outer_loop          mov   esp, ebp        pop   ebp        ret       _exit:        mov   eax, 1        mov   ebx, 0        int   80h  

.section .data       array:        .byte  89, 10, 67, 1, 4, 27, 12,               34, 86, 3       array_end:        .equ ARRAY_SIZE, array_end - array       array_fmt:        .asciz "  %d"       usort_str:        .asciz "unsorted array:"       sort_str:        .asciz "sorted array:"       newline:        .asciz "\n"      .section .text         .globl _start       _start:          pushl $usort_str        call  puts        addl  $4, %esp          pushl $ARRAY_SIZE        pushl $array        pushl $array_fmt        call  print_array10        addl  $12, %esp          pushl $ARRAY_SIZE        pushl $array        call  sort_routine20    # Adjust the stack pointer        addl  $8, %esp          pushl $sort_str        call  puts        addl  $4, %esp          pushl $ARRAY_SIZE        pushl $array        pushl $array_fmt        call  print_array10        addl  $12, %esp        jmp   _exit           print_array10:        pushl %ebp        movl  %esp, %ebp        subl  $4, %esp        movl  8(%ebp), %edx        movl  12(%ebp), %ebx        movl  16(%ebp), %ecx          movl  $0, %esi       push_loop:        movl  %ecx, -4(%ebp)          movl  8(%ebp), %edx        xorl  %eax, %eax        movb  (%ebx, %esi, 1), %al        pushl %eax        pushl %edx          call  printf        addl  $8, %esp        movl  -4(%ebp), %ecx        incl  %esi        loop  push_loop          pushl $newline        call  printf        addl  $4, %esp        movl  %ebp, %esp        popl  %ebp        ret       sort_routine20:        pushl %ebp        movl  %esp, %ebp    # Allocate a word of space in stack        subl  $4, %esp    # Get the address of the array        movl  8(%ebp), %ebx    # Store array size        movl  12(%ebp), %ecx        decl  %ecx    # Prepare for outer loop here        xorl  %esi, %esi       outer_loop:  # This stores the min index        movl  %esi, -4(%ebp)        movl  %esi, %edi        incl  %edi       inner_loop:        cmpl  $ARRAY_SIZE, %edi        jge   swap_vars        xorb  %al, %al        movl  -4(%ebp), %edx        movb  (%ebx, %edx, 1), %al        cmpb  %al, (%ebx, %edi, 1)        jge   check_next        movl  %edi, -4(%ebp)       check_next:        incl  %edi        jmp   inner_loop       swap_vars:        movl  -4(%ebp), %edi        movb  (%ebx, %edi, 1), %dl        movb  (%ebx, %esi, 1), %al        movb  %dl, (%ebx, %esi, 1)        movb  %al, (%ebx,  %edi, 1)          incl  %esi        loop  outer_loop          movl  %ebp, %esp        popl  %ebp        ret       _exit:        movl  $1, %eax        movl  0, %ebx        int   $0x80  

初看起來清單 4 似乎非常複雜,實際上它是非常簡單的。這個清單演示了函數、各種內存定址方案、堆棧和庫函數的使用方法。這個程序對包含 10 個數字的數組進行排序,並使用外部 C 庫函數 puts 和 printf 輸出未排序數組和已排序數組的完整內容。為了實現模塊化和介紹函數的概念,排序常式本身實現為一個單獨的過程,數組輸出常式也是這樣。我們來逐一分析一下。

在聲明數據之後,這個程序首先執行對 puts 的調用(第 31 行)。puts 函數在控制台上顯示一個字元串。它惟一的參數是要顯示的字元串的地址,通過將字元串的地址壓入堆棧(第 30 行),將這個參數傳遞給它。

在 NASM 中,任何不屬於我們的程序但是需要在鏈接時解析的標籤都必須預先定義,這就是 extern 關鍵字的作用(第 24 行)。GAS 沒有這樣的要求。在此之後,字元串的地址 usort_str 被壓入堆棧(第 30 行)。在 NASM 中,內存變數(比如 usort_str)代表內存位置本身,所以 push usort_str 這樣的調用實際上是將地址壓入堆棧的頂部。但是在 GAS 中,變數 usort_str 必須加上前綴 $,這樣它才會被當作地址。如果不加前綴 $,那麼會將內存變數代表的實際位元組壓入堆棧,而不是地址。

因為在堆棧中壓入一個變數會讓堆棧指針移動一個雙字,所以給堆棧指針加 4(雙字的大小)(第 32 行)。

現在將三個參數壓入堆棧,並調用 print_array10 函數(第 37 行)。在 NASM 和 GAS 中聲明函數的方法是相同的。它們僅僅是通過 call 指令調用的標籤。

在調用函數之後,ESP 代表堆棧的頂部。esp + 4 代表返回地址,esp + 8 代表函數的第一個參數。在堆棧指針上加上雙字變數的大小(即 esp + 12、esp + 16 等等),就可以訪問所有後續參數。

在函數內部,通過將 esp 複製到 ebp (第 62 行)創建一個局部堆棧框架。和程序中的處理一樣,還可以為局部變數分配空間(第 63 行)。方法是從 esp 中減去所需的位元組數。esp – 4 表示為一個局部變數分配 4 位元組的空間,只要堆棧中有足夠的空間容納局部變數,就可以繼續分配。

清單 4 演示了基間接定址模式(第 64 行),也就是首先取得一個基地址,然後在它上面加一個偏移量,從而到達最終的地址。在清單的 NASM 部分中,[ebp + 8] 和 [ebp – 4](第 71 行)就是基間接定址模式的示例。在 GAS 中,定址方法更簡單一些:4(%ebp) 和 -4(%ebp)。

在 print_array10 常式中,在 push_loop 標籤後面可以看到另一種定址模式(第 74 行)。在 NASM 和 GAS 中的表示方法如下:

NASM:mov al, byte [ebx + esi]

GAS:movb (%ebx, %esi, 1), %al

這種定址模式稱為基索引定址模式。這裡有三項數據:一個是基地址,第二個是索引寄存器,第三個是乘數。因為不可能決定從一個內存位置開始訪問的位元組數,所以需要用一個方法計算訪問的內存量。NASM 使用位元組操作符告訴彙編器要移動一個位元組的數據。在 GAS 中,用一個乘數和助記符中的 b、w 或 l 後綴(例如 movb)來解決這個問題。初看上去 GAS 的語法似乎有點兒複雜。

GAS 中基索引定址模式的一般形式如下:

%segment:ADDRESS (, index, multiplier)

%segment:(offset, index, multiplier)

%segment:ADDRESS(base, index, multiplier)

使用這個公式計算最終的地址:

ADDRESS or offset + base + index * multiplier.

因此,要想訪問一個位元組,就使用乘數 1;對於字,乘數是 2;對於雙字,乘數是 4。當然,NASM 使用的語法比較簡單。上面的公式在 NASM 中表示為:

Segment:[ADDRESS or offset + index * multiplier]

為了訪問 1、2 或 4 位元組的內存,在這個內存地址前面分別加上 byte、word 或 dword。





其他方面

清單 5 讀取命令行參數的列表,將它們存儲在內存中,然後輸出它們。

清單 5. 讀取命令行參數,將它們存儲在內存中,然後輸出它們
行號 NASM GAS
001  002  003  004  005  006  007  008  009  010  011  012  013  014  015  016  017  018  019  020  021  022  023  024  025  026  027  028  029  030  031  032  033  034  035  036  037  038  039  040  041  042  043  044  045  046  047  048  049  050  051  052  053  054  055  056  057  058  059  060  061  

section .data    ; Command table to store at most  ;  10 command line arguments     cmd_tbl:        %rep 10           dd 0        %endrep    section .text       global _start       _start:  ; Set up the stack frame        mov   ebp, esp  ; Top of stack contains the  ;  number of command line arguments.  ; The default value is 1        mov   ecx, [ebp]    ; Exit if arguments are more than 10        cmp   ecx, 10        jg    _exit          mov   esi, 1        mov   edi, 0    ; Store the command line arguments  ;  in the command table     store_loop:        mov   eax, [ebp + esi * 4]        mov   [cmd_tbl + edi * 4], eax        inc   esi        inc   edi        loop  store_loop          mov   ecx, edi        mov   esi, 0          extern puts          print_loop:  ; Make some local space        sub   esp, 4  ; puts function corrupts ecx        mov   [ebp - 4], ecx        mov   eax, [cmd_tbl + esi * 4]        push  eax        call  puts        add   esp, 4        mov   ecx, [ebp - 4]        inc   esi        loop  print_loop          jmp   _exit          _exit:        mov   eax, 1        mov   ebx, 0        int   80h  

.section .data    // Command table to store at most  //  10 command line arguments     cmd_tbl:        .rept 10           .long 0        .endr    .section .text       .globl _start       _start:  // Set up the stack frame        movl  %esp, %ebp  // Top of stack contains the  //  number of command line arguments.  // The default value is 1        movl  (%ebp), %ecx    // Exit if arguments are more than 10        cmpl  $10, %ecx        jg    _exit             movl  $1, %esi        movl  $0, %edi    // Store the command line arguments  //  in the command table     store_loop:        movl  (%ebp, %esi, 4), %eax        movl  %eax, cmd_tbl( , %edi, 4)        incl  %esi        incl  %edi        loop  store_loop          movl  %edi, %ecx        movl  $0, %esi           print_loop:  // Make some local space        subl  $4, %esp  // puts functions corrupts ecx        movl  %ecx, -4(%ebp)        movl  cmd_tbl( , %esi, 4), %eax        pushl %eax        call  puts        addl  $4, %esp        movl  -4(%ebp), %ecx        incl  %esi        loop  print_loop          jmp   _exit       _exit:        movl  $1, %eax        movl  $0, %ebx        int   $0x80  

清單 5 演示在彙編程序中重複執行指令的方法。很自然,這種結構稱為重複結構。在 GAS 中,重複結構以 .rept 指令開頭(第 6 行)。用一個 .endr 指令結束這個指令(第 8 行)。.rept 後面是一個數字,它指定 .rept/.endr 結構中表達式重複執行的次數。這個結構中的任何指令都相當於編寫這個指令 count 次,每次重複佔據單獨的一行。

例如,如果次數是 3:

.rept 3
     movl $2, %eax
.endr

就相當於:

movl $2, %eax
movl $2, %eax
movl $2, %eax

在 NASM 中,在預處理器級使用相似的結構。它以 %rep 指令開頭,以 %endrep 結尾。%rep 指令後面是一個表達式(在 GAS 中 .rept 指令後面是一個數字):

%rep <expression>
     nop
%endrep

在 NASM 中還有另一種結構,times 指令。與 %rep 相似,它也在彙編級起作用,後面也是一個表達式。例如,上面的 %rep 結構相當於:

times <expression> nop

以下代碼:

%rep 3
     mov eax, 2
%endrep

相當於:

times 3 mov eax, 2

它們都相當於:

mov eax, 2
mov eax, 2
mov eax, 2

在清單 5 中,使用 .rept(或 %rep)指令為 10 個雙字創建內存數據區。然後,從堆棧一個個地訪問命令行參數,並將它們存儲在內存區中,直到命令表填滿。

在這兩種彙編器中,訪問命令行參數的方法是相似的。ESP(堆棧頂部)存儲傳遞給程序的命令行參數數量,默認值是 1(表示沒有命令行參數)。esp + 4 存儲第一個命令行參數,這總是從命令行調用的程序的名稱。esp + 8、esp + 12 等存儲後續命令行參數。

還要注意清單 5 中從兩邊訪問內存命令表的方法。這裡使用內存間接定址模式(第 31 行)訪問命令表,還使用了 ESI(和 EDI)中的偏移量和一個乘數。因此,NASM 中的 [cmd_tbl + esi * 4] 相當於 GAS 中的 cmd_tbl(, %esi, 4)。





結束語

儘管在這兩種彙編器之間存在實質性的差異,但是在這兩種形式之間進行轉換並不困難。您最初可能覺得 AT&T 語法難以理解,但是掌握了它之後,它其實和 Intel 語法同樣簡單。

(責任編輯:A6)



[火星人 ] Linux 彙編器:對比 GAS 和 NASM已經有1265次圍觀

http://coctec.com/docs/linux/show-post-69465.html