【Linux內核模塊】調試技巧

內核模塊開發最讓人頭疼的不是寫代碼,而是調試 —— 代碼編譯通過了,加載后卻要么沒反應,要么直接讓系統崩潰。這就像在黑屋子里修機器,看不見摸不著。其實內核調試有一套成熟的工具箱,掌握這些工具和技巧,就能給內核裝個監控監控儀,讓問題無所遁形。


目錄

一、調試前的安全須知:別讓系統崩潰

二、最基礎也最常用:printk 打印日志

2.1 printk 的基本用法

2.2 控制日志輸出

2.3 printk 的高級技巧

三、內核 Oops 分析:系統崩潰時的現場照片

3.1 認識 Oops 信息

3.2 定位 Oops 錯誤位置

3.3 常見 Oops 錯誤及原因

四、動態調試:按需開啟的監控攝像頭

五、內核調試器 kgdb:像 gdb 一樣調試內核

5.1 搭建 kgdb 環境

5.2 使用 kgdb 調試模塊?

5.3 kgdb 的優缺點

六、內存調試工具:檢測內存泄漏和越界

6.1 kmemleak:檢測內存泄漏

6.2 KASAN:檢測內存越界

七、用戶態調試工具:從外部觀察模塊行為

八、調試方法論:解決問題的步驟


一、調試前的安全須知:別讓系統崩潰

內核模塊調試有個特點:一旦出錯可能直接導致系統死機,所以安全措施必須做好。就像拆彈專家要穿防爆服,咱們調試內核也得有防護措施。

1. 必備的調試環境

  • 虛擬機優先:90% 的內核調試應該在虛擬機里進行(推薦 VirtualBox 或 VMware),死機了重啟就行
  • 多終端連接:用 SSH 或串口連接虛擬機,即使圖形界面卡死,還能通過終端查看日志
  • 快照備份:調試前給虛擬機拍快照,搞崩了能快速恢復(血的教訓!)

2. 調試的三不原則

  • 不要在生產環境調試新模塊
  • 不要加載來源不明的模塊
  • 調試時不要運行重要程序

二、最基礎也最常用:printk 打印日志

如果只能選一個調試工具,那一定是printk。它就像醫生用的聽診器,簡單直接卻能解決大部分問題。

2.1 printk 的基本用法

和用戶態的printf類似,但多了個日志級別參數:

printk(KERN_INFO "模塊初始化成功,當前狀態: %d\n", status);

日志級別決定了消息是否顯示以及存到哪里,常用的有:

  • KERN_EMERG:緊急情況(系統崩潰前消息)
  • KERN_ALERT:必須立即處理
  • KERN_CRIT:嚴重錯誤
  • KERN_ERR:錯誤信息
  • KERN_WARNING:警告信息
  • KERN_NOTICE:正常但重要的信息
  • KERN_INFO:普通信息(最常用)
  • KERN_DEBUG:調試信息(默認不顯示)

2.2 控制日志輸出

默認情況下,級別高于KERN_WARNING的消息才會顯示到控制臺。可以通過dmesg命令查看所有日志:?

dmesg | tail  # 查看最新的10條日志
dmesg -w      # 實時監控日志輸出

臨時調整日志級別(數值越小級別越高):

sudo echo 7 > /proc/sys/kernel/printk  # 顯示所有級別日志(調試時用)

2.3 printk 的高級技巧

  • 添加模塊名和函數名:方便定位日志來源?

printk(KERN_INFO "[MY_MODULE] %s: 設備已打開\n", __func__);

__func__是編譯器內置宏,會自動替換為當前函數名

  • 條件編譯調試信息:只在調試模式輸出詳細日志?

#ifdef DEBUG
#define DBG_PRINT(fmt, args...) printk(KERN_DEBUG "[DBG] %s: " fmt, __func__, ##args)
#else
#define DBG_PRINT(fmt, args...)
#endif// 使用
DBG_PRINT("緩沖區大小: %d\n", buf_size);

編譯時添加-DDEBUG參數啟用調試日志

  • 避免日志刷屏:高頻操作中限制日志輸出

static int log_counter = 0;
if (log_counter % 1000 == 0) {  // 每1000次打印一次printk(KERN_INFO "已處理 %d 個請求\n", log_counter);
}
log_counter++;

三、內核 Oops 分析:系統崩潰時的現場照片

當模塊代碼有嚴重錯誤(如空指針訪問),內核會產生 Oops 信息,這相當于系統崩潰時的現場照片,包含大量調試線索。

