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。


书籍推荐