内核镜像的虚拟地址
不管内核被加载到什么物理地址 ,在虚拟地址转物理地址函数里面,都会根据实际的offset来转换
__virt_to_phys
1.通过判断最高有效位如果为1,则表示是(memory)DRAM的地址;直接虚拟地址偏移 + 物理内存起始地址
2.如果为0,则表示是kernel image的地址;直接虚拟地址 - (内核镜像起始虚拟地址 - 内核镜像起始物理地址);也即内核的虚拟地址偏移 + 内核镜像的物理内存起始地址
#define __virt_to_phys(x) ({ \
phys_addr_t __x = (phys_addr_t)(x); \
__x & BIT(VA_BITS - 1) ? (__x & ~PAGE_OFFSET) + PHYS_OFFSET : \
(__x - kimage_voffset); })
符号表概念
符号表是记录了Linux内核中的符号列表以及对应符号在内存中的虚拟地址,符号其实就是Linux内核镜像中的变量和函数名称
静态和动态符号表
内核在编译过程中生成的System.map与proc/kallsyms的区别在于System.map是在编译阶段生成的内核符号表,我们可以称为静态Linux内核符号表,而proc/kallsyms方式看到的是在内核启动后生成的动态符号表。
符号表用来做什么
1.自己编写的内核模块中EXPORT_SYMBOL函数,其他的内核模块也可以通过extern声明后,能找到对应函数的地址,并进行调用
2.内核panic时,往往都会打印出调用栈,这个调用栈会打印一系列地址,通过使用符号表,会将这些地址转换为函数名
3.同样dump_stack也是通过符号表的查询,打印的对应函数名。
4.根据名称查到对应地址(内核中相关API),进行hook函数,实现更高级的debug功能,kprobe hook函数时就是利用符号表进行查询的对应地址。
模块的符号地址空间
通过adb shell cat proc/kallsyms > sys.txt导出动态符号表,可以看到编译时产生的静态符号,和加载模块产生的动态符号;
对于ARM构架的设备,模块挂载后,模块中包含的函数和静态变量的虚拟地址不在3G(0xC0000000)以上,而是3G以下一点的位置,可能以0xBF000000开始
......
c063df58 T setup_zone_pageset
c063df78 T init_currently_empty_zone
c063dfcc T init_per_zone_wmark_min
c063e024 T zone_pcp_update
c063e048 T _einittext
bfe63000 t $t [mfp]
bfe63000 t fastpath_debug_store [mfp]
......
insmod模块会调用sys_init_module
SYSCALL_DEFINE3(init_module, void __user *, umod,
unsigned long, len, const char __user *, uargs)
{
int err;
struct load_info info = { };
err = may_init_module();
if (err)
return err;
pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
umod, len, uargs);
err = copy_module_from_user(umod, len, &info);
if (err)
return err;
return load_module(&info, uargs, 0);
}
load_module会调用module_alloc来分配虚拟地址给模块镜像,而且这个虚拟地址的范围就应该在: MODULES_VADDR----MODULES_END
void *module_alloc(unsigned long size)
{
gfp_t gfp_mask = GFP_KERNEL;
void *p;
/* Silence the initial allocation */
if (IS_ENABLED(CONFIG_ARM_MODULE_PLTS))
gfp_mask |= __GFP_NOWARN;
p = __vmalloc_node_range(size, 1, MODULES_VADDR, MODULES_END,
gfp_mask, PAGE_KERNEL_EXEC, 0, NUMA_NO_NODE,
__builtin_return_address(0));
if (!IS_ENABLED(CONFIG_ARM_MODULE_PLTS) || p)
return p;
return __vmalloc_node_range(size, 1, VMALLOC_START, VMALLOC_END,
GFP_KERNEL, PAGE_KERNEL_EXEC, 0, NUMA_NO_NODE,
__builtin_return_address(0));
}
在/arch/arm/include/asm/memory.h;所以arm架构,如果是1/3的内核和用户空间的分配的话 ;模块的符号地址空间(MODULES_VADDR,MODULES_END)= (0xBF000000,0xC0000000)
#ifndef CONFIG_THUMB2_KERNEL
#define MODULES_VADDR (PAGE_OFFSET - SZ_16M)
#else
/* smaller range for Thumb-2 symbols relocation (2^24)*/
#define MODULES_VADDR (PAGE_OFFSET - SZ_8M)
#endif
#if TASK_SIZE > MODULES_VADDR
#error Top of user space clashes with start of module space
#endif
/*
* The highmem pkmap virtual space shares the end of the module area.
*/
#ifdef CONFIG_HIGHMEM
#define MODULES_END (PAGE_OFFSET - PMD_SIZE)
#else
#define MODULES_END (PAGE_OFFSET)
#endif