环境

32位MIPS平台,采用O32的ABI,linux操作系统,GCC编译器

需求

我们准备好C++对象的this指针,对象成员函数指针,对象的参数,然后模拟编译器来填充好参数并调用该对象成员函数. 需要注意,对象不固定,成员函数不固定,成员函数的参数个数以及类型都不固定.

MIPS的一些约定

MIPS寄存器的约定

Reg_MIPS

MIPS的O32标准ABI函数调用的约定

  • 输入参数要依次填充寄存器a0, a1, a2, a3,如果还有参数则需要填充到栈上
  • 调用函数在调用被调用函数时候,需要为被调函数预留16字节的栈上空间以备被调函数存储a0,a1,a2和a3的值,所以a0,a1,a2,a3寄存器放不下的参数需要放在被调函数的栈帧+16字节以上的空间
  • 输入参数的填充时候要遵守对齐原则,比如参数依次为int, long long那么int放在a0,long long放在a2,a3寄存器而不是a1, a2寄存器

实现

我们这里的实现机制是,把所有参数按照4字节分割,比如long long需要分割成两个4字节,然后我们把这个一一个分割后的4字节的数据按照MIPS O32的ABI的约定填充到指定寄存器和栈上,然后再调用该函数即可.

处理对齐

我们需要分析每种参数类型到底是4字节对齐还是8字节对齐等等,然后根据不同的对齐确定参数寄存器和栈的对齐

代码实现

我们以一个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]);
    ...
}

这样做很简单清楚,不过就难度太小了...

- EOF -

声明:本文采用BY-NC-SA协议进行授权.转载请注明: Mips下仿照编译器实现C++成员函数调用



comments powered by Disqus

Hitwebcounter.com Free