Linux - 解读aarch64_insn_(read/write/patch_text)
在 AArch64架构(ARM 64位架构)中,以 aarch64_insn_xxx
命名的一系列函数(例如 aarch64_insn_read
、aarch64_insn_write
、aarch64_insn_patch
等)主要用于直接操作或分析 机器指令(machine instructions)。它们是底层系统编程的核心工具,常见于操作系统内核、调试器、虚拟机监控程序(Hypervisor)或即时编译(JIT)引擎中。典中典的用途就是bpf啦!
我们先从最开始的源代码去看,额就是看头文件啦:
// May 28, 2021 Linux Kernel 5.13版本开始支持以下操作
#ifndef __ASSEMBLY__
int aarch64_insn_read(void *addr, u32 *insnp);
int aarch64_insn_write(void *addr, u32 insn);
int aarch64_insn_patch_text_nosync(void *addr, u32 insn);
int aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt);
#endif /* __ASSEMBLY__ */
// Linux Kernel 6.2-rc2加入
int aarch64_insn_write_literal_u64(void *addr, u64 val);
// Linux Kernel 6.8-rc7加入
void *aarch64_insn_set(void *dst, u32 insn, size_t len);
void *aarch64_insn_copy(void *dst, void *src, size_t len);
头文件路径为
arch/arm64/include/asm/patching.h
其实也不算加入,低版本内核这几个函数的位置不在同一个地方而已
先初略的说一下这几个玩意的用途,不按顺序:
aarch64_insn_write_literal_u64
:安全且原子地写入一个自然对齐的 64 位字面量(literal)到内核代码段中,所谓原子就是要满足点击跳转过去的特性。至于什么是字面量?你当我是宝宝巴士快乐启蒙呢!什么都给你说,爬一边问Deepseek去!aarch64_insn_set
|aarch64_insn_copy
在 ARM64 架构的内核开发中,text_poke
是一个新实现的 动态代码修改接口,其核心目标是提供一种安全、通用的方式,对内核的 只读且可执行(RO+X)的代码段 进行读写操作(如批量写入或填充指令)。该接口的灵感来源于 x86 架构的同名实现,旨在支持诸如 BPF JIT 编译器等需要动态生成或修改机器码的场景。(什么?你说没看见text_poke?哦,那玩意在patching.c
里面,嘻嘻嘻)
aarch64_insn_read
这个东西怎么说呢?就是字面意思读一下就完事了,hhh(((
int __kprobes aarch64_insn_read(void *addr, u32 *insnp)
{
int ret;
__le32 val;
ret = copy_from_kernel_nofault(&val, addr, AARCH64_INSN_SIZE);
if (!ret)
*insnp = le32_to_cpu(val);
return ret;
}
真就读一下,给你看源代码喏!
aarch64_insn_write
在看之前,我们先说一下patch_map
,显而易见
static void __kprobes *patch_map(void *addr, int fixmap)
{
phys_addr_t phys;
if (is_image_text((unsigned long)addr)) { // 用断某个地址是否位于内核镜像的.text section中
phys = __pa_symbol(addr);
} else {
struct page *page = vmalloc_to_page(addr);
BUG_ON(!page);
phys = page_to_phys(page) + offset_in_page(addr);
}
return (void *)set_fixmap_offset(fixmap, phys); // 设置可读可写,就是把pte改成PAGE_KERNEL原本应该是KERNEL_RO?
}
static void __kprobes patch_unmap(int fixmap)
{
clear_fixmap(fixmap);
}
你是不是有什么小算盘说那这个patch_map到处乱改?其实这个patch_map使用的是内核里面的那个叫fixmap的东西,恰恰好,这玩意有限制的,我们看看,它可以修改哪些地址的权限
enum fixed_addresses { FIX_EARLYCON_MEM_BASE, // 早期控制台的内存映射 __end_of_permanent_fixed_addresses, // 永久固定映射区域的结束标记 // 高端内存映射区域的起始和结束 FIX_KMAP_BEGIN = __end_of_permanent_fixed_addresses, FIX_KMAP_END = FIX_KMAP_BEGIN + (KM_MAX_IDX * NR_CPUS) - 1, /* Support writing RO kernel text via kprobes, jump labels, etc. */ // 用于内核代码修改的临时映射槽位 FIX_TEXT_POKE0, FIX_TEXT_POKE1, __end_of_fixmap_region, /* * Share the kmap() region with early_ioremap(): this is guaranteed * not to clash since early_ioremap() is only available before * paging_init(), and kmap() only after. */ // 早期 IO 映射的配置 #define NR_FIX_BTMAPS 32 // 每个槽位组的映射数量 #define FIX_BTMAPS_SLOTS 7 // 槽位组的数量 #define TOTAL_FIX_BTMAPS (NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS) FIX_BTMAP_END = __end_of_permanent_fixed_addresses, FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1, __end_of_early_ioremap_region };
掏出我们的主角↓
static int __kprobes __aarch64_insn_write(void *addr, __le32 insn)
{
void *waddr = addr;
unsigned long flags = 0;
int ret;
raw_spin_lock_irqsave(&patch_lock, flags); // 嗝,奇怪的锁
waddr = patch_map(addr, FIX_TEXT_POKE0);
ret = copy_to_kernel_nofault(waddr, &insn, AARCH64_INSN_SIZE); // 字面意思就是不会panic而已
patch_unmap(FIX_TEXT_POKE0);
raw_spin_unlock_irqrestore(&patch_lock, flags);
return ret;
}
aarch64_insn_set
和aarch64_insn_write
大差不差,区别就是改完之后他会同步icache
!