java調試模式,我偶爾會用到的調試方法 | Linux 內核

 2023-10-06 阅读 31 评论 0

摘要:文章轉自我朋友的公眾號,以下為內容正文====大家好,我是你們的工具人老吳。今天,和大家分享一下幾個 Linux 內核的調試小技巧。當你遇到一個 bug,你調試了 1 年半載都解決不了,這其實一件好事。java調試模式,因為它會時刻提醒你

文章轉自我朋友的公眾號,以下為內容正文

====

大家好,我是你們的工具人老吳。

今天,和大家分享一下幾個 Linux 內核的調試小技巧。

當你遇到一個 bug,你調試了 1 年半載都解決不了,這其實一件好事。

java調試模式,因為它會時刻提醒你平時寫代碼時要謹慎、要多看書、多去認識一些更資深的人,別問我為什么會有這樣的感受,因為是親身經歷~

掌握一個調試工具是需要學習成本的,這里只是列舉我自己會用到的工具,如果有某個你覺得特別牛逼的工具而我沒提到的話,請原諒我。

好,下面開始正文。

最重要的是:思路

調試 bug 時不要急著做實驗,先梳理一下思路。

一般可以總結成如下步驟:

1、理解問題;

UNIX/LINUX、2、重現問題;

3、定位問題,找到相關的代碼;

4、嘗試修復問題;

5、如果失敗,回到第 1 步;

bug 一般分為這幾類:

1、Crash,最常遇到的,可能是因為我是做設備驅動開發的緣故;

shell腳本調試、2、Lockup,比較少,這類問題預防比事后調試更重要;

3、Logic/implementation error,這個也比較容易遇到,一般是運行不報錯,但是運行的結果不符合預期;

4、Resource leak,偶爾會遇到;

5、Performance,偶爾會遇到,對于做驅動開發的話,一般是先考慮功能,當性能達不到要求時,再考慮優化性能。

調試工具的類別:

1、很多人不知道,調試最重要的工具是:我們的大腦。換句話說,也就是我們對內核個子系統、驅動開發的理解;

vim debug。2、Logs and dump analysis。內核很貼心,許多異常發生時都會有一堆的 Kernel Panic 的信息,經常能讓我們直接定位到引起異常的代碼;

3、Tracing/profiling。這類工具一般能讓我們理解程序的運行流程,不僅適合用來調試問題,也適合用來學習和理解內核的各種功能實現。

4、Interactive debugging。主要就是 gdb,我個人用得很少。

5、Debugging frameworks。許多的調試工具經過不斷地發展和完善后,就慢慢地形成了一整套的調試框架,例如 Ftrace、SystemTap。


下面是幾個我常用的調試技巧 / 工具。

最常用的方法:打印

點擊查看大圖

關于打印的工具,主要是這 3 種:

LINUX教程、1、printk()

最原始的打印 api,可以用但是主流觀點已經不推薦使用了。

與之相關的是啟動參數 loglevel,它決定了可以被打印出來的信息的最低優先級。


2、pr_*()

推薦用 pr_*() 來代替 printk(),這是一個函數族:

pr_emerg(), pr_alert(), pr_crit(), pr_err(), pr_warning(), pr_notice(), pr_info(), pr_cont(), pr_debug()

怎么調試程序?例如:

pr_info("Booting?CPU?%d\n",?cpu);

內核會打印:

[?202.350064]?Booting?CPU?1


3、dev_*()

同樣是一個函數族:

dev_emerg(),?dev_alert(),?dev_crit(),?dev_err(),?dev_warn(),?dev_notice(),?dev_info(),?dev_dbg()

它們的最大特點是需要傳入一個 struct device 的參數,并且會打印出這個 device 的名字,一邊是在驅動相關的代碼里使用。

例如:

dev_info(&pdev->dev,?"in?probe\n");

軟件調試的任務,內核會打印:

[?25.878382]?serial?48024000.serial:?in?probe


關于 pr_debug() and dev_dbg()

要使用這兩個 api,需要在對應的代碼里 #deinfe DEBUG。

當內核使能了 CONFIG_DYNAMIC_DEBUG,我們就可以通過 /sys/kernel/debug/dynamic_debug/control 動態地是否要打印 log,以及打印哪些 log。

