弄着玩的。功能是简单的实现函数转发,即
调用CALL(func),转为调用func(),
调用CALL(func, arg1, arg2) ,转为调用func(arg1, arg2)
代码中,宏CALL/STDCALL分别用来调用 __cdecl/__stdcall 调用规定的函数
unsafe_call 两者都可调用,但它不是多线程安全的。
代码只支持x86 32位, 除内嵌汇编部分,尽量符合C++11标准。
原理:
刚进入函数时,
[esp] 函数返回地址
[esp + 4] 第一个参数,即转发函数的地址
[esp + 8] 第二个参数,即转发函数的的第一个参数
...
只要写三行汇编指令实现一个c_call函数,就可调用转发函数
pop eax ; eax为函数返回地址
xchg dword ptr[esp], eax ; eax为转发函数的地址,[esp]为函数返回地址
jmp eax
当转发函数是__cdecl,即转发函数不会调节栈,由于在c_call,pop eax,使esp多加了4,因而在调用完c_call后应该手动将esp值减4,保证栈平衡。
当转发函数是__stdcall,转发函数会调节栈,调用转发函数完毕后,栈已经保持平衡,因而调用c_call完毕,不应该进行栈指针调节。似乎将c_call的调用改为__stdcall即可,但实际上c_call有变长参数,改成__stdcall没效果,每次调用编译器还是会自动生成调节栈指针代码。因而只能每次调用完毕,编译器给esp加了多少,就手动减多少。(编译器不一定会生成 call xxxx; add esp, xx这样的代码,通过改函数返回地址,忽略后面的add esp, xx指令是很糟糕的做法。)
call_redirect
1 #include <cstdio>
2 #include <cstdarg>
3 #include <windows.h>
4
5 #define CALL() c_call(__VA_ARGS__); ASM_SUB_ESP(4);
6 #define STDCALL() c_call(__VA_ARGS__); ASM_SUB_ESP(MACRO_ARGS(__VA_ARGS__) * 4);
7
8 #define MACRO_EXPAND(x) x
9 #define MACRO_NTH_ARG(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, ) a9
10 #define MACRO_ARGS() \
11 MACRO_EXPAND(MACRO_NTH_ARG(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
12
13 #if __GNUC__
14 // please enable option, -masm=intel
15 #define ASM_SUB_ESP(x) asm("lea esp, dword ptr[esp - %0]" : : "i"(x))
16
17 int c_call(void* pfn, ) asm("c_call");
18 asm(" \n\
19 c_call: \n\
20 pop eax; \n\
21 xchg dword ptr[esp], eax; \n\
22 jmp eax; \n\
23 ");
24
25 int unsafe_call(void* pfn, ) asm("unsafe_call");
26 asm(" \n\
27 .lcomm old_ret_addr, 4 \n\
28 .lcomm old_esp_value,4 \n\
29 unsafe_call: \n\
30 pop dword ptr old_ret_addr; \n\
31 mov dword ptr old_esp_value, esp; \n\
32 mov eax, dword ptr[esp]; \n\
33 mov dword ptr[esp], offset last; \n\
34 jmp eax; \n\
35 last: \n\
36 mov esp, dword ptr old_esp_value; \n\
37 jmp dword ptr old_ret_addr; \n\
38 ");
39
40 #elif _MSC_VER
41 #define ASM_SUB_ESP(x) __asm lea esp, dword ptr [esp - x]
42
43 __declspec(naked) int c_call(void* pfn, )
44 {
45 __asm {
46 pop eax;
47 xchg dword ptr[esp], eax;
48 jmp eax;
49 }
50 }
51 __declspec(naked) int unsafe_call(void* pfn, )
52 {
53 static void* old_ret_addr;
54 static void* old_esp_value;
55 __asm {
56 pop old_ret_addr;
57 mov old_esp_value, esp;
58 mov eax, dword ptr[esp];
59 mov dword ptr[esp], offset last;
60 jmp eax;
61 last:
62 mov esp, old_esp_value;
63 jmp old_ret_addr;
64 }
65 }
66
67 #else
68 #error "only gcc and msvc are supported"
69 #endif
70
71
72 void show(const char* format, )
73 {
74 va_list args;
75 va_start(args, format);
76 vprintf(format, args);
77 va_end (args);
78 }
79
80 void mysleep() { Sleep(10); }
81
82 void test1()
83 {
84 int a = 5, b = 6;
85 show("%d + %d = %d\n", a, b, a + b);
86 CALL((void*)printf, "%d + %d = %d\n", a, b, a + b);
87 CALL((void*)mysleep);
88 CALL((void*)show, "%d + %d = %d\n", a, b, a + b);
89 }
90
91 void test2()
92 {
93 STDCALL((void*)MessageBoxA, NULL, "text1", "caption1", MB_OK);
94 STDCALL((void*)Sleep, 10);
95 STDCALL((void*)MessageBoxA, NULL, "text2", "caption2", MB_OK);
96 STDCALL((void*)Sleep, 100);
97 }
98
99 void test3()
100 {
101 int a = 5, b = 6;
102 unsafe_call((void*)printf, "%d + %d = %d\n", a, b, a + b);
103 unsafe_call((void*)mysleep);
104 unsafe_call((void*)show, "%d + %d = %d\n", a, b, a + b);
105 unsafe_call((void*)MessageBoxA, NULL, "text1", "caption1", MB_OK);
106 unsafe_call((void*)Sleep, 10);
107 unsafe_call((void*)MessageBoxA, NULL, "text2", "caption2", MB_OK);
108 unsafe_call((void*)Sleep, 100);
109 }
110
111 int main()
112 {
113 test1();
114 test2();
115 test3();
116 return 0;
117 }
118
119
120