Linux嵌入式kgdb調試環境搭建


======================= 我的環境 ==========================
PC 端: win7 + vmware-15 ubuntu16.04
開發板:Freescale i.MX6  單CPU  Linux-4.1.15
 交叉編譯器為 arm-none-linux-gnueabi-gcc(gcc version 4.6.3)
===========================================================

1. 前言

  上篇博文介紹了應用程序調試工具 gdb + gdbserver, 那有沒有調試內核的呢?  沒錯, 就是本文介紹的kgdb, 當然早期有kdb, 后面kdb合並到kgdb了, 作為kgdb的前端, 后面我們會介紹, 而kgdb工具跟開發板通信支持kgdboc(串口)和kgdboe(網絡),但新版內核只整合kgdboc, 網絡被廢棄了, 所以下文我們只介紹串口通信。

  串口通信有個問題就是, 如果開發板有多余的串口接出來是最好的, 但一般只有控制台console接出來, 所以當我們占用console作為kgdboc的通信接口, 那內核printk等打印我們是沒辦法通過shell  CRT軟件看到的, 只有退出kgdb的時候才可以使用,

另外需要非常注意的是, 虛擬機必須用vmware, 不能用virtbox, 我用vbox-6.4版本的經常通信一會兒就沒反應了。  

 

2. kernel配置選項

Kernel hacking --->
  [*] KGDB: kernel debugger --->

                

  如果想用kdb 則選上“KGDB_KDB: include kdb frontend for kgdb”, 但觸發kgdb是會先進入kdb模式, 也配置文件多出以下幾個選項(注意紅色):

--- target/linux/imx6ul/config-4.1    (revision 8040)
+++ target/linux/imx6ul/config-4.1    (working copy)
@@ -386,10 +386,12 @@
 # CONFIG_COMPILE_TEST is not set
 CONFIG_CONFIGFS_FS=y
 # CONFIG_CONNECTOR is not set
+CONFIG_CONSOLE_POLL=y
 CONFIG_CONSOLE_TRANSLATIONS=y
 # CONFIG_CORDIC is not set
 CONFIG_COREDUMP=y
 # CONFIG_CORESIGHT is not set
+# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
 # CONFIG_CPUFREQ_DT is not set
 CONFIG_CPU_32v6K=y
 CONFIG_CPU_32v7=y
@@ -550,7 +552,10 @@
 # CONFIG_DEBUG_FS is not set
 # CONFIG_DEBUG_GPIO is not set
 CONFIG_DEBUG_IMX_UART_PORT=1
-# CONFIG_DEBUG_INFO is not set
+CONFIG_DEBUG_INFO=y
+# CONFIG_DEBUG_INFO_DWARF4 is not set
+# CONFIG_DEBUG_INFO_REDUCED is not set
+# CONFIG_DEBUG_INFO_SPLIT is not set
 CONFIG_DEBUG_KERNEL=y
 # CONFIG_DEBUG_KMEMLEAK is not set
 # CONFIG_DEBUG_KOBJECT is not set
@@ -751,6 +756,7 @@
 CONFIG_FW_LOADER_USER_HELPER=y
 CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y
 # CONFIG_GAMEPORT is not set
+# CONFIG_GDB_SCRIPTS is not set
 # CONFIG_GENERIC_ADC_BATTERY is not set
 CONFIG_GENERIC_ALLOCATOR=y
 CONFIG_GENERIC_BUG=y
@@ -1139,6 +1145,9 @@
 # CONFIG_JUMP_LABEL is not set
 CONFIG_KALLSYMS=y
 CONFIG_KALLSYMS_ALL=y
+CONFIG_KDB_CONTINUE_CATASTROPHIC=0
+CONFIG_KDB_DEFAULT_ENABLE=0x1
+# CONFIG_KDB_KEYBOARD is not set
 CONFIG_KERNEL_GZIP=y
 # CONFIG_KERNEL_LZ4 is not set
 # CONFIG_KERNEL_LZMA is not set
