如何将字符串形式的地址赋值给Linux内核模块中的C指针
内核模块中十六进制地址转指针的问题
问题描述
开发Ubuntu内核模块时,需要为模块字符串参数指定的地址设置watchpoint。当前卡在将字符串形式的十六进制地址转换为可用指针地址的阶段:
- 尝试将十六进制地址转十进制整数后,用循环递增指针或直接相加的方式赋值,均无法得到正确目标地址
- 手动赋值可以生效,但不符合任务要求
- 目标地址每次重启机器后会变化
- 试过直接使用
void*类型指针无效,适配5.7+内核的kallsyms_lookup_name函数返回0x0,也无法解决问题
现有代码片段
char *address = NULL; volatile void *zero_address = 0; volatile long long unsigned int actual_address; // ... if(starts_with(address, "0x")) { actual_address = simple_strtoul(address + 2, NULL, 16); } else { actual_address = simple_strtoul(address, NULL, 16); } printk(KERN_INFO "Address in int = %llu", actual_address); for (volatile long long unsigned int i = 0; i < actual_address; i++, zero_address++); // zero_address = zero_address + actual_address; printk(KERN_INFO "Address converted = %p", zero_address); write_attributes.bp_addr = (long long unsigned int) zero_address;
完整内核模块代码
#include <linux/module.h> /* Needed by all modules */ #include <linux/kernel.h> /* Needed for KERN_INFO, KERN_ALERT */ #include <linux/init.h> /* Needed for the macros */ #include <linux/stat.h> #include <linux/moduleparam.h> /* Needed for module_param */ #include <linux/perf_event.h> #include <linux/hw_breakpoint.h> #include <linux/version.h> #include <linux/string.h> #include <linux/math.h> #define DRIVER_AUTHOR "Denys Berezniuk" #define DRIVER_DESC "Test Driver" /* * On Linux kernels 5.7+, kallsyms_lookup_name() is no longer exported, * so we have to use kprobes to get the address. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0) #define KPROBE_LOOKUP 1 #include <linux/kprobes.h> static struct kprobe kp = { .symbol_name = "kallsyms_lookup_name" }; #endif MODULE_LICENSE("GPL"); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); struct perf_event * __percpu *write_watchpoint; struct perf_event * __percpu *read_watchpoint; char *address = NULL; volatile void *zero_address = 0; volatile long long unsigned int actual_address; struct perf_event_attr write_attributes; struct perf_event_attr read_attributes; module_param(address, charp, S_IRUGO); MODULE_PARM_DESC(address, "Address to place a Watchpoint on"); static void write_handler(struct perf_event *bp, struct perf_sample_data *data, struct pt_regs *regs) { //printk(KERN_INFO "%s value is changed\n", address); //dump_stack(); //printk(KERN_INFO "Dump stack from write_handler\n"); } bool starts_with(const char *a, const char *b) { if(strncmp(a, b, strlen(b)) == 0) return 1; return 0; } int __init initialize_driver(void) { #ifdef KPROBE_LOOKUP typedef unsigned long (*kallsyms_lookup_name_t)(const char *name); kallsyms_lookup_name_t kallsyms_lookup_name; register_kprobe(&kp); kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr; unregister_kprobe(&kp); #endif printk(KERN_ALERT "Driver loaded\nAddress is: '%s'\n", address); hw_breakpoint_init(&write_attributes); if(starts_with(address, "0x")) { actual_address = simple_strtoul(address + 2, NULL, 16); } else { actual_address = simple_strtoul(address, NULL, 16); } printk(KERN_INFO "Address in int = %llu", actual_address); for (volatile long long unsigned int i = 0; i < actual_address; i++, zero_address++); // zero_address = zero_address + actual_address; printk(KERN_INFO "Address converted = %p", zero_address); write_attributes.bp_addr = (long long unsigned int) zero_address; write_attributes.bp_len = HW_BREAKPOINT_LEN_1; write_attributes.bp_type = HW_BREAKPOINT_W; write_watchpoint = register_wide_hw_breakpoint(&write_attributes, (perf_overflow_handler_t) write_handler, NULL); if(IS_ERR((void __force *) write_watchpoint)) { int result = PTR_ERR((void __force *) write_watchpoint); printk(KERN_ALERT "Watchpoint registration failed\n"); return result; } printk(KERN_INFO "HW Watchpoint (Breakpoint) for %s write installed (0x%p)\n", address, (void*)write_attributes.bp_addr); return 0; } void __exit driver_removal(void) { printk(KERN_ALERT "Driver removed.\n"); } module_init(initialize_driver); module_exit(driver_removal);
问题分析与修正方案
核心错误点
- 指针转换方式完全错误:用循环递增
zero_address的方式不仅效率极低,还可能因为内核内存模型(比如指针宽度和long long的关系)导致地址计算错误,完全没必要绕这个弯。 - 类型匹配问题:
simple_strtoul返回的是unsigned long类型,与内核中指针的宽度匹配,用long long存储没问题,但转换为指针时可以直接强转,不需要通过循环或加法操作。
修正后的代码片段
把原来的循环/加法部分替换为直接类型转换:
if(starts_with(address, "0x")) { // 改用unsigned long更贴合返回值类型 unsigned long addr_val = simple_strtoul(address + 2, NULL, 16); zero_address = (volatile void *)addr_val; } else { unsigned long addr_val = simple_strtoul(address, NULL, 16); zero_address = (volatile void *)addr_val; } printk(KERN_INFO "Address converted = %p", zero_address); // bp_addr是unsigned long类型,直接转换即可 write_attributes.bp_addr = (unsigned long)zero_address;
关于kallsyms_lookup_name的补充
如果你的模块参数支持传入符号名(比如内核变量名)而非直接地址字符串,需要在代码中增加判断:如果输入不是十六进制地址格式,再调用kallsyms_lookup_name获取地址。但当前你是直接传入地址字符串,所以不需要用到这个函数,重点解决地址的直接转换即可。
内容的提问来源于stack exchange,提问作者Denis Berezniuk
相关产品推荐
相关产品推荐