使用方法,大致如下:

$?mount?-t?debugfs?none?/sys/kernel/debug/
$?cd?/sys/kernel/debug/dynamic_debug/
$?echo?“file?xxx.c?+p”?>?control
$?echo?“file?svcsock.c?line?1603?+p”?>?control
$?echo?“file?drivers/usb/core/*?+p”?>?control
$?echo?“file?xxx.c?-p”?>?control

具體地,可以參考:

多線程調試、https://training.ti.com/sites/default/files/docs/Kernel-Debug-Series-Part4-dynamic-debug.pdf


分析 Kernel Panic 的 信息

舉個例子,下面是一次 Kernel Panic:

$?cat?/sys/class/gpio/gpio504/value
[23.688107]?Unable?to?handle?kernel?NULL?pointer?dereference?at?virtual?address?00000000
[23.696431]?pgd?=?(ptrval)
[23.699167]?[00000000]?*pgd=28bd4831,?*pte=00000000,?*ppte=00000000
[23.705596]?Internal?error:?Oops:?17?[#1]?SMP?ARM
[23.710316]?Modules?linked?in:
[23.713394]?CPU:?1?PID:?177?Comm:?cat?Not?tainted?4.19.17?#8
[23.719060]?Hardware?name:?Freescale?i.MX6?Quad/DualLite?(Device?Tree)
[23.725606]?PC?is?at?mcp23sxx_spi_read+0x34/0x84
[23.730241]?LR?is?at?_regmap_raw_read+0xfc/0x384
[23.734866]?pc?:?[<c0539c44>]
lr?:?[<c067d894>]
psr:?60040013
[23.741142]?sp?:?d8c6da48?ip?:?00000009?fp?:?d8c6da6c
[23.746375]?r10:?00000040?r9?:?d8a94000?r8?:?d8c6db30
[23.751608]?r7?:?c12ed9d4?r6?:?00000001?r5?:?c0539c10?r4?:?c1208988
[23.758145]?r3?:?d8789f41?r2?:?2afb07c1?r1?:?d8789f40?r0?:?00000000
[...]?//?省略


關鍵信息:

  • PC is at mcp23sxx_spi_read+0x34

  • pc : [<c0539c44>]

PC 是當前執行的指令的地址。

調試模式、接下來,我們可以借助 addr2line, 定位到具體是哪一行代碼引起了panic:

$?arm-linux-addr2line?-f?-e?vmlinux?0xc0539c44
mcp23sxx_spi_read
/home/sprado/elce/linux/drivers/pinctrl/pinctrl-mcp23s08.c:357

另外,還可以用 gdb 來定位代碼:

$?arm-linux-gdb?vmlinux
(gdb)?list?*(mcp23sxx_spi_read+0x34)
0xc0539c44?is?in?mcp23sxx_spi_read?(drivers/pinctrl/pinctrl-mcp23s08.c:357)


earlyprintk

earlyprintk 一般用來處理一些發生在啟動初期時的異常。

最常見的現象就是系統打印完 Starting Kernel... 后就 hang 住了。

用法:

1、配置內核:

CONFIG_EARLY_PRINTK
CONFIG_DEBUG_LL

程序的調試、2、設置啟動參數,類似:

root=/dev/mmcblk0p2 rootwait rw earlyprintk console=ttyS0,115200


WARN_ON()

這個函數可以打印出當前的函數調用棧。

我一般會在高度可疑的地方使用它。

舉個例子:

static?int?sun6i_spi_probe(struct?platform_device?*pdev)
{struct?spi_master?*master;struct?sun6i_spi?*sspi;[...]//?用于調試
WARN_ON(1);master?=?spi_alloc_master(&pdev->dev,?sizeof(struct?sun6i_spi));[...]


當運行到 WARN_ON(1) 時,內核會打印:

[????1.847018]?WARNING:?CPU:?1?PID:?1?at?drivers/spi/spi-sun6i.c:549?sun6i_spi_probe+0x20/0x3ac
[????1.855454]?Modules?linked?in:
[????1.858525]?CPU:?1?PID:?1?Comm:?swapper/0?Not?tainted?4.14.111?#196
[????1.864781]?Hardware?name:?sun8i
[????1.868032]?[<c02287fc>]?(unwind_backtrace)?from?[<c0225398>]?(show_stack+0x10/0x14)
[????1.875776]?[<c0225398>]?(show_stack)?from?[<c0a1ba3c>]?(dump_stack+0x94/0xa8)
[????1.882997]?[<c0a1ba3c>]?(dump_stack)?from?[<c0240c24>]?(__warn+0xe8/0x100)
[????1.889953]?[<c0240c24>]?(__warn)?from?[<c0240cec>]?(warn_slowpath_null+0x20/0x28)
[????1.897517]?[<c0240cec>]?(warn_slowpath_null)?from?[<c06a03c0>]?(sun6i_spi_probe+0x20/0x3ac)
[????1.905953]?[<c06a03c0>]?(sun6i_spi_probe)?from?[<c0617980>]?(platform_drv_probe+0x4c/0xb0)
[????1.914299]?[<c0617980>]?(platform_drv_probe)?from?[<c06160dc>]?(driver_probe_device+0x234/0x2f0)
[????1.923162]?[<c06160dc>]?(driver_probe_device)?from?[<c0616244>]?(__driver_attach+0xac/0xb0)
[????1.931592]?[<c0616244>]?(__driver_attach)?from?[<c06144ec>]?(bus_for_each_dev+0x68/0x9c)
[????1.939762]?[<c06144ec>]?(bus_for_each_dev)?from?[<c0615654>]?(bus_add_driver+0x198/0x210)
[????1.948020]?[<c0615654>]?(bus_add_driver)?from?[<c0616aec>]?(driver_register+0x78/0xf8)
[????1.956017]?[<c0616aec>]?(driver_register)?from?[<c0201a70>]?(do_one_initcall+0x40/0x16c)
[????1.964193]?[<c0201a70>]?(do_one_initcall)?from?[<c1000e6c>]?(kernel_init_freeable+0x1c8/0x264)
[????1.972884]?[<c1000e6c>]?(kernel_init_freeable)?from?[<c0a2ef4c>]?(kernel_init+0x8/0x114)
[????1.981054]?[<c0a2ef4c>]?(kernel_init)?from?[<c0222058>]?(ret_from_fork+0x14/0x3c)
[????1.988686]?---[?end?trace?dc4e090f55ad2de8?]---

自我調試的方法有哪些,我們可以很清晰地看到 sun6i_spi_probe() 被調用的流程。

這個方法跑起來很簡單,但是每次使用都得編譯和更新內核,非常不方便,只適合輕度使用。


Pstore

如果發生 Kernel panic 時,我們并沒有連接串口終端,那么這一次的崩潰信息就丟失了。

Pstore (persistent storage) 就可以用來處理這種情況。

當發生 Kernel painic 時,Pstore 會自動保存 oops 和 panic 的 log,并且在軟重啟后仍可以查看 log 信息。

默認情況下,log 是存儲在 RAM 的某個保留區域中,但也可以使用存儲設備,例如閃存。

ubuntu開機切換內核。
用法:

1、配置內核:

CONFIG_PSTORE
CONFIG_PSTORE_RAM

2、配置 dts,為 Pstore 預留一塊內存,類似:

reserved-memory {#address-cells = <1>;#size-cells = <1>;ranges;ramoops: ramoops@0b000000 {compatible = "ramoops";reg = <0x20000000 0x200000>; /* 2MB */record-size = <0x4000>; /* 16kB */console-size = <0x4000>; /* 16kB */};
};

