根据Arm Development Studio User Guide的 About debugging Linux kernel modules ,ArmDS是可以调试动态加载的Linux kernel 模块。 但是需要ArmDS支持你所使用的kernel版本,ArmDS支持的版本可以参考Arm Development Studio User Guide的 About OS awareness
可以看到这里支持的版本非常有限,如果使用的kernel不在上面的列表里面,那就没有办法用上面的方法调试了。
ArmDS的实现的原理我猜估计也是使用linux kernel里面提供的模块加载信息,然后再把模块的符号表加载到对应的位置。
下面的步骤就是介绍如何在不使用ArmDS提供的OS信息的情况下如何调试动态加载的kernel的模块。
1. 准备要调试的kernel模块
假设这里有如下模块的代码 mod_debug_test.c:
#include <linux/module.h>
#include <linux/printk.h>
#define MODULE_NAME "mod_debug_test"
__attribute__((__noinline__)) static void mod_debug_test_func(void )
{
pr_info("%s \n",__FUNCTION__);
}
static int __init mod_debug_test_init(void)
{
pr_info("%s \n",__FUNCTION__);
mod_debug_test_func();
return 0;
}
static void __exit mod_debug_test_exit(void)
{
pr_info("%s \n",__FUNCTION__);
}
module_init(mod_debug_test_init);
module_exit(mod_debug_test_exit);
MODULE_LICENSE("GPL");
在同样目录下面,有Makefile:
obj-m += mod_debug_test.o
DEBUG_CFLAGS += -g
CC=/workspace/tools/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04/bin/clang
CROSS_COMPILE=/workspace/tools/arm-gnu-toolchain-13.2.Rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
LINUX_DIR=/workspace/src/linux
all:
make -C $(LINUX_DIR) ARCH=arm64 CC=$(CC) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules EXTRA_CFLAGS="$(DEBUG_CFLAGS)"
clean:
make -C $(LINUX_DIR) ARCH=arm64 CC=$(CC) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) EXTRA_CFLAGS="$(DEBUG_CFLAGS)" clean
这里为了能够在ko里面带有debug符号表信息,所以就需要有 -g 选项。
2. kernel module相关的section
这里可以通过objdump来查看要调试的kernel module 有多少的section,我们要调试的代码处在哪个section中。如下:
~/workspace/src/testmod$ aarch64-none-linux-gnu-objdump -S mod_debug_test.ko mod_debug_test.ko: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 <mod_debug_test_func>: #include <linux/printk.h> #define MODULE_NAME "mod_debug_test" __attribute__((__noinline__)) static void mod_debug_test_func(void ) { 0: d503233f paciasp 4: a9bf7bfd stp x29, x30, [sp, #-16]! 8: 910003fd mov x29, sp pr_info("%s \n",__FUNCTION__); c: 90000000 adrp x0, 0 <mod_debug_test_func> 10: 91000000 add x0, x0, #0x0 14: 90000001 adrp x1, 0 <mod_debug_test_func> 18: 91000021 add x1, x1, #0x0 1c: 94000000 bl 0 <_printk> } 20: a8c17bfd ldp x29, x30, [sp], #16 24: d50323bf autiasp 28: d65f03c0 ret Disassembly of section .init.text: 0000000000000000 <init_module>: static int __init mod_debug_test_init(void) { 0: d503233f paciasp 4: a9bf7bfd stp x29, x30, [sp, #-16]! 8: 910003fd mov x29, sp pr_info("%s \n",__FUNCTION__); c: 90000000 adrp x0, 0 <init_module> 10: 91000000 add x0, x0, #0x0 14: 90000001 adrp x1, 0 <init_module> 18: 91000021 add x1, x1, #0x0 1c: 94000000 bl 0 <_printk> mod_debug_test_func(); 20: 94000000 bl 0 <init_module> return 0; 24: 2a1f03e0 mov w0, wzr 28: a8c17bfd ldp x29, x30, [sp], #16 2c: d50323bf autiasp 30: d65f03c0 ret Disassembly of section .exit.text: 0000000000000000 <cleanup_module>: } static void __exit mod_debug_test_exit(void) { 0: d503233f paciasp 4: a9bf7bfd stp x29, x30, [sp, #-16]! 8: 910003fd mov x29, sp pr_info("%s \n",__FUNCTION__); c: 90000000 adrp x0, 0 <cleanup_module> 10: 91000000 add x0, x0, #0x0 14: 90000001 adrp x1, 0 <cleanup_module> 18: 91000021 add x1, x1, #0x0 1c: 94000000 bl 0 <_printk> } 20: a8c17bfd ldp x29, x30, [sp], #16 24: d50323bf autiasp 28: d65f03c0 ret Disassembly of section .plt: 0000000000000000 <.plt>: ... Disassembly of section .text.ftrace_trampoline: 0000000000000000 <.text.ftrace_trampoline>: ...
通过观察,text section主要分成三个, .text .init.text 和 .exit.text, 其他的section跟这里的调试关系不大,暂时可以忽略。
而每个section的地址都是在insmod的时候,通过 module_memory_alloc-> module_alloc -> __vmalloc_node_range 来分配。
所以每次分配的VA地址是根据系统使用的情况来决定,每次可能分配VA的地址可能是不一样的。
3. kernel module section 加载地址
在kernel module加载到系统之后,可以通过查看/sys/module/<mod_name>/sections 下面module加载的信息知道每个section的VA地址,如下:
# cat /sys/module/mod_debug_test/sections/.text
0xffff80007ab40000
# cat /sys/module/mod_debug_test/sections/.init.text
0xffff80007ab46000
# cat /sys/module/mod_debug_test/sections/.exit.text
0xffff80007ab4002c
但是这样的方式找section的加载地址,是有些限制的,就是必须的等到module加载到系统之后才能找到这些section的地址。 可是module init相关的代码,是在module 加载完成之前就已经运行完成了。
如果想要调试module init相关代码,可能需要在module加载完成之前找到它对应的 .init.text section的加载地址。
在do_init_module 函数的时候,系统已经调用了vmalloc的接口给每个section分配了对应的VA。
这样就可以分析struct module的的信息找到对应的section地址。
struct module {
/* Unique handle for this module */
char name[MODULE_NAME_LEN];
/* Startup function. */
int (*init)(void);
struct module_memory mem[MOD_MEM_NUM_TYPES] __module_memory_align;
enum mod_mem_type {
MOD_TEXT = 0,
MOD_DATA,
MOD_RODATA,
MOD_RO_AFTER_INIT,
MOD_INIT_TEXT,
MOD_INIT_DATA,
MOD_INIT_RODATA,
MOD_MEM_NUM_TYPES,
MOD_INVALID = -1,
};
这个时候需要先把断点设在do_init_module 函数,来查看module->mem[x].base找到对应的section 加载的地址。 如下:
>bt
#0 do_init_module(mod = (struct module*) 0xFFFF80007AB42040) at main.c:2510
#1 load_module(info = <Value currently has no location>, uargs = 0xAAAADAF0F2A0 "", flags = 0) at main.c:3001
#2 init_module_from_file(f = (struct file*) 0xFFFF00080017B200, uargs = 0xAAAADAF0F2A0 "", flags = 0) at main.c:3168
#3 hash_64_generic(val = <Value currently has no location>, bits = <Value currently has no location>) at hash.h:78
#4 __do_sys_finit_module(fd = <Value currently has no location>, uargs = <Value currently has no location>, flags = <Value currently has no location>) at spinlock.h:0
#5 __arm64_sys_finit_module(regs ERROR(CMD367-IMG96):
! Error computing the variable "regs"
! Unimplemented DWARF opcode 0xa3
) at file.h:0
#6 __invoke_syscall(regs = (struct pt_regs*) 0xFFFF8000834FBEB0, syscall_fn = <Value currently has no location>) at syscall.c:0
#7 __kern_my_cpu_offset() at percpu.h:40
#8 read_ti_thread_flags(ti = <Value optimised away by compiler>) at thread_info.h:127
#9 el0_svc_common(regs = (struct pt_regs*) 0xFFFF8000834FBEB0, scno = <Value currently has no location>, sc_nr = 462, syscall_table ERROR(CMD367-IMG96):
! Error computing the variable "syscall_table"
! Unimplemented DWARF opcode 0xa3
) at syscall.c:142
#10 do_el0_svc(regs ERROR(CMD367-IMG96):
! Error computing the variable "regs"
! Unimplemented DWARF opcode 0xa3
) at syscall.c:153
#11 exit_to_user_mode_prepare(regs = (struct pt_regs*) 0xFFFF8000834FBEB0) at entry-common.c:165
#12 el0_da(regs = <Value not available : Undefined value in stack frame for register X0>, esr = <Value not available : Undefined value in stack frame for register X1>) at entry-common.c:575
#13 el0t_64_sync_handler(regs ERROR(CMD367-IMG96):
! Error computing the variable "regs"
! Unimplemented DWARF opcode 0xa3
) at entry-common.c:0
>p/x mod->mem[0].base
$11 = (void*) 0xFFFF80007AB40000
>p/x mod->mem[4].base
$12 = (void*) 0xFFFF80007AB46000
这样的方式也可以找到.text 和.init.text section 加载的地址。
4. 在ArmDS中加载kernel module符号表
如果把ko文件直接加载到ArmDS中的话,会出现如下错误:
>add-symbol-file src/testmod/mod_debug_test.ko
ERROR(CMD685-COR11-COR236):
! Failed to load symbols for "mod_debug_test.ko"
! Failed to read the symbols from src/testmod/mod_debug_test.ko
! Unable to read OS module symbols, OS support is not yet active.
这是因为ArmDS检测到是ko的时候,会走一个特殊的路径来加载KO的符号表。所以不能用简单的命令来加载,这里需要把单独的section加载到它对应的地址,命令如下:
>add-symbol-file src/testmod/mod_debug_test.ko -s .init.text 0xFFFF80007AB46000 -s .text 0xFFFF80007AB40000 -s .exit.text 0xffff80007ab4002c
或者你只需要加载一个section的符号表,可以使用下面的命令:
>add-symbol-file src/testmod/mod_debug_test.ko -s .init.text 0xFFFF80007AB46000
解下来就可以正常设置断点,开始调试了。
>break mod_debug_test_init
Breakpoint 4 at EL2N:0xFFFF80007AB46000
on file mod_debug_test.c, line 12
>break mod_debug_test_func
Breakpoint 5 at EL2N:0xFFFF80007AB40000
on file mod_debug_test.c, line 7
>break mod_debug_test_exit
Breakpoint 6 at EL2N:0xFFFF80007AB4002C
on file mod_debug_test.c, line 19
>c
Execution stopped in EL2h mode at breakpoint 4: EL2N:0xFFFF80007AB46000
On core ARM_AEM-A_MP_0 (ID 0)
In mod_debug_test.c
EL2N:0xFFFF80007AB46000 12,0 {
>l
007: {
008:
009: pr_info("%s \n",__FUNCTION__);
010: }
011: static int __init mod_debug_test_init(void)
* 012: {
013: pr_info("%s \n",__FUNCTION__);
014: mod_debug_test_func();
015: return 0;
016: }
>c
Execution stopped in EL2h mode at breakpoint 5: EL2N:0xFFFF80007AB40000
On core ARM_AEM-A_MP_0 (ID 0)
EL2N:0xFFFF80007AB40000 7,0 {
>l
002: #include <linux/printk.h>
003:
004: #define MODULE_NAME "mod_debug_test"
005:
006: __attribute__((__noinline__)) static void mod_debug_test_func(void )
* 007: {
008:
009: pr_info("%s \n",__FUNCTION__);
010: }
011: static int __init mod_debug_test_init(void)
>c
Execution stopped in EL2h mode at breakpoint 6: EL2N:0xFFFF80007AB4002C
On core ARM_AEM-A_MP_2 (ID 2)
EL2N:0xFFFF80007AB4002C 19,0 {
>l
014: mod_debug_test_func();
015: return 0;
016: }
017:
018: static void __exit mod_debug_test_exit(void)
* 019: {
020: pr_info("%s \n",__FUNCTION__);
021: }
022: module_init(mod_debug_test_init);
023: module_exit(mod_debug_test_exit);
Comments !