使用汇编来传递不定参数
前言
有时候我会想能不能 : 有个统一的入口函数func(id, …), 只要输入id和不定参数args, 例如输入id_X,args_X, 就能调用到id_X对应的func_X,而且传入args_X给funcX
即: func(id_x, args_x) ==> func_x(args)
为什么我有这个想法呢?
例如: 我们的类工厂创建某些product的时候, 因为每一个类的构造函数不一样,因而很难统一一个CreateProduct函数
C++有不定参数 … ,但是输入之后就要通过va_list来传递. 这样传递给构造函数是非常不优雅的~
解决原理
下面是我的解决方法:
PS:
(1)函数都是 __cdecl, 不同的的调用方法会导致编译出来的汇编不一样. 另外,选 __cdecl 的另外一个原因是因为它有不定参数
(2)我的机子是win32, 用的是vc
先来清楚一些概念
栈底为高地址, 等于 寄存器 EBP的值
栈顶为低地址, 等于 寄存器 ESP的值
函数中的参数是压人栈来传递的, 返回值存在EAX里面
如:
(1) 调用 func(1, 2), 对应的汇编:
push 2
push 1
call func
这时候栈为
| 返回地址(call压进来的) |
| 1 |
| 2 |
(2) 在函数里面 func里面,开始做的汇编处理:
push ebp
mov ebp, esp
sub esp, 40+xx //抬高堆栈, xx为局域变量的大小
push ebx
push esi
push edi
这时候栈为
| edi |
| esi |
| ebx |
| 大小为40+xx |
| ebp |
| 返回地址 |
| 1 |
| 2 |
(3)函数处理完成之后:
先将结果保存到EAX, 然后将栈弹出到如(1)的状态, 最后调用ret指令, 弹出返回地址, 跳到原来的函数里面去
(4)回到原来的函数, 把最后压进去的参数弹出, 然后就读EAX
汇编代码:
add esp, 8 // 8为两个int参数的大小
解决思路
入口函数 bool func(int id, ...)
目标函数 bool func_xx(int* ret, int x, int y, int k)
(1)先获取到目标函数的地址,保存到eax (后面修改栈后, 那些局域变量都没用了)
(2)然后把函数的状态回退到下面的状态
栈:
| 返回地址 |
| id |
| 不定参数 |
(3)弹出返回地址, 并且用全部变量保存
栈:
| id |
| 不定参数 |
(4)弹出id, 并用全局变量保存 这个时候的esp
栈:
| 不定参数 |
(5)压人一个label RETHANDLE
栈:
| label RETHANDLE |
| 不定参数 |
这个状态func_xx就能处理传进来的不定参数了
而且完成之后调回我们的 RETHANDLE
(6)jmp eax(跳到目标函数里面,上面保存了)
(7)这里就是RETHANDLE 了…. 等函数返回,
函数返回后, 栈什么东西都没了~~~~
(8)抬高栈到保存的esp的高度, 为的就是返回而已~
栈:
| 乱码.. 但是和(3)的高度一样 |
(9)压人 函数应该返回地址
栈:
| 函数返回地址 |
| 乱码 |
(10)终于返回了 :-)
用掉了那个返回地址, 调用代码也帮我们清理了乱码~~更神奇的是结果保存到EAX了....
代码
如果我说得不明白, 看代码吧~
#include <stdio.h>
#define id_xx 1
bool func_xx(int* ret, int x, int y, int k)
{
*ret = x + y + k;
return true;
}
#define id_yy 2
bool func_yy(int* ret, int x, int y)
{
*ret = x * y;
return true;
}
int retAddrVal;
int espVal;
bool func(int id, ...)
{
int funcTarget;
if(id == id_xx)
funcTarget = (int)func_xx;
else
if(id == id_yy)
funcTarget = (int)func_yy;
else
return false;
__asm
{
mov eax, funcTarget
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
pop retAddrVal
mov espVal, esp
add esp, 4 // sizeof(id)
push offset RETHANDLE
jmp eax
RETHANDLE:
mov esp, espVal
push retAddrVal
retn
}
return false;
}
int main()
{
int k = 0;
if(func(id_xx, &k, 100, 2, 1000))
printf("%d\n", k);
if(func(id_yy, &k, 100, 2))
printf("%d\n", k);
return 0;
}
后记
上面的代码可以顺利编译过, 经过实验, 能通过ID指定的目标函数, 然后把不定参数传到传到目标函数.
当然, 这个代码会受到编译器和x86, x64机器的影响, 但是思路还是一样的...
另外也没做线程保护.
Just for fun, by YJL…..
浙公网安备 33010602011771号