King3399(ubuntu文件系统)KGDB配置与功能测试

0 引言

上一篇文章提到了GDB/GDBServer的调试方法,这种方法无法对驱动模块.ko文件进行调试,对于驱动模块的开发我们需要用到KGDB,本人在配置及使用KGDB时遇到很多问题,虽然KGDB对于驱动模块的开发很重要,但网上的资料大都不涉及具体步骤,本文将仔细记录该过程以供大家参考,内容虽以king3399为目标板进行配置及测试,但流程适用于rk/rp官方SDK

1 内核(kernel)配置KGDB

这里先梳理KGDB配置及调试的大致流程以便有一个宏观的了解:

  1. 在内核(kernel)中开启KGDB相应配置开关
  2. 编译内核(kernel)
  3. 在设备树中传入KGDB相应参数
  4. 重新编译内核(kernel),并将生成的boot.img文件烧录
  5. 交叉编译带有调试信息的驱动模块文件,并将驱动模块文件传至板子
  6. 板子上电,加载模块触发调试等待主机连接
  7. 主机通过调试串口连接板子并开始调试

rk/rp关于KGDB这部分的官方资料几乎没有,如果还在试图寻找的不用再浪费时间了!!!,下边是具体过程,郑重提示:保险起见,任何文件修改前请做好备份,做好修改记录以及虚拟机的快照备份!!!

