编写MIPS机器指令并从C执行
我正在尝试用C和MIPS编写一些自我修改的代码 因为我想稍后修改代码,所以我尝试编写实际的机器指令(与内联汇编相反),并尝试执行这些指令。有人告诉我,可以只调用一些内存,在那里编写指令,将C函数指针指向它,然后跳转到它。(我包括下面的示例) 我已经用我的交叉编译器(sourcery codebench toolchain)尝试过了,但它不起作用(是的,事后看来,我认为它确实看起来很幼稚)。我怎样才能正确地做到这一点编写MIPS机器指令并从C执行,c,assembly,mips,inline-assembly,mips32,C,Assembly,Mips,Inline Assembly,Mips32,我正在尝试用C和MIPS编写一些自我修改的代码 因为我想稍后修改代码,所以我尝试编写实际的机器指令(与内联汇编相反),并尝试执行这些指令。有人告诉我,可以只调用一些内存,在那里编写指令,将C函数指针指向它,然后跳转到它。(我包括下面的示例) 我已经用我的交叉编译器(sourcery codebench toolchain)尝试过了,但它不起作用(是的,事后看来,我认为它确实看起来很幼稚)。我怎样才能正确地做到这一点 #include <stdio.h> #include <st
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
void inc(){
int i = 41;
uint32_t *addone = malloc(sizeof(*addone) * 2); //we malloc space for our asm function
*(addone) = 0x20820001; // this is addi $v0 $a0 1, which adds one to our arg (gcc calling con)
*(addone + 1) = 0x23e00000; //this is jr $ra
int (*f)(int x) = addone; //our function pointer
i = (*f)(i);
printf("%d",i);
}
int main(){
inc();
exit(0);}
#包括
#包括
#包括
void公司(){
int i=41;
uint32_t*addone=malloc(sizeof(*addone)*2);//我们为asm函数分配malloc空间
*(addone)=0x20820001;//这是addi$v0$a0 1,它将一个添加到我们的arg(gcc调用con)
*(addone+1)=0x23e00000;//这是jr$ra
int(*f)(int x)=addone;//我们的函数指针
i=(*f)(i);
printf(“%d”,i);
}
int main(){
公司();
退出(0);}
我在这里遵循gcc调用约定,其中参数被传递到$a0,函数的结果预计在$v0中。我实际上不知道返回地址是否会被放入$ra(但我还不能测试它,因为我无法编译)。
我使用int作为指令,因为我正在编译MIPS32(因此32位int应该足够了)调用函数比直接跳到指令要复杂得多
- 参数是如何传递的?它们是存储在寄存器中还是被推送到调用堆栈中
- 如何返回值
- 返回跳转的返回地址在哪里?如果有递归函数,
不会将其剪切$ra
- 调用方或被调用方是否负责在被调用函数完成时弹出堆栈帧
对于这些问题,不同的编译器有不同的答案。虽然我从未尝试过类似于您所做的任何事情,但我会假设您必须编写符合约定的机器代码,然后告诉编译器您的函数指针使用该约定(不同的编译器有不同的方法执行此操作-).您不恰当地使用了指针。或者,更准确地说,您没有在应该使用指针的地方使用指针 试试这个尺码:
uint32_t *addone = malloc(sizeof(*addone) * 2);
addone[0] = 0x20820001; // addi $v0, $a0, 1
addone[1] = 0x23e00000; // jr $ra
int (*f)(int x) = addone; //our function pointer
i = (*f)(i);
printf("%d\n",i);
您可能还需要在写入内存后,但在调用内存之前,将其设置为可执行:
mprotect(addone, sizeof(int) * 2, PROT_READ | PROT_EXEC);
为了实现这一点,您可能还需要分配相当大的内存块(4k左右)因此,地址是页面对齐的。您还需要确保所讨论的内存是可执行的,并确保在写入dcache后正确地从dcache中刷新该内存,并在执行之前将其加载到icache中。如何做到这一点取决于mips机器上运行的操作系统 在Linux上,可以使用mprotect系统调用使内存可执行,使用cacheflush系统调用进行缓存刷新 编辑 例如:
#include <unistd.h>
#include <sys/mman.h>
#include <asm/cachecontrol.h>
#define PALIGN(P) ((char *)((uintptr_t)(P) & (pagesize-1)))
uintptr_t pagesize;
void inc(){
int i = 41;
uint32_t *addone = malloc(sizeof(*addone) * 2); //we malloc space for our asm function
*(addone) = 0x20820001; // this is addi $v0 $a0 1, which adds one to our arg (gcc calling con)
*(addone + 1) = 0x23e00000; //this is jr $ra
pagesize = sysconf(_SC_PAGESIZE); // only needs to be done once
mprotect(PALIGN(addone), PALIGN(addone+1)-PALIGN(addone)+pagesize,
PROT_READ | PROT_WRITE | PROT_EXEC);
cacheflush(addone, 2*sizeof(*addone), ICACHE|DCACHE);
int (*f)(int x) = addone; //our function pointer
i = (*f)(i);
printf("%d",i);
}
#包括
#包括
#包括
#定义paign(P)((char*)((uintpttr__t)(P)和(pagesize-1)))
uintptr_t页面大小;
void公司(){
int i=41;
uint32_t*addone=malloc(sizeof(*addone)*2);//我们为asm函数分配malloc空间
*(addone)=0x20820001;//这是addi$v0$a0 1,它将一个添加到我们的arg(gcc调用con)
*(addone+1)=0x23e00000;//这是jr$ra
pagesize=sysconf(_SC_pagesize);//只需执行一次
mprotect(PALIGN(addone),PALIGN(addone+1)-PALIGN(addone)+页面大小,
保护读取|保护写入|保护执行|);
cacheflush(addone,2*sizeof(*addone),ICACHE | DCACHE);
int(*f)(int x)=addone;//我们的函数指针
i=(*f)(i);
printf(“%d”,i);
}
请注意,我们将包含代码的整个页面设置为可写和可执行。这是因为内存保护在每个页面都有效,我们希望malloc能够继续使用页面的其余部分对于其他方面,您可以使用
valloc
或memalign
来分配整个页面,在这种情况下,您可以使代码成为只读的可执行文件。OP编写的代码使用Codesourcery mips linux gnu gcc编译时不会出错
如上所述,MIPS上的自修改代码要求在写入代码后指令缓存与数据缓存同步。MIPS体系结构的MIPS32R2版本添加了SYNCI
,这是一条用户模式指令,可以满足您的需要。所有现代MIPS CPU都实现了MIPS32R2,包括gSYNCI
内存保护是MIPS上的一个选项,但大多数MIPS CPU都没有选择此功能,因此在大多数真正的MIPS硬件上可能不需要使用mprotect系统调用
请注意,如果使用除-O0
之外的任何优化,编译器可以并且确实优化了*addone
的存储和函数调用,这会破坏代码。使用volatile
关键字可防止编译器执行此操作
以下代码生成了正确的MIPS程序集,但我手头没有MIPS硬件可供测试:
int inc() {
volatile int i = 41;
// malloc 8 x sizeof(int) to allocate 32 bytes ie one cache line,
// also ensuring that the address of function addone is aligned to
// a cache line.
volatile int *addone = malloc(sizeof(*addone) * 8);
*(addone) = 0x20820001; // this is addi $v0 $a0 1
*(addone + 1) = 0x23e00000; //this is jr $ra
// use a SYNCI instruction to flush the data written above from
// the D cache and to flush any stale data from the I cache
asm volatile("synci 0(%0)": : "r" (addone));
volatile int (*f)(int x) = addone; //our function pointer
int j = (*f)(i);
return j;
}
int main(){
int k = 0;
k = inc();
printf("%d",k);
exit(0);
}
当你说“它不工作”时,会发生什么情况呢?你将
malloc
返回的指针存储在一个int中。int是否足够大?为什么不使用合适的指针类型?另外,你确定你的int是指令的正确大小吗?我对MIPS了解不够,但是当我完成这类工作时(1)您需要确保内存具有使用mprotect
的正确权限,(2)如果体系结构具有不一致的单独指令/数据缓存,则可能需要刷新指令和数据缓存。请记住jr
在将控制权转移到t之前从其延迟槽执行指令