如何正确获取内核符号的物理地址?转换结果不稳定问题解决
如何正确获取内核符号的物理地址?
我需要获取内核符号的物理地址(供仅支持物理地址的系统读取),目前用__pa(vaddr)和virt_to_phys(vaddr)做转换,但结果并非总是正确。
通过内核模块读取值,并和fmem、另一SMM驱动系统读取的值对比验证:当内核符号虚拟地址为0xffffffffb3400000时,执行
printk(KERN_INFO "Addr: 0x%lx , Value: 0x%lx\n", _stext, *_stext);
得到结果:Addr: 0xffffffffb3400000 , Value: 0x4801e03f51258d48。转换正确时,__pa得到的物理地址在fmem和另一系统中读取的值和这个一致;但有时转换错误,读取值不同,虚拟地址解引用却能得到正确值0x4801e03f51258d48。
我知道每次启动内核代码、数据及符号地址会变化,但虚拟地址到物理地址的映射应该是一致的,请问怎么正确获取内核符号的物理地址?
当前我的代码:
u64 _stext_pa = __pa(_stext_va); printk(KERN_INFO "Value of _stext (va: 0x%lx | pa: 0x%x) = 0x%lx\n", _stext_va, _stext_pa, *_stext_va);
我需要得到正确的物理地址paddr,让另一系统执行int value = *paddr;能得到和虚拟地址读取一致的结果。
解决方法
- 先确认地址类型:
__pa和virt_to_phys只对线性映射区的虚拟地址有效(比如内核逻辑地址、直接映射的物理内存地址)。如果_stext_va属于内核虚拟地址空间的其他区域(比如vmalloc分配的地址、模块加载地址,或者KASLR随机化后的代码段地址),这两个宏会失效。可以先检查_stext_va是否在直接映射区间:x86_64上直接映射区的虚拟地址通常是PAGE_OFFSET + 物理地址,默认PAGE_OFFSET是0xffff800000000000或0xffffffff80000000,取决于内核配置。 - 用页结构体转换适配所有地址:对于非直接映射的虚拟地址,先通过
virt_to_page获取对应的页结构体,再用page_to_phys得到物理页基地址,最后加上页内偏移,这个方法适用于所有已映射的内核虚拟地址:struct page *page = virt_to_page(_stext_va); u64 page_pa = page_to_phys(page); u64 _stext_pa = page_pa + (_stext_va & (PAGE_SIZE - 1)); - 排查KASLR和内存加密:如果开了KASLR,内核代码段虚拟地址每次启动会变,但映射关系本身是对的。要是系统启用了内存加密(比如AMD SME/SEV、Intel TDX),直接读取物理地址会得到加密后的值,需要确保读取端支持解密机制。
- 修正打印格式错误:你的代码里用
0x%x打印u64类型的_stext_pa,会截断高位地址,导致看起来转换错误,应该改成0x%lx:printk(KERN_INFO "Value of _stext (va: 0x%lx | pa: 0x%lx) = 0x%lx\n", _stext_va, _stext_pa, *_stext_va); - 验证转换结果:转换后可以用
phys_to_virt反向转成虚拟地址,对比解引用的值,确认转换是否正确:u64 converted_va = (u64)phys_to_virt(_stext_pa); if (*(u64*)converted_va == *(u64*)_stext_va) { printk(KERN_INFO "Physical address conversion is correct\n"); } else { printk(KERN_WARNING "Physical address conversion failed\n"); }
内容的提问来源于stack exchange,提问作者asallan3
相关产品推荐
相关产品推荐

