平衡堆栈

news/2024/10/18 9:19:54

理解并观测函数调用母函数做什么,子函数做什么

cdecl调用约定

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

int __cdecl method(int x, int y)

{

return x + y;

}

int main()

{

__asm mov eax, eax; // 此处设置断点

method(1, 2);

return 0;

}

可以看出__cdecl就是C语言默认的调用约定。

二、_stdcall调用约定

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

int __stdcall method(int x, int y)

{

return x + y;

}

int main()

{

__asm mov eax, eax; // 此处设置断点

method(1, 2);

return 0;

}

和__cdecl一样都是从右往左入栈参数,不过该调用约定使用的平栈方式是内平栈,从下图可以看到,这里已经看不到堆栈的处理了。

F11不断执行,直到进入call指令调用的method函数中:

平栈操作跑到函数内部了,__cdecl约定是调用者(main)函数进行平栈,而__stdcall约定是函数内部自身进行平栈。

三、_fastcall调用约定

这是一个比较特殊的调用约定,当函数参数为两个或者以下时,该约定的效率远远大于上面两种,当然随着参数越来越多,该约定与上面两种约定的差距逐渐缩小。

证明如下:

首先,我们使用__fastcall调用约定并传入两个参数。

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

int __fastcall method(int x, int y)

{

return x + y;

}

int main()

{

__asm mov eax, eax; // 此处设置断点

method(1, 2);

return 0;

}

可以看出函数内部和外部都没有清理堆栈的操作。这也就是__fastcall效率高的原因。因为寄存器就是属于CPU的,然后堆栈是内存,使用CPU进行操作的效率肯定会大于使用内存,所以我们使用寄存器的效率肯定比push传参效率高很多。

那么为什么没有平栈操作呢?因为我们没有使用堆栈,我们只是用了寄存器,并没有使用堆栈操作。但是当我们传入更多的参数的时候就需要用到堆栈了,因为__fastcall他只给我们提供了两个寄存器ECX/EDX可以用来传参。

使用四个参数:

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

int __fastcall method(int x, int y,int t,int u)

{

return x + y + t +u;

}

int main()

{

__asm mov eax, eax; // 此处设置断点

method(1, 2, 3, 4);

return 0;

}

F11进入函数内部查看:

通过四个参数的传递,证明了:

函数参数除了前两个参数使用寄存器、其他的依旧使用堆栈从右往左传参,并且是自身清理堆栈,不是调用者清理。

当参数越来越多的时候,__fastcall与其他调用约定的差距越来越小的原因是使用寄存器(CPU)的效率远远大于使用堆栈(内存),然而__fastcall约定也只能使用两个寄存器,当函数参数只有两个时,__fastcall可以完全使用寄存器进行函数传参,所以这个时候他和__cdecl和__stdcall的差距最大。随着参数越来越多,__fastcall依旧只能使用两个寄存器,这样一来参数越多,__fastcall使用内存的占比就越大,所以性能差距也就越来越小。

2.平衡堆栈的3种模式,谁释放参数空间

堆栈平衡是指在函数调用过程中,保证堆栈的栈顶指针(ESP)在函数返回前恢复到调用前的状态,以避免堆栈的混乱或溢出1。堆栈平衡的三种模式分别是:

外平栈:在函数外部使用 add esp, xx 指令来清理堆栈中的参数,其中 xx 是参数所占用的字节数。这种模式的优点是可以适应不同的调用约定,缺点是需要额外的指令来调整堆栈。

; 外平栈模式

push param3 ; 将参数3推入堆栈

push param2 ; 将参数2推入堆栈

push param1 ; 将参数1推入堆栈

call myFunction ; 调用函数

add esp, 12 ; 清理堆栈

myFunction:

push ebp ; 保存旧的基址

mov ebp, esp ; 设置新的基址

; 在函数内部使用 [ebp+xx] 来访问参数

; 在函数内部执行一些操作

pop ebp ; 恢复旧的基址

ret ; 返回

内平栈:在函数内部使用 ret xx 指令来返回并清理堆栈中的参数,其中 xx 是参数所占用的字节数。这种模式的优点是可以节省一条指令,缺点是需要函数的定义和调用都遵循相同的约定。