3.1 認識 Oops 信息

典型的 Oops 信息長這樣:?

BUG: unable to handle kernel NULL pointer dereference at 0000000000000010
IP: [<ffffffffc0a01056>] my_module_write+0x16/0x50 [my_module]
PGD 80000001f8e7067 PUD 1f8e71067 PMD 0 
Oops: 0002 [#1] SMP PTI
CPU: 1 PID: 1234 Comm: insmod Tainted: G        W  OE     5.4.0-100-generic #101-Ubuntu
Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/29/2019
RIP: 0010:my_module_write+0x16/0x50 [my_module]
...
Call Trace:<TASK>SyS_write+0x5f/0xe0do_syscall_64+0x57/0x190entry_SYSCALL_64_after_hwframe+0x44/0xa9
...</TASK>
  • NULL pointer dereference:空指針引用錯誤
  • my_module_write+0x16/0x50:錯誤發生在my_module_write函數,偏移 0x16 處
  • Call Trace:函數調用棧,顯示錯誤發生前的調用路徑

3.2 定位 Oops 錯誤位置

addr2line工具將內存地址轉換為代碼行號:?

addr2line -e my_module.ko 0x16

會輸出類似/home/user/my_module.c:42的結果,直接定位到出錯的代碼行。

3.3 常見 Oops 錯誤及原因

  • NULL pointer dereference:訪問空指針(最常見)
  • use-after-free:使用已釋放的內存
  • stack overflow:棧溢出
  • invalid opcode:非法指令(通常是匯編錯誤)

四、動態調試:按需開啟的監控攝像頭

內核的動態調試(Dynamic Debug)機制可以像開關燈一樣控制特定代碼的日志輸出,不用重新編譯模塊。

1. 開啟動態調試支持

首先確認內核支持動態調試(大部分發行版默認支持):?

grep CONFIG_DYNAMIC_DEBUG /boot/config-$(uname -r)

如果輸出CONFIG_DYNAMIC_DEBUG=y,說明支持。

2. 動態調試的基本用法

通過/sys/kernel/debug/dynamic_debug/control文件控制日志輸出:?

# 先掛載debugfs
sudo mount -t debugfs none /sys/kernel/debug# 顯示my_module.c中所有函數的調試信息
sudo echo 'file my_module.c +p' > /sys/kernel/debug/dynamic_debug/control# 只顯示特定函數的調試信息
sudo echo 'func my_module_write +p' > /sys/kernel/debug/dynamic_debug/control# 關閉調試信息
sudo echo 'file my_module.c -p' > /sys/kernel/debug/dynamic_debug/control

3. 在代碼中使用動態調試

在代碼中用pr_debugdev_dbg代替printk(KERN_DEBUG):?

pr_debug("數據長度: %d\n", data_len);  // 動態調試支持的打印函數

這些函數默認不輸出日志,只有通過動態調試開關啟用后才會輸出。

五、內核調試器 kgdb:像 gdb 一樣調試內核

如果 printk 和 Oops 分析還不夠,就需要kgdb—— 內核版的 gdb 調試器,支持斷點、單步執行等高級調試功能。

5.1 搭建 kgdb 環境

kgdb 需要兩臺機器(或虛擬機)通過串口連接:

  • 目標機:運行待調試的內核和模塊
  • 主機:運行 gdb,通過串口控制目標機

配置步驟(以虛擬機為例):

  1. 給目標虛擬機添加一個串口設備(如 /dev/ttyS0)
  2. 目標機內核啟動參數添加:kgdboc=ttyS0,115200 kgdbwait(啟動時等待調試連接)
  3. 主機通過screen連接串口:screen /dev/ttyS0 115200

5.2 使用 kgdb 調試模塊?

# 在主機上啟動gdb
gdb ./vmlinux  # vmlinux是帶調試信息的內核鏡像# 連接目標機
(gdb) target remote /dev/ttyS0# 設置斷點(模塊加載后)
(gdb) break my_module_init# 查看變量
(gdb) print buffer_size# 單步執行
(gdb) step# 繼續執行
(gdb) continue

5.3 kgdb 的優缺點

  • 優點:可以像調試用戶態程序一樣單步調試內核代碼
  • 缺點:配置復雜,需要兩臺機器,調試過程會暫停整個系統

六、內存調試工具:檢測內存泄漏和越界

內核模塊最容易出內存問題,這些問題隱蔽性強,需要專門工具檢測。

6.1 kmemleak:檢測內存泄漏

kmemleak 可以跟蹤內核內存分配,發現未釋放的內存:

啟用 kmemleak:

# 掛載debugfs
sudo mount -t debugfs none /sys/kernel/debug# 手動觸發內存泄漏檢查
sudo echo scan > /sys/kernel/debug/kmemleak# 查看內存泄漏報告
sudo cat /sys/kernel/debug/kmemleak

典型的內存泄漏報告:

unreferenced object 0xffff888123456780 (size 128):comm "insmod", pid 1234, jiffies 4567890 (age 30.000s)hex dump (first 32 bytes):00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f  ................10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f  ................backtrace:[<ffffffffc0a01020>] my_module_init+0x20/0x100 [my_module][<ffffffff81000200>] do_one_initcall+0x50/0x220...

報告會顯示泄漏內存的地址、大小、分配位置,幫助定位問題。

6.2 KASAN:檢測內存越界

KASAN(Kernel Address Sanitizer)能檢測數組越界、使用已釋放內存等錯誤,但需要使用帶 KASAN 支持的內核:

# 查看內核是否支持KASAN
grep CONFIG_KASAN /boot/config-$(uname -r)

當檢測到內存錯誤時,會輸出詳細報告:

==================================================================
BUG: KASAN: out-of-bounds in my_module_write+0x30/0x50 [my_module]
Write of size 4 at addr ffff88812345678c by task insmod/1234CPU: 1 PID: 1234 Comm: insmod Tainted: G        W  OE     5.4.0-100-generic #101-Ubuntu
Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/29/2019
Call Trace:<TASK>__dump_stack+0x70/0xa0...
Allocated by task 1234:my_module_init+0x20/0x100 [my_module]do_one_initcall+0x50/0x220...
==================================================================

七、用戶態調試工具:從外部觀察模塊行為

除了內核態工具,還有一些用戶態工具可以幫助觀察模塊的行為。

1. lsmod 和 modinfo:查看模塊信息?

lsmod  # 查看所有加載的模塊及使用計數
lsmod | grep my_module  # 查看特定模塊
modinfo my_module.ko  # 查看模塊詳細信息(版本、作者、依賴等)

2. proc 和 sys 文件系統:模塊狀態接口

在模塊中創建 proc 或 sys 接口,暴露內部狀態:

創建 proc 文件示例

#include <linux/proc_fs.h>
#include <linux/seq_file.h>static int my_proc_show(struct seq_file *m, void *v) {seq_printf(m, "當前連接數: %d\n", conn_count);seq_printf(m, "緩沖區使用率: %d%%\n", buf_usage);return 0;
}static int my_proc_open(struct inode *inode, struct file *file) {return single_open(file, my_proc_show, NULL);
}static const struct file_operations my_proc_fops = {.owner = THIS_MODULE,.open = my_proc_open,.read = seq_read,.llseek = seq_lseek,.release = single_release,
};// 在初始化函數中創建
proc_create("my_module_stats", 0, NULL, &my_proc_fops);

