• 栈回溯简单实现(x86)


    0x01  栈简介

        首先局部变量的分配释放是通过调整栈指针实现的,栈为函数调用和定义局部变量提供了一块简单易用的空间,定义在栈上的变量不必考虑内存申请和释放。只要调整栈指针就可以分配和释放内存。

        每个函数在栈中使用的区域叫做栈帧Stack Frame,在X86中,通常使用EBP寄存器作为帧指针使用,EBP寄存器所指向的栈单元中保存的是前一个EBP寄存器的值,通常也就是父函数的EBP值。帧指针不仅对函数中的代码起到定位变量和参数的参照物作用,而且将栈中的一个个栈帧串联在一起,形成了一个可以遍历所有栈帧的链条,这也就是栈回溯的基本原理。值得注意的是必须要保证函数返回时栈指针的值与进入函数时一致,即保证栈平衡。
     
     
    0x02  栈帧
         百度百科的经典解释:“栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。

        可以理解为:栈帧就是存储在用户栈上的(当然内核栈同样适用)每一次函数调用涉及的相关信息的记录单元。栈帧表示程序的函数调用记录,而栈帧又是记录在栈上面,很明显栈上保持了N个栈帧的实体

        

    0x03  栈回溯简单实现

         通过帧指针ebp来实现函数返回地址,调用地址的获取。

         对于这里的call指令,研究的是E8类型的call指令机器码,其他类型的call指令并未采取相关的操作。

         机器码e8后面的四字节是一个相对偏移,即当前指令指针中(EIP)(即下一条指令的地址)的值与目的地址(被调函数首地址)的差值。所以目的地址(被调函数首地址)的计算方法为: 目的地址 =  返回地址 +  相对偏移(四字节机器码)

         

         下一条指令地址:0x011c1722 + 5 = 011c1727

         如上图:0x011c1727 + 0xFFFFF933 = 0x011c105a目的地址(被调函数首地址)

         所以可以根据call机器指令的特征码E8来找到函数返回地址和被调用函数地址。

         

    void __stdcall StackTraceFunction(int StackBase, int ebp, int esp)
    {
    	LONG_PTR LimitCount = 30; 
    	LONG_PTR RetAddress = 0;
    	LONG_PTR CalleeFunctionAddress = 0;
    	printf("ebp        RetAddress  CalleeFunctionAddress
    ");
    
    	while ((ebp > esp) && (ebp < StackBase) && (LimitCount--)) 
    	{
    		int v1 = ebp;
    		int v2 = esp;
    		RetAddress = *(LONG_PTR *)(ebp + 4);
    		CalleeFunctionAddress = 0;
    
    		if (*(unsigned char *)(RetAddress - 5) == 0xe8)
    		{
    			CalleeFunctionAddress = *(LONG_PTR *)(RetAddress - 4) + RetAddress;
    		}
    	
    		printf("%08x   %08x    %08x
    ", ebp, RetAddress, CalleeFunctionAddress);
    		ebp = *(LONG_PTR *)ebp;
    	}
    }
    

      

  • 相关阅读:
    WPF中的句柄
    WPF中的焦点问题
    Vue项目(一):VSCode环境开发Vue程序以及其中遇到的问题
    C#实现三种方式的模拟按键
    c++温故之结构体写法
    WPF搜索框
    vue框架学习
    Git连接失败问题解决方案
    单击双击冲突解决 小程序
    uniapp 微信小程序 wx.createAnimation 实现向上滚动弹幕
  • 原文地址:https://www.cnblogs.com/lsh123/p/7804845.html
Copyright © 2020-2023  润新知