汇编看指针

前言

我们都知道 可以使用指针间接的去获取或者修改某个变量的值, 那么指针到底是什么?编译器是怎么翻译指针代码, 或者说指针在汇编层面到底是什么, 用汇编去分析模棱两可的知识 可以看到知识的底层和本质 能加深我们对事物的认知深度。

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被成为立即数 可以不借助寄存器 直接写入内存 
    // 把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的形式出现

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  

重点看这两行汇编

 leaq   -0x14(%rbp), %rax
 movq   %rax, -0x20(%rbp)

看到这样的组合 就应该知道是指针
-0x14(%rbp) 就是指针指向的地址
-0x20(%rbp) 就是指针自己的地址
%rax, -0x20(%rbp) 就是把指针指向的地址 存储的自己的内存空间里面

如果是 intel汇编的话 我们模拟写2行 应该是这样

lea  eax, [ebp-och]
// dword ptr 8字节长度  mov  dword ptr 等价上面的 movq  8字节
mov  dword ptr [ebp-18h], eax

在结合图示看下
请添加图片描述

指针修改变量值

了解完 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  

猜你喜欢

转载自blog.csdn.net/u014641631/article/details/121425044
今日推荐