x86汇编入门

Intel® 64 and IA-32 Architectures Software Developer’s Manual 文档官方地址:
https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf

很好的入门文档:https://www.ibm.com/developerworks/cn/linux/l-assembly/

汇编语言由三部分组成:

  • 汇编指令:跟机器指令一一对应,实际就算一个个的助记符。有 AT&T 和 INTEL 两种语法
  • 伪指令:给编译器看,用于指示编译器该如何做。不同编译器的语法不同,有 NASM、GNU as 和 MASM 三种常用编译器
  • 运算符:±*/ 之类的符号

汇编指令

具体可以参考:https://blog.csdn.net/kennyrose/article/details/7575952

AT&T 和 INTEL 两种语法的主要差异有:

- AT&T INTEL
操作数方向 从左向右 从右向左
立即数表示方式 $0x01 30h
寄存器表示方式 %eax eax
助记符指定操作数长度 b8位,w16位,l32位, movl $lb, %eax mov eax, dw ptr lb
长跳转和调用 ljmp $sect, $off jmp sect:off
内存单元 圆括号 mov 5(%ebx), %eax 方括号 mov eax, [ebx + 5]
间接寻址方式 %segreg:disp(base,index,scale) segreg:[base+index*scale+disp]
注释 单行用 #// 多行用 /* ... */ 只有;

AT&T 语法比较难受,但是 UNIX 类系统的源码都在用,所以还是要学习。

x86 和 x86-64 平台

这两个平台,一个是 32 位,一个是 64 位,其系统调用编号完全不同。

汇编编译器

  • NASM:INTEL 语法,跨 Linux 和 Windows 平台。
  • GNU as:默认 ATT 语法,可以通过 .intel_syntax 伪指令声明使用 INTEL 语法。
  • MASM:Windows 平台专用,不讨论。

NASM

NASM 编译器中,所有伪指令就是一般单词,没有用句点开头。

常用选项:

  • -f 选项:指定目标代码的平台,可以通过 nasm -hf 查看所有支持的。linux 常用的是 ELF32 和 ELF64
  • -o 选项:指定输出文件名字

GNU as

Unix 系列的操作系统基本默认用这个。伪指令全部用句点开头。可以直接通过 --32/--64/--x32 generate 32bit/64bit/x32 code 选项生成指定位数的代码。

Hello world 示例

NASM 编译器 + INTEL 语法

源码:

section .text				; 声明代码段

global _start			; 声明程序入口
_start:
        mov eax, 4					; 参数一:系统调用号,4表示写
        mov ebx, 1					; 参数二:写的目的地,1表示 stdout
        mov ecx, msg				; 参数三:要写的字符串首地址
        mov edx, len				; 参数四:字符串长度
        int 80h							; 开始系统调用

        mov eax, 1
        mov ebx, 0
        int 80h

section .data									; 声明数据段
        msg db "hello world!", 0xA
        len equ $ - msg

编译链接执行,链接器 LD 用 -m elf_i386 指明链接的是 32 位的目标文件:

nasm hello.asm -o hello.o -f elf
ld hello.o -o hello -m elf_i386

./hello

GNU as 编译器 + AT&T 语法

.data                    # 数据段声明
        msg : .string "Hello, world!\\n" # 要输出的字符串
        len = . - msg                   # 字串长度
.text                    # 代码段声明
.global _start           # 指定入口函数
         
_start:                  # 在屏幕上显示一个字符串
        movl $len, %edx  # 参数三:字符串长度
        movl $msg, %ecx  # 参数二:要显示的字符串
        movl $1, %ebx    # 参数一:文件描述符(stdout) 
        movl $4, %eax    # 系统调用号(sys_write) 
        int  $0x80       # 调用内核功能
         
                         # 退出程序
        movl $0,%ebx     # 参数一:退出代码
        movl $1,%eax     # 系统调用号(sys_exit) 
        int  $0x80       # 调用内核功能

编译执行:

kika@kika-VirtualBox:~/test/gas$ as att.s -o att.o
kika@kika-VirtualBox:~/test/gas$ ld att.o -o att
kika@kika-VirtualBox:~/test/gas$ ./att
Hello, world!\n

GNU as 编译器 + INTEL 语法

这个太折腾了,建议不用

  • .intel_syntax:使用 INTEL 语法,注意寄存器前面仍然需要 %
  • .intel_syntax noprefix:使用 INTEL 语法,寄存器前面不需要 %
上一篇:C语言编译过程:预编译、编译、汇编、链接


下一篇:OpenEuler中C语言中的函数调用测试(选做)