@@ -1149,7 +1158,10 @@
 # CONFIG_KEXEC is not set
 CONFIG_KEYBOARD_SNVS_PWRKEY=y
 CONFIG_KEYS=y
-# CONFIG_KGDB is not set
+CONFIG_KGDB=y
+CONFIG_KGDB_KDB=y
+CONFIG_KGDB_SERIAL_CONSOLE=y
+# CONFIG_KGDB_TESTS is not set
 # CONFIG_KMX61 is not set
 # CONFIG_KPROBES is not set
 # CONFIG_KSM is not set
@@ -2011,6 +2023,7 @@
 # CONFIG_SERIAL_IFX6X60 is not set
 CONFIG_SERIAL_IMX=y
 CONFIG_SERIAL_IMX_CONSOLE=y
+# CONFIG_SERIAL_KGDB_NMI is not set
 # CONFIG_SERIAL_MAX3100 is not set
 # CONFIG_SERIAL_MAX310X is not set
 # CONFIG_SERIAL_NONSTANDARD is not set

 

3.  啟動參數

  上面選項只是編譯相關調試代碼, 如何告知kgdboc使用哪個串口呢?  一般有兩種方式:

a. 在uboot傳給kernel的cmdline 加上關鍵字 “console=ttyAMA4,115200 kgdboc=ttyAMA4,115200”
b. 系統起來后 echo "kgdboc=ttyAMA4,115200" > /sys/module/kgdboc/parameters/kgdboc

啟動過程中出現log:
[    2.055290] KGDB: Registered I/O driver kgdboc

 

4. 觸發進入kgdb調試模式

  在控制台輸入: echo g > /proc/sysrq-trigger 

    

  如果沒有選中配置選項“KGDB_KDB: include kdb frontend for kgdb”, 會直接進入kgdb模式, 否則需要在kdb下鍵入命令“kgdb”

 

5. 連接開發板

  首先我的PC機是win7系統, 虛擬機vmware-15裝ubuntu-16.04, 然后串口配置為虛擬機獨占win7的COM1, 對應就是ubuntu的/dev/ttyS0

  開發板跑的是Linux的Image鏡像, 但調試得是帶有調試信息的vmlinux, 同時也要有源碼, 跟調試應用程序一樣道理, 跑到kernel根目錄下執行:

linux-4.1.15$ sudo /opt/toolchain/arm-2012.03/bin/arm-none-linux-gnueabi-gdb vmlinux
GNU gdb (Sourcery CodeBench Lite 2012.03-57) 7.2.50.20100908-cvs
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=arm-none-linux-gnueabi".
For bug reporting instructions, please see:
<https://support.codesourcery.com/GNUToolchain/>...
Reading symbols from /home/vedic/project/firmware_3/build_dir/linux-imx6ul_imx6_pax/linux-4.1.15/vmlinux...done.
(gdb) set remotebaud 115200
(gdb) target remote /dev/ttyS0
Remote debugging using /dev/ttyS0
kgdb_breakpoint () at kernel/debug/debug_core.c:1071
1071        arch_kgdb_breakpoint();
(gdb) set detach-on-fork on /* 后面有解釋 */
(gdb) l
1066     */
1067    noinline void kgdb_breakpoint(void)
1068    {
1069        atomic_inc(&kgdb_setting_breakpoint);
1070        wmb(); /* Sync point before breakpoint */
1071        arch_kgdb_breakpoint();
1072        wmb(); /* Sync point after breakpoint */
1073        atomic_dec(&kgdb_setting_breakpoint);
1074    }
1075    EXPORT_SYMBOL_GPL(kgdb_breakpoint);
(gdb) step
0xc00619bc in arch_kgdb_breakpoint () at kernel/debug/debug_core.c:1070
1070        wmb(); /* Sync point before breakpoint */
(gdb) step
kgdb_breakpoint () at kernel/debug/debug_core.c:1072
1072        wmb(); /* Sync point after breakpoint */
(gdb) l
1067    noinline void kgdb_breakpoint(void)
1068    {
1069        atomic_inc(&kgdb_setting_breakpoint);
1070        wmb(); /* Sync point before breakpoint */
1071        arch_kgdb_breakpoint();
1072        wmb(); /* Sync point after breakpoint */
1073        atomic_dec(&kgdb_setting_breakpoint);
1074    }
1075    EXPORT_SYMBOL_GPL(kgdb_breakpoint);
1076    
(gdb) step
1073        atomic_dec(&kgdb_setting_breakpoint);
(gdb) b wake_up_process /* 設置斷點, 這個函數會被系統頻繁調度的 */
Breakpoint 1 at 0xc003b858: file kernel/sched/core.c, line 1762.
(gdb) c
Continuing.

