C语言代码的x86-64汇编指令分析过程记录

先通过Xcode创建一个terminal APP,语言选择C。代码如下:

cpp 复制代码
#include <stdio.h>

int main(int argc, const char * argv[]) {
    int a[7]={1,2,3,4,5,6,7};
    int *ptr =(int*)(&a+1);
    printf("%d\n",*(ptr));
    return 0;
}

在return 0处打上断点,并且Xcode菜单里选择Debug|Debug Workflow|Always Show Disassembly,点击运行。这时候断点会跳到汇编代码里,得到汇编代码如下:

bash 复制代码
Terminal`main:
    0x100003ec0 <+0>:   pushq  %rbp
    0x100003ec1 <+1>:   movq   %rsp, %rbp
    0x100003ec4 <+4>:   subq   $0x50, %rsp
    0x100003ec8 <+8>:   movq   0x131(%rip), %rax         ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard
    0x100003ecf <+15>:  movq   (%rax), %rax
    0x100003ed2 <+18>:  movq   %rax, -0x8(%rbp)
    0x100003ed6 <+22>:  movl   $0x0, -0x34(%rbp)
    0x100003edd <+29>:  movl   %edi, -0x38(%rbp)
    0x100003ee0 <+32>:  movq   %rsi, -0x40(%rbp)
    0x100003ee4 <+36>:  movq   0xa5(%rip), %rax
    0x100003eeb <+43>:  movq   %rax, -0x30(%rbp)
    0x100003eef <+47>:  movq   0xa2(%rip), %rax
    0x100003ef6 <+54>:  movq   %rax, -0x28(%rbp)
    0x100003efa <+58>:  movq   0x9f(%rip), %rax
    0x100003f01 <+65>:  movq   %rax, -0x20(%rbp)
    0x100003f05 <+69>:  movl   0x9d(%rip), %eax
    0x100003f0b <+75>:  movl   %eax, -0x18(%rbp)
    0x100003f0e <+78>:  leaq   -0x30(%rbp), %rax
    0x100003f12 <+82>:  addq   $0x1c, %rax
    0x100003f16 <+86>:  movq   %rax, -0x48(%rbp)
    0x100003f1a <+90>:  movq   -0x48(%rbp), %rax
    0x100003f1e <+94>:  movl   (%rax), %esi
    0x100003f20 <+96>:  leaq   0x85(%rip), %rdi          ; "%d\n"
    0x100003f27 <+103>: movb   $0x0, %al
    0x100003f29 <+105>: callq  0x100003f5a               ; symbol stub for: printf
    0x100003f2e <+110>: movq   0xcb(%rip), %rax          ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard
    0x100003f35 <+117>: movq   (%rax), %rax
    0x100003f38 <+120>: movq   -0x8(%rbp), %rcx
    0x100003f3c <+124>: cmpq   %rcx, %rax
    0x100003f3f <+127>: jne    0x100003f4d               ; <+141> at main.c
->  0x100003f45 <+133>: xorl   %eax, %eax
    0x100003f47 <+135>: addq   $0x50, %rsp
    0x100003f4b <+139>: popq   %rbp
    0x100003f4c <+140>: retq   
    0x100003f4d <+141>: callq  0x100003f54               ; symbol stub for: __stack_chk_fail
    0x100003f52 <+146>: ud2    

首先介绍下面会用到的几个寄存器:

rip :程序计数寄存器

rsp : 栈指针寄存器,指向栈顶

rbp : 栈基址寄存器,指向栈底

edi : 函数参数

rsi/esi : 函数参数

eax : 累加器或函数返回值用

1、 pushq %rbp

将rbp的地址压栈,rsp继续指向栈顶

2、 movq %rsp, %rbp

将栈顶rsp的值赋值给栈底rbp,

3、 subq $0x50, %rsp

栈顶往下移5*16个字节,可以理解成给后面预留的80字节的空间。X64中栈开辟的大小都是0x10的倍数。

4、movq 0x131(%rip), %rax ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard

0x131(%rip)的意思是将下一条指令的地址(0x100003ecf)加上0x131得到目标地址(0x100004000),然后取得其中的8字节值,设置给rax寄存器。

选中Debug workflow | View Memory,在Address里输入'0x100004000'然后回车,我们可以看到此处的内容为0x00007ff84ef2f8a0(注意大小端问题):

5、movq (%rax), %rax

将rax寄存器所存地址指向的值,传给rax寄存器。类似前面的操作,我们发现此处的值为0x55d1d55afee700d6:

6、movq %rax, -0x8(%rbp)

把rax中的值,也就是上面这个图上所示的8个字节,存入rbp-0x8的位置。我们先打印下rbp和rsp的值,然后跳到rsp处查看内存:

红框处即是我们存放值的地方。右边8字节则是rbp指向的位置。

7、movl $0x0, -0x34(%rbp)

将4字节0设置到rbp-0x34的位置,这里的目的是将下一条指令中-0x38(%rbp)的高字节清零。该位置为0x7ff7bfeff3ac:

8、movl %edi, -0x38(%rbp)

该命令保存edi寄存器的值到rbp-0x38位置,也就是上图的0x7ff7bfeff3a8,值是1。前面我们说edi是用来保存函数参数的,也就是int argc,在这个例子中argc的值为1,所以edi寄存器的值为1。

9、movq %rsi, -0x40(%rbp)

该命令保存rsi寄存器的值到rbp-0x40位置,也就是上图的0x7ff7bfeff3a0,值是0x7ff7bfeff518。这里是参数argv的值0x7ff7bfeff708。由于argv是const char **,因此这也是个地址值,我们前往该地址查看其内容。

10、movq 0xa5(%rip), %rax

获取位于0x100003eeb+0xa5=0x100003f90处的8字节内容,然后存入rax寄存器:

11、movq %rax, -0x30(%rbp)

将rax寄存器的值存入rbp-0x30的位置:

12-17跟上面两步相同,就是存3,4,5,6,7的值,注意7是单独存的,因为movl表示4字节,而movq代表8字节,也就是2个int。

bash 复制代码
0x100003eef <+47>:  movq   0xa2(%rip), %rax
    0x100003ef6 <+54>:  movq   %rax, -0x28(%rbp)
    0x100003efa <+58>:  movq   0x9f(%rip), %rax
    0x100003f01 <+65>:  movq   %rax, -0x20(%rbp)
    0x100003f05 <+69>:  movl   0x9d(%rip), %eax
    0x100003f0b <+75>:  movl   %eax, -0x18(%rbp)

18、leaq -0x30(%rbp), %rax

获取rbp-0x30=0x7ff7bfeff3b0,然后存入rax寄存器。

19、addq $0x1c, %rax

将rax寄存器中的值0x7ff7bfeff3b0,加上0x1c后(0x7ff7bfeff3cc)存入rax寄存器。这里对应代码`int *ptr =(int*)(&a+1);`0x1c是28,也就是数组的大小28字节,说明指针的加法是在编译阶段将数值替换为具体的值,也就是该值乘以指针类型大小后的值。

20、movq %rax, -0x48(%rbp)

接下去把rax的值0x7ff7bfeff3cc存入rbp-0x48的位置:

21、movq -0x48(%rbp), %rax

接着又把刚存入的这个值存入寄存器rax

22、movl (%rax), %esi

把rax寄存器所存地址对应的值(1)存入寄存器esi,这是作为下面要调用的print方法的第二个参数。

23、leaq 0x85(%rip), %rdi ; "%d\n"

rip=0x100003f27,加上0x85后为0x100003fac,然后设置给rdi寄存器,也就是print调用的第一个参数。0x100003fac这个位置的值刚好是字符串"%d\n"。

24、movb $0x0, %al

将立即数0存储到al寄存器中。那么如何理解eax,ax,al(ah)之间的关系

专业点可以这样解释:eax是32位寄存器,ax是16位寄存器,al(ah)是八位寄存器。

对于变长参数的函数,要用%al指明用到的vector registers的个数 ,比如printf,这里我们没有用到可变参数,所以要给al寄存器设置0。参考:Why are the %al register and stack modified before calling printf x86 assembly from C "Hello World" program compiled by gcc

汇编函数调用的参数传递

25、callq 0x100003f5a ; symbol stub for: printf

调用printf函数。call有一个作用:将call指令的下一条指令地址压栈:

26、movq 0xcb(%rip), %rax ; (void *)0x00007ff84ef2f8a0: __stack_chk_guard

27、movq (%rax), %rax

26和27步骤跟4和5处类似,这里不再赘述。

28、movq -0x8(%rbp), %rcx

将栈底8字节存入rcx寄存器

29、cmpq %rcx, %rax

比较rcx寄存器和rax寄存器的值是否相等,并把结果写入状态寄存器

30、jne 0x100003f4d ; <+141> at main.c

如果29的比较结果为不相等,就跳转0x100003f4d处继续执行,也就是35处。相等的话执行31处。这里主要是使用__stack_chk_guard_ptr来判断是否发生栈溢出,导致栈底开始8字节被篡改。可以参考關於__stack_chk_guard_ptr的理解

31、xorl %eax, %eax

将eax寄存器清零,作为main函数的return值

32、addq $0x50, %rsp

这句正好对应前面的subq $0x50, %rsp。通过给栈顶指针加上开辟栈的大小,回收栈顶指针开辟的空间。

33、popq %rbp

这句指令表示出栈,同时将出栈的值放入寄存器rbp

34、retq

这句表示退出main函数,会恢复rip值。本例没有体现这一点,由main函数的调用者保存rip。

35、callq 0x100003f54 ; symbol stub for: __stack_chk_fail

调用__stack_chk_fail函数

36、ud2

UD2 指令的字节编码为 0F 0B,它是一个两字节的指令。在汇编 语言中,可以使用 UD2 指令来实现一些特殊的功能,比如触发调试断点或者中断程序的执行。 UD2 指令常用于调试程序。具体可查看:ud2 汇编指令

最终的栈布局

相关推荐
188号安全攻城狮12 小时前
【PWN】HappyNewYearCTF_2_栈上变量覆写1
linux·运维·汇编·安全·网络安全
草莓熊Lotso12 小时前
Linux 进程创建与终止全解析:fork 原理 + 退出机制实战
linux·运维·服务器·开发语言·汇编·c++·人工智能
爱编码的小八嘎14 小时前
汇编语言全接触-105.Natas 幽灵王病毒的分析
汇编
老鱼说AI1 天前
深入理解计算机系统1.5:抽象的重要性:操作系统与虚拟机
c语言·开发语言·汇编
猫猫的小茶馆2 天前
【Linux 驱动开发】一. 搭建开发环境
linux·汇编·arm开发·驱动开发·stm32·嵌入式硬件·mcu
猫猫的小茶馆2 天前
【Linux 驱动开发】二. linux内核模块
linux·汇编·arm开发·驱动开发·stm32·嵌入式硬件·架构
切糕师学AI3 天前
ARM 中的 SVC 监管调用(Supervisor Call)
linux·c语言·汇编·arm开发
ベadvance courageouslyミ3 天前
硬件基础中断
汇编·硬件·中断
你爱写程序吗(新H)3 天前
基于单片机的洗衣机控制系统设计 单片机洗衣机控制(设计+文档)
c语言·汇编·单片机·嵌入式硬件·matlab
VekiSon4 天前
ARM架构——用汇编语言点亮 LED
汇编·arm开发·嵌入式硬件