《嵌入式Linux系统开发标准教程》9 内核调试技术.pdf
《《嵌入式Linux系统开发标准教程》9 内核调试技术.pdf》由会员分享,可在线阅读,更多相关《《嵌入式Linux系统开发标准教程》9 内核调试技术.pdf(33页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、 嵌入式学院华清远见旗下品牌:www.embedu.org 嵌入式 Linux 系统开发标准教程 嵌入式 Linux 系统开发标准教程第 9 章、内核调试技术 嵌入式学院华清远见旗下品牌:www.embedu.org 第 9 章 内核调试技术 本章目标 本章介绍了各种 Linux 内核调试方法。内核的调试需要从内核源码本身、调试工具等方面做好准备。通过本章的学习,读者可以了解不同调试方式的特点和使用方法,根据需要选择不同的内核调试方式。内核调试方法 内核打印函数 获取内核信息 处理出错信息 内核源码调试 嵌入式 Linux 系统开发标准教程第 9 章、内核调试技术 嵌入式学院华清远见旗下品牌:
2、www.embedu.org 9.1 内核调试方法 对于庞大的 Linux 内核软件工程,单靠阅读代码查找问题已经非常困难,需要借助调试技术解决 BUG。通过合适的调试手段,可以有效地查找和判断 BUG 的位置和原因。9.1.1 内核调试概述 当内核运行出现错误的时候,首先要明确定义和可靠地重现这个错误现象。如果一个 BUG 不能重现,修正起来只有凭想象和读代码。内核、用户空间和硬件之间的交互非常,在特定配置、特定机器、特殊负载条件下,运行某些程序可能会产生一个BUG,其他条件下就不一定产生。这在嵌入式 Linux 系统上很常见,例如:在 X86 平台上运行正常的驱动程序,在 ARM 平台上就
3、可能会出现 BUG。在跟踪 BUG 的时候,掌握的信息越多越好。内核的 BUG 是多种多样的。可能由于不同原因出现,并且表现形式也多种多样。BUG 范围,从完全不正确的代码(例如:没有在适当的地址存储正确的值)到同步的错误(例如:不适当地对一个共享变量加锁)。它们的表现形式也各种各样,从系统崩溃的错误操作到系统性能差等。通常 BUG 是一系列事件,内核代码的错误使得用户程序出现错误。例如:一个不带引用数的共享结构体可能引起条件竞争。没有合适的统计,一个进程可以释放这个结构体,但是另外一个进程仍然想要用它。再往下,第二个进程可能会使用通过一个无效的指针访问一个不存在的结构体。这就会导致 NULL
4、 指针废弃、读垃圾数据,如果这个数据还没有被覆盖,也可能基本正常。NULL 指针废弃会产生 oops;垃圾数据导致数据错误(接下来可能是错误的行为或者 oops);应用程序报告 oops 或者错误的行为。内核开发者必须处理这个错误,知道这个数据是在释放以后访问的,这存在一个条件竞争。修正的方法是为这个结构体添加引用计数,并且可能需要加锁保护。调试内核很难,实际上内核不同于其他软件工程。内核有操作系统独特的问题,例如:时间管理和条件竞争,这可以使多个线程同时在内核中执行。因此,调试 BUG 需要有效的调试手段。几乎没有一种调试工具或者方法能够解决全部问题。即使在一些集成测试环境中,也要划分不同测
5、试调试功能,例如:跟踪调试、内存泄漏测试、性能测试等。掌握的调试方法越多,调试 BUG 就越方便。Linux有很多开放源代码的工具,每一个工具的调试功能专一,所以这些工具的实现一般也比较简单。9.1.2 学会分析内核源程序 正是由于内核的复杂性,无论使用什么调试手段,都需要熟悉内核源码。只有熟悉了内核各部分的代码实现,才能够找到准确的跟踪点;只有熟悉操作系统的内核机制,才能准确地判断系统运行状态。对于初学者来说,阅读内核源代码将是非常枯燥的工作。最好先掌握一种搜索工具,学会从源码树中搜索关键词。当能够对内核源代码进行情景分析的时候,你就能感到其中的乐趣了。嵌入式 Linux 系统开发标准教程第
6、 9 章、内核调试技术 嵌入式学院华清远见旗下品牌:www.embedu.org 调试是无法逃避的任务。进行调试有很多种方法,比如将消息打印到屏幕上、使用调试器,或只是考虑程序执行的情况并仔细地分析问题所在。在修正问题之前,必须先找出问题的源头。举例来说,对于段错误,需要了解段错误发生在代码的哪一行。一旦发现了代码中出错的行,请确定该方法中变量的值、方法被调用的方式以及关于错误如何发生的详细情况。使用调试器将使找出所有这些信息变得很简单。如果没有调试器可用,还可以使用其他的工具。(请注意:有些 Linux软件产品中可能并不提供调试器)。9.1.3 调试方法介绍 内核调试方法很多,主要有以下 4
7、 类。n 通过打印函数。n 获取内核信息。n 处理出错信息。n 内核源码调试。在调试内核之前,通常需要配置内核的调试选项。图 9.1 给出了“Kernel hacking”配菜单下的各种调试选项。不同的调试方法,需要配置对应的选项。每一种调试选项针对不同的调试功能。并且不是所有的调试选项在所有的平台上都能够支持。这里介绍一些“Kernel hacking”的调试选项,具体配置使用可以根据情况选择。图 9.1 内核调试选项(1)编译选项“omit-frame-pointer”。CONFIG_FRAME_POINTER 在“Kernel hacking”中缺省的定义为“Y”。在 Makefile根
8、据这个选项来选择 GCC 编译选项。看看顶层 Makefile 中的这段代码就明白了。嵌入式 Linux 系统开发标准教程第 9 章、内核调试技术 嵌入式学院华清远见旗下品牌:www.embedu.org ifdef CONFIG_FRAME_POINTER CFLAGS +=-fno-omit-frame-pointer$(call cc-option,-fno-optimize-sibling-calls,)else CFLAGS +=-fomit-frame-pointer endif (2)“Show timing information on printks”。CONFIG_PRIN
9、TK_TIME 在 printk 打印的信息中包含时间信息,可以用来测量内核操作之间的时间间隔。(3)“Kernel debugging”。CONFIG_DEBUG_KERNEL 选择调试内核选项以后,才可以显示有关的内核调试子项。大部分内核调试选项都依赖于它。(4)“Magic SysRq key”。CONFIG_MAGIC_SYSRQ 使能系统请求键,可以用于系统调试。(5)“Kernel log buffer size(16=64KB,17=128KB)”。CONFIG_LOG_BUF_SHIFT 设置内核日志缓冲区(log_buf)大小,16 表示 64KB,17 表示 128KB。(
10、6)“Detect Soft Lockups”。CONFIG_DETECT_SOFTLOCKUP 探测内核软死锁状态,例如:有些 BUG 导致内核一直循环超过 10s,而无法调度其他任务执行。一旦探测到死锁状态,内核将打印出当前的堆栈回溯信息。(7)“Collect scheduler statistics”。CONFIG_SCHEDSTATS 可以在调度器相关的子程序中插入一些代码,采集统计调度器的动作。通过/proc/schedstat 接口可以读取这些信息。(8)“Debug memory allocations”。CONFIG_DEBUG_SLAB 让内核对内存分配进行有限的验证,这会
11、控制管理空闲的内存,会使 kmalloc()等函数速度变慢。(9)“Debug preemptible kernel”。CONFIG_DEBUG_PREEMPT 使能内核抢占调试功能。如果在非抢占安全的状况下使用,将打印警告信息。另外,还可以探测抢占技术下溢。(10)“Spinlock debugging”。CONFIG_DEBUG_SPINLOCK 使能自旋锁(spinlock)调试功能。捕捉自旋锁初始化等方面的错误,结合 NMI watchdog 可以调试死锁。(11)“Sleep-inside-spinlock checking”。CONFIG_DEBUG_SPINLOCK_SLEEP
12、检查程序中自旋锁内休眠的情况。因为持有自旋锁休眠将可能引起其他任务等待锁。(12)“kobject debugging”。CONFIG_DEBUG_KOBJECT 可以把更多 kobject 的调试信息发送到系统日志中。(13)“Highmem debugging”。嵌入式 Linux 系统开发标准教程第 9 章、内核调试技术 嵌入式学院华清远见旗下品牌:www.embedu.org CONFIG_DEBUG_HIGHMEM 对高端内存系统进行额外的检查。(14)“Verbose BUG()reporting(adds 70K)”。CONFIG_DEBUG_BUGVERBOSE 使 BUG()
13、的 panic 报告执行的文件名和调用BUG()的行号。(15)“Compile the kernel with debug info”。CONFIG_DEBUG_INFO 使内核映像包含调试信息,可以方便内核源码调试。(16)“Enable ioremap()debugging”。CONFIG_DEBUG_IOREMAP 这个选项会使内核区分 ioremap 映射的内存和物理内存,并且打印一些回溯信息。(17)“Debug Filesystem”。CONFIG_DEBUG_FS 支持伪文件系统 debugfs,用来存放调试文件。(18)“Compile the kernel with fra
14、me pointers”。CONFIG_FRAME_POINTER 配置内核编译选项:no-omit-frame-pointer。(19)“Verbose user fault messages”。CONFIG_DEBUG_USER 在应用程序因为例外崩溃的时候,打印错误信息。(20)“Wait queue debugging”。CONFIG_DEBUG_WAITQ 使能调试等待队列的功能。(21)“Verbose kernel error messages”。CONFIG_DEBUG_ERRORS 当内核探测到内部错误的时候,打印调试信息。(22)“Kernel low-level debu
15、gging functions”。CONFIG_DEBUG_LL 包含 printascii、printch、printhex 等调试子程序,方便控制台启动以前的调试工作。(23)“Kernel low-level debugging via EmbeddedICE DCC channel”。CONFIG_DEBUG_ICEDCC 把调试信息重定向到 EmbeddedICE 的 DCC 通道。这仅对于 ARM9 类型的 ICE 仿真器可以使用。(24)“Kernel low-level debugging messages via footbridge serial port”。CONFIG_
16、DEBUG_DC21285_PORT 把调试信息重定向到 DC21285(Footbridge)串口。这仅对于特定的硬件平台可用。(25)“Kernel low-level debugging messages via S3C2410 UART”。CONFIG_DEBUG_S3C2410_PORT 把调试信息重定向到 S3C2410 串口。这仅对于 S3C2410 平台可用。9.2 内核打印函数 嵌入式系统一般都可以通过串口与用户交互。大多数 Bootloader 可以向串口打印信息,并且接收命令。内核同样可以向串口打印信息。但是在内核启动过程中,不同阶段的打印函数不同。分析这些打印函数的实现
17、,可以更好地调试内核。9.2.1 内核映像解压前的串口输出函数 从启动信息可以看出,zImage 在解压之前就向串口输出提示信息了。回顾一下第 嵌入式 Linux 系统开发标准教程第 9 章、内核调试技术 嵌入式学院华清远见旗下品牌:www.embedu.org 7 章介绍的 Linux 启动过程,decompresss_kernel()函数调用了 putstr()函数,直接向串口打印内核解压的信息。putstr()函数实现了向串口输出字符串的功能。因为不同的处理器可以有不同的串口控制器,所以 putstr()函数的实现依赖于硬件平台。分析一下 S3C2410 平台中 putstr()函数的实
18、现,它是在头文件 uncompress.h 中实现的。/*include/asm/arch/uncompress.h*/*写 UART 寄存器的函数实现*/static _inline_ void uart_wr(unsigned int reg,unsigned int val)volatile unsigned int*ptr;ptr=(volatile unsigned int*)(reg+uart_base);*ptr=val;/*读 UART 寄存器的函数实现*/static _inline_ unsigned int uart_rd(unsigned int reg)volatil
19、e unsigned int*ptr;ptr=(volatile unsigned int*)(reg+uart_base);return*ptr;/*输出单个字符的函数*/static void putc(char ch)/*要处理 UART 运行在 FIFO 模式的情况,不能因为等待 TX 信号而停止执行*/int cpuid=S3C2410_GSTATUS1_2410;if(ch=n)putc(r);/*扩展成回车换行符rn*/if(uart_rd(S3C2410_UFCON)&S3C2410_UFCON_FIFOMODE)/*FIFO 模式*/int level;while(1)/*如
20、果 FIFO 模式,等待 FIFO 非满的状态*/level=uart_rd(S3C2410_UFSTAT);if(cpuid=S3C2410_GSTATUS1_2440)level&=S3C2440_UFSTAT_TXMASK;level=S3C2440_UFSTAT_TXSHIFT;else level&=S3C2410_UFSTAT_TXMASK;嵌入式 Linux 系统开发标准教程第 9 章、内核调试技术 嵌入式学院华清远见旗下品牌:www.embedu.org level=S3C2410_UFSTAT_TXSHIFT;if(level FIFO_MAX)break;else /*如果
21、不使用 FIFO 的模式,就等待 TX 状态*/while(uart_rd(S3C2410_UTRSTAT)&S3C2410_UTRSTAT_TXE)!=S3C2410_UTRSTAT_TXE);/*向发送寄存器写字节操作*/uart_wr(S3C2410_UTXH,ch);/*输出字符串的函数*/static void putstr(const char*ptr)for(;*ptr!=0;ptr+)putc(*ptr);/*调用输出单个字符的函数*/这些函数的实现比较简单,基本上就是 RAW 设备操作的方式。直接对串口寄存器读写,实现串口输入输出的功能。9.2.2 内核错误报告子程序 在内核
22、解压完成,跳转到 vmlinux 映像入口。这时还没有初始化控制台设备,但是执行系统初始化的过程中也可能出现严重的错误,导致系统崩溃。怎样才能报告这种错误信息呢?可以通过 printascii 子程序来向串口打印。printascii、printhex8 等子程序包含在 arch/arm/kernel/debug.S 文件中。如果要编译链接这些子程序,需要内核使能图 9.1 中的“Kernel low-level debugging functions”选项。printascii 子程序实现向串口打印字符串的功能,printhex 也调用了 printascii 子程序来显示数字。在 prin
23、tascii 子程序中,调用了宏(macro):addruart、waituart、senduart、busyuart,这些宏都是在 include/asm/arch/debug-macro.S 中定义的。这里简单分析一下printascii 的程序。/*arch/arm/kernel/debug.S*/.ltorg ENTRY(printascii)addruart r3 b 2f 1:waituart r2,r3 嵌入式 Linux 系统开发标准教程第 9 章、内核调试技术 嵌入式学院华清远见旗下品牌:www.embedu.org senduart r1,r3 busyuart r2,r3
24、 teq r1,#n moveq r1,#r beq 1b 2:teq r0,#0 ldrneb r1,r0,#1 teqne r1,#0 bne 1b mov pc,lr 其中一个应用就是_error_a 报错子程序,报告系统平台类型(machine type)的检查结果。这里就是调用 printascii 打印信息的。/*arch/arm/kernel/head.S*/.type _error_a,%function _error_a:#ifdef CONFIG_DEBUG_LL mov r4,r1 preserve machine ID adr r0,str_a1 bl printasc
25、ii mov r0,r4 bl printhex8 adr r0,str_a2 bl printascii adr r3,3f ldmia r3,r4,r5,r6 get machine desc list sub r4,r3,r4 get offset between virt&phys add r5,r5,r4 convert virt addresses to add r6,r6,r4 physical address space 1:ldr r0,r5,#MACHINFO_TYPE get machine type bl printhex8 mov r0,#t bl printch l
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 嵌入式Linux系统开发标准教程 嵌入式Linux系统开发标准教程9 内核调试技术 嵌入式 Linux 系统 开发 标准 教程 内核 调试 技术
限制150内