寄存器
寄存器是处理器临时保存数据指令的的一部分。在x86_64架构中,寄存器能处理高达64位的数据。这意味着每个寄存器都可以保存该值
没符号整数:0〜18,446,744,073,709,551,616
有符号整数:-9,223,372,036,854,775,808 - 9,223,372,036,854,775,807
这是x86_64架构下的所有寄存器的逻辑图表。
这是我们的寄存器列表,表中有有四列:
- 64位寄存器的列;
- 32位寄存器
- 16位寄存器
- 8位寄存器:
各个寄存器的关系
这里是rax寄存器的图,你可以看到我们的一个64位寄存器叫做rax,还有eax 32位寄存器,那实际上不是它自己的寄存器,它是rax寄存器的一半,那么ax是16位寄存器是eax寄存器的一半。同理al寄存器是ax寄存器的一半。使用al寄存器就好像是一个8位寄存器,但它是 实际上修改了ax寄存器的低8位。以上说的并不需要刻意记忆,因为本文的立足点是让你能够读懂别人或C/C++编译器转译的汇编代码,你不记得的话,可以收藏本文已被你翻查。
系统调用(Syscall)
系统调用是指程序向系统内核请求服务,系统调用因操作系统的不同而不同,因为不同的操作系统使用不同的内核。
- 所有系统调用都有一个与之关联的ID(一个数字)
-
系统调用的输入接受一个参数列表。
这是一个非常重要的表,当你使用系统调用时,它有许多输入是基于存储在寄存器中的值,并且是讲求顺序的,如果你想使用系统调用,你可以将对应系统调用的ID加载到rax寄存器中 并按上表指定的顺序加载参数在你的RAX寄存器中,如果你在这里还没有概念的话,可以跳过,往下看完一个示例,回头看就明白了。
- 第一个参数是rdi寄存器,
- 第二个参数是rsi寄存器,
- 第三个参数是rdx寄存器
- 第四个参数是r10寄存器
- 第五个参数是r8寄存器
- 第六个参数是r9寄存器.
系统调用列表
我们这里只列出常用系统调用的ID,完整的系统调用ID列表,可以在网上都可以找得到。- 正如你所见,文件操作的系统调用是和Unix/Linux交互的常用接口.ID列对应的数字和对应syscall列对应系统调用名称相关联。
- 与系统调用相关的还有参数,我这里用颜色标记了,和'#'相关这表明是直接来自寄存器的一个数字,用'$'符号和绿色标记的也是来自寄存器,但它不是真实的值,而是寄存器里面缓存的内存地址所指向的位置保存的值(如果你有C指针的基础,这就很容易理解了)。
sys_write系统调用示例引入
前文写到Hello World程序将"Hello World"字符串打印到屏幕,已经用到了sys_write系统调用,我们作为示例来讲解一下寄存器和系统调用之间是如何交互的。
- 第一个参数是文件描述符0代表文件输入,1代表文件输出,2代表文件错误。如果你只是将文本写到屏幕,那么就使用标准输出即可
- 第二个文件描述符是缓存,即字符串输出到屏幕之前,首先写入的是高速缓存的位置
- 第三个参数是字符串的长度
看完上面参数表,再回顾一下上面的寄存器ID参数表,看如何使用寄存器ID参数和下表的参数相关联,其实很简单,只需要将那些寄存器的值替换下表的参数列名即可。
所以,正如你所见
- rax寄存器应该处理值1(也即是sys_write系统调用的ID),
- rdi寄存器应该处理我们的文件描述符,
- rsi寄存器应该处理字符串的缓存对象,
- rdx寄存器应该处理字符串的长度.
最后,按照《参数类型表》的描述替换为各列寄存器对应的值,具体分析逻辑如下,这样就生成我们最后关于Hello World程序关于系统调用的相关指令。
汇编语言常用关键字
-
mov:表示move的意思就是移动数据,所以诸如如下语句
- “move rax,1”的意思就是将ID为1的sys_write系统调用移动寄存器rax,那么你应该记住,
以后遇到类似move rax ID这样格式的命令就应该记住这是通过寄存器执行某项系统调用
。 - "move rsi text":的意思就是从text变量指定的内存位置加载字符串到rsi寄存器
- “move rax,1”的意思就是将ID为1的sys_write系统调用移动寄存器rax,那么你应该记住,
syscall:每调用move指令后以syscall结尾,表示执行一次系统调用,也就是说你执行一个不同的系统调用都需要以syscall关键字结束。
从上面的两点理解,应该不难看出"mov rax 60"这条指令是关于系统调用ID等于60(从网上查到是sys_exit),关于告知操作系统,你的程序已经执行完毕,
同理,move rdi 0是执行与描述符有关的操作,即告知程序退出的状态 ,事实上这个状态码你可以任意定义的,按照程序设计的一般约定,0代表程序执行正常退出,其他为异常状态
。-
section:所有x86_64的汇编文件有三种section
- .data:该section下定义的所有变量,在汇编之前,编译器会完成所有相关变量指向对应内存位置的值的替换。你可以理解为C/C++的变量相关的操作。
- .bss 该section,为某些功能的使用而分配内存,和.data section有些类似
- .text 该section,主要定义程序运行的逻辑和各种的系统调用。
-
label:标签用于标记代码的一部分。在编译时,编译将计算标签在内存中的位置。 每次使用标签的名称后,该名称将被编译器替换为内存中的位置。如果你看看helloword定义text的位置,它基本上是相同的,除了我们可以在位置插入这些标签。在我们的代码中将会看到为什么它在将来会有用武之地。
- _start:标签对所有程序都至关重要。当您编写程序并稍后执行时,它首先在“_start”的位置执行。如果链接器找不到“_start”标签,则会抛出错误。
- global:当您希望链接器能够知道某个标签的地址时,使用global关键字。 生成的目标文件将包含指向“global”的每个标签的链接。在helloworld这个示例中,我们必须将“_start”声明为全局,因为代码必须正确链接。