零.阅读目的
C++开发的游戏服务器避免不了偶尔出现的宕机问题,在查找宕机问题时,一般都会分析dump,但由于编译器优化问题和64位dump调试的不方便,能看懂汇编可以起到事半功倍的效果,通常可以通过反汇编查找空指针或者程序的执行过程,所以阅读本书的目标是:看懂汇编,并不深究。
一.汇编基础
1.基础指令汇总
- mov //传送指令
- cmov //条件传送指令
- xchg //交换指令
- push //压栈
- pop //出栈
- pusha/popa //压入/弹出所有16位通用寄存器
- pushad/popad //压入/弹出所有32位通用寄存器
- add //加法
- sub //减法
- inc //自增
- dec //递减
- mul //无符号乘
- imul //带符号乘
- div //无符号除
- idiv //带符号除
- sal //向左移位,右边补0
- shr //无符号,向右移位,左边补0
- sar //带符号,向右移位,左边补1
- lea //赋值地址
- xor //异或,可用来清零 ^
- or //或者 ||
- not //非 !
- and //并且 &&
- nop //空指令
- test //位判断
- je //j开头的均为条件跳转指令,带n的为反义
- call //调用函数
- enter //替代函数操作esp,pushl %ebp movl %esp, %ebp
- leave //替代函数操作esp,movl %ebp, %esp popl %ebp
- ret //函数返回指令
- jmp //跳转到某个地址
- int //中断
- rep //重复执行某个操作,知道ecx为0
- loop //循环直到ecx寄存器为0
以上列举了一些非常常见的汇编指令,在调试过程中,这些指令无处不在,也是必须要掌握的基本指令。
2.数据类型
- AT&T语法
使用L,W,B来表示数据大小,分别代表四位long,两位word,一位byte; - intel语法
byte(字节)、word(字)、dword(双字)、qword(四字)、tbyte(十字节),可以放在ptr前面
二.通用寄存器
1.32位寄存器
- EAX 用于操作数和结果数据的累加器
- EBX 指向数据内存段中的数据的指针
- ECX 字符串和循环操作的计数器
- EDX I/O指针
- EDI 用于字符串操作的目标的数据指针
- ESI 用于字符串操作的源的数据指针
- ESP 堆栈指针
- EBP 堆栈数据指针
2.64位寄存器
- RAX
- RBX
- RCX
- RDX
- RDI
- RSI
- RSP
- RBP
3.系统寄存器
- EIP 系统寄存器,用来记录CPU要执行的指令地址
4.寄存器的特定使用
linux程序中,程序的退出的状态码保存在%ebx寄存器中
movl $8, %ebx
echo $? //可以显示上一个程序的退出码,也就是ebx寄存器的值
linux平台可以使用echo $?查看程序返回值,若使用汇编变成,那么可以将返回值传送到ebx寄存器
5.8位、16位、32位寄存器
位数 | 寄存器 | 寄存器 | 寄存器 | 寄存器 |
---|---|---|---|---|
32位 | EAX | EBX | ECX | EDX |
16位 | AX | BX | CX | DX |
8位 | AH/AL | BH/BL | CH/CL | DH/DL |
三.开发工具
1.汇编器
MASM 微软开发的 http://www.masm32.com/
NASM
GAS GNU系列,另外有gcc、g++
HLA
2.连接器
ld:把汇编语言目标代码和其他库连接在一起,生成操作系统可执行文件
3.调试器
gdb:停止程序、检查修改数据
4.编译器
as:把高级语言转换为处理器能够执行的指令码
5.目标代码反汇编器
objdump:将可执行文件或者目标代码文件转换成汇编语言
6.简档器
gprof:跟踪每个函数在程序执行过程中被使用时花费了多长处理器时间
7.一些需要用到的工具
gdb
kdbg 图形化调试工具
objdump 查看反汇编
gprof 性能分析工具:可以查看函数被调用多少次多少时间
gcc -o demo demo.c -pg
./demo
gprof demo > gprof.txt
gcc过程:
gcc -S ctest.c //生成ctest.s
as ctest.s -o ctest.o
ld ctest.o -o ctest //这一步如果涉及到调用C库函数,那么就得带其他参数
四.操作码语法(Intel和AT&T的语法不同)
AT&T和intel汇编语法,比较明显的是操作数顺序相反,主要区别有以下几点:
编号 | Intel | AT&T | AT&T说明 |
---|---|---|---|
1 | 4 | $4 | AT&T使用$表示立即操作数 |
2 | eax | %eax | AT&T在寄存器名称前面加上前缀% |
3 | mov eax, 4 | movl $4, %eax | 处理源和目标使用相反的顺序 |
4 | mov eax, dword ptr test | movl $test, %eax | AT&T不用指定数据长度,但mov后面要指定L,W,B |
5 | jmp section:offset | ljmp section, offset | 长调用和跳转使用不同语法定义段和偏移值 |
6 | -4(%ebp) | [ebp-4] | 间接寻址 |
7 | foo(,%eax,4) | [foo + eax*4] | 间接寻址 |
这里只是列举了几个常见并且比较基本的区别,太复杂的语法没有深究。
五.汇编程序
1.基本模板
#注释
.section .data
.section .bss
.section .text
.globl _start
_start:
movl $0, %eax
2.编译
as cpuid.s -o cpuid.o (-gstabs 添加调试信息)
ld cpuid.o -o cpuid
3.调试(几个gdb常用调试命令)
- break * label + offset 下断点,指定行数也行
- next 下一行
- step 下一步,若有函数,则进入函数
- continue
- run
- info registers 查看寄存器
- print /x $ebx 查看寄存器十六进制值
- layout asm 切到反汇编
- x/nyz: 显示内存位置
- n是字段数: 个数
- y是输出格式 c字符 d十进制 x十六进制
- z是显示字段长度 b字节 h半字 w32位字
- 例子:x/42cb &output 查看该变量42位字符
4.测试程序
#cpuid2.s
.section .data
output:
.asciz "The processor Vendor ID is '%s'\n"
.section .bss
.lcomm buffer, 12
.section .text
.globl _start
_start:
movl $0, %eax
cpuid
movl $buffer, %edi
movl %ebx, (%edi)
movl %edx, 4(%edi)
movl %ecx, 8(%edi)
pushl $buffer
pushl $output
call printf
addl $8, %esp
pushl $0
call exit
编译运行
gzshun@gzshun-vm:~/c$ as cpuid2.s -o cpuid2.o
gzshun@gzshun-vm:~/c$ ld cpuid2.o