用戶態查看:

cat /proc/my_module_stats

3. perf:性能分析工具

perf可以分析模塊的性能瓶頸:?

# 記錄模塊的函數調用情況
sudo perf record -g -e 'module:my_module:*' sleep 10# 查看報告
sudo perf report

八、調試方法論:解決問題的步驟

掌握工具后,更重要的是形成一套調試思路。遇到問題時可以按這個步驟排查:

①復現問題:明確觸發條件,確保問題可重復

②縮小范圍:通過注釋代碼或添加日志,定位問題所在的大致范圍

③針對性調試

  • 功能問題:用 printk 打印關鍵變量值
  • 崩潰問題:分析 Oops 信息
  • 內存問題:用 kmemleak 和 KASAN 檢測
  • 性能問題:用 perf 分析

④驗證修復:確認問題解決,且沒有引入新問題

調試 checklist

  • ?模塊是否正確加載?(lsmod 檢查)
  • ?有沒有 Oops 信息?(dmesg 查看)
  • ?關鍵變量的值是否符合預期?(printk 輸出)
  • ?內存分配和釋放是否配對?(檢查 kmalloc 和 kfree)
  • ?函數返回值是否正確處理?(是否檢查錯誤碼)

內核模塊調試確實有難度,但只要掌握了正確的工具和方法,大部分問題都能解決。記住:

  1. 從簡單工具開始:先用 printk 和 dmesg 解決 80% 的問題
  2. 善用系統提供的調試機制:動態調試、kmemleak 等內核自帶工具
  3. 復雜問題才需要 kgdb:簡單問題用高級工具反而效率低
  4. 安全第一:始終在虛擬機中調試,做好快照備份