需要注意的是,下边提到的文件及路径应根据手中的板子决定,不可照抄照搬,即使使用的是同一颗芯片(rk3399),如果板子的型号不同(king3399)或者系统不同,那么涉及的文件也大概率不同!

  1. 查看板子所用配置文件

    在...sdk/下执行 ./build.sh kernel

    如果之前没对kernel做其他的修改该编译过程将持续大约2分钟,从打印日志可提取如下信息:

    1. RK_DEFCONFIG=/home/username/sdk/device/rockchip/.chips/rk3399/king-rk3399-ubuntu_defconfig

      该文件提到了板子所用设备树的相对目录及文件名,RK_KERNEL_DTS_NAME="rk3399/king-rk3399"

      绝对路径为:/home/username/sdk/kernel/arch/arm64/boot/dts/rockchip/rk3399/

    2. K_KERNEL_CFG=rockchip_linux_defconfig

      该文件为板子的内核配置文件

      绝对路径为 /home/username/sdk/kernel/arch/arm64/configs/

    这两个文件是主要配置文件,找不到这两个文件将无法完成本文后续操作

  2. 修改kernel配置

    1. 备份需要修改的文件

      bash 复制代码
      # 备份板子的默认配置文件
      cd ~/sdk/kernel/arch/arm64/configs
      
      cp rockchip_linux_defconfig rockchip_linux_defconfig.20250421.00.bak
      
      # 备份kernel默认配置文件
      cd ~/sdk/kernel
      
      cp .config .config.20250421.00.bak
      
      # 备份修改kernel前的boot.img文件
      cd ~/sdk/kernel
      
      cp boot.img boot.img.20250421.00.bak
      
      # 备份板子设备树文件(king3399 底板)编译后的.dtb文件
      cd ~/sdk/kernel/arch/arm64/boot/dts/rockchip/rk3399
      
      cp king-rk3399.dtb king-rk3399.dtb.20250421.00.bak
      
      # 备份板子设备树文件(rp 层级)
      cd ~/sdk/kernel/arch/arm64/boot/dts/rockchip/rk3399
      
      cp rp-rk3399-board.dtsi rp-rk3399-board.dtsi.20250421.00.bak
      
      # 备份板子设备树文件(rk 层级)
      cd ~/sdk/kernel/arch/arm64/boot/dts/rockchip
      
      cp rk3399-linux.dtsi rk3399-linux.dtsi.20250421.00.bak
      
      # 备份板子设备树文件(king 层级)
      cd ~/sdk/kernel/arch/arm64/boot/dts/rockchip/rk3399
      
      cp king-rk3399.dts king-rk3399.dts.20250421.00.bak

      备份主要是为了后边可能会进行对比/恢复,因为修改kernel的配置后,编译一次大约需要花费60分钟!!!,另外,编译前的部分文件我们在后边可能还会重新用到

    2. 利用make menuconfig修改kernel配置

      这部分内容参考脚注【a】

      bash 复制代码
      cd ~/sdk/kernel
      
      make menuconfig KCONFIG_CONFIG=arch/arm64/configs/rockchip_linux_defconfig ARCH=arm64

      通过界面进入到如下目录并做下述修改

      Kernel hacking > Generic Kernel Debugging Instruments

      Kernel hacking > Generic Kernel Debugging Instruments > KGDB: kernel debugger

      Kernel hacking > Compile-time checks and compiler options

      修改完成后,选择右下角 Save 进行保存,注意不要保存到原路径(这里的原路径指的是 rockchip_linux_defconfig 所在的路径),而是保存到 .config(~/sdk/kernel/ 下,这也就是前文为何将.config备份的原因)

      使用下述命令来保存 defconfig 文件并覆盖原来的配置文件

      bash 复制代码
      # 保存 defconfig 文件
      make savedefconfig ARCH=arm64
      
      # 覆盖原来的配置文件(这也就是前文为何将rockchip_linux_defconfig备份的原因)
      cp defconfig arch/arm64/configs/rockchip_linux_defconfig

      可利用对比软件对比修改前后的rockchip_linux_defconfig,通过对比会发现部分配置位置与备份不同,这并不影响,重要的是多了如下两个配置

      CONFIG_KGDB=y

      CONFIG_KGDB_KDB=y

    3. 编译修改后的kernel,该过程持续约60分钟!!!

      bash 复制代码
      cd ~/sdk
      
      ./build.sh kernel

    完成后会在 ~/sdk/kernel下生成新的boot.img,但这并不是最终烧录的boot.img文件,到这,已经完成kernel的相关配置工作

  3. 修改设备树,传入KGDB启动参数

    1. 指定系统的标准输出(stdout)设备路径

      bash 复制代码
      cd ~/sdk/kernel/arch/arm64/boot/dts/rockchip/rk3399
      
      vim rp-rk3399-board.dtsi
      
      # 修改如下
      chosen {
                // stdout-path = "serial2:1500000n8"; // <--- 修改项
                stdout-path = "serial2:115200n8";
      };
      
      &fiq_debugger {
                rockchip,serial-id = <2>;
                rockchip,wake-irq = <0>;
                rockchip,irq-mode-enable = <1>;  /* If enable uart uses irq instead of fiq */
                rockchip,baudrate = <115200>;  /* Only 115200 and 1500000 */ <--- 核对项
                interrupts = <GIC_SPI 150 IRQ_TYPE_LEVEL_HIGH 0>;
                pinctrl-names = "default";
                pinctrl-0 = <&uart2c_xfer>;
      };

      这个文件中只修改了stdout-path属性,另外还需要注意rockchip,baudrate属性,这里需要核对一下,看是否是115200

      stdout-path:告诉内核应该将控制台输出(如printk、早期启动信息等)发送到哪个设备,在内核完全初始化之前,就确定一个可用的输出设备用于调试信息,当系统有多个可能的输出设备(如UART、LCD、VGA等)时,明确指定使用哪一个[摘自 deepseek]

    2. 输入KGDB启动参数

      这部分内容参考脚注【b】

      bash 复制代码
      cd ~/sdk/kernel/arch/arm64/boot/dts/rockchip
      
      vim rk3399-linux.dtsi
      
      # 修改如下
       chosen {
               bootargs = "loglevel=7 earlycon=uart8250,mmio32,0xff1a0000 console=ttyFIQ0 initcall_debug=1 kgdboc=ttyFIQ0,115200 kgdbcon root=PARTUUID=614e0000-0000 rootfstype=ext4 rw rootwait snd_aloop.index=7 kgdbwait"; // <--- 修改项
       };
      
       fiq_debugger: fiq-debugger {
               compatible = "rockchip,fiq-debugger";
               rockchip,serial-id = <2>;
               rockchip,wake-irq = <0>;
               rockchip,irq-mode-enable = <1>;  /* If enable uart uses irq instead of fiq */
               // rockchip,baudrate = <1500000>;  /* Only 115200 and 1500000 */
               rockchip,baudrate = <115200>;  /* Only 115200 and 1500000 */ <--- 修改项
               interrupts = <GIC_SPI 150 IRQ_TYPE_LEVEL_HIGH 0>;
               pinctrl-names = "default";
               pinctrl-0 = <&uart2c_xfer>;
       };

      这个文件中只修改了bootargs属性,另外还需要注意rockchip,baudrate属性,这里需要核对一下,看是否是115200(这个文件中的 rockchip,baudrate 属性会被rp-rk3399-board.dtsi 覆盖,1500000这个波特率也大概率用不到,可直接修改为115200)

      对于bootargs中的各个参数具体的意义这里不过多解释,但有以下几个需要注意

      kgdbcon:(KGDB Console),允许通过同一串口(或终端)同时进行内核调试和输出控制台信息,如果之前没有接触过kgdb应该不清楚这是什么意思,想要搞明白得大致了解内核调试的过程,正常情况下当加载符号文件并设置断点后我们就会continue让程序继续运行,在程序运行的过程中可能会通过串口A输出一些日志,当程序运行到断点处会停止运行,此时我们将由串口B输入指令(比如在当下断点我们想查看某个变量的状态),那这种情况下就会涉及到两个串口,不管是从硬件还是软件层面来说,这无疑会增加某项成本,那如何解决这一问题?这里我了解到的有两个方案,其一就是这里提到的"kgdbcon",该参数将允许运行日志与调试指令共用一个串口(分时复用),另一个就是"agent-proxy",后者是在软件层面将一个串口分成两个端口,一个用于调试,一个用于日志打印,关于后者的配置及使用可以参考【c】

      kgdbwait:在内核启动早期暂停系统并等待调试器(GDB)连接,这里建议不添加这个参数,因为对于驱动开发来说,我们一般会动态加载模块(.ko),等到没有问题了才会合并,动态加载需要系统完全启动后才操作,添加该参数后,每次都卡在启动过程,然后连接串口,再输入"go",一系列操作下来比较麻烦

    3. 添加调试驱动模块子节点(kgdb_test)

      为更加符合外设的整体驱动配置结构,这里设置了一个kgdb_test子结点,该节点不与任何硬件属性绑定(例如引脚、电平之类的),子节点除了 status 与 compatible 是必要的,其他的属性可有可无,time属性是随意添加的,无实际意义,仅仅是想在该节点中添加一个非字符串属性,在后续调试时,如果有必要可以进行相关操作

      bash 复制代码
      cd ~/sdk/kernel/arch/arm64/boot/dts/rockchip/rk3399
      
      vim king-rk3399.dts
      
      # 添加kgdb_test子节点如下
      kgdb_test: kgdb_test {
           status = "okay";
           compatible="king3399,kgdb-test";
           time = <1000>;
      };
    4. 重新编译内核并核对设备树参数

      完成设备树修改后还需要重新编译内核kernel,本次编译大约持续2分钟

      bash 复制代码
      cd ~/sdk
      
      ./build.sh kernel

      为确保成功修改设备树中的启动参数,强烈建议完成下述核对操作,本人一开始配置KGDB就是过于自信,没有进行反编译处理,导致没有注意到stdout-path这个属性(这个属性在本人所查配置KGDB资料中根本没人提到!!!)

      bash 复制代码
      cd ~/sdk/kernel/arch/arm64/boot/dts/rockchip/rk3399
      
      # 将更新的dtb文件反编译为dts
      # 该过程可能会产生一些警告,可不处理
      dtc -I dtb -O dts king-rk3399.dtb -o  king-rk3399-dtc.dts
      
      # 打开反编译后的文件
      vim king-rk3399-dtc.dts
      
      # 查看chosen以及fiq-debugger节点
      chosen {
              bootargs = "loglevel=7 earlycon=uart8250,mmio32,0xff1a0000 console=ttyFIQ0 initcall_debug=1 kgdboc=ttyFIQ0,115200 kgdbcon root=PARTUUID=614e0000-0000 rootfstype=ext4 rw rootwait snd_aloop.index=7 kgdbwait"; // <--- 核对项
              stdout-path = "serial2:115200n8"; // <--- 核对项
      };
      
      
      fiq-debugger {
              compatible = "rockchip,fiq-debugger";
              rockchip,serial-id = <0x2>;
              rockchip,wake-irq = <0x0>;
              rockchip,irq-mode-enable = <0x1>;
              rockchip,baudrate = <0x1c200>; // <--- 核对项 0x1c200 = 115200
              interrupts = <0x0 0x96 0x4 0x0>;
              pinctrl-names = "default";
              pinctrl-0 = <0x4e>;
              phandle = <0x1a7>;
      };
    5. 烧录内核镜像boot.img

      完成上述操作并核对没有问题后便可将生成的boot.img文件烧录到板子中,boot.img路径为

      bash 复制代码
      cd ~/sdk/kernel/
  4. 编译具有调试信息的驱动模块.ko文件

    在生成带调试信息的.ko文件前,需要知道如何判断.ko文件是否带有调试信息,如果完成前面的内核(kernel)配置并生成新的内核,此时再去编译.ko模块通常就会带有调试信息

    bash 复制代码
    # 这里以之前文章风扇驱动模块举例,
    # 对fan_test.ko文件直接执行如下操作(不重新编译)
    file fan_test.ko 
    
    # 返回
    # fan_test.ko: ELF 64-bit LSB relocatable, 
    # ARM aarch64, version 1 (SYSV), BuildID
    # [sha1]=2a59c...xxx...94c, with debug_info, not stripped
    
    # 为方便对比这里再创建一个新的文件夹,
    # 将 Makefile fan_test.c fan_app.c复制到该文件夹,
    # 并在该文件夹下执行
    make
    
    # 此时生成的.ko文件是配置了kgdb的内核所编译的,
    # 对刚生成的.ko文件执行如下操作
    file fan_test.ko
    
    # 返回
    # fan_test.ko: ELF 64-bit LSB relocatable, 
    # ARM aarch64, version 1 (SYSV), BuildID
    # [sha1]=2a59c...xxx...94c, with debug_info, not stripped

    通过对比发现好像没啥区别(本人的确实没有区别,正常来说应该是不一样的,如果返回的信息中存在 "with debug_info, not stripped",则说明.ko文件包含调试信息),后来我查了一下,如果内核配置了"CONFIG_DEBUG_INFO=y",生成的 .ko 文件通常会包含调试信息,而这个选项(CONFIG_DEBUG_INFO)本人的rockchip_linux_defconfig中一直为开启的(CONFIG_DEBUG_INFO=y),如果配置kgdb后的内核编译的.ko没有"with debug_info, not stripped",可以在模块生成脚本Makefile中强制开启调试信息,具体配置如下

    bash 复制代码
    # 模块同级目录下的Makefile脚本中添加如下两条配置
    EXTRA_CFLAGS += -g  # 强制启用调试信息
    STRIP = :           # 禁用 strip 操作

    此外,还可以通过如下指令确认模块是否带有调试信息(若包含调试信息则通常存在.debug_*字段)

    bash 复制代码
    readelf -S fan_test.ko | grep debug
    
    # 返回类似如下字段
    # [31] .debug_info       PROGBITS         0000000000000000  00000dfc
    # [32] .rela.debug_info  RELA             0000000000000000  0002fa38
    # [33] .debug_abbrev     PROGBITS         0000000000000000  0001b267
    # [34] .debug_loc        PROGBITS         0000000000000000  0001c066
    # ......

    然而就算满足上述两个也不能顺利调试,编译时会对程序进行优化,导致断点无法定位到指定的行,因此还需要在Makefile中添加如下配置

    bash 复制代码
    # 参数                     作用                                   必要性
    # -g3                      生成最大调试信息(包括宏定义)            必需
    # -O0                      完全禁用优化                           必需
    # -Wall                    启用所有警告                           推荐
    # -Wextra                  启用额外警告                           推荐
    # -fno-omit-frame-pointer  保留帧指针(便于回溯)                 内核调试需要
    EXTRA_CFLAGS = -O0
    KBUILD_CFLAGS+="-fno-gnu-unique"

    为突出本文的重点及适用范围,文末仓库将会给出一个简单的驱动模块示例kgdb_module(示例包含kgdb_test.c,kgdb_app.c,Makefile),本文后续调试过程将以此模块进行,该模块不依赖于硬件,且模块逻辑简单明了

    将文末仓库中的kgdb_module文件夹复制到虚拟机的home目录下,修改Makefile内的KERNEL_DIR 与 CROSS_COMPILE路径,这两个路径本系列博文之前有提到,可自行查看,修改完后在kgdb_module文件夹内执行 make,稍等片刻后将生成的kgdb_test.ko与kgdb_app传到板子的home目录下

    bash 复制代码
    scp kgdb_test.ko kgdb_app username@192.168.aaa.bbb:/home/username/xxx
    
    # username:板子的用户
    # 192.168.aaa.bbb:板子的ip
    # /home/username/xxx:home目录下用户username的任意路径
