裸函数和调用约定

news/2024/10/4 1:27:16

一、裸函数

在正常的函数编译中,即使函数没有定义函数体的内容,编译器也依然会编译出部分汇编指令用来执行函数。

image-20240606124917091

但是如果定义一个裸函数

void _declspec(naked) test()

编译器将不会操作这个函数,不会给其生成汇编指令(但是会在主函数中生成call和jmp指令指向这个裸函数)

可以看到此处全为int 3(断点),编译器没有生成汇编指令,此时函数继续运行将会报错

如果要让这个裸函数正常运行,我们至少要给出一个retn指令

void _declspec(naked) test()
{_asm  //在函数中添加汇编指令的格式 _asm __asm均可{retn}
}

image-20240606130644678

几点注意

  • 函数的参数在函数调用之前就已经存在堆栈中了
  • 传参顺序调用约定决定
  • 函数的局部变量存在函数缓冲区
  • 函数返回值存在eax或内存
  • 必须平衡堆栈

二、函数的调用约定

1.常见的函数调用约定

调用约定 参数入栈顺序 平衡堆栈
__cdecl 从右至左 外平栈
__stdcall 从右至左 内平栈
__fastcall 前两个参数存ECX/EDX
剩下的:从右至左入栈
无需平衡堆栈/内平栈

2.__cdecl

c与c++默认使用__cdecl这种调用约定,使用堆栈传递参数,从最后一个参数开始入栈,平衡堆栈为在函数外平衡,即add ebp,X的形式。

例子:

