汇编让人着迷,本来想温习下,结果变成了一个系列。
准备工作
准备通过一台可以上网的ubuntu系统机器(现在默认是64位机器了),其实其他系统也可以,只是ubuntu方便安装工具,例如nams,所以便于学习。
通过命令#apt install nasm就可以完成汇编器的安装。
引子
每天可以写出大量的程序,例如:
#include <stdio.h>
int main() {
int x = 10;
printf("hello world,I get %d!\n", x);
return 0;
}
学过C语言的同学都可以轻松理解这段代码, 但代码在低层是如何工作?并非所有人都能回答这个问题。 我们可以用C,C++,JAVA,Erlang,Go等高级编程语言编写代码,但是编译后不知道它在低级如何工作。
首个64位汇编程序
section .data
msg db "hello, world!"
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, msg
mov rdx, 13
syscall
mov rax, 60
mov rdi, 0
syscall
先定义了数据段,和字符串常量。
然后代码段和入口。
这里有mov指令,将第二个值给第一个,这里的rax,rdi,rsi,rdx,都是处理器的寄存器。
寄存器可以理解成CPU的御用的存储,CPU指令的数据和指令都来自寄存器。当然寄存器的值来自内存,内存又来自磁盘。
代码中,我们来下个寄存在在代码中的作用。
rax是临时寄存器,当调用系统调用,rax保存系统调用号。第1号系统调用即sys_write.
rdx传递第三个参数给函数,表示字符串大小,这里是13个字符。
rdi传递第一个参数给函数,是函数句柄,表示stdout。
rsi传递第二个参数给函数,是字符串所在地址。
我们在内核源码文件fs/read_write.c文件中可以到找如下定义:
size_t sys_write(unsigned int fd, const char * buf, size_t count);
最后传递60给rax寄存,然后传递0给rdi寄存器,第60号的系统调用是exit。
编译
编译命令很简单:
nasm -f elf64 -o hello.o hello.asm
ld -o hello hello.o
通过nasm进行编译,然后通过ld进行连接,执行./hello后可以看到有字符串输出。
这个就是我们的第一个汇编程序代码。
(apt install nasm即可在ubuntu上完成nasm安装)
NAMS介绍:
NASM全称The Netwide Assembler,是一款基于80x86和x86-64平台的汇编语言编译程序,其设计初衷是为了实现编译器程序跨平台和模块化的特性。NASM支持大量的文件格式,包括Linux,*BSD, ELF,COFF等,同时也支持简单的二进制文件生成。语法相较Intel的语法更为简单,支持目前已知的所有x86架构之上的扩展语法,同时也拥有对宏命令的良好支持。
Linux 平台的标准汇编器是 GAS,它是 GCC 所依赖的后台汇编工具,通常包含在 binutils 软件包中。GAS 使用标准的 AT&T 汇编语法,可以用来汇编用 AT&T 格式编写的程序。我们在后面也会一并学习到。
我们这里前面还是使用Intel汇编来回顾下,后面会切到AT&T汇编上。
关于nasm语法
汇编所有语法不能一下子全部描述完毕,这里我们会根据文章逐步提到需要。
在刚才的代码,我们会遇到两个段。
数据段和代码段。
数据库用于声明常数,不会再运行中变化。
定义数据段如下:
section .data
代码段用
section .text, 必须使用global _start开始,表示程序执行的开始位置。