2 调试前最后的准备

为了使本文描述更加准备易懂,这里做如下几个定义:

  1. 虚拟机:代指调试机,也就是前文用来编译boot.img、.ko文件的电脑
  2. 虚拟机-终端a:虚拟机中开启的一个终端,以此类推-终端b、c...x
  3. 板子:代指目标机,也就是开发板king3399
  4. 板子-终端1:板子中开启的一个终端,以此类推-终端2、3...n

将usb转串口模块插入电脑,另一端接入板子调试串口的接口(板子引出的接口上有标注丝印DEBUG,具体型号的板子可查看原理图,交叉连接TX-RX,RX-TX,GND-GND)

在虚拟机中打开虚拟机-终端a,查看usb转串口模块所在路径(文件夹),若存在多个则可将usb转串口拔掉后再执行该指令以确定具体的路径,这里假设所用路径为/dev/ttyUSB0

bash 复制代码
ls /dev/ttyUSB*

在虚拟机-终端a中打开串口并进行相应配置(minicom的安装与使用可自行网上查找)

bash 复制代码
sudo minicom -s

# 在弹出的一级界面中通过上下键选择 Serial port setup,回车打开串口配置界面

# 在弹出的二级界面中通过键盘字母A切到第一行,并修改为相应路径,回车保存

