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

这篇具有很好参考价值的文章主要介绍了C语言代码的x86-64汇编指令分析过程记录。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

#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,点击运行。这时候断点会跳到汇编代码里,得到汇编代码如下:

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(注意大小端问题):

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

 5、movq   (%rax), %rax

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

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

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

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

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

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

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

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

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

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

 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 **,因此这也是个地址值,我们前往该地址查看其内容。

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

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

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

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

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

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

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

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

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

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的位置:

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

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

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

22、movl   (%rax), %esi

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

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

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

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

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

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指令的下一条指令地址压栈:

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

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 汇编指令

最终的栈布局

C语言代码的x86-64汇编指令分析过程记录,汇编文章来源地址https://www.toymoban.com/news/detail-631530.html

到了这里,关于C语言代码的x86-64汇编指令分析过程记录的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • x86汇编_MUL/IMUL乘法指令_笔记52

    32位模式下整数乘法可以实现32、16或8位的操作,64位下还可以使用64位操作数。MUL执行无符号乘法,IMUL执行有符号乘法。 MUL指令:无符号数乘法 32 位模式下,MUL(无符号数乘法)指令有三种类型: 执行 8 位操作数与 AL 寄存器的乘法; 执行 16 位操作数与 AX 寄存器的乘法;

    2024年02月07日
    浏览(28)
  • 【ARM64 常见汇编指令学习 16 -- ARM64 SMC 指令】

    上篇文章:ARM64 常见汇编指令学习 15 – ARM64 标志位的学习 下篇文章:ARM64 常见汇编指令学习 17 – ARM64 BFI 指令 在ARMv8架构中,同步异常主要包括以下几种: Undefined Instruction :未定义指令异常,当处理器尝试执行一条未定义的指令时会触发。 Supervisor Call (SVC) :这是一种特殊

    2024年02月13日
    浏览(32)
  • 【ARM64 常见汇编指令学习 13 -- ARM 汇编 ORG 伪指令学习】

    上篇文章:ARM64 常见汇编指令学习 12 – ARM 汇编函数 的学习 下篇文章:ARM64 常见汇编指令学习 14 – ARM 汇编 .balign,.balignw,.balign 伪指令学习 在ARM汇编中,\\\" org \\\"是一个汇编器伪指令,用于设置下一条指令的装入地址。\\\" org \\\"后面跟着的是一个表达式,这个表达式的值就是下一条

    2024年02月14日
    浏览(34)
  • 协程切换原理与实践 -- 从ucontext api到x86_64汇编

    目录 1.协程切换原理理解 2.ucontext实现协程切换 2.1 实现流程 2.2 根据ucontext流程看协程实现 2.3 回答开头提出的问题 3.x86_64汇编实现协程切换 3.1libco x86_64汇编代码分析 3.2.保存程序返回代码地址流程 3.3.恢复程序地址以及上下文  4.实现简单协程框架 协程可以实现在一个线程中

    2024年02月05日
    浏览(31)
  • 【ARM 常见汇编指令学习 5 -- arm64汇编指令 wzr 和 xzr】

    上篇文章:ARM 常见汇编指令学习 4 – ARM64 比较指令 cbnz 与 b.ne 区别 下篇文章:ARM 常见汇编指令学习 6 - bic(位清除), orr(位或), eor(异或) ARMv8 在硬件层名引入了一个新的 zero 寄存器 : XZR (64-bits), WZR (32-bits)。比如要将某一变量赋值为0x0, 由于ARM不允许直接操作内存单元上的数据

    2024年02月15日
    浏览(42)
  • 【ARM64 常见汇编指令学习 14 -- ARM 汇编 .balign,.balignw,.balign 伪指令学习】

    上篇文章:ARM64 常见汇编指令学习 13 – ARM 汇编 ORG 伪指令学习 下篇文章:ARM64 常见汇编指令学习 15 – ARM 标志位的学习 .balignl 是一个伪操作符,伪操作符的意思就是机器码里,并没有一个汇编指令与其对应,是编译器来实现其功能的。. balignl 是 .balign 的变体。 .balignl 完整

    2024年02月14日
    浏览(31)
  • 【ARM64 常见汇编指令学习 12 -- ARM 汇编函数 的学习】

    上篇文章:ARM64 常见汇编指令学习 11 – ARM 汇编宏 .macro 的学习 下篇文章:ARM64 常见汇编指令学习 13 – ARM 汇编 ORG 伪指令学习 ARM汇编中的函数定义并不像高级语言那样有特定的语法,但通常可以通过 标签(label) 和 子程序调用指令 (如BL,BLX) 来实现类似于函数的功能。 例如

    2024年02月14日
    浏览(35)
  • MIPS指令集处理器设计(支持64条汇编指令)

    一、题目背景和意义 二、国内外研究现状 (略) (1) .mips 基础 指令集格式 总结 MIPS是(Microcomputer without interlocked pipeline stages)[10]的缩写,含义是无互锁流水级微处理器。MIPS 是最早、最成功的RISC处理器之一[11],源于Stanford 大学的John Hennessy 教授的研究成果。(Hennessy 于1984年在

    2024年02月07日
    浏览(28)
  • ARM64 寄存器、常用汇编指令收集

    ARM64 有34个寄存器,包括31个通用寄存器、SP、PC、CPSR。 寄存器 位数 描述 x0-x30 64bit 通用寄存器,如果有需要可以当做32bit使用:WO-W30 FP(x29) 64bit 保存栈帧地址(栈底指针) LR(x30) 64bit 通常称X30为程序链接寄存器,保存子程序结束后需要执行的下一条指令 SP 64bit 保存栈指针,使用

    2023年04月20日
    浏览(32)
  • ARM64 常见汇编指令学习 11 -- ARM 汇编宏 .macro 的学习

    下篇文章:ARM64 常见汇编指令学习 12 – ARM 汇编函数 的学习 上篇文章:ARM64 常见汇编指令学习 10 – 无符号位域提取指令 BFXIL 在 ARM 汇编中,“ .macro ” 是用来定义一个宏的指令。宏可以看作是一个可以在汇编程序中重用的代码段。当调用宏时,汇编器会将宏的内容插入到调

    2024年02月14日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包