; 内平栈模式

push param3 ; 将参数3推入堆栈

push param2 ; 将参数2推入堆栈

push param1 ; 将参数1推入堆栈

call myFunction ; 调用函数

myFunction:

push ebp ; 保存旧的基址

mov ebp, esp ; 设置新的基址

; 在函数内部使用 [ebp+xx] 来访问参数

; 在函数内部执行一些操作

pop ebp ; 恢复旧的基址

ret 12 ; 返回并清理堆栈

平衡堆栈:在函数内部使用 push 和 pop 指令来平衡堆栈的变化,使得函数返回前堆栈的状态和调用前一致。这种模式的优点是可以保证堆栈的平衡,缺点是需要更多的指令来操作堆栈。

; 平衡堆栈模式

push param3 ; 将参数3推入堆栈

push param2 ; 将参数2推入堆栈

push param1 ; 将参数1推入堆栈

call myFunction ; 调用函数

myFunction:

push ebp ; 保存旧的基址

mov ebp, esp ; 设置新的基址

; 在函数内部使用 [ebp+xx] 来访问参数

; 在函数内部执行一些操作

; 在函数内部使用 pop 指令来平衡堆栈

pop ebp ; 恢复旧的基址

ret ; 返回

根据不同的模式,释放参数空间的责任也不同。在外平栈模式中,调用者负责释放参数空间;在内平栈模式中,被调用者负责释放参数空间;在平衡堆栈模式中,调用者和被调用者都不需要释放参数空间,因为堆栈已经平衡

3.debug模式下ebp寻址,release模式下esp寻址(工具Ida)

ebp寻址与esp寻址

源码:

#define _CRT_SECURE_NO_WARNINGS

// Function.cpp : Defines the entry point for the console application.

//

#include<stdio.h>

typedef void (*p)();

// ebp 与 esp访问

void InNumber()

{

// 局部变量定义

int nInt = 1;

scanf("%d", &nInt);

char cChar = 2;

scanf("%c", &cChar);

printf("%d %c\r\n", nInt, cChar);

}

// 两数交换

void AddNumber(int nOne)

{

nOne += 1;

printf("%d \r\n", nOne);

}

int GetAddr(int nNumber)

{

int nAddr = *(int*)(&nNumber - 1);

return nAddr;

}

struct tagTEST

{

int m_nOne;

int m_nTwo;

};

tagTEST RetStruct()

{

tagTEST testRet;

testRet.m_nOne = 1;

testRet.m_nTwo = 2;

return testRet;

}

void AsmStack()

{

__asm

{

push eax

pop eax

}

int nVar = 0;

scanf("%d", &nVar);

printf("AsmStack %\r\n", nVar);

}

void main()

{

AsmStack();

tagTEST test;

test = RetStruct();

int nAddr = GetAddr(1);

int nReload = (nAddr + *(int *)(nAddr - 4)) - (int)GetAddr;

int nNumber = 0;

scanf("%d", &nNumber);

AddNumber(nNumber);

InNumber();

}

main proc near ArgList=byte ptr -0Ch Arglist=byte ptr -5var_4=dword ptr -4argc=dword ptr 8argv=dword ptr echenvp=dword ptr10h push ebp mov ebp,esp sub esp,0ch mov eax, security_cookie xor eax,ebp mov [ebp var_4],eax push eax pop eax lea eax,[ebp ArgList] mov dword ptr [ebp ArgList],e push eax ;Arglist push offset aD ;"%d" call sub401050 push dword ptr [ebp ArgList]ArgList push offset Format"AsmStack %r\n" call sub401626 lea eax,[ebp ArgList] mov dword ptr [ebp ArgList],1 push eax ;Arglist push offset aD ;"%dn call sub401050 lea eax,[ebp Arglist] mov [ebp Arglist],2 push eax ;Arglist push offset ac ;"%c" call sub 401050 movsx eax,[ebp Arglist] push eax push dword ptr [ebp ArgList]ArgList push offset aDC ;"%d%c\r\n" call sub401826 mov ecx,[ebp var_4] add esp,2Ch xor ecx,ebp call sub_4010FE