# 在弹出的二级界面中通过键盘字母E切到第五行,并修改为前文设备树中设定的波特率,回车保存

# 在弹出的二级界面中通过键盘字母F切到第六行,直接回车可切换状态为 No

# 完成上述修改后双击回车退出二级界面

# 在弹出的一级界面中通过上下键选择 Save Setup as dfl,并回车,保存此次修改

# 在弹出的一级界面中通过上下键选择 Exit,并回车,退出一级界面并打开串口

# 以上便是minicom最基本也是最常用的指令/配置,对于其他配置可通过 Ctrl + A 再按 Z 调出配置快捷键界面(帮助界面)

如果在bootargs中添加kgdbwait,此时将板子上电,可以看到大量系统启动日志在虚拟机-终端a(串口)中输出,片刻后系统启动过程会卡住,此时按下虚拟机键盘的上键,可调出[kdb]输入提示符,注意,这里是 kdb 不是 kgdb ,出现这种情况的原因是因为我们在前文配置内核(kernel)时勾选了 "KGDB_KDB:include kdb frontend for kgdb",这个选项会让系统先进入kdb调试状态(关于kdb与kgdb的区别自行网上查看),由于我们不用kdb这种调试方法,因此现在有两条路:其一,在[kdb]输入提示符下输入"go"让系统继续启动,其二,在[kdb]输入提示符下输入"kgdb"进入kgdb调试模式,由于本人没有添加kgdbwait参数,因此这两条路不细说,此外,本人在配置内核(kernel)没有勾选KGDB_KDB时系统启动过程好像会出问题,不知是不是个人原因,由于kernel的编译时间太长了,因此没有深究

如果在bootargs中没有添加kgdbwait,此时将板子上电,可以看到大量系统启动日志在虚拟机-终端a(串口)中输出,系统应该会正常完全启动,板子上的风扇会呼呼转或者板子上接的屏幕会显示系统桌面

假设到这一步系统已经完全启动,那么关于虚拟机部分的准备工作已经完成,剩下的就是板子的准备工作

bash 复制代码
# 打开板子-终端1,将路径切到kgdb_module目录下,加载模块
sudo /sbin/insmod kgdb_test.ko

