本文展示一個在 Windows x86 平台上,透過內嵌組合語言實現的 C 函數動態調用機制。可支援 __stdcall、__cdecl、thiscall 等呼叫慣例,並能處理整數、浮點數、結構體回傳值。核心參考自 UnderC 直譯器原始碼。
// 呼叫慣例旗標
const int DC_STDCALL = 0, DC_CDECL = 1, DC_QWORD = 2, DC_NOWORD = 4, DC_RET_OBJ = 8, DC_RET_VAL = 24;
void *gObjectReturnPtr = NULL;
void callfn(void* fn, int args[], int argc, void *optr, int flags, void *buff) {
int sz = sizeof(int) * argc;
_asm {
mov ecx, argc
mov ebx, args
// 參數由右至左壓入堆疊
a_loop:
cmp ecx, 0
jz a_out
mov eax, [ebx + 4*ecx]
push eax
dec ecx
jmp a_loop
a_out:
// thiscall: this 指標放入 ecx
mov ecx, optr
call fn
// cdecl 需手動清理堆疊
mov ecx, flags
test ecx, DC_CDECL
jz a_over
add esp, sz
a_over:
// 處理回傳值
test ecx, DC_RET_OBJ
jz a_again
cmp ecx, DC_RET_VAL
jl a_skip
// GCC 結構體回傳約定 (edx:eax)
mov ebx, gObjectReturnPtr
mov [ebx], eax
mov [ebx+4], edx
jmp a_finish
a_skip:
sub esp, 4
a_again:
mov ebx, buff
test ecx, DC_QWORD
jnz a_dbl
mov dword ptr [ebx], eax
jmp a_finish
a_dbl:
fstp qword ptr [ebx]
a_finish:
}
}
class Function {
void *addr; int flags; void *thisPtr;
union CalRet { __int64 ll; struct { int lo, hi; } i2; char b8[8]; } ret;
public:
Function(void *func, int flag) : addr(func), flags(flag), thisPtr(NULL) {}
Function(HMODULE hmod, const char *name, int flag)
: addr(GetProcAddress(hmod, name)), flags(flag), thisPtr(NULL) {}
Function(void *func, void *thisPtr, int flag)
: addr(func), thisPtr(thisPtr), flags(flag) {}
int operator()(int nargs, ...) {
callfn(addr, &nargs, nargs, thisPtr, flags, ret.b8);
return ret.i2.lo;
}
};
class DllModule {
HMODULE hmod; char dllName[128];
public:
DllModule(const char *name) { hmod = LoadLibrary(name); strncpy(dllName, name, 127); }
~DllModule() { FreeLibrary(hmod); }
void* get(const char *name) { return GetProcAddress(hmod, name); }
Function cFunc(const char *name, void *thisPtr = NULL) { return Function(get(name), thisPtr, DC_CDECL); }
Function winFunc(const char *name, void *thisPtr = NULL) { return Function(get(name), thisPtr, DC_STDCALL); }
};
int main() {
DllModule cdll("msvcrt.dll");
// cdecl: printf
cdll.cFunc("printf")(2, "Hello, %s\n", "baby");
DllModule user32("user32.dll");
// stdcall: MessageBoxW
user32.winFunc("MessageBoxW")(4, 0, L"Hello", L"OK", 0);
// cdecl: strstr
cdll.cFunc("strstr")(2, "Caller has already cleaned the stack", "has");
// thiscall: 成員函數
class A { public: void show(char *msg) { printf("msg:%s\n", msg); } };
A a;
Function(addressof(A::show), &a, DC_STDCALL)(1, "Class function call");
// stdcall: FindWindowW
user32.winFunc("FindWindowW")(2, NULL, L"Google - Chrome");
return 0;
}
// 取得成員函數位址 (MSVC 專用)
template<class T> void* addressof(T t) {
void *ret; __asm { mov eax, t; mov ret, eax; } return ret;
}
__cdecl 由呼叫者清理 (add esp, sz);__stdcall/thiscall 由被呼叫者清理 (RET n)。this 指標置於 ECX (MSVC 約定)。EAX;64 位整數/浮點數在 EDX:EAX 或 ST(0);大型結構體由呼叫者配置空間並傳入隱藏指標 (GCC 約定)。operator()(int nargs, ...) 配合 &nargs 取得參數陣列起始位址。libffi、Boost.TypeErasure 或 C++17 std::any/std::function 等型別安全方案。