日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

kgdb调试linux内核以及驱动模块

發布時間:2023/12/8 linux 60 豆豆
生活随笔 收集整理的這篇文章主要介紹了 kgdb调试linux内核以及驱动模块 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

kgdb調試linux內核以及驅動模塊

本文將簡要描述如何配置kgdb進行內核以及驅動模塊調試,以嵌入式開發為例,但同樣對于其他有需要調試kernel有一定的參考價值。本文實驗環境為qemu搭建的riscv64模擬器環境,筆者之前有系列博客詳細描述了環境搭建,可供參考——《基于qemu-riscv從0開始構建嵌入式linux系統》。

修改內核配置

在linux 5.10版本上,KGDB已經受支持,因此僅需要修改內核配置就可以打開此功能。

CONFIG_KGDB

配置CONFIG_KGDB=y,以啟用kgdb功能。

CONFIG_GDB_SCRIPTS

配置CONFIG_GDB_SCRIPTS=y,這樣在內核編譯時會在根目錄生成vmlinux-gdb.py文件,這個python腳本需要在gdb調試時加載,以此提供一些供gdb調試內核的擴展命令,對于調試外部ko文件帶來極大的方便。

CONFIG_DEBUG_INFO

配置CONFIG_DEBUG_INFO=y,gdb調試內核需要從vmlinx中加載相關符號信息,但默認的內核編譯選項沒有-g選項,因此該elf文件將不含有調試信息,調試時看不到源碼,僅能做匯編級別的調試跟蹤,因此需要配置該選項,打開調試信息(打開后會增大內核文件)。

CONFIG_DEBUG_INFO_DWARF4

配置CONFIG_DEBUG_INFO_DWARF4=y,如果不啟用這個選項內核調試信息將不包含DWARF4信息,只能進行一些backtarce的查看,而無法debug變量,打開該選項可以使用(打開后會進一步增大內核文件)。

CONFIG_STRICT_KERNEL_RWX/CONFIG_STRICT_MODULE_RWX

配置CONFIG_STRICT_KERNEL_RWX/CONFIG_STRICT_MODULE_RWX選項關閉,這個選項的存在會對內核代碼段進行寫保護,因此在使用kgdb時如果進行添加斷點操作,目標系統如果不支持硬件斷點機制,就必須改寫內核代碼段來使用斷點指令觸發軟件斷點達到目的,而該選項的存在會導致斷點指令無法寫入,因此需要關閉(關閉后會增加內核所在內存被破壞風險)。

修改內核啟動參數

進行如下修改:

- bootargs = "root=/dev/vda2 rw console=ttyS0 earlycon=sbi"; + bootargs = "root=/dev/vda2 rw console=ttyS0 earlycon=sbi nokaslr kgdboc=ttyS1,115200 kgdbwait";

nokaslr

KASLR是一種安全性的手段,會讓代碼運行在隨機化的地址上,傳入nokaslr會關閉這個功能以此方便調試的時候地址匹配vmlinx中的符號信息,不過并不是所有arch都支持KASLR功能的,也許在一些架構上就不支持KASLR,這里傳不傳這個選項也就無所謂了。

kgdboc

kgdboc用來指定內核調試信息從哪里輸出,這里我們使用了ttyS1串口輸出,未來gdb便需要連接到對應串口來接收調試數據。

kgdbwait

添加該參數可以讓內核啟動時準備好數據后等待gdb接入再繼續啟動內核。

啟動調試

完成修改后重新編譯內核和設備樹文件就可以開始內核調試了,由于我們使用的是qemu模擬器,因此需要打開三個終端。

啟動qemu

首先運行qemu:

SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)GRAPHIC_PARAM="-nographic --serial telnet::3441,server,nowait --serial telnet::3442,server,nowait --serial telnet::3443,server,nowait --monitor stdio --parallel none"$SHELL_FOLDER/output/qemu/bin/qemu-system-riscv64 \ -M quard-star \ -m 1G \ -smp 8 \ -drive if=pflash,bus=0,unit=0,format=raw,file=$SHELL_FOLDER/output/fw/fw.bin \ -drive file=$SHELL_FOLDER/output/rootfs/rootfs.img,format=raw,id=drive0 \ -fsdev local,security_model=mapped-xattr,id=fsdev0,path=$SHELL_FOLDER \ -netdev user,id=net0,net=192.168.31.0/24,dhcpstart=192.168.31.100,hostfwd=tcp::3522-:22,hostfwd=tcp::3580-:80 \ -global virtio-mmio.force-legacy=false \ -device virtio-blk-device,id=hd0,drive=drive0 \ -device virtio-gpu-device,id=video0,xres=1280,yres=720\ -device virtio-mouse-device,id=input0 \ -device virtio-keyboard-device,id=input1 \ -device virtio-9p-device,id=fs0,fsdev=fsdev0,mount_tag=hostshare \ -device virtio-net-device,netdev=net0 \ -fw_cfg name="opt/qemu_cmdline",string="qemu_vc=:vn:24x80:" \ $GRAPHIC_PARAM

qemu的設備配置不是本文重點,這里注意GRAPHIC_PARAM配置,將仿真模擬的三個串口分別映射到telnet::3441、3442、3443這三個端口,而我們的內核啟動后將從串口0輸出啟動log,從串口1輸出kgdb數據包,那么主機則需要連接3441和3442進行調試。

啟動telnet連接

使用telnet連接串口0:

telnet localhost 3441

可以從這里看到uboot終端輸出,啟動內核后,內核將進行一部分初始化后,開始等等gdb連接。

啟動gdb

首先編寫一個gdb.script腳本。

add-auto-load-safe-path ./linux-5.10.42/ file ./linux-5.10.42/vmlinux source ./linux-5.10.42/vmlinux-gdb.py target remote :3442

以上gdb命令分別用于

  • 指定./linux-5.10.42/路徑為可信的路徑,便于gdb執行啟動的python腳本;
  • 指定符號文件./linux-5.10.42/vmlinux
  • 執行./linux-5.10.42/vmlinux-gdb.py添加環境用于kgdb的命令擴展
  • 連接到本地:3442端口接受gdb數據包

啟動GDB

riscv64-unknown-linux-gnu-gdb -x ./gdb.script

三個終端最終啟動后結果如圖:

調試技巧

上一文中,在gdb終端內輸入c,內核將繼續完成初始化工作,進入系統,此時是無法在gdb中主動停止運行的,因為內核的調試是一種被動調試,如果內核沒有主動進入斷點也沒有觸發斷點指令,gdb是無法將內核進入調試狀態的,此時可以在目標系統內輸入如下指令:

echo g > /proc/sysrq-trigger

內核將進入假死狀態,即主動觸發了bearkpoint斷點,此時gdb中可以輸入命令bt,查看斷點處的調用棧。

(gdb) bt #0 kgdb_breakpoint () at kernel/debug/debug_core.c:1234 #1 0xffffffe0000c35d4 in sysrq_handle_dbg (key=<optimized out>) at kernel/debug/debug_core.c:978 #2 0xffffffe00036b55c in __handle_sysrq (key=<optimized out>, check_mask=check_mask@entry=false) at drivers/tty/sysrq.c:598 #3 0xffffffe00036ba22 in write_sysrq_trigger (file=<optimized out>, buf=0xd1810 <error: Cannot access memory at address 0xd1810>, count=2, ppos=<optimized out>) at drivers/tty/sysrq.c:1157 #4 0xffffffe0001b8ca4 in pde_write (ppos=<optimized out>, count=<optimized out>, buf=<optimized out>, file=<optimized out>, pde=0xffffffe00187ab00) at fs/proc/inode.c:345 #5 proc_reg_write (file=<optimized out>, buf=<optimized out>, count=<optimized out>, ppos=<optimized out>) at fs/proc/inode.c:357 #6 0xffffffe000154cc6 in vfs_write ( file=file@entry=0xffffffe001921540, buf=buf@entry=0xd1810 <error: Cannot access memory at address 0xd1810>, count=count@entry=2, pos=pos@entry=0xffffffe00302fe80) at fs/read_write.c:603 #7 0xffffffe000154fb0 in ksys_write (fd=<optimized out>, buf=0xd1810 <error: Cannot access memory at address 0xd1810>, count=2) at fs/read_write.c:658 #8 0xffffffe00015502a in __do_sys_write (count=<optimized out>, buf=<optimized out>, fd=<optimized out>) at fs/read_write.c:670 #9 __se_sys_write (fd=<optimized out>, buf=<optimized out>, count=<optimized out>) at fs/read_write.c:667 #10 0xffffffe00003b30a in handle_exception () at arch/riscv/kernel/entry.S:205 Backtrace stopped: frame did not save the PC