# 为确定该模块已经加载还可以查看加载模块列表(板子-终端1)
lsmod
# 板子-终端1打印信息如下
# Module             Size           User by
# kgdb_test          16384          0

# 查看设备列表,后续会对该文件写入得确保已经存在(板子-终端2)
ls /dev/kgdb*
# 板子-终端2打印信息如下
# /dev/kgdb_test

# 查看已分配的主设备号(板子-终端2)
cat /proc/devices
# 板子-终端2打印信息如下
# 505 kgdb_test (可能不同)

# 打开板子-终端2,查询驱动模块加载地址,可进入如下路径查看
cd /sys/module/kgdb_test/sections

sudo cat .text
# 返回如下地址,这需要记录,后边在虚拟机中调试时需要输入
# 0xffffffc001775000 (可能不同)

# 打开板子-终端1,运行应用程序(kgdb_app),检测是否正常
sudo ./kgdb_test 0
# 板子-终端1打印信息如下
# kgdb test begin

# 打开板子-终端3,最后一步准备工作,
# 启用 Linux 系统的 SysRq(Magic SysRq)功能
sudo sh -c "echo 1 > /proc/sys/kernel/sysrq"
# 执行完该指令板子-终端3应该没有任何日志输出,
# 为验证是否生效可执行如下指令

sudo cat /proc/sys/kernel/sysrq
# 若输出 1,表示已启用

# 上述配置在重新上电后将会失效,持久化配置(重启后生效)如下
# 下述操作本人没有做,仅供参考
sudo vim /etc/sysctl.conf
# 在其中添加一行 kernel.sysrq = 1
# 运行加载配置
sudo sysctl -p

# 关于这最后一步"启用 Linux 系统的 SysRq(Magic SysRq)功能",
# 很多博文中并没有提到,这里做一个小的扩展,
# 当时本人在执行sudo sh -c "echo g > /proc/sysrq-trigger"后
# 并没有触发调试,仅在虚拟机-终端a中打印几行提示日志
# 后来查找相关资料得知是当前用户权限不够,
# 在没有执行"sudo sh -c "echo 1 > /proc/sys/kernel/sysrq"前,
# 我们可以查询当前用户权限,在板子-终端3中输入如下指令
sudo cat /proc/sys/kernel/sysrq
# 本人的日志返回176
# SysRq 标志位解析
# /proc/sys/kernel/sysrq 的值是一个位掩码(bitmask)
# 各二进制位代表的权限如下:

# 位(二进制)	值(十进制)   功能描述
# 000000001	   1	         启用 0-9 控制台日志级别(0 紧急,9 调试)
# 000000010	   2	         允许键盘控制(调整键盘映射、切换终端等)
# 000000100	   4	         允许进程管理(终止任务、kill 信号等)
# 000001000	   8	         允许同步文件系统(sync 命令)
# 000010000	   16	         允许重新挂载文件系统为只读(remount-ro 命令)
# 000100000	   32	         允许发送信号(term/kill 进程)
# 001000000	   64	         允许重启/关机(reboot/poweroff 命令)
# 010000000	   128	         允许修改进程优先级(nice 调整)
# 100000000	   256	         允许所有功能(完全启用 SysRq)

# 将 176 转换为二进制并匹配标志位:176 (十进制) = 10110000 (二进制)

# 对应标志位组合:

# 128 (010000000):允许修改进程优先级(nice)。

# 32 (00100000):允许发送信号(term/kill 进程)。

# 16 (00010000):允许重新挂载文件系统为只读(remount-ro)。

# 176 所拥有权限(当前配置下,SysRq 仅支持以下操作)

# 重新挂载文件系统为只读(Alt+SysRq+u 或 echo u > /proc/sysrq-trigger)。

# 终止进程(如 Alt+SysRq+e 终止所有进程)。

# 调整进程优先级(较少使用)。

完成上述准备工作后终于进入真正的调试部分了

3 最后的调试

为了避免有遗漏的环节,这里对虚拟机与板子的配置与状态做一个总结:

  1. 虚拟机

    1. kernel配置了kgdb并编译通过
    2. 设备树配置了kgdbcon、波特率、添加子节点等并编译kernel通过
    3. 安装了minicom并配置且打开
    4. 虚拟机编译生成带调试信息的.ko文件
  2. 板子

    1. 烧录了上述配置了kgdb的boot.img
    2. 加载了编译后的.ko文件并查询相关地址,
    3. 启用 Linux 系统的 SysRq(Magic SysRq)功能
bash 复制代码
# 打开板子-终端3,触发 Linux 内核的 SysRq (Magic System Request) 功能
sudo sh -c "echo g > /proc/sysrq-trigger"
# 该指令强制内核生成一个崩溃转储,用于 内核调试 或 故障分析
# 此时板子的卡住,板子上连接的外设像键盘、鼠标一类的都不可用(没有反应)
# 虚拟机-终端a 返回如下日志
# Entering kdb (current=0xffffff80386763c0, pid 2377) on processor 2 due to Keyby

