[英]Writing ARM machine instructions and executing them from C (On the Raspberry pi)
[英]Writing MIPS machine instructions and executing them from C
我正在尝试用C和MIPS编写一些自我修改的代码。
由于稍后我想修改代码,因此我尝试编写实际的机器指令(而不是内联汇编),并尝试执行这些指令。 有人告诉我,可以只分配一些内存,在其中写入指令,将C函数指针指向该内存,然后跳转到该内存。 (我包括以下示例)
我已经使用我的交叉编译器(源代码平台工具链)进行了尝试,但是它不起作用(是的,在我看来,我想它看起来确实很幼稚)。 我该怎么做呢?
#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);}
我在这里遵循gcc调用约定,将参数传递给$ a0,并且函数的结果应位于$ v0中。 我实际上不知道是否将返回地址放入$ ra(但由于无法编译,因此我目前无法对其进行测试。我将int用于指令,因为我正在编译MIPS32(因此为32位int应该足够)
您不恰当地使用了指针。 或者,更准确地说,您没有在应该使用的位置使用指针。
尝试以下尺寸:
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中正确刷新,并在执行之前将其加载到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);
}
请注意,我们使包含代码的整个页面都是可写和可执行的。 那是因为内存保护在每个页面上都有效,并且我们希望malloc能够继续将页面的其余部分用于其他用途。 您可以改用valloc
或memalign
来分配整个页面,在这种情况下,可以使代码安全地变为只读可执行文件。
OP的书面代码使用Codesourcery mips-linux-gnu-gcc编译时没有错误。
正如其他人在上面提到的那样,在MIPS上进行自我修改的代码要求在编写代码后将指令高速缓存与数据高速缓存同步。 MIPS体系结构的MIPS32R2版本添加了SYNCI
指令 ,这是一种用户模式指令,可满足您在此处的需要。 所有现代MIPS CPU都实现MIPS32R2,包括SYNCI
。
内存保护是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);
}
调用函数比跳转到指令要复杂得多。
如何传递参数? 它们是存储在寄存器中还是被压入调用堆栈?
值如何返回?
返回跳转的返回地址在哪里? 如果您具有递归函数,则$ra
不会削减它。
当被调用函数完成时,调用方或被调用方是否负责弹出堆栈框架?
对于这些问题,不同的调用约定有不同的答案。 尽管我从未尝试过像您正在做的事情,但是我认为您必须编写机器代码来匹配约定,然后告诉编译器您的函数指针使用该约定(不同的编译器具有不同的处理方式这-gcc使用功能属性来做到这一点 )。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.