前言
开发中经常使用指针访问变量修改变量值, 那么编译器是怎么翻译指针的 或者说指针在汇编层面到底是什么
mov 和 lea 指令
指针的分析离不开这两个指令,简单看下mov指令
mov指令
GNU汇编器输出AT&T汇编时为 mov 指令添加了一个维度,在其中必须声明要传送的数据元素的长度
因此,指令就变成了如下:
movx
其中 x 可以是下面的字符:
1,q用于64位的4字值
2,l用于32位的长字值
3,w用于16位的字值
4,b用于8位的字节值
源码
- (void)asm_point {
int a = 6;
}
AT&T汇编
GUN汇编器输出AT&T汇编 和 Intel 汇编的语法顺序是相反
YangASM`-[ViewController asm_point]:
0x10ed88ee0 <+0>: pushq %rbp
0x10ed88ee1 <+1>: movq %rsp, %rbp
0x10ed88ee4 <+4>: movq %rdi, -0x8(%rbp)
0x10ed88ee8 <+8>: movq %rsi, -0x10(%rbp)
0x10ed88eec <+12>: movl $0x6, -0x14(%rbp)
-> 0x10ed88ef3 <+19>: popq %rbp
0x10ed88ef4 <+20>: retq
汇编转化内存地址
(lldb) p &a
(int *) $4 = 0x00007ffee0e7503c
(lldb) register read rbp
rbp = 0x00007ffee0e75050
(lldb) p/x 0x00007ffee0e75050-0x8
(long) $5 = 0x00007ffee0e75048
(lldb) p/x 0x00007ffee0e75050-0x10
(long) $6 = 0x00007ffee0e75040
(lldb) p/x 0x00007ffee0e75050-0x14
(long) $7 = 0x00007ffee0e7503c
(lldb)
分析
```c
YangASM`-[ViewController asm_point]:
// rbp = 0x00007ffee0e75050
// 开启asm_point 函数的函数栈
0x10ed88ee0 <+0>: pushq %rbp
0x10ed88ee1 <+1>: movq %rsp, %rbp
// 上面我们得到 -0x8(%rbp) p/x 0x00007ffee0e75050-0x8 = 0x00007ffee0e75048
// 从 movq 可以看出来 需要霸占内存8个字节的区间
// 那么这条指令就是 rdi的值存放到 从0x00007ffee0e75048开始 往下的8个字节内
0x10ed88ee4 <+4>: movq %rdi, -0x8(%rbp)
// rsi的值存放到 从0x00007ffee0e75040开始 往后的8个字节内
0x10ed88ee8 <+8>: movq %rsi, -0x10(%rbp)
// int a = 6
// 把6存放到 从0x00007ffee0e7503c往下4个字节的区间内
// movl 看到需要霸占4个字节区间 int类型 4个字节
0x10ed88eec <+12>: movl $0x6, -0x14(%rbp)
// 回收asm_point函数的函数栈空间
-> 0x10ed88ef3 <+19>: popq %rbp
0x10ed88ef4 <+20>: retq
上面的汇编有movl 和 movq
也就是说 在 AT&T汇编中 mov指令都是以movx的形式出现
间接寻址
上面的汇编中 movq %rdi, -0x8(%rbp) 还有内括号 还有 - 号
分别含义如下:
-
movl %ebx, %edi
ebx寄存器中的值 加载到edi寄存器中 -
movl %ebx, (%edi)
edi加上内括号 就是把 ebx寄存器中的值传递给 edi寄存器中包含的内存地址 -
movl %ebx, 4(%edi)
把edx寄存器中的值存放到edi寄存器指向的位置之后的4个字节的内存位置中 -
也可以把它存放到相反的方向
movl %ebx, -4(%edi)
把edx寄存器中的值存放到edi寄存器指向的位置之前的4个字节的内存位置中
内存布局:
上面的汇编代码 绘制出内存布局如下
leq 指令
leq 后面跟地址 直接把地址 给寄存器
mov 后面跟地址 把地址上的数据 给寄存器
源码
- (void)asm_point {
int a = 6;
int *p = a;
}
汇编
YangASM`-[ViewController asm_point]:
0x10da42ee0 <+0>: pushq %rbp
0x10da42ee1 <+1>: movq %rsp, %rbp
// 安排 rdi rsi
0x10da42ee4 <+4>: movq %rdi, -0x8(%rbp)
0x10da42ee8 <+8>: movq %rsi, -0x10(%rbp)
// int a = 6 6存入 -0x14(%rbp)
0x10da42eec <+12>: movl $0x6, -0x14(%rbp)
// 讲-0x14(%rbp)这个地址 赋给rax
0x10da42ef3 <+19>: leaq -0x14(%rbp), %rax
// rax里面的值 存入-0x20(%rbp)
// rax里面的值 是一个地址 -0x14(%rbp)
0x10da42ef7 <+23>: movq %rax, -0x20(%rbp)
-> 0x10da42efb <+27>: popq %rbp
0x10da42efc <+28>: retq
指针修改变量值
了解完 leq 和 mov 我们看指针修改
- (void)asm_point {
int a = 6;
int *p = &a;
*p = 12;
}
汇编
YangASM`-[ViewController asm_point]:
0x10f5e4ed0 <+0>: pushq %rbp
0x10f5e4ed1 <+1>: movq %rsp, %rbp
0x10f5e4ed4 <+4>: movq %rdi, -0x8(%rbp)
0x10f5e4ed8 <+8>: movq %rsi, -0x10(%rbp)
0x10f5e4edc <+12>: movl $0x6, -0x14(%rbp)
0x10f5e4ee3 <+19>: leaq -0x14(%rbp), %rax
0x10f5e4ee7 <+23>: movq %rax, -0x20(%rbp)
0x10f5e4eeb <+27>: movq -0x20(%rbp), %rax
0x10f5e4eef <+31>: movl $0xc, (%rax)
-> 0x10f5e4ef5 <+37>: popq %rbp
0x10f5e4ef6 <+38>: retq
内存地址
(lldb) register read rbp
rbp = 0x00007ffee0619050
(lldb) p/x 0x00007ffee0619050-0x8
(long) $5 = 0x00007ffee0619048
(lldb) p/x 0x00007ffee0619050-0x10
(long) $6 = 0x00007ffee0619040
(lldb) p/x 0x00007ffee0619050-0x14
(long) $7 = 0x00007ffee061903c
(lldb) p/x 0x00007ffee0619050-0x20
(long) $8 = 0x00007ffee0619030
(lldb)
内存模型
图一
// 安排 rsi rdi
0x10f5e4ed4 <+4>: movq %rdi, -0x8(%rbp)
0x10f5e4ed8 <+8>: movq %rsi, -0x10(%rbp)
图二
// int a = 6 6存入 -0x14(%rbp)
0x10f5e4edc <+12>: movl $0x6, -0x14(%rbp)
图三
0x10f5e4ee7 <+23>: movq %rax, -0x20(%rbp)
图四
0x10f5e4ee7 <+23>: movq %rax, -0x20(%rbp)
0x10f5e4eef <+31>: movl $0xc, (%rax)
完整汇编分析
YangASM`-[ViewController asm_point]:
0x10f5e4ed0 <+0>: pushq %rbp
0x10f5e4ed1 <+1>: movq %rsp, %rbp
// 安排 rdi rsi
0x10f5e4ed4 <+4>: movq %rdi, -0x8(%rbp)
0x10f5e4ed8 <+8>: movq %rsi, -0x10(%rbp)
// int a = 6 6存入 -0x14(%rbp)
0x10f5e4edc <+12>: movl $0x6, -0x14(%rbp)
// 讲-0x14(%rbp)这个地址 赋给rax
0x10f5e4ee3 <+19>: leaq -0x14(%rbp), %rax
// rax里面的值 存入-0x20(%rbp)
0x10f5e4ee7 <+23>: movq %rax, -0x20(%rbp)
// 讲-0x20(%rbp)的值赋给rax rax 现在指向-0x20(%rbp)
// 这个地方很容易让人疑问 上面刚刚把rax 存入内存 怎么有从内存取出来了?
// 上面的存入内存是因为源码的 int *p = &a 汇编要完成 p指向一个地址
// 这里是因为代码中 出现了 *p = 12
// *p = 12 其实是2行汇编代码 首先取到 *p 另一行是赋12
// 这里是为了后面 取(rax)提供方便
0x10f5e4eeb <+27>: movq -0x20(%rbp), %rax
// (%rax) rax加一个内括号 就是操作这块内存区域里面的值
// movl 说明是4个字节
// 讲 这块区域的值改成 12 0xc
0x10f5e4eef <+31>: movl $0xc, (%rax)
-> 0x10f5e4ef5 <+37>: popq %rbp
0x10f5e4ef6 <+38>: retq