# 在虚拟机-终端a 中按下键盘的方向键"上键"调出输入提示符
# [2]kdb>
# 在该输入提示符输入 kgdb 切换为 kgdb调试
[2]kdb> kgdb   
# 虚拟机-终端a 返回如下日志
# Entering please attach debugger or use $D#44+ or $3#33 
# 到此,虚拟机-终端a 的使命就算是完成了,可以直接关掉了(叉掉)

# 打开虚拟机-终端b,进入如下路径并打开vmlinux(Linux 内核 ELF 可执行文件,
# 该文件在修改配置文件后编译kernel时生成的)
cd ~/sdk/kernel

sudo /home/username/sdk/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gdb ./vmlinux
# 除了上述指令(sdk自带的)还可以使用gdb-multiarch工具
# gdb-multiarch工具需自行安装(网上自查)
# ~/sdk/kernel sudo gdb-multiarch ./vmlinux
# 不过gdb-multiarch有一些问题,文末会说明

# 虚拟机-终端b返回如下信息
# ...
# For help, type "help".
# Type "apropos word" to search for commands related to "word"...
# Reading symbols from ./vmlinux...

# 设置板子架构为aarch64(根据实际架构设置)
(gdb) set architecture aarch64
# The target architecture is set to "aarch64".

# 设置虚拟机端调试串口所用波特率为115200,这个需要与前文对应
(gdb) set serial baud 115200

# 通过虚拟机usb转串口连接板子
# 这里是软件层面的连接,前提是完成前文的硬件实物连接
(gdb) target remote /dev/ttyUSB0
# Remote debugging using /dev/ttyUSB0
# warning: multi-threaded target stopped without sending a thread-id, using first non-exited thread
# [Switching to Thread 4294967294]
# arch_kgdb_breakpoint () at ./arch/arm64/include/asm/kgdb.h:21
# 21		asm ("brk %0" : : "I" (KGDB_COMPILED_DBG_BRK_IMM));
# 注:这里可能会返回如下错误
# /dev/ttyUSB0: Input/output error.
# 本人多次执行 target remote /dev/ttyUSB0 后便可正常连接(仅供参考)

# 查看当前加载的文件
(gdb) l
# 16
# 17	#ifndef	__ASSEMBLY__
# 18	
# 19	static inline void arch_kgdb_breakpoint(void)
# 20	{
# 21		asm ("brk %0" : : "I" (KGDB_COMPILED_DBG_BRK_IMM));
# 22	}
# 23	
# 24	extern void kgdb_handle_bus_error(void);
# 25	extern int kgdb_fault_expected;
# 从打印的代码可以看到这不是我们需要调试的代码

# 更换调试文件
(gdb) file
# A program is being debugged already.
# Are you sure you want to change the file? (y or n) y
# No executable file now.
# Discard symbol table from `/home/username/sdk/kernel/vmlinux'? (y or n) y
# No symbol file now.

# 确认当前已无调试文件
(gdb) l
# No symbol table is loaded.  Use the "file" command.

# 加载kgdb_test符号文件
# /home/username/kgdb_module/kgdb_test.ko:虚拟机中kgdb_test.ko文件的路径
# 0xffffffc001775000:kgdb_test.ko在板子上加载后查询到的地址
(gdb) add-symbol-file /home/username/kgdb_module/kgdb_test.ko 0xffffffc001775000
# add symbol table from file "/home/username/kgdb_module/kgdb_test.ko" at
# 	.text_addr = 0xffffffc001775000
# (y or n) y
# Reading symbols from /home/username/kgdb_module/kgdb_test.ko...

# 查看当前加载的文件(此处返回我们想要调试的文件内容)
(gdb) l
# 1	#include <linux/init.h>
# 2	#include <linux/module.h>
# 3	#include <linux/fs.h>
# 4	#include <linux/cdev.h>
# 5	#include <linux/uaccess.h>
# 6	#include <linux/types.h>
# 7	#include <linux/kernel.h>
# 8	#include <linux/delay.h>
# 9	#include <linux/ide.h>
# 10	#include <linux/errno.h>

# 此时可以多次输入 l (list) 查看调试的代码,
# 或者直接 回车 ,代表重复上上次的指令
# 或者跳转到指定行 l num ,其中 num 代表行号

# l (list)打印的代码左侧是 行号,在需要的行设置断点
# 打断点 break num
(gdb) b 44
# Breakpoint 1 at 0xffffffc00177554c: file /home/xxx/kgdb_module/kgdb_test.c, line 44.
(gdb) b 65
# Breakpoint 2 at 0xffffffc0017756e4: file /home/xxx/kgdb_module/kgdb_test.c, line 65.
(gdb) b 77
# Breakpoint 3 at 0xffffffc001775710: file /home/xxx/kgdb_module/kgdb_test.c, line 77.
(gdb) b 81
# Breakpoint 4 at 0xffffffc001775720: file /home/xxx/kgdb_module/kgdb_test.c, line 81.
(gdb) b 86
# Breakpoint 5 at 0xffffffc00177572c: file /home/xxx/kgdb_module/kgdb_test.c, line 86.

