arm linux内核源码级调试器kgdb配置与使用

来源:互联网 发布:linux php编译 编辑:程序博客网 时间:2024/06/07 11:55

0x1 介绍

  kgdb是源码级调试器,与gdb配合调试linux kenrel,就像使用gdb调试应用程序一样,停止内核运行,可以查看内存,变量和调用栈信息。它可以在源码处设置断点,进行单步调试,函数进入等。
  kgdb需要两台机器,目标机和调试主机。调试主机运行gdb时加载含有符号表的vmlinux,调试运行时的目标机内核,gdb指定连接参数连接到kgdb,需要通过目标架构内核中的kgdb I/O modules进行连接,可以是rs232或以太网。

0x2 使用方法

0x21 内核配置

  CONFIG_KGDB使能KGDB
  Kernel hacking —>
    [*] KGDB: kernel debugger —>

  CONFIG_KGDB_SERIAL_CONSOLE选项确定串口为KGDB的I/O modules driver
  Kernel hacking —>
    [*] KGDB: kernel debugger —>
      <*> KGDB: use kgdb over the serial console(kgdboc)
  
  CONFIG_DEBUG_RODATA选项会标记某些内存区域为只读属性,影响软件断点的使用,这时候需要关闭。如果处理器支持硬件断点,该选项可以打开
  System Type —>
    [ ] Make kernel text and rodata read-only

  CONFIG_FRAME_POINTER选项更好的支持栈回溯,该选项依赖于CONFIG_HAVE_FUNCTION_GRAPH_TRACER
  Kernel hacking —>
    [*] Tracers —>
      [*] Kernel Function Tracer
      [*] Kernel Function Graph Tracer
  CONFIG_HAVE_FUNCTION_GRAPH_TRACER选中后CONFIG_FRAME_POINTER默认选中

  CONFIG_DEBUG_INFO选项在内核中加入调试信息
  Kernel hacking —>
    Compile-time checks and compiler options —>
      [*] Compile the kernel with debug info

  CONFIG_KALLSYMS选项可以在运行时通过name访问符号信息
  General setup —>
    [*] Configure standard kernel features (expert users) —>
       -*- Load all symbols for debugging/ksymoops

#CONFIG_DEBUG_RODATA is not setCONFIG_KGDB=yCONFIG_KGDB_SERIAL_CONSOLE=yCONFIG_HAVE_FUNCTION_GRAPH_TRACER=yCONFIG_FRAME_POINTER=yCONFIG_DEBUG_INFO=yCONFIG_KALLSYMS=y

0x22 激活

  内核启动参数:
  kgdboc=<tty-device>,[baud]
  示例1:
  添加到内核引导参数

kgdboc=ttyAMA0,115200

  示例2:
  运行时激活

echo ttyAMA0 > /sys/module/kgdboc/parameters/kgdboc

  运行时关闭

echo "" > /sys/module/kgdboc/parameters/kgdboc

0x23 触发

  发生oops或fault触发kgdb。或者手动触发kgdb,手动进入命令:

echo g > /proc/sysrq-trigger

0x3 qemu参数

清单 3-1. qmeu运行时 ttyAMA0作为服务器通信IO,控制台转换为tcp服务器

qemu-system-arm -M vexpress-a9 \     -m 512M -kernel ~/src/linux/kernel/vexpress-a9-linux-4.10.9/arch/arm/boot/zImage \     -dtb ~/src/linux/kernel/vexpress-a9-linux-4.10.9/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \     -nographic \     -append “root=/dev/mmcblk0 console=ttyAMA0 kgdboc=ttyAMA0,115200” \     -sd /home/marquis/src/qemu/image/rootfs.ext4 \     -serial tcp::8181,server,nowait

0x4 代理使用

  在uart接口足够多的情况下,调试串口和kgdb I/O串口可以分别占用。如果只有一个调试串口,可以考虑使用代理工具agent-proxy,它是一个tty到tcp的连接,tty是硬件平台的调试终端,tcp做为服务端,允许多个客户端同时程序连接。

这里写图片描述

