• 通过反汇编理解函数调用机制(x86和ARM)


    如下,一个简单的程序

     1 #include <stdio.h>
     2 int add(int a, int b)
     3 {
     4         return a + b;
     5 }
     6 
     7 void main()
     8 {
     9         int a = 1, b = 2;
    10         int result;
    11         result = add(a, b);
    12         printf("%d",result);
    13 }

    执行反汇编指令:gcc -g test.c
    objdump -S

    得到x86机器的汇编代码(除去一些初始化的代码)如下:

     在分析上面的汇编程序之前,需要了解rbp、rsp为栈基址寄存器、栈顶寄存器,分别指向栈底和栈顶;edx、eax、esi、edi均为x86CPU上的通用寄存器,可以存放数据(虽然它们还有别的作用,但是本文章不涉及)

    x86下栈生长是从高地址往低地址,即push操作一次,rsp减少4个字节,pop操作一次,rsp增加4个字节。

    对上面汇编代码的分析:

    进入main函数,保护现场,将rbp压入堆栈;

    然后为main函数开拓新的堆栈框架,rbp与当前rsp相同,rsp再向上扩充16个字节(0x10);(以前的C程序只能在函数前面声明变量,是因为编译器还么有那么“智能”,它只能通过分析前部分的变量,一次性的为程序扩充堆栈)

    然后向栈底上方的偏移地址为8和12的单元存入数据1和2;

    把数据送入通用寄存器中,以供新的函数调用;

    跳转到add;

    再次将main的rbp压栈,保护;

    新的rbp与当前rsp相同,把通用寄存器中的数据赋给栈底上方偏移地址为4和8的单元(此为函数参数传递的关键);

    将传入新栈的参数赋给通用寄存器,进行加法操作,结果存入eax;

    pop出rbp,回到main函数;

    将eax中的运算结果赋给栈底上方偏移地址为4的单元;

    然后调用printf函数显示结果。

    使用arm-linux-gcc编译并反汇编:arm-linux-objdump -D -m arm a.out

    得到arm机器的汇编代码(除去一些初始化的代码)如下:

    这段代码的解析与x86类似,只不过需要了解几个arm汇编指令和寄存器名称。fp为帧寄存器,起“标签”作用。lr是连接寄存器,在ARM体系结构中lr的用途有两种:一是用来保存子程序返回地址;二是当异常发生时,lr保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以返回到异常发生前的相应位置继续执行。bx lr即跳转到lr存放的地址处。sp为栈顶指针。str 源寄存器 存储地址,即将源存储器数据送到存储器中,ldr为其逆操作。

    ARM为堆栈提供硬件支持,它有一个专门的寄存器sp指向栈顶,ARM支持四种堆栈工作方式,最常用的也是和x86类似,即从高地址向低地址生长。

    参考资料:http://mooc.study.163.com/course/USTC-1000029000#/info

  • 相关阅读:
    求斐波那契数列的第n项
    八大经典排序算法
    [BZOJ 3083] 遥远的国度
    [BZOJ 3306] 树
    [SCOI 2010] 序列操作
    [AHOI 2013] 差异
    [USACO2006 DEC] Milk Patterns
    [JSOI 2007] 字符加密
    [BZOJ 2588] Count on a tree
    [NOIP 2018 Day1] 简要题解
  • 原文地址:https://www.cnblogs.com/jacklu/p/4617859.html
Copyright © 2020-2023  润新知