3、假設剛發生了一次 Panic,并且已經軟重啟:

$?mount?-t?pstore?pstore?/sys/fs/pstore/$?ls?/sys/fs/pstore/
dmesg-ramoops-0
dmesg-ramoops-1

通過上面這兩個文件就可以看到內核的崩潰信息了。

內核文檔:

LINUX系統。Documentation/admin-guide/ramoops.rst


devmem2

這是一個命令行工具,它可以在用戶空間去讀寫內存。

大多數情況,我是用它來讀寫寄存器,簡單粗暴。

用法:

$?apt-get?install?devmem2

1、查看寄存器 TMR_IRQ_EN_REG:

$?devmem2?0x0x01C20C00
/dev/mem?opened.
Memory?mapped?at?address?0xb6f38000.
Value?at?address?0x0?(0xb6f38000):?0xEA000016

2、修改 TMR_IRQ_EN_REG:

#?devmem2?0x0x01C20C00?w?0xEA000018
/dev/mem?opened.
Memory?mapped?at?address?0xb6fe8000.
Value?at?address?0x0?(0xb6fe8000):?0xEA000016
Written?0xEA000018;?readback?0xEA000018


GDB

軟件調試的方法、如果你想完全控制內核的運行,例如單步執行、查看變量等,可以用 GDB。