图4-1 agent-proxy共享控制台

  通过kgdboc可以连接一个交互telnet客户端和一个调试端口,两种连接方法分别为:
  方法一,连接主机到目标机的串口作为服务端:
  agent-proxy 2223^2222 0 /dev/ttyS0,115200 &
  /dev/ttyS0为PC端串口,115200为波特率

  方法二,远程服务器或模拟器的连接方法:
  agent-proxy 2223^2222 localhost 8181 &
  2223为控制台客户端连接的端口,2222为gdb连接的端口,8181为服务端口(qemu运行时的监听端口),agent-proxy作为客户端连接服务端。

  agent-proxy源码下载地址
  git clone git://git.kernel.org/pub/scm/utils/kernel/kgdb/agent-proxy.git
  编译
  cd agent-proxy ;
  make

0x5 调试技巧

0x51 agent-proxy的使用

  1. agent-proxy运行
    agent-proxy 2223^2222 localhost 8181 &
  2. 运行qemu
    直接执行0x3节( qemu参数)所列出的命令,其中参数-serial tcp::8181,server,nowait创建agent-proxy所连接的服务器,监听端口8181
  3. 连接console
    telnet localhost 2223
    Enter
  4. 触发kgdb
    echo g > /proc/sysrq-trigger
  5. 连接gdb
    加载内核符号表
    arm-none-linux-gnueabi-gdb vmlinux
    进入gdb后,执行
    target remote localhost:2222
    这里写图片描述
    图5-1 agent-proxy操作

0x52 模块的符号表加载

  1. 装载模块
    insmod test_panic.ko
  2. 查看代码段和数据段的虚拟地址
    cat /sys/module/test_panic/sections/.text
    cat /sys/module/test_panic/sections/.bss
  3. gdb加载模块符号表
    add-symble-file test_panic.ko ADDR_SEC_TEXT -s .bss ADDR_SEC_BSS
    这里写图片描述
    图5-3 加载模块符号表

0x53 断点

  1. 设置断点
    (gdb)break test_proc_write
  2. 查询断点信息
    (gdb)info break
  3. 触发断点
    控制台执行echo 1 > /proc/test_panic/test
  4. 删除断点
    (gdb)delete 1
    这里写图片描述
    图5-2 设置断点和删除断点

0x56 kgdb遗留问题

  arm kgdb不支持硬件断点,所以无法使用。因为kgdb和kdb使用相同的后端,所以在arm平台kdb也不支持硬件断点。

0x6 KGDB其他

0x61 KGDB与KDB

  kgdb和kdb是内核两个不同的调试器前端,如果你恰当的编译和运行linux kernel,两个调试器是可以运行时动态切换的。

0x62 kdb和kgdb互相切换

  1. kdb到kgdb
    进入kdb shell后,gdb内远程连接kgdb,则直接进入kgdb模式。
  2. kgdb到kdb
    退出远程gdb,在调试串口输入”$3#33”。
    P.S. 实验的过程中从kgdb切换回kdb会导致系统挂起,没有任何信息,不知道是qemu的问题还是kgdb的问题。

0x63 kgdb现状(不完整,待补充)

  1. kgdb和kdb使用相同的后端
  2. kgdboe的方式不可靠
  3. kgdboc虽然速度慢但是比较可靠
  4. EHCI debug port是最快的kgdb连接方式

0x7 测试环境

0x71 版本信息

  主机系统:ubuntu 14.04
  linux版本号: 4.10.9
  交叉编译工具版本:
  arm-none-linux-gnueabi-gcc (Sourcery CodeBench Lite 2014.05-29) 4.8.3
  arm-none-linux-gnueabi-gdb GNU gdb (Sourcery CodeBench Lite 2014.05-29) 7.7.50.20140217-cvs
  qemu版本:QEMU emulator version 2.7.1
  模拟开发板:arm vexpress a9

0x72 测试代码

清单7-1.

