使用汇编打印“Hello, world!“
- 实现打印"Hello, world!"的汇编代码
- 代码详细剖析
实现打印"Hello, world!"的汇编代码
我们来直接贴代码
section .text
global _start
_start:
mov edx,len
mov ecx,msg
mov ebx,1
mov eax,4
int 0x80
mov eax,1
int 0x80
section .data
msg db 'Hello, world!', 0xa
len equ $ - msg
编译及执行如下:
代码详细剖析
每一行代码的详细解释如下:
section .text
指定了一个.text段,该段用于存放程序的代码。
global _start
定义了全局符号 _start
,它是程序的入口点,告诉链接器(ld)需要将该程序的起始点作为可执行文件的入口地址。
_start:
标记了程序的入口点,告诉链接器这里是程序的起始位置。
mov edx,len
将要输出的字符串的长度 len 存储在寄存器 edx 中。
mov ecx,msg
将要输出的字符串的内存地址 msg 存储在寄存器 ecx 中。
mov ebx,1
将要使用的文件描述符 stdout 的编号 1 存储在寄存器 ebx 中。
mov eax,4
将系统调用号 sys_write 的编号 4 存储在寄存器 eax 中。
write 系统调用的参数分别存储在 edx、ecx 和 ebx 寄存器中,具体来说:
- 将字符串长度 len 存储在 edx 中。
- 将字符串地址 msg 存储在 ecx 中。
- 将文件描述符 1(即标准输出)存储在 ebx 中。
int 0x80
int 0x80用于在 Linux 和其他基于 x86 架构的操作系统中调用内核功能。它可以将 CPU 控制权从用户空间切换到内核空间,以执行一些特权级别较高的操作,例如读写文件、网络通信、进程管理等。
具体来说,当进程需要执行系统调用时,通常会将相应的系统调用号存储在 EAX 寄存器中,然后使用 int 0x80 指令触发中断,使 CPU 转到内核态。在内核态下,内核会根据 EAX 中存储的系统调用号执行相应的操作,并将结果返回给用户空间。
在早期的 Linux 内核版本中,int 0x80 指令是唯一的系统调用方式。随着时间的推移,Linux 内核引入了更加高效的系统调用方式,如使用 syscall 指令,但仍保留了 int 0x80 方式,以便向后兼容旧版应用程序。
总之,int 0x80 是一种在 x86 架构上实现的系统调用方式,可以使进程在用户态和内核态之间切换,以执行各种系统级别的操作。
mov eax,1
将系统调用号 sys_exit 的编号 1 存储在寄存器 eax 中。
int 0x80
再次调用中断 0x80,退出程序。
section .data
指定了一个.data段,该段用于存放程序的数据。
msg db 'Hello, world!', 0xa
定义了一个以 0xa(即 ‘\n’)结尾的字符串 “Hello, world!”,并将其存储在名为 msg 的标签中。
具体来说,db 是一个伪指令,用于告诉汇编器将后面的数据按字节存储到内存中。在这里,msg 被定义为一个字符数组,包含文本字符串 “Hello, world!” 和一个换行符(\n)。最后的 0xa 表示字符串的结束,即 NULL 终止符。整个 msg 数组所占用的字节数是 14。
常见的汇编器使用 syntax 语句来控制汇编语言的风格和表示方法。在 AT&T 语法中,字符串通常用单引号括起来,且字符串末尾不需要显式添加 NULL 终止符。而在 Intel 语法中,字符串通常用双引号括起,并且必须显式添加 NULL 终止符。
len equ $ - msg
用于计算字符串 msg 的长度。
$ 符号表示当前位置(即当前指令的地址),因此,$ - msg 表示当前位置减去 msg 的地址,得到的结果就是字符串的长度。
equ 是一个伪指令,.用于定义一个符号常量。符号常量类似于 C 语言中的 #define 常量,可以用来代替数字或字符串等常量。在这里,len 被定义为一个常量,它的值等于 $ - msg,也就是字符串的长度。