.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
的系統調用號4
,ebx、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,%eax
和int $0x80
兩條指令
這個函數不可能完全用C代碼來寫,因為任何C代碼都不會編譯生成int
指令,所以這個函數有可能是完全用彙編寫的,也可能是用C內聯彙編寫的,甚至可能是一個宏定義(省了參數入棧出棧的步驟)
_exit
函數也是如此,我們講過這些系統調用的包裝函數位於Man Page的第2個Section。