Breakpoint 1, wake_up_process (p=0xc8279c00) at kernel/sched/core.c:1762
1762        WARN_ON(task_is_stopped_or_traced(p));
(gdb) l
1757     * It may be assumed that this function implies a write memory barrier before
1758     * changing the task state if and only if any tasks are woken up.
1759     */
1760    int wake_up_process(struct task_struct *p)
1761    {
1762        WARN_ON(task_is_stopped_or_traced(p));
1763        return try_to_wake_up(p, TASK_NORMAL, 0);
1764    }
1765    EXPORT_SYMBOL(wake_up_process);
1766    
(gdb) bt
#0  wake_up_process (p=0xc8279c00) at kernel/sched/core.c:1762
#1  0xc0033484 in __queue_work (cpu=0, wq=0xc819aa80, work=0xc06d4f94) at kernel/workqueue.c:1386
#2  0xc004dc48 in call_timer_fn (timer=<value optimized out>, fn=0xc0033494 <delayed_work_timer_fn>, 
    data=<value optimized out>) at kernel/time/timer.c:1153
#3  0xc004de1c in __run_timers (h=<value optimized out>) at kernel/time/timer.c:1221
#4  run_timer_softirq (h=<value optimized out>) at kernel/time/timer.c:1415
#5  0xc00267e8 in __do_softirq () at kernel/softirq.c:273
#6  0xc0026b64 in do_softirq_own_stack () at include/linux/interrupt.h:446
#7  invoke_softirq () at kernel/softirq.c:357
#8  irq_exit () at kernel/softirq.c:391
#9  0xc00467f4 in __handle_domain_irq (domain=0xc8006000, hwirq=<value optimized out>, lookup=true, 
    regs=<value optimized out>) at kernel/irq/irqdesc.c:391
#10 0xc0009340 in handle_domain_irq (regs=0xc8715e78) at include/linux/irqdesc.h:147
#11 gic_handle_irq (regs=0xc8715e78) at drivers/irqchip/irq-gic.c:275
#12 0xc0011b40 in __irq_svc () at arch/arm/kernel/entry-armv.S:206
Backtrace stopped: frame did not save the PC

