-
内核符号可以通过/proc/kallsyms查看。
-
符号类型说明
在Linux内核中,kallsyms是一个符号表,它包含了内核中所有的符号信息,包括函数、变量、常量等等。这些符号信息可以被内核模块和其他程序使用,这些符号类型可以帮助开发人员更好地理解内核中的符号信息,从而更好地进行内核开发和调试。在kallsyms中,每个符号都有一个类型,表示它的用途。下面是一些常见的符号类型:
-
T:表示该符号是一个函数,可以被其他代码调用。
-
t:表示该符号是一个局部函数,只能在当前文件中使用。
-
D:表示该符号是一个全局变量,可以被其他代码访问和修改。
-
d:表示该符号是一个局部变量,只能在当前文件中使用。
-
R:表示该符号是一个只读变量,不能被修改。
-
r:表示该符号是一个只读局部变量,只能在当前文件中使用。
-
A:表示该符号是一个可读写的变量,可以被其他代码访问和修改。
-
a:表示该符号是一个可读写的局部变量,只能在当前文件中使用。
-
B:表示该符号是一个未初始化的全局变量,它的值在程序启动时被初始化为0。
-
b:表示该符号是一个未初始化的局部变量,它的值在程序启动时被初始化为0。
-
G:表示该符号是一个全局变量,但是它的值在程序运行时可能会被修改。
-
C:表示该符号是一个常量,它的值在程序运行时不能被修改。
-
W:表示该符号是一个弱符号,
-
?: 表示该符号的类型未知。
-
关于弱符号的一些说明:__weak 是一个关键字,用于声明一个弱符号。在 C 语言中,如果一个符号被定义了多次,编译器会报错。但是,如果一个符号被声明为弱符号,那么编译器就不会报错,而是在链接时选择其中一个符号作为最终的符号。这个特性在编写库时非常有用,因为它允许用户在链接时覆盖库中的函数。
如下为一个例子:
/**
* probe_kernel_read(): safely attempt to read from a location
* @dst: pointer to the buffer that shall take the data
* @src: address to read from
* @size: size of the data chunk
*
* Safely read from address @src to the buffer at @dst. If a kernel fault
* happens, handle that and return -EFAULT.
*
* We ensure that the copy_from_user is executed in atomic context so that
* do_page_fault() doesn't attempt to take mmap_sem. This makes
* probe_kernel_read() suitable for use within regions where the caller
* already holds mmap_sem, or other locks which nest inside mmap_sem.
*/
long __weak probe_kernel_read(void *dst, const void *src, size_t size)
__attribute__((alias("__probe_kernel_read")));
long __probe_kernel_read(void *dst, const void *src, size_t size)
{
long ret;
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
pagefault_disable();
ret = __copy_from_user_inatomic(dst,
(__force const void __user *)src, size);
pagefault_enable();
set_fs(old_fs);
return ret ? -EFAULT : 0;
}
EXPORT_SYMBOL_GPL(probe_kernel_read);
-
内核驱动获取符号地址
/* Lookup the address for this symbol. Returns 0 if not found. */ unsigned long kallsyms_lookup_name(const char *name) { char namebuf[KSYM_NAME_LEN]; unsigned long i; unsigned int off; for (i = 0, off = 0; i < kallsyms_num_syms; i++) { off = kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf)); if (strcmp(namebuf, name) == 0) return kallsyms_sym_address(i); } return module_kallsyms_lookup_name(name); } EXPORT_SYMBOL_GPL(kallsyms_lookup_name);
上述函数可以使用符号名找到在内核中的地址,使用该方法可以使用内核中未导出的内核函数。如下所示:
/** * text_poke - Update instructions on a live kernel * @addr: address to modify * @opcode: source of the copy * @len: length to copy * * Only atomic text poke/set should be allowed when not doing early patching. * It means the size must be writable atomically and the address must be aligned * in a way that permits an atomic write. It also makes sure we fit on a single * page. */ void *text_poke(void *addr, const void *opcode, size_t len) { unsigned long flags; char *vaddr; struct page *pages[2]; int i; /* * While boot memory allocator is runnig we cannot use struct * pages as they are not yet initialized. */ BUG_ON(!after_bootmem); lockdep_assert_held(&text_mutex); if (!core_kernel_text((unsigned long)addr)) { pages[0] = vmalloc_to_page(addr); pages[1] = vmalloc_to_page(addr + PAGE_SIZE); } else { pages[0] = virt_to_page(addr); WARN_ON(!PageReserved(pages[0])); pages[1] = virt_to_page(addr + PAGE_SIZE); } BUG_ON(!pages[0]); local_irq_save(flags); set_fixmap(FIX_TEXT_POKE0, page_to_phys(pages[0])); if (pages[1]) set_fixmap(FIX_TEXT_POKE1, page_to_phys(pages[1])); vaddr = (char *)fix_to_virt(FIX_TEXT_POKE0); memcpy(&vaddr[(unsigned long)addr & ~PAGE_MASK], opcode, len); clear_fixmap(FIX_TEXT_POKE0); if (pages[1]) clear_fixmap(FIX_TEXT_POKE1); local_flush_tlb(); sync_core(); /* Could also do a CLFLUSH here to speed up CPU recovery; but that causes hangs on some VIA CPUs. */ for (i = 0; i < len; i++) BUG_ON(((char *)addr)[i] != ((char *)opcode)[i]); local_irq_restore(flags); return addr; }
text_poke
为非导出函数,在驱动中无法使用,可以使用kallsyms_lookup_name
获取函数地址并赋值给函数指针,便可以使用该函数。void *(*text_poke_fn)(void *addr, const void *opcode, size_t len); text_poke_fn = (void *(*)(void *, const void *, size_t))kallsyms_lookup_nama("text_poke");
EXPORT_SYMBOL
内核导出函数有内核符号表和字符串表。
root # cat /proc/kallsyms | grep blk_steal_bios
ffffffff81d15cb0 T blk_steal_bios
ffffffff8389ab80 r __ksymtab_blk_steal_bios
ffffffff838c2e4f r __kstrtab_blk_steal_bios
- __ksymtab_blk_steal_bios 是一个内核符号表(kernel symbol table)中的条目,用于存储 blk_steal_bios 函数的地址。这个符号允许内核模块在运行时查找和引用 blk_steal_bios 函数。它是内核符号表的一部分,提供了动态链接的能力,使得模块可以在不重新编译内核的情况下使用内核中的函数。
- __kstrtab_blk_steal_bios 是一个字符串表(string table)中的条目,存储了 blk_steal_bios 符号的名称。这个符号用于在内核中提供符号的名称字符串,通常用于调试和日志记录。通过这个字符串,内核可以在输出日志或错误信息时引用符号的名称,使得调试过程更加清晰。
crash> sym ffffffff8389ab80
ffffffff8389ab80 (r) __ksymtab_blk_steal_bios
crash> rd ffffffff8389ab80
ffffffff8389ab80: ffffffff81d15cb0 .\......
crash> rd ffffffff838c2e4f 8
ffffffff838c2e4f: 616574735f6b6c62 6200736f69625f6c blk_steal_bios.b
ffffffff838c2e5f: 72655f71725f6b6c 0073657479625f72 lk_rq_err_bytes.
ffffffff838c2e6f: 65736e695f6b6c62 656e6f6c635f7472 blk_insert_clone
ffffffff838c2e7f: 7365757165725f64 74696d6275730074 d_request.submit