如何使用ArmDS调试动态加载的Linux kernel模块

根据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 !