點擊查看大圖

這里采用的是 C/S 架構,在板子上運行 server (kgdb),在 PC 機上運行 client (gdb),通訊的方式可以是串口,或者網絡,我一般是用串口。


如何配置:

1、配置內核:

CONFIG_KGDB
CONFIG_KGDB_SERIAL_CONSOLE
CONFIG_KGDB_KDB

2、設置啟動參數:kgdoc

console=ttyS0,115200?kgdboc=ttyS0,115200?earlyprintk?root=/dev/mmcblk0p2?oops=panic?panic=0

ttyS0 是板子的調試串口。

要使用 kgdb,必須為其設置一個 I/O driver,我一般使用 kgdb over serial console (簡稱 kgdboc)

代碼調試?oops=panic panic=0 很重要。

另外,也通過在啟動后設置 kgdboc:

echo?ttyS0?>?/sys/module/kgdboc/parameters/kgdboc

3、讓內核進入 debug 模式:

$?echo?g?>?/proc/sysrq-trigger
[?1958.025927]?sysrq:?SysRq?:?DEBUG
[?1958.029191]?KGDB:?Entering?KGDB

4、讓 PC 機連接板子

$?arm-linux-gdb?vmlinux(gdb)?set?serial?baud?115200
(gdb)?target?remote?/dev/ttyUSB0
Remote?debugging?using?/dev/ttyUSB0
0xc02c3540?in?kgdb_breakpoint?()


舉個例子:

配置好之后,通過 gdb 調試內核跟通過 gdb 調試應用的操作是一樣的。

這里我舉一個小例子。

多線程怎么調試?首先,人為讓內核 Crash:

$?echo?WRITE_KERN?>?/sys/kernel/debug/provoke-crash/DIRECT
Entering?kdb?(current=0xffffffc0de55f040,?pid?1470)?on?processor?4?Oops:?(null)
due?to?oops?@?0xffffff80108bfa48
CPU:?4?PID:?1470?Comm:?bash?Not?tainted?5.3.0-rc2+?#13
pc?:?__memcpy+0x48/0x180
lr?:?lkdtm_WRITE_KERN+0x4c/0x90
...

下面開始調試。

1、查看調用棧:

(gdb)?bt
Call?trace:
dump_backtrace+0x0/0x138
show_stack+0x20/0x2c
kdb_show_stack+0x60/0x84
...
do_mem_abort+0x4c/0xb4
el1_da+0x20/0x94
__memcpy+0x48/0x180
lkdtm_do_action+0x24/0x44
direct_entry+0x130/0x178

2、查看棧幀的內容:

(gdb)?frame?1
#1?0xffffff801056584c?in?lkdtm_WRITE_KERN?()?at?.../drivers/misc/lkdtm/perms.c:116
116
memcpy(ptr,?(unsigned?char?*)do_nothing,?size);

基本可以確定是使用 memcpy() 時導致 Crash。

3、查看相關代碼:

(gdb)?list
112??size?=?(unsigned?long)do_overwritten?-?(unsigned?long)do_nothing;
[...]
116??memcpy(ptr,?(unsigned?char?*)do_nothing,?size);

需要核查一下 ptr、do_nothing、size,這 3 個參數是否合法。

4、打印變量值:

(gdb)?print?size
$3?=?18446744073709551584(gdb)?print?do_overwritten?-?do_nothing
$4?=?-32

