如题.
Intro
操作不同架构的处理器的语言不同, 其统称为 汇编语言 (Assembly Language).Intel
, AMD
等厂家的处理器为x86
架构, Apple
与树莓派等为arm
架构, 此外还有其他类型处理器与相应架构, 于此我们讨论面向x86
处理器的汇编语言.
就算是面向同一架构的汇编语言, 也有不同语法之分, 例如面向x86
架构的汇编语言也有AT&T
和Intel
两种语法:
1 | mov $0x6, %edi ; AT&T |
可以看出, AT&T
语法中源操作数在目标操作数之前, 而Intel
正好相反, 于此我们使用Intel
语法.
Compiler
面向各种架构的任何汇编语言都是非常典型的编译式语言, 需要 编译器 (Compiler) 把 源程式 (Source) 编译为 目标代码 (Object Code), 再连接为可执行文件.
面向x86
架构的汇编器有Microsoft
的masm
, Borland
的tasm
, 而我们今天将要用到的是nasm
(Netwide Assembler)10, 它是一个最初是在 Julian Hall 的协助下由 Simon Tatham 开发的, 目前由 Hans Peter Anvin 领导的小团队所维护的 2-clause BSD License
开源项目11.
在Debian
系的Linux
中可以直接通过apt
进行安装:
1 | $ apt install nasm |
Structure
asm (代指汇编语言
) 的结构分为 段落 (Section) 与其他部分 (这些我们暂且称为过程 (Routine), 因为很多人都这么叫):
Section
.text
: 用于宣告主程式入口的部分, 一般会宣告为_start
:X86 Assembly 1
2.text
_startSection
.bss
: 全称为 Block Starting Symbol, 其包含整个程式周期中变化的变量们, 可认为是宣告变量名的部分, 一般格式为:X86 Assembly 1
2
3.bss
<name> <type> <content>
... ; Other Variables每行宣告一个变量,
<name>
处为变量名,<type>
为类型,<content>
为初始内容, 字节类型的变量可以宣告类型为resb
(Reserve Byte), 于是<name>
处的名称便是表示这个变量起始地址的标识, 可以类比C/C++
中的 指针 (Pointer), 具体写法可以参考如下:X86 Assembly 1
2.bss
inst resb 16Section
.data
: 用于宣告程式中不变的数据, 可以视为常量, 宣告形式与.bss
中的变量类似, 但是对字节数据的宣告类型为db
(Define Bytes):X86 Assembly 1
2
3.data
<name> <type> <content>
... ; Other ConstantsFor Instance:
X86 Assembly 1
2
3
4
5
6.data
txt1 db "Sharing the World", 0xa, 0
txt2 db "We're Sharing an Endless Love", 0xa, 0
txt3 db "Sharing our World", 0xa, 0
txt4 db "Sharing your Sound", 0xa, 0
txt5 db "Connecting our Feelings", 0xa, 0目前看来, 64位的asm的主过程必须以
_start
为标识符, 例如:X86 Assembly 1
2_start:
... ; do something
Operations
在笔者个人看来, asm实质上是某种函数式执行, 即把数据置于特定的寄存器, 处理器内部的逻辑电路以其方式读取这些寄存器中的数据, 执行相应的操作:
1 | (_start) |
mov
指令即move, 但其行为是把源寄存器中的内容向目标寄存器复制, 在这里我们便是设置 rax=60
, rdi=0
.syscall
命令用于根据寄存器中当前存储的值调用相应的系统函数, 在此我们调用的是 sys_exit
, 这是表示在程式结束之时退出程式的功能.
这里我们给出一个列表, 表示不同系统函数调用时应向寄存器中存储的值:
syscall | rax |
… | rdx |
… | rdi |
… | rsi |
---|---|---|---|---|---|---|---|
sys_read |
0 | 读取的最大字节数 | 0 (表示写入) | 表示写入字节的地址的寄存器 | |||
sys_print |
1 | 输出的最大字节数 | 1 (表示输出) | 表示输出字节源地址的寄存器 | |||
… | |||||||
sys_exit |
60 | 可认为是整个程式的返回值, 一般为0, 表示正常结束 |
Sub Routine
子过程, 用于程式分解与复用:
1 | _start: |
这里可以看出来_printext
部分输出了txt1
指向的前18格字节, 并通过call
命令在主程式中进行了调用.
但如若最后不使用ret
返回, 则会向下执行后面的程式块, 这一点与C/C++
中的goto
跳转以及switch-case
结构相似.
Macro
计算机科学里的 宏 (Macro) 是一种抽象, 它根据一系列预定义的规则替换一定的文本模式.
在asm中我们可以认为macro是一种比sub-routine更为泛化更为抽象的代码复用机制, 在x86
的asm中具体形式如下:
1 | %macro <name> <num_parameters> |
譬如我们定义一个接收一个参数的macro, 用于程式结束:
1 | %macro exit 1 |
%1
接收的便是提供的第一个参数, 譬如我们使用
1 | _start: ; or any other Routine |
便等效于以下代码:
1 | _start: ; or any other Routine |
Program
于此展示笔者根据视频教程2编写的一份示例:
1 | .data |
执行以下命令即可编译运行:
1 | $ nasm ./init-01.asm -f elf64 |
(括号里面的内容the World
是用户在终端里面的输入)
References
1. 100秒了解汇编语言 Bilibili: av213755649
↩
2. 一套YouTube
上很受欢迎的汇编语言教程, 在Bilibili
上的搬运: 3 4 5 6 7 8 9 ↩
3. [英语字幕]NASM linux 汇编语言-1 x86_64 Linux Assembly #1 - ‘Hello, World!’ Bilibili: av672911046
↩
4. [英语字幕]NASM linux汇编语言 - 2 x86_64 Linux Assembly #2 - “Hello, World!” Breakdown Bilibili: av757892807
↩
5. [英语字幕]NASM linux汇编语言 -3 x86_64 Linux Assembly #3 - Jumps, Calls, Comparisons Bilibili: av972925372
↩
6. [英语字幕]NASM linux汇编语言 -4 x86_64 Linux Assembly #4 - Getting User Input Bilibili: av587897493
↩
7. [英语字幕]NASM linux汇编语言 -5 x86_64 Linux Assembly #5 - Math Operations and the Stack Bilibili: av845445104
↩
8. [英语字幕]NASM linux汇编语言 -6 x86_64 Linux Assembly #6 - Subroutine to Print Strings Bilibili: av757891011
↩
9. [英语字幕]NASM linux汇编语言 -7 x86_64 Linux Assembly #7 - Macros Bilibili: av930512566
↩
10. NASM ↩
11. 百度百科词条 NASM ↩