section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov edx, len ;message length
mov ecx, msg ;message to write
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
这是一个基本的 32 位 x86 Linux 汇编代码,用于在屏幕上打印"Hello, World!"(标准输出(。 构建 + 运行它
nasm -felf -g -Fdwarf hello.asm
gcc -g -m32 -nostdlib -static -o hello hello.o
./hello
(编者注:或gdb ./hello
调试/单步执行。 这就是为什么我们使用nasm -g -Fdwarf
和gcc -g
. 或者使用 GDB 内部的layout reg
进行不依赖于调试符号的反汇编+寄存器视图。 见 https://stackoverflow.com/tags/x86/info 底部(
现在我想问一下这段代码是如何在幕后工作的。就像所有这些说明的需要是什么
_start: ;tell linker entry point
mov edx, len ;message length
mov ecx, msg ;message to write
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
只是为了打印"你好,世界!"和声明
_start:
以上!是主要功能吗?
和声明
int 0x80
为什么要使用它?你们能给我一个关于这个程序的基本工作的深入解释吗?
在机器代码中,没有函数。至少,处理器对功能一无所知。程序员可以随心所欲地构建他的代码。_start
是一种称为符号的东西,它只是程序中某个位置的名称。符号用于指代您还不知道其地址的位置。它们在链接期间解析。符号_start
用作入口点(参见此答案(,这是操作系统跳转以启动程序的位置。除非通过其他方式指定入口点,否则每个程序都必须包含_start
。程序使用的其他符号是msg
,它由链接器解析为字符串Hello, world!
所在的地址,len
msg
的长度。
程序的其余部分执行以下操作:
- 设置系统调用
write(1, msg, len)
的寄存器。write
具有存储在eax
中的系统调用编号 4,以使操作系统知道您想要系统调用 4。此系统调用将数据写入文件。提供的文件描述符编号为 1,代表标准输出。 - 使用
int $0x80
执行系统调用。此指令会中断您的程序,操作系统会拾取它并执行其编号存储在eax
中的功能。 这就像调用操作系统内核的函数调用。 调用约定与其他函数不同,参数在寄存器中传递。 - 设置系统调用
_exit(?)
的寄存器。它的系统呼叫号码是 1,进入eax
.可悲的是,代码忘记为_exit
设置参数,它应该为 0 表示成功。取而代之的是,使用之前ebx
中的任何内容,这似乎是 1。 - 使用
int $0x80
执行系统调用。由于_exit
结束程序,因此它不会返回。您的程序到此结束。
指令db
告诉汇编程序将以下数据放入我们当前所在的程序中。这会将字符串Hello, world!
后跟换行符放入程序中,以便我们可以告诉write
系统调用写入该字符串。
len equ $ - msg
告诉汇编程序的行比len
是$
(我们目前所在的位置(和msg
之间的区别。这是这样定义的,因此我们可以向write
传递我们要打印的文本有多长。
程序中分号(;
(之后的所有内容都是汇编程序忽略的注释。