AT&T 汇编入门 - Hello World

时间:2021-12-25 03:17:41

AT&T 汇编入门 - Hello World


文档的目的是为了能够入门x86_64 GNU Assembly 和 能够看懂 Linux Kernel 的启动代码。涉及的汇编知识未必是深入和最优的,如发现错误或者对实际的汇编程序编写的影响,请评论指出。

平台:Intel x86_64

编译器: gas

汇编组成


汇编程序是由定义好的段组成的,每个段的意义都不一样。最常用的由以下几个段:

  • data 段 - 存放大部分的数据
  • bss 段 - 存放未初始化的数据
  • rodata 段 - 存放只读的数据
  • text 段 - 存放代码

对于最简单一个程序而言,text段是必须的,其他都是可选的。

那么text 段又是由什么组成的呢?对,它是由各种指令组成,而指令又是由操作码,寄存器,立即数和内存地址组成。

操作码

但是操作码都是一堆16进制字符,不太人性化,所以就就产生了助记符来方便程序员来编写汇编代码。

例如:

55 对应 push

0f 05 对应 syscall

寄存器

在让我们看看 x86_64 平台提供了哪些寄存器给我们使用,

  • 16个通用寄存器,例如,rax,rbx,rcx,rdx等
  • 6个16位段寄存器,例如:cs,ds,等

更多的寄存器请参见Intel Technical RM. 这里就不再详述了。

立即数

x86_64 平台规定立即数的最大值不能超过32位。

Hello World


接下来我们来看看我们学习任何一种编程语言会写的第一个程序 - helloworld。

    .section .data
message:
    .ascii "hello world!\n"
    length = . - message

    .section .text
    .global _start      # must be declared for linker
_start:
    movq $1, %rax      # 'write' syscall number
    movq $1, %rdi      # file descriptor, stdout
    lea message(%rip), %rsi # relative addressing string message 
    movq $length, %rdx
    syscall

    movq $60, %rax     # 'exit' syscall number
    xor %rdi, %rdi      # set rdi to zero
    syscall
➜  learning-asm git:(master) ✗ as hello.s -o hello.o
➜  learning-asm git:(master) ✗ ld hello.o -o hello
➜  learning-asm git:(master) ✗ ./hello 
hello world!

其中 .section .data.section .text 定义个数据段 和代码段。

message 只是一个label 方便我们来引用 hello world 字符串。

length = . - message 用来计算字符串的长度。. 用来表示当前的地址

_start 是程序的入口

然后程序调用 write syscall 来讲hello world 字符串输出。首先我们要知道syscall 的号码才能调用对用的方法。其中32位的syscall 号码与64位是不同的,你可以从/usr/include/asm/unistd_64.h Linux的这个文件中查看,如果你要编写32位的汇编程序请查看/usr/include/asm/unistd_32.h

#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_stat 4
#define __NR_fstat 5
#define __NR_lstat 6
#define __NR_poll 7
#define __NR_lseek 8
#define __NR_mmap 9
#define __NR_mprotect 10
#define __NR_munmap 11
#define __NR_brk 12
#define __NR_rt_sigaction 13
#define __NR_rt_sigprocmask 14
#define __NR_rt_sigreturn 15
#define __NR_ioctl 16
#define __NR_pread64 17
#define __NR_pwrite64 18
#define __NR_readv 19
......

这里我们是通过write 直接将字符串写到 stdout 描述符来进行输出。那么我们来看看sys_write 的原型。

long sys_write(unsigned int fd, const char __user *buf, size_t count);

从中我们可以看到需要传入3个参数,第一个fd,这里是stdout,所以就是1;第二个参数就是“hello world\n” 字符串的地址;第三个就是这个字符串的长度。

但是System V ABI 规定了对64位程序的接口,同时也规定了函数参数的传递规则。

AT&T 汇编入门 - Hello World

所以文件描述符1需要加载到rdi寄存器,“hello world\n” 字符串的地址需要加载到 %rsi,字符串的长度加载到 rdx。

lea message(%rip), %rsi # relative addressing string message 这里为什么没有用mov 而是使用lea,这是为了变成地址无关的代码。如果你编写的不是地址无关的代码,那么可以使用mov 来取代。

这样这个helloworld 程序我们就基本讲解完了。如果下次再调用什么别的syscall,我相信应该也是没有问题的。