分析环境
- 编译器 x86-64 gcc 10.2
- 汇编风格 AT&T 风格
概述
汇编代码基于 x86-64
,由于 x86 系列的 CPU 向后兼容,在指令上,兼容就是 intel8086
这个 16bit
的处理器的指令,在 intel80386
这个32bit
的处理器上也能使用。以此类推。先来了解下寄存器的变化。详细的可以查看 x86 寄存器历史。
以 ax 为例
16bit | 32bit | 64bit |
---|---|---|
ax | eax | rax |
可以了解 eax 是 32bits,rax 是 64bits。 其他指令类似。用
e
和r
来区分 32bits 还是 64bits
不同位数下的指令如何区分?
以 mov
为例
8bit | 16bit | 32bit | 64bit |
---|---|---|---|
movb | movw | movl | movq |
可以看到使用不同的后缀来表示这个是多少 bit 的指令
CPU 如何寻址?
CPU 寻址方式, 也就是拿到数据的方式.
立即数
movb $0x05,%al
表示为:R[al] = 0x05;> 将立即数 0x05(1 byte) 复制到寄存器 al
R 表示寄存器(register)
间接寻址
就是到内存里去寻找数据。
内存如何寻址?
通过
signed-offset(base,index,scale)
来表示。
内存地址计算:
memory address = scale * index + base + signed-offset
举例说明
mov (%esi,%ebx,4), %edx
Move the 4 bytes of data at address esi+ 4 * ebx into edx.
%edx = 4 * %ebx + %esi
至此,如果将 scale = 8, 那就是 C 语言中的 long 型数据,如果同时递增 %ebx,那就是依次访问数组元素了。
register to memory (指针)
movl %eax, -4(%ebp)
表示为: mem[R[ebp]-4] = R[eax];
将寄存器eax 里面的值
复制到寄存器 ebp 的值减去 4
指向的内存地址处(也就是 R[ebp] -4 的值是一个内存地址).
通过寄存器指向了内存地址
, 是不是很熟悉的指针啊, 对, 就是指针。 C 语言的指针就是这么玩的啊!
memory to register
movl -4(%ebp), %eax
%eax 表示为: R[eax] = mem[R[ebp] -4];
将寄存器esp 的值减去 4
的值指向的内存地址处
存放的值, 复制到寄存器 eax
代码分析
int main(){
int i = 10;
int *pi = &i;
int ii = *pi;
char c = 'z';
char *pc = &c;
char cc = *pc;
long l = 1000000000L;
long *pl = &l;
long ll = *pl;
return 0;
}
图解分析
C 代码和汇编对照
汇编代码分析
- 执行完
1
和2
这两个汇编指令后,假定
%rsp = %rbp = 1024(内存地址)。 汇编第 4 行
:movl $10, -52(%rbp) , 可以从上图中看到-52
内存地址处被赋值为 10,占据 4Bytes。movl
表示操作的是32bit 的整数
。这和C 代码中第 6 行 int i = 10
保持一致。汇编第 5 行
: leaq -52(%rbp), %rax, 表示的是将 %rbp - 52 的内存地址
,复制到 rax。因为使用的编译器是 x86-64gcc 10.2,是 64bits 的汇编代码,内存地址是 64bits。此时也就是 C 代码中,变量 i
的地址复制给 rax。汇编第 6 行
:movq %rax, -8(%rbp), 将此时 rax 复制到内存地址 %rbp - 8 的内存地址处。至此,完成了 C代码中第 7 行 int *pi = &i
;汇编第 7 行
:movq -8(%rbp),%rax,将 %rbp - 8 的内存地址处的值()指针(&i
),复制到 rax。汇编第 8 行
: movl (%rax), %eax, 将 %rax指向的值(i)
复制到 eax。可以看到此时使用的是 eax
,也就是 32bits,正是 C 语言中 int 型数据。汇编第 9 行
: movl %eax, -12(%rbp), 将此时 eax 的值(i) 复制到 %rbp - 12 的内存地址处。至此,完成了 C 代码中第 8 行 int ii = *pi
。- 其他的 char 和 long 类型,同理。