在使用了esp寻址后,不必在每次进人函数后都调整栈底ebp,这样既减少了ebp的使用又省去了维护ebp的相关指令,因此可以有效提升程序的执行效率。但是,缺少了 ebp 就无法保存进入函数后的栈底指针,也就无法进行栈平衡检测。

在每次访向变量都需要计算,如果在函数执行过程中 esp 发生了改变,再次访问变量就需要重新计算偏移,这真是个令人头疼的问题。为了省去对偏移量的计算,方便分析,IDA在分析过程中事先将函数中的每个变量的偏移值计算出来,得出了一个固定偏移值,使用标号将其记录。IDA是采用负数作为偏移值,将其作为标号,参与变量寻址计算。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/72931.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

视频监控人员行为识别

视频监控人员行为识别技术基于先进的计算机视觉和深度学习算法。视频监控人员行为识别利用大量的视频数据进行模型训练,使算法能够学习和识别员工的不同行为特征。然后,将训练好的模型应用到实际的监控系统中,对员工的行为进行实时监测和分析。视频监控人员行为识别通过视频…

海面漂浮物垃圾识别检测

海面漂浮物垃圾识别检测系统基于先进的图像识别技术,海面漂浮物垃圾识别检测通过海边已经安装了的高清摄像设备,能够实时拍摄海面情况,并将图像信号传输到专业的图像分析平台进行处理。海面漂浮物垃圾识别检测通过对这些图像进行深度学习和算法分析,系统能够准确识别并分类…

luckfox1106初次使用

luckfox1106初次使用 下载rk驱动 https://files.luckfox.com/wiki/Luckfox-Pico/Software/DriverAssitant_v5.12.zip 安装驱动SD 卡烧录工具 https://files.luckfox.com/wiki/Luckfox-Pico/Software/SocToolKit_v1.98_20240705_01_win.zip 右键以管理员方式运行

考生作弊行为分析系统

考生作弊行为分析系统的核心特点如下:考生作弊行为分析系统通过收集和汇总所有考场的录像视频,考生作弊行为分析系统利用图像处理和智能算法对考生的行为进行分析和识别。这有助于发现任何异常行为,包括传统的作弊手段以及新型的作弊技巧。考生作弊行为分析系统经过算法服务…

防溺水智能预警系统解决方案

防溺水智能预警系统解决方案的关键特点如下:防溺水智能预警系统解决方案通过将前端摄像头等设备统一安装并集中接入系统,防溺水智能预警系统解决方案实现对危险水域的全方位监测。这些设备将覆盖所有可能发生溺水事故的区域,并通过网络连接到监控平台,实现信息的实时收集与…

医疗行业文件同步管理,如何保障准确性和高效性双管齐下?

医院、诊所、制药公司等医疗行业企业需要管理患者的健康记录、病历、药物研发数据等敏感信息。这些数据的丢失可能导致严重的法律后果和声誉损失,因此文件同步备份是必须的。常用的文件同步方式有以下几种: 云存储服务:云存储服务是最简单的文件同步方式之一。用户可以选择云…

k8s-Longhorn系统配置 20241017 -分布式存储

目录一 Longhorn存储部署1.1 Longhorn概述 1.2 Longhorn部署 1.5 动态sc创建 1.6 测试PV及PVC 1.7 Ingress暴露Longhorn 1.8 确认验证附加 Helm部署附0.1 helm安装 附0.2 helm安装 回到顶部 一 Longhorn存储部署1.1 Longhorn概述 Longhorn是用于Kubernetes的开源分布式块存储系…

AI实战篇:Spring AI + 混元 手把手带你实现企业级稳定可部署的AI业务智能体

前言 在之前的内容中,我们详细讲解了Spring AI的基础用法及其底层原理。如果还有小伙伴对此感到困惑,欢迎参考下面这篇文章,深入学习并进一步掌握相关知识:https://www.cnblogs.com/guoxiaoyu/p/18441709 今天,我们将重点关注AI在实际应用中的落地表现,特别是Spring AI如…