(gdb) next
1761    {
(gdb) next
1762        WARN_ON(task_is_stopped_or_traced(p));
(gdb) next
1763        return try_to_wake_up(p, TASK_NORMAL, 0);
(gdb) step
1764    }
(gdb) step
Cannot access memory at address 0x2
(gdb) 
try_to_wake_up (p=0xc8279c00, state=3, wake_flags=0) at kernel/sched/core.c:1657
1657    {
(gdb) l
1652     * Return: %true if @p was woken up, %false if it was already running.
1653     * or @state didn't match @p's state.
1654     */
1655    static int
1656    try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
1657    {
1658        unsigned long flags;
1659        int cpu, success = 0;
1660    
1661        /*
(gdb) s
1668        raw_spin_lock_irqsave(&p->pi_lock, flags);
(gdb) n
1669        if (!(p->state & state))
(gdb) n
1707        raw_spin_unlock_irqrestore(&p->pi_lock, flags);
(gdb) n
1710    }
(gdb) n
__queue_work (cpu=0, wq=0xc819aa80, work=0xc06d4f94) at kernel/workqueue.c:1389
1389    }
(gdb) n
call_timer_fn (timer=<value optimized out>, fn=0xc0033494 <delayed_work_timer_fn>, 
    data=<value optimized out>) at kernel/time/timer.c:1158
1158        if (count != preempt_count()) {
(gdb) n
1169    }
(gdb) n
__run_timers (h=<value optimized out>) at kernel/time/timer.c:1204
1204            while (!list_empty(head)) {
(gdb) c
Continuing.

Breakpoint 1, wake_up_process (p=0xc8278380) at kernel/sched/core.c:1762
1762        WARN_ON(task_is_stopped_or_traced(p));
(gdb) c
Continuing.

Breakpoint 1, wake_up_process (p=0xc8106e00) at kernel/sched/core.c:1762
1762        WARN_ON(task_is_stopped_or_traced(p));
(gdb) c
Continuing.

Breakpoint 1, wake_up_process (p=0xc804e700) at kernel/sched/core.c:1762
1762        WARN_ON(task_is_stopped_or_traced(p));
(gdb) 
在用gdb來調試內核的時候,由於內核在初始化的時候,會創建很多子線程。而默認gdb會接管所有的線程,如果你從一個線程切換到另外一個線程,gdb會馬上把原先的線程暫停。但是這樣很容易導致kernel死掉,所以需要設置一下gdb。 
一般用gdb進行多線程調試,需要注意兩個參數:follow-fork-mode和detach-on-fork。

detach-on-fork參數,指示GDB在fork之后是否斷開(detach)某個進程的調試,或者都交由GDB控制:
set detach-on-fork [on|off]
    on: 斷開調試follow-fork-mode指定的進程。
    off: gdb將控制父進程和子進程。


follow-fork-mode指定的進程將被調試,另一個進程置於暫停(suspended)狀態。follow-fork-mode的用法為:
set follow-fork-mode [parent|child]
    parent: fork之后繼續調試父進程,子進程不受影響。
    child: fork之后調試子進程,父進程不受影響。

  當沒有斷點, 輸入continue讓系統跑的時候,  串口將不會被kgdboc占用, 所以控制台又可以用了

 

6.  調試module.ko

  略......

 

7. 注意事項

a. 虛擬機用vmware, vbox串口有問題
b. 確保kgdboc加載成功, 如果出現“kgdb: Unregistered I/O driver, debugger disabled” 或者沒有節點“/sys/module/kgdboc/parameters/kgdboc”
    很有可能串口驅動沒有支持如下三個函數:
    
                                            struct uart_ops {
                                                #ifdef CONFIG_CONSOLE_POLL
                                                    int        (*poll_init)(struct uart_port *);
                                                    void    (*poll_put_char)(struct uart_port *, unsigned char);
                                                    int        (*poll_get_char)(struct uart_port *);
                                                #endif
                                            };

                                        其實也簡單, 在所在的串口驅動必然有提供讀寫TX/RX寄存器和判斷FIFO的函數, 直接while查詢即可
                                            static int serial_get_poll_char(struct uart_port *port)
                                            {
                                                if((serial_in(port, ARM_UART_STS1) & 0xff) == 0)
                                                    return NO_POLL_CHAR;

                                                return serial_in(port, ARM_UART_RXD);
                                            }
                                            static inline void wait_for_xmitr(struct uart_port *port);

                                            static void serial_put_poll_char(struct uart_port *port, unsigned char c)
                                            {

                                                wait_for_xmitr(port);
                                                serial_out(port, ARM_UART_TXD, c);
                                            }

c. 如果希望系統啟動過程中就進入kgdb, 而不是后面“echo g > /proc/sysrq-trigger”
    cmdline改成 -> “console=ttyAMA4,115200 kgdboc=ttyAMA4,115200 kgdbwait”
    
d. PC端ubuntu用的gdb還是跟上篇博文的一樣

 

  建議也看一下上篇博文: https://www.cnblogs.com/vedic/p/11104204.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM