32位MIPS平台,采用O32的ABI,linux操作系统,GCC编译器
我们准备好C++对象的this指针,对象成员函数指针,对象的参数,然后模拟编译器来填充好参数并调用该对象成员函数. 需要注意,对象不固定,成员函数不固定,成员函数的参数个数以及类型都不固定.
我们这里的实现机制是,把所有参数按照4字节分割,比如long long需要分割成两个4字节,然后我们把这个一一个分割后的4字节的数据按照MIPS O32的ABI的约定填充到指定寄存器和栈上,然后再调用该函数即可.
我们需要分析每种参数类型到底是4字节对齐还是8字节对齐等等,然后根据不同的对齐确定参数寄存器和栈的对齐
我们以一个VARIANT结构体为例,对于其他类型可以做同样的处理
VARIANT
#define DWORD 4 #define __ASM_CFI(str) str #define __ASM_DEFINE_FUNC(name,suffix,code) asm(".text\n\t.align 4\n\t.globl " #name suffix "\n\t.type " #name suffix ",@function\n" #name suffix ":\n\t.cfi_startproc\n\t" code "\n\t.cfi_endproc\n\t.previous"); #define __ASM_GLOBAL_FUNC(name,code) __ASM_DEFINE_FUNC(name,"",code) typedef intptr_t (CALLBACK *FARPROC)(); extern "C" uintptr_t CDECL call_method(void *func, int nb_args, const unsigned int *args, int nb_fargs, const double* fargs); __ASM_GLOBAL_FUNC( call_method, ".set noreorder\r\n" ".set arch=mips3\r\n" "sw $16,-28($29)\r\n" // $16压栈 "sw $17,-32($29)\r\n" // $16压栈 "move $16, $29\r\n" // store $29 "sll $17, $5, 2\r\n" "and $17, $17,0xffffff80\r\n" "addiu $17, $17, 256\r\n" // stack size "subu $29,$29,$17\r\n" "sw $31,-4($16)\r\n" //将返回地址压入堆栈 "sw $30,-8($16)\r\n" //将fp压入堆栈 "sdc1 $f12,-16($16)\r\n" //将f12到f15寄存器保存到堆栈中 "sdc1 $f14,-24($16)\r\n" "move $30,$29\r\n" //将$29保存到fp "sw $4,0($16)\r\n" //将参数*func压栈 "sw $5,4($16)\r\n" //将参数nb_args压栈 "sw $6,8($16)\r\n" //将参数*args压栈 "sw $7,12($16)\r\n" //将参数nb_fargs压栈 "move $11,$7\r\n" //将nb_fargs存入$11 "lw $10,16($29)\r\n" //将*fargs存入$10 "addu $11,$11,-2\r\n" //nb_fargs-2 "bltz $11, 1f\r\n" //如果nb_fargs-2<0,跳转到1 "nop \r\n" "ldc1 $f14,8($10)\r\n" //将*fargs[1]移入$f14(高位隐含存入$f15) "1: addu $11,$11,1\r\n" "bltz $11,2f\r\n" //如果nb_fargs-1<0,跳转到2 "nop \r\n" "ldc1 $f12,0($10)\r\n" //将*fargs[2]移入$f12,$f13 "2: move $8,$5\r\n" //将nb_args 移入$8 "move $9,$6\r\n" //将*args移入$9 "blez $8, 4f\r\n" //如$8小于等于0调转到call func "nop\r\n" "lw $4,($9)\r\n" //将args[0]移入$4 "addu $8,$8,-1\r\n" //$8=$8-1 "blez $8,4f\r\n" //如$8小于等于0跳转到call func "nop\r\n" "lw $5,4($9)\r\n" //将args[1]移入$5 "addu $8,$8,-1\r\n" //$8=$8-1 "blez $8, 4f\r\n" //如$8小于等于0跳转到call func "nop\r\n" "lw $6,8($9)\r\n" //将args[2]移入$6 "addu $8,$8,-1\r\n" //$8=$8-1 "blez $8,4f\r\n" //如$8小于等于0跳转到call func "nop \r\n" "lw $7,12($9)\r\n" //将args[3]移入$7 "addu $9,$9,16\r\n" //将指针移到args[4] "addu $11,$29,16\r\n" //$11=$29+16 "3: addu $8,$8,-1\r\n" //$8=$8-1 "blez $8,4f\r\n" //如$8小于等于0跳转到call func "nop\r\n" "lw $10,($9)\r\n" //将args[n]移入$10 "sw $10,($11)\r\n" //将$10存入$29+16+m "addu $9,$9,4\r\n" //指向args[n+1] "addu $11,$11,4\r\n" //指向$29+16+m "b 3b\r\n" //跳转到 "nop\r\n" "4: lw $25,0($16)\r\n" //将*func存入$25 "nop\r\n" "jalr $25\r\n" //call func "nop\r\n" "ldc1 $f14,-24($16)\r\n" "ldc1 $f12,-16($16)\r\n" "lw $30,-8($16)\r\n" "lw $31,-4($16)\r\n" "lw $16, -28($16)\r\n" "addu $29,$29,$17\r\n" "lw $17, -32($29)\r\n" "jr $31\r\n" "nop\r\n" ".set reorder\r\n") struct VARIANT { WORD a; //2 字节 WORD b; WORD c; WORD d; INT64 e; // 8 字节 }; class A{ public: int fun1(VARIANT a, int c, int d){ c = c + d; return 12345; } }; int main(int argc,char *argv[]) { VARIANT var = {0,1,2,3,4}; A a; std::vector<uint32> args(100); int argspos = 0; //this指针作为第一个参数 args[argspos++] = (unsigned int)&a; //准备第二个参数,注意对齐 if(argspos % 2 == 1) argspos++; memcpy( &args[argspos], &var, sizeof(var) ); argspos += sizeof(var) / sizeof(DWORD); //准备第三和第四个参数 args[argspos++] = 3333; args[argspos++] = 4444; //调用汇编编写的模拟调用 int ret = call_method2((void*)(&A::fun1), argspos, &args[0], 0, NULL); }
这个只是简单的以VARIANT举例,为了更加通用的处理各种参数类型,需要对参数类型进行分类讨论然后采用与此类似的操作.
其实,这段实现也可以完全C语言化,那么我们就需要根据参数个数写一个大switch…case针对每种参数个数显示的调用func:
switch(num_args){ case 1: func(this, args[0]); case 2: func(this, args[0], args[1]); case 3: func(this, args[0], args[1], args[2]); ... }
这样做很简单清楚,不过就难度太小了…
声明:本文采用BY-NC-SA协议进行授权.转载请注明: Mips下仿照编译器实现C++成员函数调用