調試能力是區分內核開發者水平的關鍵指標。剛開始可能會覺得挫敗,但每解決一個調試難題,你的內核開發水平就會上一個臺階。就像醫生通過不斷積累病例提高診斷能力,內核開發者也是在一次次調試中成長的。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/91038.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/91038.shtml
英文地址,請注明出處:http://en.pswp.cn/web/91038.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

RK3568筆記九十一:QT環境搭建

若該文為原創文章,轉載請注明原文出處。 記錄按照正點原子給的手冊搭建QT環境 參考《09【正點原子】ATK-DLRK3568_Qt開發環境搭建V1.2.pdf》 一、安裝 1、下載 https://mirrors.sau.edu.cn/qt/archive/online_installers/4.6/qt-unified-linux-x64-4.6.0-online.run 2、賦…

面試實戰,問題十六,Java面試,消息隊列,如何避免消息重復消費,怎么回答

在Java面試中&#xff0c;關于消息隊列如何防止消息被重復消費的問題&#xff0c;可以從以下幾個方面進行回答&#xff0c;結合系統架構設計、消息隊列機制和業務邏輯處理&#xff0c;確保在不同場景下實現消息的冪等性。 1. 消息隊列重復消費的根本原因 消息重復消費的根本原因…

PDF轉圖片實用指南:如何批量高效轉換?

將PDF轉換為圖片后&#xff0c;可以更方便地在演示文稿、網頁或電子相冊中使用這些資料&#xff0c;以便更好地展示信息。它 是一款支持多文件批量轉換的工具&#xff0c;可將多個 PDF 文檔一鍵轉換為圖片格式。雖然界面為英文&#xff0c;但操作簡單&#xff0c;不影響使用。你…

走入Linux的世界:編輯器Vim

嘿&#xff0c;各位技術潮人&#xff01;好久不見甚是想念。生活就像一場奇妙冒險&#xff0c;而編程就是那把超酷的萬能鑰匙。此刻&#xff0c;陽光灑在鍵盤上&#xff0c;靈感在指尖跳躍&#xff0c;讓我們拋開一切束縛&#xff0c;給平淡日子加點料&#xff0c;注入滿滿的pa…

PyTorch中神經網絡的模型構建

要構建自定義模型&#xff0c;需完成兩個核心步驟&#xff1a;繼承 nn.Module 類&#xff1b;重載 __init__ 方法&#xff08;初始化&#xff09;和 forward 方法&#xff08;前向計算&#xff09; 神經網絡的構造 初始化方法&#xff08;__init__&#xff09; def __init__…

QML QtCharts坐標軸系統

QtCharts是Qt框架中強大的數據可視化模塊&#xff0c;它提供了豐富的圖表類型和靈活的坐標軸系統&#xff0c;能夠滿足各種數據展示需求。本文將全面介紹QML中QtCharts的坐標軸系統&#xff0c;包括數值坐標軸(ValueAxis)、對數坐標軸(LogValueAxis)、分類坐標軸(CategoryAxis)…

TI 2025全國電賽猜題

本科組可能的題目方向本科組器材更側重高頻信號處理、復雜控制系統、精密測量及多設備協同&#xff0c;可能涉及以下題目&#xff1a;四旋翼飛行器相關任務題目示例&#xff1a;設計 “基于四旋翼的 UV 光控自主導航系統”任務要求&#xff1a;利用四旋翼飛行器&#xff08;最大…

Python自動化運維實戰指南

什么是自動化運維定義與背景自動化運維是指利用工具和腳本自動執行傳統上需要人工操作的IT運維任務&#xff0c;包括但不限于服務器配置管理、軟件部署、監控告警、日志分析等日常工作。隨著互聯網業務規模的擴大&#xff0c;傳統手工運維方式已無法滿足快速部署、規模化管理等…

k8s的csi對接GPFS

在 Kubernetes&#xff08;k8s&#xff09;集群中&#xff0c;通過 CSI&#xff08;Container Storage Interface&#xff09;對接 GPFS&#xff08;General Parallel File System&#xff0c;現為 IBM Spectrum Scale&#xff09;是實現高性能共享存儲的重要方案。GPFS 作為并…

HTB賽季8靶場 - era

