大约一个月前,我尝试用 C 语言实现一个类似弹床的结构,它封装了一小段汇编代码,将一个函数与另一个函数挂钩。
这个想法很简单:每当创建
bounce_bed
时,我都会将挂钩函数的第一条指令替换为到 bounce_bed
的分支,并在 bounce_bed
中编码一个执行堆栈操作的短汇编函数,然后调用钩子并返回,最后跳回该函数。
这是我的代码:
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
typedef uint32_t u32;
static inline u32
encode_b (unsigned long target_addr)
{
return 0x14000000 | (0x03ffffff & (target_addr >> 2));
}
static inline u32
encode_bl (unsigned long target_addr)
{
return 0x94000000 | (0x03ffffff & (target_addr >> 2));
}
/**
* a bounce_bed is an entry to a hook function
* use trace(func, hook) to set it up.
*/
struct __attribute__ ((aligned (4))) bounce_bed
{
u32 first_inst; // 0xa9bc7bfd
u32 second_inst; // 0x910003fd
u32 stp_x0_x1; // 0xa9be07e0
u32 stp_x2_x3; // 0xa9be0fe2
u32 bl_trace_hook;
u32 ldp_x2_x3; // 0xa8c20fe2
u32 ldp_x0_x1; // 0xa8c207e0
u32 b_trace_point;
};
struct bounce_bed *
trace (void *func_addr, void *hook_addr)
{
u32 *ptr = func_addr;
size_t page_size = getpagesize ();
void *page_aligned_addr;
struct bounce_bed *tnode = malloc (sizeof (struct bounce_bed));
tnode->first_inst = ptr[0]; // normally stp fp, lr, [sp, #-32]!
tnode->second_inst = ptr[1]; // normally mov fp, sp
tnode->stp_x0_x1 = 0xa9be07e0; // stp x0, x1, [sp, #-32]!
tnode->stp_x2_x3 = 0xa9be0fe2; // stp x2, x3, [sp, #-32]!
tnode->bl_trace_hook
= encode_bl ((void *)hook_addr - (void *)&tnode->bl_trace_hook);
tnode->ldp_x2_x3 = 0xa8c20fe2; // ldp x2, x3. [sp], #32
tnode->ldp_x0_x1 = 0xa8c207e0; // ldp x0, x1, [sp], #32
tnode->b_trace_point
= encode_b (((void *)func_addr + 8) - (void *)&tnode->b_trace_point);
page_aligned_addr = (void *)((uintptr_t)tnode & ~(page_size - 1));
mprotect (page_aligned_addr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
page_aligned_addr = (void *)((uintptr_t)func_addr & ~(page_size - 1));
mprotect (page_aligned_addr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
ptr[0] = encode_b ((void *)tnode - (void *)func_addr); // b tnode
ptr[1] = 0xd5033fdf; // isb
return tnode;
}
/** computes x power y */
int
mypow (int x, int y)
{
if (y > 0)
return x * mypow (x, y - 1);
else
return 1;
}
/** this is called before hooked function */
void
myhook (void)
{
unsigned long x1;
asm ("mov %0, x1" : "=r"(x1));
printf ("%s: x1 = %#lx\n", __func__, x1);
return;
}
int
main (int argc, char *argv[])
{
if (argc != 3)
return -1;
/* read arguments */
int x = strtod (argv[1], NULL);
int y = strtod (argv[2], NULL);
/* attach myhook to mypow */
struct bounce_bed *tp = trace (mypow, myhook);
/* test */
int c = mypow (x, y);
printf ("%d\n", c);
free (tp);
return 0;
}
所有测试都是在arm64板子(rk3588 with linux bsp kernel 5.10)上执行的,但后来我也在arm64服务器(ampere,kernel 5.10,gcc-12.0)上测试了它,答案是一样的。
我使用 gcc-13.1 使用
-g
选项编译它,并尝试使用参数 2 4
运行它,它正常返回,但没有显示打印。但是,预计会从 myhook
打印出来。这意味着,myhook
没有被调用,实际上,没有跳转到弹跳床。
但是当我用gdb一步步运行它时,似乎每一步都是正确的,
myhook
的打印显示如预期,最后它正常返回......我检查了地址,发现一切都是正确的。
更重要的是,我还尝试用
valgrind
运行它来检测是否存在内存泄漏,我发现myhook
的打印也显示,没有检测到内存泄漏。
我尝试禁用 ASLR(地址空间布局随机化),但没有任何反应。
这太奇怪了,我在stackoverflow上检查了相关问题,但没有发现对此有帮助的想法。
@Siguza 给了你答案,但它被嵌入到关于
u32
的争论中,所以它可能被错过了。
各种 ARM 芯片具有独立的数据和指令缓存。您可能已经用另一条指令覆盖了该指令,这将反映在数据缓存中,但指令缓存仍将具有旧指令。
gcc 提供了
__builtin__clear_cache
可以做你想做的事。