一、/proc/kallsyms

内核符号可以通过/proc/kallsyms查看。

(1)符号类型说明

在Linux内核中,kallsyms是一个符号表,它包含了内核中所有的符号信息,包括函数、变量、常量等等。这些符号信息可以被内核模块和其他程序使用,这些符号类型可以帮助开发人员更好地理解内核中的符号信息,从而更好地进行内核开发和调试。

在kallsyms中,每个符号都有一个类型,表示它的用途。下面是一些常见的符号类型:

  • T:表示该符号是一个函数,可以被其他代码调用。
  • t:表示该符号是一个局部函数,只能在当前文件中使用。
  • D:表示该符号是一个全局变量,可以被其他代码访问和修改。
  • d:表示该符号是一个局部变量,只能在当前文件中使用。
  • R:表示该符号是一个只读变量,不能被修改。
  • r:表示该符号是一个只读局部变量,只能在当前文件中使用
  • A:表示该符号是一个可读写的变量,可以被其他代码访问和修改。
  • a:表示该符号是一个可读写的局部变量,只能在当前文件中使用。
  • B:表示该符号是一个未初始化的全局变量,它的值在程序启动时被初始化为0。
  • b:表示该符号是一个未初始化的局部变量,它的值在程序启动时被初始化为0。
  • G:表示该符号是一个全局变量,但是它的值在程序运行时可能会被修改。
  • C:表示该符号是一个常量,它的值在程序运行时不能被修改。
  • W:表示该符号是一个弱符号,
  • ?: 表示该符号的类型未知。

(2)弱符号

关于弱符号的一些说明:__weak 是一个关键字,用于声明一个弱符号。在 C 语言中,如果一个符号被定义了多次,编译器会报错。但是,如果一个符号被声明为弱符号,那么编译器就不会报错,而是在链接时选择其中一个符号作为最终的符号。这个特性在编写库时非常有用,因为它允许用户在链接时覆盖库中的函数。

如下为一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 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);

(3)内核驱动获取符号地址

符号存在于/proc/kallsyms 文件中,可以通过kallsyms_lookup_name获取其符号的地址。在内核版本 2.6.33 - 5.7.0 可以用kallsyms_lookup_name来获取该函数的地址。在内核2.6.33 以下和 5.7.0 以上可以用 kprobe 来获取该函数的地址

kallsyms_lookup_name是内核API,源码如下:

使用kallsyms_lookup_name函数需要#include <linux/kallsyms.h>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 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);

上述函数可以使用符号名找到在内核中的地址,使用该方法可以使用内核中未导出的内核函数。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 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获取函数地址并赋值给函数指针,便可以使用该函数。

1
2
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");

描述文字
© 2024 mengchao666 开心地使用 Stellar 主题,星际地球第
收集了 62 颗星星 ✧ 共 83.3k 粒星尘,好像写完一本 鲁迅 的《呐喊》了啊