Linux系统上的汇编语言可以使用不同的语法风格,主要包括Intel语法和AT&T语法。这两种语法有各自的特点和风格区别,尽管它们表示的底层机器指令相同。下面分别对两种语法进行简要说明:
Intel语法是由Intel公司为其处理器编写官方文档时所采用的语法。它广泛用于Windows操作系统和一些跨平台的程序中。特点是相对直观,操作数的顺序是"操作 目的地, 源"。这意味着第一个操作数是将要被赋值的对象,而第二个操作数是赋值的值。例如:
mov eax, 1 ; 将数值1赋给寄存器eax
AT&T语法由Unix系统V的开发者使用,并且在GNU汇编器(GAS)中被广泛采纳。它在用于x86架构上的Linux系统中非常普遍。与Intel语法相反,它采用的是"操作 源, 目的地"的格式。特点是操作数带有明确的大小标识符,如`%eax`(32位寄存器)和`$1`(立即数)。同样的例子在AT&T语法中为:
movl $1, %eax ; 将数值1赋给寄存器eax
- 操作数顺序:Intel语法以"目标, 源"的顺序,而AT&T语法则相反,采用"源, 目标"。
- 寄存器前缀:AT&T语法使用`%`作为寄存器前缀,而Intel语法不使用。
- 立即数前缀:AT&T语法使用`$`作为立即数前缀,而Intel语法不使用。
- 大小标识符:AT&T语法对操作数的大小使用后缀,如`b`(字节)、`w`(字)、`l`(长字,32位)。
- 地址表示:AT&T语法使用`段寄存器:偏移量(基址寄存器,索引寄存器,比例因子)`的格式,而Intel语法则不同,不使用冒号而是用括号来区分不同的寄存器角色。
- 指令后缀:AT&T语法的指令通常有后缀来标识操作数类型,而Intel语法通常没有指令后缀。
这些差异使得同一个汇编程序在两种语法中看起来非常不同。但无论采用哪种语法,最终产生的机器码是相同的,只是人类编程者的表达方式不同而已。在进行汇编语言编程时,需要根据所使用的工具和个人偏好来选择适合的语法。
在基于Debian的Linux发行版(如Ubuntu)中,可以使用以下命令安装NASM:
sudo apt update
sudo apt install nasm
在基于Red Hat的发行版(如Fedora或CentOS)中,使用:
sudo dnf install nasm
或者(较旧的版本使用yum):
sudo yum install nasm
安装完成后,在终端验证NASM版本确认安装成功:
nasm -v
创建一个名为 hello_world.asm
的文本文件,并将以下汇编代码复制到文件中:
section .data ; 这是数据段
msg db 'Hello, World!', 0xA ; 'Hello, World!' 字符串和一个换行符
len equ $ - msg ; 字符串长度
section .text ; 以下是代码段
global _start ; _start 是程序入口
_start:
; 写入字符串到 stdout
mov eax, 4 ; '4' 是写系统调用的编号
mov ebx, 1 ; '1' 是文件描述符 stdout
mov ecx, msg ; 将消息的地址移到 'ecx'
mov edx, len ; 消息的长度
int 0x80 ; 调用内核
; 退出程序
mov eax, 1 ; '1' 是退出系统调用的编号
mov ebx, 0 ; 返回值 0 ,表示无错误
int 0x80 ; 调用内核
编译刚才写的 hello_world.asm
。在终端中运行:
nasm -f elf32 hello_world.asm -o hello_world.o
这将生成一个名为 hello_world.o
的目标文件。
使用链接器创建可执行程序:
ld -m elf_i386 hello_world.o -o hello_world
此命令会创建一个名为 hello_world
的可执行文件。
运行程序并看到其输出:
./hello_world
应该会在屏幕上看到 Hello, World!
的信息。
如果想要观察程序在运行时的具体行为,可以使用调试器,例如 gdb
。运行以下命令来启动调试器:
gdb ./hello_world
在 gdb
中,可以设置断点,运行程序,逐步执行指令,并且观察寄存器和内存的状态。例如,要运行程序直到其完成,可以在 gdb
提示符下输入 run
命令:
(gdb) run
要退出 gdb
,可以使用 quit
命令。
汇编语言依赖于使用的架构和操作系统。不同的汇编器和链接器可能需要不同的指令和参数。上述示例假设使用基于 Intel 语法的 x86 架构,且在 Linux 系统上。如果在其他平台上工作,需要适当调整这些命令。
GCC允许在C程序中嵌入汇编代码,或者直接编写一个纯汇编文件并使用GCC进行编译和链接。
下面是一个使用AT&T语法的简单汇编程序示例,该程序在Linux系统上打印"Hello, World!"。这个程序是为x86架构编写的,并且假设正在使用32位系统或已经安装了必要的多架构支持。
首先,创建一个名为hello.s
的汇编源文件:
# hello.s
.section .data
hello_string:
.string "Hello, World!\n"
.section .text
.global _start
_start:
# 写入系统调用
movl $4, %eax # 系统调用号 (sys_write)
movl $1, %ebx # 文件描述符 (stdout)
movl $hello_string, %ecx # 字符串地址
movl $14, %edx # 字符串长度(包括换行符)
int $0x80 # 调用内核
# 退出系统调用
movl $1, %eax # 系统调用号 (sys_exit)
xorl %ebx, %ebx # 退出状态码
int $0x80 # 调用内核
然后,使用GCC编译并链接这个程序:
gcc -static -o hello hello.s -nostartfiles -nostdlib
这里的编译选项解释如下:
-static
:生成静态链接的可执行文件,这样就不需要动态链接器来加载运行时库。-nostartfiles
:不链接标准启动文件,这些文件通常包含程序入口点(如_start
),因为我们已经在汇编代码中提供了。-nostdlib
:不链接标准C库,这样GCC就不会自动包含例如libc
这样的库。编译成功后,就可以运行生成的可执行文件了:
./hello
如果一切正常,它应该在终端上打印出"Hello, World!"。
这个程序没有使用C标准库或任何其他的库函数。它直接通过Linux的系统调用来输出字符串和结束程序。此外,这个程序是针对32位系统的;如果正在使用64位系统,需要对代码进行一些修改,包括使用不同的寄存器和系统调用号。在64位系统上,可能还需要使用-m32
选项来告诉GCC生成32位代码(并且确保已经安装了必要的32位开发工具和库)。