myAssembly

.data                    # section declaration

msg:
    .ascii    "Hello, world!\n"    # our dear string
    len = . - msg            # length of our dear string

.text                    # section declaration

            # we must export the entry point to the ELF linker or
    .global _start    # loader. They conventionally recognize _start as their
            # entry point. Use ld -e foo to override the default.

_start:

# write our string to stdout

    movl    $len,%edx    # third argument: message length
    movl    $msg,%ecx    # second argument: pointer to message to write
    movl    $1,%ebx        # first argument: file handle (stdout)
    movl    $4,%eax        # system call number (sys_write)
    int    $0x80        # call kernel

# and exit

    movl    $0,%ebx        # first argument: exit code
    movl    $1,%eax        # system call number (sys_exit)
    int    $0x80        # call kernel
  • .data段有一個標號msg,代表字元串"Hello, world!\n"的首地址,相當於C程序的一個全局變數。

  • 注意在C語言中字元串的末尾隱含有一個'\0',而彙編指示.ascii定義的字元串末尾沒有隱含的'\0'。彙編程序中的len代表一個常量,它的值由當前地址減去符號msg所代表的地址得到,換句話說就是字元串"Hello, world!\n"的長度。

  • 現在解釋一下這行代碼中的“.”彙編器總是從前到後把彙編代碼轉換成目標檔案,在這個過程中維護一個地址計數器,當處理到每個段的開頭時把地址計數器置成0,然後每處理一條彙編指示指令就把地址計數器增加相應的位元組數,在彙編程序中用“.”可以取出當前地址計數器的值,該值是一個常量。

  • _start中調了兩個系統調用,第一個是write系統調用,第二個是以前講過的_exit系統調用。

  • 在調write系統調用時,eax寄存器保存著write的系統調用號4ebx、ecx、edx寄存器分別保存著write系統調用需要的三個參數

    • ebx保存著檔案描述符,進程中每個打開的檔案都用一個編號來標識,稱為檔案描述符,檔案描述符1表示標準輸出,對應於C標準I/O庫的stdout

      movl    $1,%ebx        # first argument: file handle (stdout)
      
    • ecx保存著輸出緩衝區的首地址

      movl   $msg,%ecx    # second argument: pointer to message to write
      
    • edx保存著輸出的位元組數

      movl    $len,%edx    # third argument: message length
      
    • write系統調用把從msg開始的len個位元組寫到標準輸出。

$ as -o hello.o hello.s
$ ld -o hello hello.o
$ ./hello
Hello, world!

這段彙編相當於以下C代碼:

#include <unistd.h>

char msg[14] = "Hello, world!\n";
#define len 14

int main(void)
{
    write(1, msg, len);
    _exit(0);
}
  • C代碼中的write函數是系統調用的包裝函數,其內部實現就是把傳進來的三個參數分別賦給ebx、ecx、edx寄存器,然後執行movl $4,%eaxint $0x80兩條指令

  • 這個函數不可能完全用C代碼來寫,因為任何C代碼都不會編譯生成int指令,所以這個函數有可能是完全用彙編寫的,也可能是用C內聯彙編寫的,甚至可能是一個宏定義(省了參數入棧出棧的步驟)

  • _exit函數也是如此,我們講過這些系統調用的包裝函數位於Man Page的第2個Section。