/* * Copyright (c) 2005-2010 Wind River Systems, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. */#include <linux/module.h>#include <linux/delay.h>#include <linux/moduleparam.h>#include <linux/types.h>#include <linux/timer.h>#include <linux/miscdevice.h>#include <linux/watchdog.h>#include <linux/fs.h>#include <linux/notifier.h>#include <linux/reboot.h>#include <linux/init.h>#include <linux/proc_fs.h>#include <linux/sched.h>#include <asm/uaccess.h>#include <linux/slab.h>#define LOCAL_MODULE_VERSION    "1.0"#define MODULE_NAME     "test_panic"#define DEAD_ADDRESS        0xdeadbeefstatic struct proc_dir_entry *panic_dir;static struct proc_dir_entry *panic_file;static struct proc_dir_entry *test_file;static struct proc_dir_entry *print_file;static int panic_proc_open(struct inode *inode, struct file *file){    int err = 0;    if (try_module_get(THIS_MODULE)) {        return err;    }    return -ENODEV;}static int panic_proc_release(struct inode *inode, struct file *file){    module_put(THIS_MODULE);    return 0;}static ssize_t panic_proc_write(struct file *file, const char __user *buffer,                       size_t count, loff_t *data){    trace_printk("Starting panic\n");    printk(KERN_INFO "=============Starting panic\n");    panic("test_panic running!\n");    return count;}static int test_aaa = 0;static int *test_addr = NULL;static ssize_t test_proc_write(struct file *file, const char __user *buffer,                       size_t count, loff_t *data){    printk(KERN_INFO "test_aaa = 0x%08x\n", test_aaa);    test_addr = &test_aaa;    return count;}static ssize_t print_proc_write(struct file *file, const char __user *buffer,                       size_t count, loff_t *data){    printk(KERN_INFO "enter printk\n");    return count;}static const struct file_operations panic_fops = {    .owner = THIS_MODULE,    .open  = panic_proc_open,    .write = panic_proc_write,    .release = panic_proc_release,};static const struct file_operations test_fops = {    .owner = THIS_MODULE,    .write = test_proc_write,};static const struct file_operations print_fops = {    .owner = THIS_MODULE,    .write = print_proc_write,};static int __init test_panic_init(void){    int rc = -ENOMEM;    trace_printk("Running test_panic_init()");    /* create directory */    panic_dir = proc_mkdir(MODULE_NAME, NULL);    if(!panic_dir)         return rc;    trace_printk("test_panic: Building panic\n");    panic_file = proc_create("panic", S_IFREG | S_IRUGO | S_IWUSR,  panic_dir, &panic_fops);    if(!panic_file)        goto fail1;    test_file = proc_create("test", S_IFREG | S_IRUGO | S_IWUSR,  panic_dir, &test_fops);    if(!panic_file)        goto fail2;    print_file = proc_create("print", S_IFREG | S_IRUGO | S_IWUSR,  panic_dir, &print_fops);    if(!panic_file)        goto fail3;    trace_printk(KERN_INFO "%s %s initialised\n", MODULE_NAME,           LOCAL_MODULE_VERSION);    printk(KERN_INFO "%s %s initialised\n", MODULE_NAME,            LOCAL_MODULE_VERSION);    return 0;fail3:    remove_proc_entry("test", panic_dir);fail2:    remove_proc_entry("panic", panic_dir);fail1:    remove_proc_entry(MODULE_NAME, NULL);    return rc;}static void __exit test_panic_exit(void){    remove_proc_entry("panic", panic_dir);    remove_proc_entry("test", panic_dir);    remove_proc_entry("print", panic_dir);    remove_proc_entry(MODULE_NAME, NULL);    printk(KERN_INFO "%s %s removed\n", MODULE_NAME, LOCAL_MODULE_VERSION);}module_init(test_panic_init);module_exit(test_panic_exit);MODULE_DESCRIPTION("base on WindRiver code");MODULE_AUTHOR("marquis song");MODULE_LICENSE("GPL v2");

参考资料

https://www.kernel.org/pub/linux/kernel/people/jwessel/kdb/
https://www.kernel.org/pub/linux/kernel/people/jwessel/dbg_webinar/State_Of_kernel_debugging_LinuxCon2014.pdf