# 查询断点 info break
(gdb) i b
# Num     Type           Disp Enb Address            What
# 1       breakpoint     keep y   0xffffffc00177554c in kgdb_chr_dev_fun 
#                                                    at /home/xxx/kgdb_module/kgdb_test.c:44
# 2       breakpoint     keep y   0xffffffc0017756e4 in kgdb_chr_dev_write 
#                                                    at /home/xxx/kgdb_module/kgdb_test.c:65
# 3       breakpoint     keep y   0xffffffc001775710 in kgdb_chr_dev_write 
#                                                    at /home/xxx/kgdb_module/kgdb_test.c:77
# 4       breakpoint     keep y   0xffffffc001775720 in kgdb_chr_dev_write 
#                                                    at /home/xxx/kgdb_module/kgdb_test.c:81
# 5       breakpoint     keep y   0xffffffc00177572c in kgdb_chr_dev_write 
#                                                    at /home/xxx/kgdb_module/kgdb_test.c:86

# 设置完断点后让程序继续运行
(gdb) c
# Continuing.
# warning: Invalid remote reply: 3

# 此时板子的控制权释放,可以正常使用
# 在板子-终端1 执行应用程序,这里单走一个 6
sudo ./kgdb_app 6

# 板子执行上述指令后由于触发断点卡住
# 控制权又交到了 虚拟机-终端b
# 虚拟机-终端b 返回信息如下
# ...
# [New Thread 4872]
# [New Thread 4882]
# [Switching to Thread 4883]

# Thread 341 hit Breakpoint 2, kgdb_chr_dev_write (filp=0xffffff800dc74800, 
#     buf=0x7ff80df0a7 "\006", cnt=1, offt=0xffffffc00db1be20)
#     at /home/sr/ws/mydriver/kgdb_module/kgdb_test.c:65
# 65		int error = copy_from_user(&write_data, buf, cnt);
(gdb) p error
# $1 = 127
(gdb) disable 2
(gdb) c
# Continuing.

# Thread 341 hit Breakpoint 3, kgdb_chr_dev_write (filp=0xffffff800dc74800, 
#     buf=0x7ff80df0a7 "\006", cnt=1, offt=0xffffffc00db1be20)
#     at /home/sr/ws/mydriver/kgdb_module/kgdb_test.c:77
# 77			aaa = write_data - 2;
(gdb) p aaa
# $2 = 0
(gdb) disable 3
(gdb) c
# Continuing.

# Thread 341 hit Breakpoint 5, kgdb_chr_dev_write (filp=0xffffff800dc74800, 
#     buf=0x7ff80df0a7 "\006", cnt=1, offt=0xffffffc00db1be20)
#     at /home/sr/ws/mydriver/kgdb_module/kgdb_test.c:86
# 86		ccc = kgdb_chr_dev_fun(aaa);
(gdb) p aaa
# $3 = 4
(gdb) p ccc
# Cannot access memory at address 0x98
(gdb) disable 5
(gdb) c
# Continuing.

# Thread 341 hit Breakpoint 1, kgdb_chr_dev_fun (n=4)
#     at /home/sr/ws/mydriver/kgdb_module/kgdb_test.c:44
# 44		bbb = n * 2;
(gdb) p bbb
# $4 = 0
(gdb) p n
# $5 = 4
(gdb) disable 1
(gdb) c
# Continuing.