int __cdecl test(int x,int y)
{return x+y;
}
int main(int argc, char* argv[])
{test(1,2);return 0;

image-20240606135043182

3.__stdcall

win32操作系统的API函数使用的是__stdcall这种调用约定,传参规则和__cdecl一样,但是此调用约定使用内平栈,即函数返回时使用ret x指令,表示修改eip的值为返回地址并且将esp加立即数。

例子:

int __stdcall Plus2(int x,int y){return x + y;
}void main(int argc, char* argv[]){Plus2();
}

image-20240616182701503

image-20240616182743783

因为有两个参数,所以ret 8完成平栈操作

4.__fastcall

​ 使用此调用约定时,前两个参数直接使用寄存器传参,并且这两个参数不需要平衡堆栈,所以当参数少时,使用此约定速度快。

​ 优势:如果一个函数需要不停调用且参数数量少,那么使用__fastcall效率会高很多,多余2的参数依然会从后到前存入堆栈,并且也是使用内平栈。

例子1:

int __fastcall test(int x,int y)
{return x+y;
}
int main(int argc, char* argv[])
{test(1,2);return 0;
}

image-20240616184149240

image-20240616184440008

可以看到函数没有执行平衡堆栈操作,但需要注意的是:寄存器中的值在参与运算时会先存入缓冲区中,然后再进行运算

例子2:

int __fastcall test(int x,int y,int z,int a)
{return x+y+z+a;
}
int main(int argc, char* argv[])
{test(1,2,3,4);return 0;
}

image-20240616184722410

image-20240616184808141

依然是在call之前将参数存入寄存器,然后多余参数依然放入堆栈,最后使用内平栈(只需要平衡多余参数)

判断一个函数参数个数方法

1.错误方法

​ 如果函数使用内平栈,不能单纯根据ret后面的数判断。因为参数可能用push存入堆栈,还可能用mov存入寄存区。平衡堆栈只需要平衡内存中的那一部分,寄存器部分不需要平衡。

​ 也不能根据call指令前面的push和mov指令数量判断参数。可能push的参数不是相邻call指令需要用到的参数。

2.正确方法

​ 一般情况:把push入栈指令或mov指令,和add指令或ret指令结合起来看。

​ 稳妥办法:进入call指令中分析汇编

00401050   push        ebp			    
00401051   mov         ebp,esp				
00401053   sub         esp,48h				
00401056   push        ebx				
00401057   push        esi				
00401058   push        edi				
00401059   push        ecx				
0040105A   lea         edi,[ebp-48h]	
0040105D   mov         ecx,12h				
00401062   mov         eax,0CCCCCCCCh				
00401067   rep stos    dword ptr [edi]				
00401069   pop         ecx				
0040106A   mov         dword ptr [ebp-8],edx  //这里有一个,而且发现函数中没有给edx赋值的语句
0040106D   mov         dword ptr [ebp-4],ecx  //这里有一个,而且发现函数中没有给ecx赋值的语句
00401070   mov         eax,dword ptr [ebp-4]			
00401073   add         eax,dword ptr [ebp-8]			
00401076   add         eax,dword ptr [ebp+8]  //结合下面的ret指令判断
00401079   mov         [g_x (00427958)],eax	  //修改全局变量	
0040107E   pop         edi				
0040107F   pop         esi				
00401080   pop         ebx				
00401081   mov         esp,ebp				
00401083   pop         ebp				
00401084   ret         4			   //上面两个寄存器,再结合这里的4,可以基本确定函数有3个参数	

四、使用裸函数实现功能

int Plus(int x,int y,int z)	
{	int a = 2;int b = 3;int c = 4;return x+y+z+a+b+c;
}int main(int argc, char* argv[]){Plus(5,6,7);   return 0;
}

裸函数定义:

int __declspec(naked) test(int x,int y,int z)
{__asm{push ebp//提升堆栈mov ebp,espsub esp,0x40   //保留现场push ebxpush esipush edi//填充缓冲区lea edi,dword ptr es:[ebp-0x40]mov ecx,0x10mov eax,0xccccccccrep stosd    //rep stos dword ptr ds:[edi]//局部变量存入缓冲区mov dword ptr es:[ebp-0x4],0x2mov dword ptr es:[ebp-0x8],0x3mov dword ptr es:[ebp-0xC],0x4//实现函数功能mov eax,dword ptr es:[ebp+0x8]add eax,dword ptr es:[ebp+0xC]add eax,dword ptr es:[ebp+0x10]add eax,dword ptr es:[ebp-0x4]add eax,dword ptr es:[ebp-0x8]add eax,dword ptr es:[ebp-0xC]//恢复现场pop edipop esipop ebx//降低堆栈mov esp,ebppop ebp//函数返回ret}
}
int main(int argc, char* argv[])
{test(5,6,7);return 0;
}

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

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

相关文章

帮猪猪修修改的代码2016年的代码记录

这是一个图片轮播的代码,但是它们的是css 动画,当时代码运行不了,我花了二天才修改,现在记录一下,凭回忆用。<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>网易科技</title> <meta name="…

input的时候, 我输入一条链接可以运行,但输入两条会报错?

大家好,我是Python进阶者。 一、前言 前几天在Python交流群【Cappuccino】问了一个Python基础的问题,问题如下:再問一個沒那麼複雜的問題,請教一下,當我改成input 的時候, 我輸入一條鏈接可以運行,但輸入兩條就會報錯,請問多於一條鏈接的輸入格式是怎樣呢? 二、实现过…

Nivdia向量数据库图检索最新标杆——CAGRA

本文连接:https://wanger-sjtu.github.io/CARGA/ CAGRA 是 N社在RAFT项目中 最新的 ANN 向量索引。这是一种高性能的、 GPU 加速的、基于图的方法,尤其是针对小批量情况进行了优化,其中每次查找只包含一个或几个查询向量。 与其他像HNSW、SONG等这类基于图的方法相似,CAGRA…

[转]32th@探索C++的模板元编程:揭秘零运行时开销的高性能编程技术@20240616

C++的模板元编程是一种强大的编程技术,它能够在编译时进行计算,生成高效的代码,而且不需要任何运行时开销。这种技术被广泛应用于高性能计算、游戏开发、金融等领域,是C++程序员必须掌握的技能之一。本文将深入探讨C++模板元编程的原理和实现方式,并通过代码案例来展示其强…

Docker部署SpringBoot项目

准备 服务器安装Docker 下载docker Windows版本并登录 根据项目需要在项目根目录下创建Dockerfile文件 # 使用官方的 OpenJDK 8 作为基础镜像 FROM openjdk:8-jdk-alpine# 维护者信息 LABEL maintainer="name"# 添加一个应用程序的工作目录 WORKDIR /app# 将 JAR 文件…

spring-5-事务

参考: spring 事务失效的 11 种场景 一、事务基础 1.什么是事务 事务是指作为单个逻辑工作单元执行的一系列操作,要么全部成功执行,要么全部失败回滚到初始状态,保证数据的一致性和完整性。事务具有ACID特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isola…

博客园入门

一、语言: 通常博客都是采用 markdown语言。 1.1 markdown语言教程1:https://markdown.com.cn/basic-syntax/ 教程2:https://forum-zh.obsidian.md/t/topic/435/ 教程3:https://markdown.com.cn/intro.html1.2 Html 语言 (1)markdown 提供了可无缝转换为 HTML 的轻量级语…

【Maven】Maven依赖管理

01 依赖配置 依赖:指当前项目运行所需要的jar包。一个项目中可以引入多个依赖: 例如:在当前工程中,我们需要用到logback来记录日志,此时就可以在maven工程的pom.xml文件中,引入logback的依赖。具体步骤如下:在pom.xml中编写标签在标签中使用引入坐标定义坐标的 groupId、…