[英]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.