我怎么能在 运行-time 在 c 中定义一个函数
How could i define a function at run-time in c
我正在尝试在 运行 时间用 c 语言在 arm cpu(cortex a72) 中定义和调用一个函数。为此,我实现了如下代码:
#include <stdio.h>
#include <sys/mman.h>
char* ibuf;
int pbuf = 0;
#define ADD_BYTE(val) do{ibuf[pbuf] = val; pbuf++;} while(0)
void (*routine)(void);
void MakeRoutineSimpleFunc(void)
{
//nop
ADD_BYTE(0x00);
ADD_BYTE(0xf0);
ADD_BYTE(0x20);
ADD_BYTE(0xe3);
//bx lr
ADD_BYTE(0x1e);
ADD_BYTE(0xff);
ADD_BYTE(0x2f);
ADD_BYTE(0xe1);
}
int main(void)
{
ibuf = (char*)mmap(NULL, 4 * 1024, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
MakeRoutineSimpleFunc();
routine = (void(*)())(ibuf);
routine();
}
如您所见,在上面的代码中,首先我分配了一个可执行内存区域并将其地址分配给 ibuf,然后我在 ibuf 中放置了一些简单的指令("nop" 和 "bx lr"这意味着 return 在 arm 中)然后我尝试通过函数指针调用此函数。
但是当我想通过函数指针调用函数时,出现了 "segmentation fault" 错误。顺便说一句,当我尝试 运行 使用 GDB 调试器程序 运行 的应用程序成功时,没有任何错误。
我在上面的代码中遗漏了什么导致 "segmentation fault"?
我想添加,当我在编译时将上面的指令("nop" 和 "bx lr" 表示 return 在 arm 中添加到如下函数时,函数工作没有任何错误。
void f2(void)
{
__asm__ volatile (".byte 0x00, 0xf0, 0x20, 0xe3");
__asm__ volatile (".byte 0x1e, 0xff, 0x2f, 0xe1");
}
编辑1:
为了检查 运行-time 函数的有效性,我用 ghidra 反汇编程序删除了 f2 prolog 和 epilogue,所以 f2 的汇编代码是这样的:
**************************************************************
FUNCTION
**************************************************************
undefined FUN_0000083c()
undefined r0:1 <RETURN>
undefined4 Stack[-0x4]:4 local_4
FUN_0000083c XREF[1]: FUN_00000868:000008a4(c)
0000083c 00 f0 20 e3 nop
00000840 00 f0 20 e3 nop
00000844 00 f0 20 e3 nop
00000848 00 f0 20 e3 nop
0000084c 00 f0 20 e3 nop
00000850 00 f0 20 e3 nop
00000854 1e ff 2f e1 bx lr
00000858 00 f0 20 e3 nop
0000085c 00 f0 20 e3 nop
00000860 00 f0 20 e3 nop
00000864 00 f0 20 e3 nop
而且它再次正常工作。
编辑2:
我想添加一些可能有助于解决问题的内容,正如我在汇编器中看到的那样,编译器使用 "blx r3" 指令调用 "routine" 函数,同时使用 "bl 'symbol name'" 调用其他函数。据我所知,blx 可以将处理器状态从 ARM 更改为 Thumb,反之亦然。这一点会导致问题吗?
编辑3:
主要功能的反汇编如下所示:
**************************************************************
FUNCTION
**************************************************************
int __stdcall main(void)
int r0:4 <RETURN>
undefined4 Stack[-0xc]:4 local_c XREF[1]: 00010d44(W)
undefined4 Stack[-0x10]:4 local_10 XREF[1]: 00010d4c(W)
main XREF[4]: Entry Point(*),
_start:00010394(*), 000103a8(*),
.debug_frame::000000a0(*)
00010d34 00 48 2d e9 stmdb sp!,{ r11 lr }
00010d38 04 b0 8d e2 add r11,sp,#0x4
00010d3c 08 d0 4d e2 sub sp,sp,#0x8
00010d40 00 30 a0 e3 mov r3,#0x0
00010d44 04 30 8d e5 str r3,[sp,#local_c]
00010d48 00 30 e0 e3 mvn r3,#0x0
00010d4c 00 30 8d e5 str r3,[sp,#0x0]=>local_10
00010d50 22 30 a0 e3 mov r3,#0x22
00010d54 07 20 a0 e3 mov r2,#0x7
00010d58 01 1a a0 e3 mov r1,#0x1000
00010d5c 00 00 a0 e3 mov r0,#0x0
00010d60 7d fd ff eb bl mmap
00010d64 00 20 a0 e1 cpy r2,r0
00010d68 50 30 9f e5 ldr r3,[->ibuf]
00010d6c 00 20 83 e5 str r2,[r3,#0x0]=>ibuf
00010d70 48 30 9f e5 ldr r3,[->ibuf]
00010d74 00 30 93 e5 ldr r3,[r3,#0x0]=>ibuf
00010d78 03 10 a0 e1 cpy r1,r3
00010d7c 40 00 9f e5 ldr r0=>s_ibuf:_%x_00010e40,[PTR_s_ibuf:_%x_00010d
00010d80 69 fd ff eb bl printf
00010d84 ae fe ff eb bl MakeRoutineSimpleFunc
00010d88 30 30 9f e5 ldr r3,[->ibuf]
00010d8c 00 30 93 e5 ldr r3,[r3,#0x0]=>ibuf
00010d90 03 20 a0 e1 cpy r2,r3
00010d94 2c 30 9f e5 ldr r3,[->routine]
00010d98 00 20 83 e5 str r2,[r3,#0x0]=>routine
00010d9c 24 30 9f e5 ldr r3,[->routine]
00010da0 00 30 93 e5 ldr r3,[r3,#0x0]=>routine
00010da4 33 ff 2f e1 blx r3
00010da8 1c 00 9f e5 ldr r0=>DAT_00010e4c,
00010dac 61 fd ff eb bl puts
00010db0 00 30 a0 e3 mov r3,#0x0
00010db4 03 00 a0 e1 cpy r0,r3
00010db8 04 d0 4b e2 sub sp,r11,#0x4
00010dbc 00 88 bd e8 ldmia sp!,{ r11 pc }
如您所见,在地址“00010da4”处使用 "blx r3" 指令调用的例程。我还打印了 ibuf 的地址,它是“0xb6ff8000”。
我认为,您可以直接在字符串 "binary-code" 中输入操作码,然后使用 ((void*)STRING)()
执行代码。但是,您可能还想阅读有关 gcc 如何实现蹦床的信息,因为这就是 gcc 生成在堆栈上创建代码并将执行跳转到那里的代码的方式。
我正在尝试在 运行 时间用 c 语言在 arm cpu(cortex a72) 中定义和调用一个函数。为此,我实现了如下代码:
#include <stdio.h>
#include <sys/mman.h>
char* ibuf;
int pbuf = 0;
#define ADD_BYTE(val) do{ibuf[pbuf] = val; pbuf++;} while(0)
void (*routine)(void);
void MakeRoutineSimpleFunc(void)
{
//nop
ADD_BYTE(0x00);
ADD_BYTE(0xf0);
ADD_BYTE(0x20);
ADD_BYTE(0xe3);
//bx lr
ADD_BYTE(0x1e);
ADD_BYTE(0xff);
ADD_BYTE(0x2f);
ADD_BYTE(0xe1);
}
int main(void)
{
ibuf = (char*)mmap(NULL, 4 * 1024, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
MakeRoutineSimpleFunc();
routine = (void(*)())(ibuf);
routine();
}
如您所见,在上面的代码中,首先我分配了一个可执行内存区域并将其地址分配给 ibuf,然后我在 ibuf 中放置了一些简单的指令("nop" 和 "bx lr"这意味着 return 在 arm 中)然后我尝试通过函数指针调用此函数。
但是当我想通过函数指针调用函数时,出现了 "segmentation fault" 错误。顺便说一句,当我尝试 运行 使用 GDB 调试器程序 运行 的应用程序成功时,没有任何错误。
我在上面的代码中遗漏了什么导致 "segmentation fault"?
我想添加,当我在编译时将上面的指令("nop" 和 "bx lr" 表示 return 在 arm 中添加到如下函数时,函数工作没有任何错误。
void f2(void)
{
__asm__ volatile (".byte 0x00, 0xf0, 0x20, 0xe3");
__asm__ volatile (".byte 0x1e, 0xff, 0x2f, 0xe1");
}
编辑1: 为了检查 运行-time 函数的有效性,我用 ghidra 反汇编程序删除了 f2 prolog 和 epilogue,所以 f2 的汇编代码是这样的:
**************************************************************
FUNCTION
**************************************************************
undefined FUN_0000083c()
undefined r0:1 <RETURN>
undefined4 Stack[-0x4]:4 local_4
FUN_0000083c XREF[1]: FUN_00000868:000008a4(c)
0000083c 00 f0 20 e3 nop
00000840 00 f0 20 e3 nop
00000844 00 f0 20 e3 nop
00000848 00 f0 20 e3 nop
0000084c 00 f0 20 e3 nop
00000850 00 f0 20 e3 nop
00000854 1e ff 2f e1 bx lr
00000858 00 f0 20 e3 nop
0000085c 00 f0 20 e3 nop
00000860 00 f0 20 e3 nop
00000864 00 f0 20 e3 nop
而且它再次正常工作。
编辑2: 我想添加一些可能有助于解决问题的内容,正如我在汇编器中看到的那样,编译器使用 "blx r3" 指令调用 "routine" 函数,同时使用 "bl 'symbol name'" 调用其他函数。据我所知,blx 可以将处理器状态从 ARM 更改为 Thumb,反之亦然。这一点会导致问题吗?
编辑3: 主要功能的反汇编如下所示:
**************************************************************
FUNCTION
**************************************************************
int __stdcall main(void)
int r0:4 <RETURN>
undefined4 Stack[-0xc]:4 local_c XREF[1]: 00010d44(W)
undefined4 Stack[-0x10]:4 local_10 XREF[1]: 00010d4c(W)
main XREF[4]: Entry Point(*),
_start:00010394(*), 000103a8(*),
.debug_frame::000000a0(*)
00010d34 00 48 2d e9 stmdb sp!,{ r11 lr }
00010d38 04 b0 8d e2 add r11,sp,#0x4
00010d3c 08 d0 4d e2 sub sp,sp,#0x8
00010d40 00 30 a0 e3 mov r3,#0x0
00010d44 04 30 8d e5 str r3,[sp,#local_c]
00010d48 00 30 e0 e3 mvn r3,#0x0
00010d4c 00 30 8d e5 str r3,[sp,#0x0]=>local_10
00010d50 22 30 a0 e3 mov r3,#0x22
00010d54 07 20 a0 e3 mov r2,#0x7
00010d58 01 1a a0 e3 mov r1,#0x1000
00010d5c 00 00 a0 e3 mov r0,#0x0
00010d60 7d fd ff eb bl mmap
00010d64 00 20 a0 e1 cpy r2,r0
00010d68 50 30 9f e5 ldr r3,[->ibuf]
00010d6c 00 20 83 e5 str r2,[r3,#0x0]=>ibuf
00010d70 48 30 9f e5 ldr r3,[->ibuf]
00010d74 00 30 93 e5 ldr r3,[r3,#0x0]=>ibuf
00010d78 03 10 a0 e1 cpy r1,r3
00010d7c 40 00 9f e5 ldr r0=>s_ibuf:_%x_00010e40,[PTR_s_ibuf:_%x_00010d
00010d80 69 fd ff eb bl printf
00010d84 ae fe ff eb bl MakeRoutineSimpleFunc
00010d88 30 30 9f e5 ldr r3,[->ibuf]
00010d8c 00 30 93 e5 ldr r3,[r3,#0x0]=>ibuf
00010d90 03 20 a0 e1 cpy r2,r3
00010d94 2c 30 9f e5 ldr r3,[->routine]
00010d98 00 20 83 e5 str r2,[r3,#0x0]=>routine
00010d9c 24 30 9f e5 ldr r3,[->routine]
00010da0 00 30 93 e5 ldr r3,[r3,#0x0]=>routine
00010da4 33 ff 2f e1 blx r3
00010da8 1c 00 9f e5 ldr r0=>DAT_00010e4c,
00010dac 61 fd ff eb bl puts
00010db0 00 30 a0 e3 mov r3,#0x0
00010db4 03 00 a0 e1 cpy r0,r3
00010db8 04 d0 4b e2 sub sp,r11,#0x4
00010dbc 00 88 bd e8 ldmia sp!,{ r11 pc }
如您所见,在地址“00010da4”处使用 "blx r3" 指令调用的例程。我还打印了 ibuf 的地址,它是“0xb6ff8000”。
我认为,您可以直接在字符串 "binary-code" 中输入操作码,然后使用 ((void*)STRING)()
执行代码。但是,您可能还想阅读有关 gcc 如何实现蹦床的信息,因为这就是 gcc 生成在堆栈上创建代码并将执行跳转到那里的代码的方式。