使用b可以打斷點,例如:b filp_open,使用s,可以單步跳入執行,使用n可以單步跳過執行,使用c可以連續執行,使用p可以打印變量,使用info可以查看一些信息如寄存器,內存信息,使用l可以查看當前位置的c源碼片段,使用disassemble可以查看當前位置匯編指令片段,輸入-可以進入交互式的調試界面,當前操作與平時操作gdb無異。

這里我們再了解一下kgdb帶來的額外擴展命令,輸入lx-然后按tab補全就可以看到kgdb的擴展命令,基本上顧名思義是比較容易理解的。

(gdb) lx- lx-clk-summary lx-dmesg lx-mounts lx-cmdline lx-fdtdump lx-ps lx-configdump lx-genpd-summary lx-symbols lx-cpus lx-iomem lx-timerlist lx-device-list-bus lx-ioports lx-version lx-device-list-class lx-list-check lx-device-list-tree lx-lsmod (gdb) lx-

這里我們著重了解下lx-symbols,使用這個命令將便于我們調試沒有隨內核一起初始化而是后加載到內核的ko文件。

首先編譯生成一個測試用的ko模塊,這里用我自己寫的一個純軟件的簡單開源驅動為例子——虛擬串口。我們生成了virts.ko文件,將它放在運行gdb所在的當前目錄,然后gdb使用c命令釋放假死的目標調試機,在目標qemu模擬器內insmod安裝該模塊,lsmod可以看到ko文件已被安裝。重新輸入echo g > /proc/sysrq-trigger進入假死。

此時使用b命令給virts.c:transmission_data打斷點是無法成功的,因為無法索引這個符號,此時我們需要使用lx-symbols,稍等片刻可以看到如下輸出

(gdb) lx-symbols loading vmlinux scanning for modules in /home/qqm/quard_star_tutorial loading @0xffffffdf80af8000: /home/qqm/quard_star_tutorial/target_driver_module/virts_tty/virts.ko

此時符合已正確加載到gdb環境,使用b virts.c:transmission_data可以成功斷點。這里稍微注意下,如沒你不使用lx-symbols,而是使用原生的gdb命令:

add-symbol-file {filename} {addr}

就需要手動查找ko文件加載到的地址,如果你的系統有很多ko,這將是很麻煩的事情,所以lx-symbols可以自動查找所有ko文件并加載符號可以帶來極大的便利。

另附:筆者使用的內核版本5.10.42中lx-symbols存在不成功的問題,錯誤如下:

Python Exception <class 'gdb.MemoryError'> Cannot access memory at address

經分析定位將linux-5.10.42/scripts/gdb/linux/symbols.py:_section_arguments函數做以下修改,就可以解決該問題。

def _section_arguments(self, module):try:sect_attrs = module['sect_attrs'].dereference()attrs = sect_attrs['attrs']section_name_to_address = {attrs[n]['battr']['attr']['name'].string(): attrs[n]['address']for n in range(int(sect_attrs['nsections']))}except gdb.error:return ""args = []for section_name in [".data", ".data..read_mostly", ".rodata", ".bss",".text", ".text.hot", ".text.unlikely"]:address = section_name_to_address.get(section_name)if address:args.append(" -s {name} {addr}".format(name=section_name, addr=str(address)))return "".join(args)

總結

OK,kgdb的簡單使用基本上就沒有問題,kgdb不是一個很常用的調試手段,但也許在一些特殊情況下能解決一些問題,尤其是嵌入式開發中。本文涉及到的代碼均已開源,可以在 https://github.com/QQxiaoming/quard_star_tutorial/tree/ext1 ,tag:ext1中找到。

總結

以上是生活随笔為你收集整理的kgdb调试linux内核以及驱动模块的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。