# 执行到这里由于应用程序结束了,控制权又交到了 板子-终端1
4 一些小问题
  1. 执行 vmlinux 的工具

    1. 如果使用自己安装的,可在"/usr/bin/"目录下查找

    2. 如果使用SDK自带的(如本文),则可在下述类似路径查找

      bash 复制代码
      /home/username/sdk/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin
    3. 如果使用gdb-multiarch,则需要自行安装(apt install ...),但本人在使用这个工具时遇到如下问题(能力有限,没有解决,不确定是工具的问题还是人的问题)

      bash 复制代码
      # 问题 1
      (gdb) add-symbol-file ~/kgdb_module/kgdb_test.ko 0xffffffc001775000
      # add symbol table from file "~/kgdb_module/kgdb_test.ko" at
      # .text_addr = 0xffffffc001775000
      # (y or n) y
      # BFD: warning: ~/kgdb_module/kgdb_test.ko: unsupported GNU_PROPERTY_TYPE (5) type: 0xc0000000
      # Reading symbols from ~/kgdb_module/kgdb_test.ko...done.
      
      # 问题 2
      (gdb) target remote /dev/ttyUSB0
      # Remote debugging using /dev/ttyUSB0
      # arch_kgdb_breakpoint () at ./arch/arm64/include/asm/kgdb.h:21
      # 21		asm ("brk %0" : : "I" (KGDB_COMPILED_DBG_BRK_IMM));
      # Call Frame Instruction op 45 in vendor extension space is not handled on this architecture.
  2. 远程连接时

    1. 返回 'vMustReplyEmpty': vMustReplyEmpty

      bash 复制代码
      (gdb) target remote /dev/ttyUSB0
      # Remote debugging using /dev/ttyUSB0
      # Remote replied unexpectedly to 'vMustReplyEmpty': vMustReplyEmpty
      # 原因:
      # 板子没有触发远程调试,需要在板子上执行
      # sudo sh -c "echo g > /proc/sysrq-trigger"
    2. 返回 Input/output error.

      bash 复制代码
         (gdb) target remote /dev/ttyUSB0
         # /dev/ttyUSB0: Input/output error.
         # 本人多次执行 target remote /dev/ttyUSB0 后便可正常连接(仅供参考)
  3. 无法触发断点

    现象是一切正常,但运行应用程序时(sudo ./kgdb_app 6),程序无法停留在断点处,从头运行到尾,这里给出一个可能的原因,检查断点信息是否有具体的地址,方法如下

    bash 复制代码
       (gdb) i b
       # Num     Type           Disp Enb Address            What
       # 1       breakpoint     keep y   <PENDING>          /home/xxx/kgdb_module/kgdb_test.c:44
       # 2       breakpoint     keep y   <PENDING>          /home/xxx/kgdb_module/kgdb_test.c:65
    
       # 可以看到断点的Address属性为 <PENDING>,
       # 原因是模块尚未被加载到内核就设置了断点,
       # 这里是板子上的模块未被加载到内核
       # 这种现象大概率是先设置的断点再在板子上加载模块
    
       # 解决方法
       # 删除断点 delete num
       (gdb) d 1
       (gdb) d 2
    
       # 在板子加载模块后重新设置断点
       (gdb) b 44
       (gdb) b 65
    
       # 再次查看断点
       (gdb) i b
       # Num     Type           Disp Enb Address            What
       # 3       breakpoint     keep y   0xffffffc00177554c in kgdb_chr_dev_fun 
       #                                                    at /home/xxx/kgdb_module/kgdb_test.c:44
       # 4       breakpoint     keep y   0xffffffc0017756e4 in kgdb_chr_dev_write 
       #                                                    at /home/xxx/kgdb_module/kgdb_test.c:65
    
       # 与之前的对比发现 Address属性有了具体的地址
       # 此时可以再次尝试执行应用程序
  4. 调试卡在 printk

    这个问题网上有提到,就是如果我们在kgdb_test.c中有使用printk,程序运行时会一直卡在(陷入)printk处(即使没有在该行打断点),这个问题本人能力有限,没有解决,在kgdb_test.c中我将所有的printk都注释掉了,如果想复现的可以释放编译再尝试调试,我的想法是既然都使用kgdb了,对于中间变量为啥不用 print(p value)

  5. 无法Continue

    现象就是当执行c时,程序一直卡在当前断点,这个问题本人能力有限,没有解决,在本文上边的例子中,用了一个很笨的办法,那就是执行到某个断点后如果要继续向下运行,那就将当前断点禁用disable(disable num),对于如何查看断点是否被禁用可在执行(i b)后查看断点的Enb列(y:使能,n:失能),程序运行完后再统一使能(enable),类似的操作还有禁用所有断点(disable),删除所有断点(delete)

  6. 其他工具

    这里提两个本人感觉很实用的工具,但安装与使用方法这里不赘述,自行网上查找

    1. agent-proxy :将一个串口变成两个,一个调试,一个打印日志
    2. CGDB :将终端窗口分为上下两部分,上面是代码窗口,下面是调试窗口
5 总结

这篇博文前后花了一个多月的时间,基本把所有步骤与问题都记录了下来,花这么多时间一方面是本人菜,另一方面是官方参考资料就像是@¥%.,关于板子配置kgdb的部分我几乎把所有能查到的都查了,rk的芯片在市场上的保有量应该不算小,这也是当初选择这个板子的主要原因,驱动开发这个方向也并不那么小众,但是相关的资料都很零碎很乱,本人并不是这方面的砖家,以前主要接触的芯片都是ST、Ti、Analog以及Xilinx等,但东西是否做的用心稍微对比一下就能看出,芯片你玩不过人家,你#@把资料与生态先做起来啊,活该%¥!

【a】 [[野火]《嵌入式Linux镜像构建与部署---基于LubanCat-RK系列板卡》_20240615.pdf------6.4 内核配置选项]

【b】 Rv1109内核调试

【c】 使用KGDB调试Linux驱动(以imx6ull开发板为例)

【d】 项目相关资料 pwd : 1nca

相关推荐
0wioiw01 小时前
Ubuntu基础(监控重启和查找程序)
linux·服务器·ubuntu
Tipriest_1 小时前
Ubuntu常用的软件格式deb, rpm, dmg, AppImage等打包及使用方法
linux·运维·ubuntu
GBXLUO1 小时前
windows的vscode无法通过ssh连接ubuntu的解决办法
vscode·ubuntu
笑衬人心。3 小时前
Ubuntu 22.04 修改默认 Python 版本为 Python3 笔记
笔记·python·ubuntu
物联网老王13 小时前
Ubuntu Linux Cursor 安装与使用一
linux·运维·ubuntu
fangeqin1 天前
ubuntu源码安装python3.13遇到Could not build the ssl module!解决方法
linux·python·ubuntu·openssl
风口上的吱吱鼠1 天前
Armbian 25.5.1 Noble Gnome 开启远程桌面功能
服务器·ubuntu·armbian
笑衬人心。1 天前
Ubuntu 22.04 + MySQL 8 无密码登录问题与 root 密码重置指南
linux·mysql·ubuntu
生如夏花℡1 天前
HarmonyOS学习记录3
学习·ubuntu·harmonyos
星宸追风1 天前
Ubuntu更换Home目录所在硬盘的过程
linux·运维·ubuntu