参考:
- <<深入理解程序设计使用Linux汇编语言>>
- Linux Assembly Tutorial Step-by-Step Guide
- Assembly Programming Tutorial for nasm with intel format
os: linux x86_64
参考:
x86有两个流行的汇编方言版本:Intel/Microsoft和AT&T/Linux.
AT&T语法是一种相当老的语法,由GAS和一些老式汇编器使用;NASM使用Intel语法,大多数汇编器都支持intel语法, 比如TASM和MASM. 但GAS的现代版本(from v2.10+)已支持.intel_syntax
指令,即允许在GAS 中使用Intel语法.
我使用英特尔语法的部分原因还有它将目的地放在左边,而源操作数放在右边,与RISC-V,ARM-32和MIPS-32的操作数顺序一致.
使用intel语法的相关改变:
- gas : 开头加
.intel_syntax noprefix ;不需要寄存器的前缀%
- gcc :
gcc -masm=intel test.c -o test
//-masm = intel
用于编译包含Intel语法内联汇编的源文件 - objdump :
objdump -M intel -d program_name
, // 我通常使用更详细的objdump -M x86-64,intel,intel-mnemonic,intel64 -d program_name
- 内联汇编: 参考这里
具体
AT&T汇编和Intel汇编差异
见这里.
参考:
- 用户模式的系统调用依次传递的寄存器为: rdi,rsi,rdx,rcx,r8和r9
- 内核接口的系统调用一次传递的寄存器为: rdi,rsi,rdx,r10,r8和r9. 注意这里和用户模式的系统调用只有第4个寄存器不同,其他都相同
- 系统调用通过syscall指令进入,不像32位下的汇编使用的是int 0x80指令;
- 系统调用号放在rax寄存器里
- 系统调用限制最多6个参数
- 系统调用的返回结果,也就是syscall指令的返回放在rax寄存器中
- 只有整形值和内存型的值可以传递给内核
参考:
-
*Linux 系统调用权威指南(2016) 翻译自The Definitive Guide to Linux System Calls
-
系统调用号(syscall)不同
参考:
- syscalls on x86 @
/arch/x86/syscalls/syscall_32.tbl
- syscalls on x86_64或Linux System Call Table for x86 64带寄存器使用说明 @
arch/x86/syscalls/syscall_64.tbl
比如:
- x84 sys_init(0x01) : eax = 0x01, ebx = int error_code
- x86_64 sys_init(0x3c) : rax = 0x3c, rdi = int error_code
手写汇编代码来发起系统调用并不是一个好主意. 其中一个重要原因是,glibc 中有 一些额外代码在系统调用之前或之后执行(而自己写的汇编代码没有做这些类似的工作). 同时内核的 ABI 可能会有不兼容更新. 而内核和 libc 实现通常(可能)会为每个系统自动选择最快的系统调用方式, 以避免出问题.
比如使用 exit 系统调用: 事实上可以用 atexit函数向 exit 注册 回调函数,在它退出的时候就会执行. 这些函数是从 glibc 里调用的,而不是内核. 因此,如果自己写的汇编代码调用 exit,那注册的回调函数就不会被执行,因为这种方 式绕过了 glibc.
然而,徒手写汇编来调系统调用是一次很好的学习方式.
- syscalls on x86 @
-
调用方法不同
在32位下用int 0x80中断(是软中断, 对应中断函数是ia32_syscall@
arch/x86/ia32/ia32entry.S
)进行系统调用,而64位下需要用syscall指令进行系统调用64bit的syscall是为了加速系统调用所引入的新指令,通过引入新的 MSR 来存放内核态的代码和栈的段号和偏移量,从而实现快速跳转.
在x86_64上使用
int 0x80
会导致发生segfault(原因: 变量地址是64bit, 用0x80号中断调用时仅用到32bit地址, 高32bit丢失, 导致内存访问时地址越界).syscall
可参考这里.调用error说明(保存在rax中):
- /usr/include/asm-generic/errno-base.h
- /usr/include/asm-generic/errno.h
-
传参方式不同(即ABI)
32位程序,我们将系统调用号传入eax,调用参数压栈传递,系统调用返回值写入eax寄存器; 而64位程序,系统调用号传入rax,而各个参数按照rdi,rsi,rdx,rcx, r8, r9的顺序写入寄存器,系统调用返回值写入rax.
见example的
cpuid*.s
的例子.
<<深入理解程序设计使用Linux汇编语言>>
: 不推荐, 内容是32-bit汇编<<x86/x64体系探索及编程>>
, 推荐