nmap掃描 └─$ nmap -p- --min-rate 1000 -T4 10.129.137.201 -oA nmapfullscan Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-07-27 21:19 EDT Warning: 10.129.137.201 giving up on port because retransmission cap hit (6). …

Bug貓學習史#1:面向對象

在Java編程中&#xff0c;掌握幾個核心概念對深入學習至關重要&#xff1a;類屬性建議采用包裝類以提升靈活性&#xff1b;建造者模式中this關鍵字能有效簡化對象構建過程&#xff1b;static關鍵字涉及類的加載機制&#xff1b;接口默認使用public修飾符并支持默認方法實現&…

優測推出HarmonyOS全場景測試服務,解鎖分布式場景應用卓越品質!

隨著HarmonyOS NEXT“純血鴻蒙”的全面商用&#xff0c;生態正以前所未有的速度重構終端操作系統格局。對于APP廠商而言&#xff0c;應用測試需要從單一設備思維向場景化服務驗證轉變。優測云服務平臺正式推出 HarmonyOS全場景測試解決方案&#xff0c;針對鴻蒙系統提供功能測試…

二層環路與三層環路:原理、區別與解決方案全解析

網絡環路是網絡運維中最常見也最具破壞性的問題之一。本文將深入淺出地解析二層環路和三層環路的核心概念&#xff0c;通過對比分析幫助讀者全面理解這兩種環路的形成機制、危害表現及解決方案。一、環路問題概述 1.1 什么是網絡環路 網絡環路是指數據包在網絡中循環傳輸無法到…

Python爬蟲庫性能與選型實戰指南:從需求到落地的全鏈路解析

目錄 一、性能基準測試&#xff1a;用數據打破認知誤區 1. 靜態頁面采集&#xff1a;效率與資源的終極對決 2. 動態頁面渲染&#xff1a;速度與真實性的博弈 二、場景化選型矩陣&#xff1a;從需求到工具的精準映射 1. 小規模快速原型開發&#xff08;≤1000頁&#xff09;…

uni-app switch(開關選擇器) BUG

uni-app switch&#xff08;開關選擇器&#xff09; BUGBUG&#xff1a;uni-app中的switch的checked屬性并不能根據根據綁定的動態數據進行調整switch開關選擇器&#xff08;BUG&#xff09;switch開關選擇器&#xff08;BUG&#xff09; - 我的使用用途switch開關選擇器&#…

微服務架構中的資源調度與負載均衡實踐

更多云服務器知識&#xff0c;盡在hostol.com在今天這個快速發展的數字化時代&#xff0c;微服務架構已經成為了現代企業系統開發的主流。隨著技術的不斷進步&#xff0c;企業的業務需求也在不斷地變化&#xff0c;傳統的單體架構已經無法滿足日益復雜的應用需求。微服務架構&a…

Rust Web 全棧開發(十一):WebAssembly 嘗鮮

Rust Web 全棧開發&#xff08;十一&#xff09;&#xff1a;WebAssembly 嘗鮮Rust Web 全棧開發&#xff08;十一&#xff09;&#xff1a;WebAssembly 嘗鮮什么是 WebAssembly&#xff1f;安裝 wasm-pack 和 cargo-generate使用項目模板構建項目生成網頁安裝依賴項在 www 中使…

Thymeleaf實戰:SpringBoot用戶管理系統

Thymeleaf 示例代碼下面是完整代碼示例&#xff0c;幫助理解 Thymeleaf 語法和后端代碼的配合&#xff1a;1. 用戶實體類 (User.java)/*** 用戶實體類*/ public class User {private Long id; // 用戶IDprivate String name; // 用戶名private String email; /…

mysql查找數據庫表中某幾個連續的編號中中斷的編號

在MySQL中查找表中連續編號中斷的位置,可以通過以下幾種方法實現: 基于范圍的查詢方法 通過自連接查詢找出ID序列中的斷點,例如查找1-100范圍內缺失的ID: SELECT a.id + 1 AS start, MIN(b.id) - 1 AS end FROM

《劍指offer》-數據結構篇-樹

題目重建二叉樹樹的子結構二叉樹的鏡像從上往下打印二叉樹&#xff08;層序遍歷&#xff09;把二叉樹打印成多行按之字形順序打印二叉樹二叉搜索樹的第k個結點&#xff08;中序遍歷&#xff09;二叉搜索樹的后序遍歷序列&#xff08;后序遍歷&#xff09;二叉樹中和為某一值的路…