x86汇编程序设计
环境搭建:
标准的开发环境的搭建并不是容易的事情,遇到和处理器平台相关的问题的时候更是无解,比如我现在要给予x86的cpu做arm的开发,或者基于arm的cpu开发x86的开发,这是件很头疼的事情,比较好的是我们可以使用docker来生成标准的开发环境,这样就可以屏蔽本机硬件的耦合。
- Dockerfile 构建环境:
1 | FROM randomdude/gcc-cross-x86_64-elf |
上述就是构建x86汇编语言标准的镜像,这里我们是基于ubuntu的,构建命令行如下:
1 | docker build buildenv -t myos-buildenv |
在vscode的环境下启动如下命令:
1 | docker run --rm -it -v "$(pwd)":/root/env myos-buildenv |
至此,我们就完成了本地环境和容器环境的共享,其中的-v就是将本地的目录和容器的环境共享,最后完成环境搭建,我们可以通过helloworld代码进行测试:

基本语法
操作系统切换线程或者进程,是通过中断来实现的,中断可以分为两种:软中断、硬中断,所谓软终端是我们在程序的过程中可能发生了异常、程序退出,所谓硬中断是cpu在一个时钟周期切换的时候发现终端标记位上有程序要切换,从而通过系统调用触发程序的切换。
操作系统内核提供了系统调用,我们可以通过寄存器来进行参数的传递,且传递参数的数量是有上限的:

这里我们通过系统调用传递参数的时候,id对应了syscall的唯一的标识,或者可以将其认为是函数的唯一标识,其他的寄存器则是传递的参数。

以上是系统调用的功能表,详见:https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86_64-64_bit
,其中的参数类型为*的代表了指针类型,其指向内存中的地址。
段
汇编语言通常通过section来定义段,我们常见的有
.data(数据段):用来存放已经初始化的变量的区域
.text(代码段):用来存放程序执行代码的区域
.bss(用来存放未初始化的全局变量)
global:当模块中有需要被连接的模块的时候可以使用global定义
qeu 用来定义常数
目前暂时用到这几种,后面再补充
数据类型
针对不同的段,我们需要使用不同类型的数据,对于data段,我们常见的数据类型为
- db:defined bytes,代表了一个8位 1字节 的数据
- dw:defined words,在汇编中通常16位对应了一个word,即是一个字
- dd:defined double words,同上代表了32位
- dq:代表了64位
以上数据类型对应了已经初始化的变量
对于.bss段,我们通常的数据类型为:resb resw resd resq和data段类型是对应的,不过数据并未初始化。以上关键字需要带一个操作数,比如:resb 1代表了保留一个字节的未初始化的空间
寄存器
x86架构中存在如下寄存器,这里我们可以看到寄存器是可以复用的,也就是8位的寄存器可以是64位寄存器的低8位,同理其他情况也是:

前面我们看到了寄存器,并且看到了寄存器是可以进行拆分的,这里我们继续看一些特殊的寄存器,或者叫做标记位会更合适:

指针也是一种特殊类型的寄存器,其用来指向一块内存空间,在其指向的内存空间中保存着数据:
上面是比较常用的几种类型的指针,其中rip就是我们常说的程序计数器pc,rsp中的s代表了stack、rbp中的b代表了bottom,也即是stack的栈底
输入输出
如本小结开始所说,函数的调用是通过syscall来触发的,输出:
1 | mov rax,1 |
以上就是输出数据到屏幕上的操作,接下来看一下从命令行读入参数的代码:
1 | mov rax, 0 |
上面的代码可以看到,输入和输出代码非常类似,不过这里我们读取数据的时候数据的内容和长度都不确定,因此我们需要时bss段来申请一块存储空间,申请的时候会指定长度,这里的length就是我们申请的时候指定的长度
控制流
常见的控制程序的方式在高等语言中有if、while、for等,这些对应到汇编中也是存在相应的影子的,if对应到汇编可以使用cmp & jne、jge等来实现,循环也可以通过jmp或者jmp的衍生来指令来进行控制
宏
宏是一种特殊的代码,其在编译的时候会展开为指定代码,其定义如上图所示,%macro、%endmacro用于标记宏的开始和结束的区间,后面的名称是我们在其他的代码片段可以引用的标识,或者将其理解为函数名称也可以,最后的argc则指代了该宏的参数的个数,我们获取参数的时候通过%num的方式来指定获取第几个参数,并且参数传递给宏的时候,使用,进行分割,我们下面通过一个简单的示例来说明宏的定义及使用:
1 | section .data |
上面我们看到了宏的定义及使用,宏的定义必须在使用之前,否则会出现符号不识别的问题,另外在宏中定义label的时候尽量使用%%开头,原因是当多次调用宏的时候,由于宏最终会被中间的代码给替代,这样就出现了多个同样的标记,因此就会出现模棱两可的问题,如下:

上面的代码还存在一个问题,就是数字的打印是有问题的,因此尝试使用汇编来完成数字的打印
读写文件
接收参数
采用堆栈来接收参数
小结
参考:https://github.com/davidcallanan/os-series https://www.youtube.com/watch?v=XuUD0WQ9kaE&list=PLetF-YjXm-sCH6FrTz4AQhfH6INDQvQSn&index=1