1.什么是汇编语言
了解汇编语言需要先从了解机器语言
开始,在计算机发展的初期阶段,机器语言是计算机直接理解和执行的二进制代码语言,其核心特点包括直接执行性、资源高效性、学习难度大以及平台依赖性。它主要由指令码构成,这些指令码包括操作码和操作数,用于指示计算机执行的具体操作。当时的程序员会将二进制指令编码打孔在纸带上,然后将这些纸带送入计算机进行读取和执行。
下图就是一段纸带程序
这种方式非常繁琐且容易出错,因为程序员需要记住每个操作的二进制代码,为了简化编程过程,汇编语言应运而生。为了简化编程过程,研究人员开始使用助记符(mnemonics)
来代表机器指令。这些助记符通常是简短的英文缩写,易于记忆和理解。随着助记符的使用,需要一种工具将这些助记符转换为机器语言。这种工具被称为汇编器(assembler)
。汇编器可以将用助记符编写的程序(汇编语言)转换为机器语言,从而使得编程更加高效和便捷。
早期的汇编语言起源于20世纪50年代,由IBM工程师为IBM 704计算机开发,与特定计算机架构紧密相关。汇编程序的雏型在EDSAC
上诞生,采用单字母指令码、十进制地址和终结字母组成。首个汇编程序是50年代中期的符号优化汇编程序(SOAP)
,专为IBM650计算机设计,旨在优化磁鼓中指令的分布以提高效率。随后,IBM704计算机的符号汇编程序(SAP)
成为汇编程序发展的重要里程碑,其模型至今影响深远。
随着计算机技术的发展,汇编语言逐渐标准化,并出现了多种不同的汇编语言,以适应不同的计算机架构。例如,x86架构有其特定的汇编语言,而ARM架构也有其自己的汇编语言。现代汇编语言常用于需要直接操作硬件或对性能要求极高的场合,如操作系统内核开发、嵌入式系统编程等。
这里我们使用NASM(Netwide Assembler
),这是一款基于8086
和x86-64
平台的汇编语言编译程序,它支持x86与x64架构的CPU(但不支持ARM架构),并可以在Linux、Mac OS X、Windows等操作系统上运行。NASM的语法被设计得简单易懂,并且支持大量的文件格式,如COFF、ELF、Mach-O等。
2.安装NASM
Windows系统安装NASM汇编语言
大家可以在NASM汇编语言代码官网查看安装方法和文档NASM官网
Windows系统可以直接下载.exe安装包Windows_64安装包下载
安装完成后将nasm.exe文件的安装路径添加到path环境中即可,安装完成后在cmd终端输入nasm -v
查验下是否安装成功
Linux系统安装NASM汇编语言
在Linux中安装NASM更为简单,只需要一行命令:
sudo apt install nasm nasm -v
3.编写第一个汇编语言代码
按照惯例,第一个程序依旧是输出"hello world"。
; hello.asm
section .datahello db 'Hello GGBond!', 0x0asection .textglobal _start_start:mov eax, 4 mov ebx, 1 mov ecx, hello mov edx, 14 int 0x80 ; exit(0)mov eax, 1 xor ebx, ebx int 0x80
代码结构如下:
-
数据段:
hello
:这是一个字节数组,存储了字符串'Hello GGBond!'
和一个换行符(ASCII码为0x0a)。
-
代码段:
global _start
:指示链接器(linker)程序的入口点是_start
标签。_start
标签下的指令是程序开始执行的地方。- 首先,通过一系列
mov
指令将系统调用sys_write
的参数准备好:eax
:系统调用号(4表示sys_write
)。ebx
:文件描述符(1表示标准输出)。ecx
:指向要写入数据的指针(hello
数组的地址)。edx
:要写入的数据长度(14个字节,包括字符串和换行符)。
- 使用
int 0x80
触发系统调用,执行写入操作。 - 接着,准备
sys_exit
系统调用的参数:eax
:系统调用号(1表示sys_exit
)。ebx
:退出状态(使用xor ebx, ebx
清零,表示正常退出)。
- 最后,再次使用
int 0x80
触发系统调用,终止程序。
接下来编译运行这段代码:
先将hello.asm汇编源代码文件编译成名为hello.o的ELF格式目标文件
nasm -f elf -o hello.o hello.asm
最后使用LD链接器来将目标文件链接成最终的可执行文件
ld -m elf_i386 -o hello hello.o
运行可执行文件后即可在终端输出"hello world"了
./hello
4.在主程序中引用子程序
在汇编语言中,实现一个主文件调用多个子文件的方法通常是通过使用包含指令(如 .include
或 %include
)来引入其他文件中的代码,这些文件可以包含函数、宏、常量定义等。
这里先新建三个.asm文件
main.asm
:主文件。sub1.asm
:子文件1。sub2.asm
:子文件2。
; 主文件 main.asm
section .datasection .bsssection .textglobal _start_start:call sub1_functioncall sub2_functionmov eax, 1 xor ebx, ebx int 0x80 %include "sub1.asm"
%include "sub2.asm"
; 子文件1 sub1.asm
section .text
sub1_function:mov eax, 4 mov ebx, 1 ; 文件描述符mov ecx, message1 ; 消息地址mov edx, len1 ; 消息长度int 0x80 ; 中断调用retsection .data
message1 db 'Hello from sub1!', 0xA
len1 equ $ - message1
; 子文件2 sub2.asm
section .text
sub2_function:mov eax, 4 mov ebx, 1 mov ecx, message2 mov edx, len2 int 0x80 retsection .data
message2 db 'Hello from sub2!', 0xA
len2 equ $ - message2
文件编写完成后,使用 nasm 汇编器将每个 .asm 文件编译成目标文件(.o 文件),再使用 ld 链接器将所有目标文件链接成一个可执行文件 main。
nasm -f elf32 main.asm -o main.o
nasm -f elf32 sub1.asm -o sub1.o
nasm -f elf32 sub2.asm -o sub2.o
ld -m elf_i386 -o main main.o sub1.o sub2.o
./main
5.NASM汇编常用指令表
NASM汇编只要指令分为:数据传输指令、算术指令、逻辑指令、控制转移指令、字符串指令与其他指令
指令 | 含义 | 应用方法 |
---|---|---|
mov | 移动数据到寄存器或者内存 | mov destination, source :将source 的内容复制到destination 中,操作字长由参数字长决定。例如,mov eax, ebx 将ebx 的值移动到eax 寄存器中。 |
lea | 加载有效地址 | lea des, src :将src 的有效地址加载到des 中。例如,lea eax, [ebx+8] 将ebx+8 的内存地址加载到eax 寄存器中。 |
inc | 操作数增加1 | inc destination :将destination 的值增加1。例如,inc eax 将eax 的值增加1。 |
dec | 操作数减1 | dec destination :将destination 的值减少1。例如,dec ebx 将ebx 的值减少1。 |
add | 加法 | add destination, source :将source 的值加到destination 上。例如,add eax, ebx 将ebx 的值加到eax 上。 |
sub | 减法 | sub destination, source :从destination 中减去source 的值。例如,sub eax, ebx 从eax 中减去ebx 的值。 |
adc | 带进位加法 | adc destination, source :将source 的值加到destination 上,并加上进位标志位。例如,adc eax, ebx 将ebx 的值加到eax 上,并加上之前的进位。 |
sbb | 带进位减法 | sbb destination, source :从destination 中减去source 的值,并减去进位标志位。例如,sbb eax, ebx 从eax 中减去ebx 的值,并减去之前的进位。 |
mul | 无符号乘法 | mul multiplier :将AL 或AX 中的值与multiplier 相乘,结果存储在AX 或DX:AX 中。例如,mul ebx 将AL 或AX 中的值与ebx 相乘。 |
imul | 有符号乘法 | imul multiplier :与mul 类似,但处理有符号数。例如,imul ebx 将AL 或AX 中的值与ebx 相乘。 |
div | 无符号除法 | div divisor :将AX 或DX:AX 中的值除以divisor ,商存储在AL 或AX 中,余数存储在AH 或DX 中。例如,div ebx 将AX 或DX:AX 中的值除以ebx 。 |
idiv | 有符号除法 | idiv divisor :与div 类似,但处理有符号数。例如,idiv ebx 将AX 或DX:AX 中的值除以ebx 。 |
cmp | 比较两个数 | cmp destination, source :比较destination 和source 的值,并设置标志寄存器。例如,cmp eax, ebx 比较eax 和ebx 的值。 |
jmp | 无条件跳转 | jmp label :无条件跳转到label 指定的地址。例如,jmp start 跳转到标签start 处。 |
je/jz | 跳转,如果相等/零 | je/jz label :如果零标志位(ZF)被设置,则跳转到label 。例如,je equal_label 在相等时跳转。 |
jne/jnz | 跳转,如果不相等/非零 | jne/jnz label :如果零标志位(ZF)未被设置,则跳转到label 。例如,jne not_equal_label 在不相等时跳转。 |
jg/jnle | 跳转,如果大于/不小于等于 | jg/jnle label :如果有符号数大于且溢出标志位(OF)未设置或符号标志位(SF)与零标志位(ZF)不同,则跳转到label 。例如,jg greater_label 在大于时跳转。 |
jge/jnl | 跳转,如果大于等于/不小于 | jge/jnl label :如果有符号数大于等于或溢出标志位(OF)与符号标志位(SF)相同,则跳转到label 。例如,jge greater_equal_label 在大于等于时跳转。 |
jl/jnge | 跳转,如果小于/不大于等于 | jl/jnge label :如果有符号数小于且溢出标志位(OF)被设置或符号标志位(SF)与零标志位(ZF)相同,则跳转到label 。例如,jl less_label 在小于时跳转。 |
jle/jng | 跳转,如果小于等于/不大于 | jle/jng label :如果有符号数小于等于或溢出标志位(OF)与符号标志位(SF)不同,则跳转到label 。例如,jle less_equal_label 在小于等于时跳转。 |
ja/jnbe | 跳转,如果无符号数大于/不小于等于 | ja/jnbe label :如果无符号数大于且没有借位(CF=0)且零标志位(ZF)未被设置,则跳转到label 。例如,ja above_label 在无符号数大于时跳转。 |
jae/jnb | 跳转,如果无符号数大于等于/不小于 | jae/jnb label :如果无符号数大于等于或没有借位(CF=0),则跳转到label 。例如,jae above_equal_label 在无符号数大于等于时跳转。 |
jb/jnae | 跳转,如果无符号数小于/不大于等于 | jb/jnae label :如果无符号数小于且有借位(CF=1),则跳转到label 。例如,jb below_label 在无符号数小于时跳转。 |
jbe/jna | 跳转,如果无符号数小于等于/不大于 | jbe/jna label :如果无符号数小于等于或有借位(CF=1)且零标志位(ZF)被设置,则跳转到label 。例如,jbe below_equal_label 在无符号数小于等于时跳转。 |
loop | 循环指令 | loop label :循环执行指令,每次循环ecx 寄存器减1,当ecx 为0时跳出循环。例如,loop start 循环执行到ecx 为0。 |
and | 逻辑与 | and operand1, operand2 :对operand1 和operand2 执行逻辑与操作,结果存储在operand1 中。例如,and eax, ebx 将eax 和ebx 进行逻辑与操作。 |
or | 逻辑或 | or operand1, operand2 :对operand1 和operand2 执行逻辑或操作,结果存储在operand1 中。例如,or eax, ebx 将eax 和ebx 进行逻辑或操作。 |
xor | 逻辑异或 | xor operand1, operand2 :对operand1 和operand2 执行逻辑异或操作,结果存储在operand1 中。例如,xor eax, ebx 将eax 和ebx 进行逻辑异或操作。 |
test | 测试(逻辑与,不修改操作数) | test operand1, operand2 :对operand1 和operand2 执行逻辑与操作,但结果不存储,只设置标志寄存器。例如,test eax, ebx 测试eax 和ebx 的逻辑与结果。 |
not | 逻辑非 | not operand :对operand 执行逻辑非操作。例如,not eax 将eax 的值取反。 |
shl | 左移 | shl operand, count :将operand 的值左移count 位。例如,shl eax, 1 将eax 的值左移1位。 |
shr | 右移 | shr operand, count :将operand 的值右移count 位。例如,shr eax, 1 将eax 的值右移1位。 |
sal | 算术左移(与shl 相同) | sal operand, count :将operand 的值左移count 位,并符号扩展。例如,sal eax, 1 将eax 的值左移1位。 |
sar | 算术右移 | sar operand, count :将operand 的值右移count 位,并符号扩展。例如,sar eax, 1 将eax 的值右 |