You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将字符串形式的地址赋值给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);

问题分析与修正方案

核心错误点

  1. 指针转换方式完全错误:用循环递增zero_address的方式不仅效率极低,还可能因为内核内存模型(比如指针宽度和long long的关系)导致地址计算错误,完全没必要绕这个弯。
  2. 类型匹配问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 11:08:13