最后發現 18446744073709551584 其實就是 (unsigned long) 的 -32。memcpy 的數據大小是 -32,導致了內核崩潰。


Ftrace

Ftrace 的作用是幫助開發人員了解 Linux 內核的運行時行為,以便進行故障調試或性能分析。

最早 Ftrace 是一個 function tracer,僅能夠記錄內核的函數調用流程。如今 ftrace 已經成為一個 framework,采用 plugin 的方式支持開發人員添加更多種類的 trace 功能。

用法:

$?mount?-t?tracefs?none?/sys/kernel/tracing
$?cd?/sys/kernel/tracing/
$?cat?available_tracers
hwlat?blk?mmiotrace?function_graph?wakeup_dl?wakeup_rt?wakeup?function?nop

跟蹤器 tracer 表示的是要跟蹤的目標。

假設我們抓一次 spi 傳輸的過程:

echo?0?>?tracing_on
echo?function_graph?>?current_tracer
echo?*spi*?>?set_ftrace_filter
echo?*dma*?>>?set_ftrace_filter
echo?*spin*?>>?set_ftrace_notrace
echo?1?>?tracing_on
./spidev_test
echo?0?>?tracing_on
cat?trace

得到的信息:

1)?+?41.292?us???|??spidev_open();1)???????????????|??spidev_ioctl()?{1)???????????????|????spi_setup()?{1)???0.417?us????|??????__spi_validate_bits_per_word.isra.0();1)???????????????|??????sunxi_spi_setup()?{1)???0.834?us????|????????sunxi_spi_check_cs();1)???0.875?us????|????????spi_set_cs();1)???0.625?us????|????????sunxi_spi_cs_control();1)?+?17.125?us???|??????}1)???0.833?us????|??????spi_set_cs();1)?+?30.458?us???|????}1)?!?699.875?us??|??}[...]

相關參考:

https://blog.csdn.net/Guet_Kite/article/details/101791125


Kdump

這個工具我沒有用過,但是它似乎很強大,所以我覺得應該簡單介紹一下。

kdump 是一種基于 kexec 系統調用 的內核崩潰轉儲機制。

當系統崩潰時,kdump 使用 kexec 啟動進入到第二個內核 (dump-capture kernel),從而獲得 coredump 信息。

用法:

1、設置啟動參數:

crashkernel=64M

2、運行 kexec:

$?kexec?--type?zImage?-p?/boot/zImage?\--initrd=<initrd-for-dump-capture-kernel>?\--dtb=<dtb-for-dump-capture-kernel>?\--command-line="XXX"

運行完 kexec 后,dump-capture kernel 就被加載進內存了。

以后如果發生了 kernel panic,dump-capture kernel 會被加載并運行。

我們可以在 dump-capture kernel 下,獲得 coredump 文件:

$?cp?/proc/vmcore?<dump-file>

然后就可以在 PC 上使用 gdb/crash 來調試分析了:

$?arm-linux-gdb?path/to/vmlinux?-c?path/to//vmcore
$?crash?path/to/vmlinux?path/to/vmcore

內核文檔:

Documentation/kdump/kdump.txt


總結

預防為主,調試為輔。

軟件開發沒有銀彈,同樣的,bug 調試也沒有銀彈。但是多熟悉一些調試工具,是有好處的。

當然還有很多調試工具、技巧是我不知道了,歡迎大家分享給我。

Anyway, what we know is a drop, what we don't know is an ocean.

祝周末愉快。

—— The End ——

感謝完成閱讀,我是喜歡打籃球的寫代碼的籃球球癡,這個是我的公眾號,感謝你關注并支持。我從大學開始接觸電子和嵌入式軟件知識,至今,已經畢業工作了9年,我喜歡嵌入式,也愿意從事這個行業。不管是從技術還是職場經驗,都積累了足夠多的經驗,目前在一個非常優秀的團隊中做開發工作。

很高興認識每一個對技術努力,對人用心的朋友。

關注公眾號,后臺回復「1024」獲取學習資料網盤鏈接。

歡迎點贊,關注,轉發,在看,您的每一次鼓勵,我都將銘記于心~

嵌入式Linux

微信掃描二維碼,關注我的公眾號

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/127269.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息