1、张林软件研发部嵌入式嵌入式Linux调试技术调试技术主要内容 嵌入式调试也是一门艺术嵌入式调试也是一门艺术 GDB CoreDump 在嵌入式软件开发过程中,一般来说,花在测试和花在编码的时间比为3:1(实际上可能更多)。这个比例随着你的编程和测试水平的提高而不断下降,但不论怎样,软件测试对一般人来讲很重要。很多年前,一位开发人员为了在对嵌入式有更深层次的理解,向Oracle询问了这样的一个问题:我怎么才能知道并懂得我的系统到底在干些什么呢?Oracle面对这个问题有些吃惊,因为在当时没有人这么问过,而同时代的嵌入式开发人员问的最多的大都围绕“我怎么才能使程序跑的更快”、“什么编译器最好”等肤
2、浅的问题。所以,面对这个不同寻常却异乎成熟的问题,Oracle感到欣喜并认真回复了他:你的问题很有深度很成熟,因为只有不断地去深入理解才有可能不断地提高水平。并且Oracle为了鼓励这位执着的程序员,把条关于嵌入式软件开发测试的秘诀告诉了他: 一个小故事一个小故事l 1.懂得使用工具l 2.尽早发现内存问题l 3.深入理解代码优化l 4.不要让自己大海捞针l 5.重现并隔离问题l 6.以退为进l 7.确定测试的完整性l 8.提高代码质量意味着节省时间l 9.发现它,分析它,解决它l 10.利用初学者的思维1010条秘诀条秘诀 就象修车需要工具一样,好的程序员应该能够熟练运用各种软件工具。不同的
3、工具,有不同的使用范围,有不同的功能。使用这些工具,你可以看到你的系统在干些什么,它又占用什么资源,它到底和哪些外界的东西打交道。让你郁闷好几天的问题可能通过某个工具就能轻松搞定,可惜你就是不知道。那么为什么那么多的人总是在折腾个半死之后才想到要用测试工具呢?原因很多,主要有两个。一个是害怕,另一个是惰性。 害怕害怕是因为加入测试用具或测试模块到代码需要技巧同时有可能引入新的错误,所以他们总喜欢寄希望于通过不断地修改重编译代码来消除bug,结果却无济于事。懒惰懒惰是因为他们习惯了使用printf之类的简单测试手段懂得使用工具常用调试工具l 1.源代码级调试器(Source-level Debu
4、gger)-gdbl 2.简单实用的打印显示工具printf l 3. ICE或JTAG调试器In-circuit Emulator BDI2000l 4. ROM监视器ROM Monitor l 5. Data监视器Data Monitorl 6. OS监视器Operating System Monitorl 7.性能分析工具Profilerl 8.内存分析工具Memory Teseter mtrace、boundscheckerl 9.运行跟踪器Execution Tracer - stracel 10.覆盖工具Coverage Tester l 11.自制工具Home-made test
5、erl 12.GUI测试工具GUI Tester robot、LoadRunner 主要有三种类型:内存泄露、内存碎片和内存崩溃 对于内存问题态度必须要明确,那就是早发现早“治疗”。尽早发现内存问题深入理解代码优化讲到系统稳定性,人们更多地会想到实时性和速度,因为代码效率对嵌入式系统来说太重要了。知道怎么优化代码是每个嵌入式软件开发人员必须具备的技能。就象女孩子减肥一样,起码知道她哪个地方最需要减,才能去购买减肥药或器材来减掉它。可见,代码优化的前提是找到真正需要优化的地方,然后对症下药,优化相应部分的代码。不要让自己大海捞针大海捞针只是对调试的一种生动比喻。经常听到组里有人对自己正在调试的代
6、码抱怨!可以理解,因为代码不是他写的,他有足够的理由去抱怨 bug百出的代码,只要他自己不要写出这种代码,否则有一天同组的其它人可能同样会抱怨他写的代码。为何会有大海捞针呢?肯定是有人把针掉到海里咯;那针为何会掉在海里呢?肯定是有人不小心或草率呗。所以当你在抱怨针那么难找的时候,你是否想过是你自己草率地丢掉的。同样,当你调试个半死的时候,你是否想过你要好好反省一下当初为了寻求捷径可能没有严格地遵守好的编码设计规范、没有检测一些假设条件或算法的正确性、没有将一些可能存在问题的代码打上记号呢? 已所不欲,勿施于人重现并隔离问题l如果你不是把针掉在大海了,而是掉在草堆里,那要好办写。因为至少我们可以
7、把草堆分成很多块,一块一块的找。l对于模块独立的大型项目,使用隔离方法往往是对付那些隐藏极深bug的最后方法。如果问题的出现是间歇性的,我们有必要设法去重现它并记录使其重现的整个过程以备在下一次可以利用这些条件去重现问题。如果你确信可以使用记录的那些条件去重现问题,那么我们就可以着手去隔离问题。怎么隔离呢?我们可以用#ifdef把一些可能和问题无关的代码关闭,把系统最小化到仍能够重现问题的地步。如果还是无法定位问题所在,那么有必要打开“工具箱”了。可以试着用ICE或数据监视器去查看某个可疑变量的变化;可以使用跟踪工具获得函数调用的情况包括参数的传递;检查内存是否崩溃以及堆栈溢出的问题。以退为进
8、l猎人为了不使自己在森林里迷路,他常常会在树木上流下一些标记,以备自己将来有一天迷路时可以根据这些标记找到出路。l对过去代码的修改进行跟踪记录对将来出现问题之后的调试很有帮助。假如有一天,你最近一次修改的程序跑了很久之后忽然死掉了,那么你这时的第一反映就是我到底改动了些什么呢,因为上次修改之前是好的。l那么如何检测这次相对于上次的修改呢?没错,代码控制系统SCS或称版本控制系统VCS(Concurrent Version Control,CVS是VCS的演化版本)。将上个版本check in下来后和当前测试版本比较。比较的工具可以是SCS/VCS/CVS自带的diff工具或其它功能更强的比较工
9、具,比如BeyondCompare和ExamDiff。通过比较,记录所有改动的代码,分析所有可能导致问题的可疑代码。确定测试的完整性你怎么知道你的测试有多全面呢?覆盖测试(coverage testing)可以回答这个问题。覆盖测试工具可以告诉你CPU到底执行了那些代码。好的覆盖工具通常可以告诉你大概20%到40%代码没有问题,而其余的可能存在bug。覆盖工具有不同的测试级别,用户可以根据自己的需要选择某个级别。即使你很确信你的单元测试已经很全面并且没有dead code,覆盖工具还是可以为你指出一些潜在的问题,看下面的代码:if (i = 0 & (almostAlwaysZero = 0
10、| (last = i)如果almostAlwaysZero为非,那么last=i赋值语句就被跳过,这可能不是你所期望的。这种问题通过覆盖工具的条件测试功能可以轻松的被发现。总之,覆盖测试对于提高代码质量很有帮助。提高代码质量意味节约时间有研究表明软件开发的时间超过80%被用在下面几个方面:l调试自己的代码(单元测试)l调试自己和其他相关的代码(模块间测试)l调试整个系统(系统测试)更糟糕的是你可能需要花费10-200倍的时间来找一个bug,而这个bug在开始的时候可能很容易就能找到。一个小bug可能让你付出巨大的代价,即使这个bug对整个系统的性能没有太大的影响,但很可能会影响让那些你可以看
11、得到的部分。所以我们必须要养成良好的编码和测试手段以求更高的代码质量,以便缩短调试的代码。这世界没有万能的膏药。profile再强大也有力不从心的时候;内存监视器再好,也有无法发现的时候;覆盖工具再好用,也有不能覆盖的地方。一些隐藏很深的问题即使用尽所有工具也有可能无法查到其根源,这时我们能做的就是通过这些问题所表现出来的外在现象或一些数据输出来发现其中的规律或异常。一旦发现任何异常,一定要深入地理解并回溯其根源,直到解决为止。发现、分析、解决利用初学者的思维有人这样说过:“有些事情在初学者的脑子里可能有各种各样的情况,可在专家的头脑里可能就很单一”。有时候,有些简单的问题会被想的很复杂,有些
12、简单的系统别设计的很复杂,就是由于你的“专家思维”。当你被问题难住时,关掉电脑,出去走走,把你的问题和你的朋友甚至你的小狗说说,或许他们可以给你意想不到的启发。 嵌入式调试也是一门艺术。就像其它的艺术一样,如果你想取得成功,你必须具备智慧、经验并懂得使用工具。只要我们能够很好地领悟Oracle这十条秘诀,我相信我们在嵌入式测试方面就能够取得成功。总结gdbl名称 gdb - GNU 调试器l语法 gdb -help -nx -q -batch -cd=dir -f -b bps -tty=dev -s symfile -e prog -se prog -ccore -x cmds -d dir
13、 progcore|procIDl描述 调试器(如GDB)的目的是允许你在程序运行时进入到某个程序内部去看看该程序在做什么,或者在该程序崩溃时它在做什么。GDB主要可以做4大类事(加上一些其他的辅助工作),以帮助用户在程序运行过程中发现bug。 o 启动您的程序,并列出可能会影响它运行的一些信息 o 使您的程序在特定条件下停止下来 o 当程序停下来的时候,检查发生了什么 o 对程序做出相应的调整,这样您就能尝试纠正一个错误并继续发现其它错误gdbgdb调试的基本步骤l编译带调试信息的可执行文件 gcc g l 运行gdb程序 GDB通过在命令行方式下输入gdb来执行。启动过后,GDB会从终端读
14、取命令,直到您输入GDB命令quit使GDB退出。您能通过GDB命令help获取在线帮助。 您能以无参数无选项的形式运行GDB,不过通常的情况是以一到两个参数运行GDB,以待调试的可执行程序名为参数 gdb 程序名 您能用两个参数来运行GDB,可执行程序名与core文件(译注:不知道怎么翻译好,就不翻译了)。 gdb 程序名 core 您可以以进程ID作为第二个参数,以调式一个正在运行的进程 gdb 程序名 1234 将会把gdb附在进程1234之上(除非您正好有个文件叫1234,gdb总是先查找core文件)gdb执行gdb启动界面gdb基本命令lfilelquitlrunlinfollis
15、tlbreaklwatchlprintlsetlsteplnextlcontinue file filename 装入想要调试的可执行文件 break file:function 在(file文件的)function函数中设置一个断点 clear 除一个断点,这个命令需要指定代码行或者函数名作为参数gdb基本命令 run arglist 运行程序 (如果指定了arglist,则将arglist作为参数运行程序) bt Backtrace: 显示程序堆栈信息 print expr 打印表达式的值 continue 继续运行您的程序 (在停止之后,比如在一个断点之后gdb基本命令 list 列出产
16、生执行文件的源代码的一部分 next 单步执行 (在停止之后); 跳过函数调用 nexti 执行下一行的源代码中的一条汇编指令 set 设置变量的值。例如:set nval=54 将把54保存到nval变量中gdb基本命令 step 单步执行 (在停止之后); 进入函数调用 stepi 继续执行程序下一行源代码中的汇编指令。如果是函数调用,这个命令将进入函数的内部,单步执行函数中的汇编代码 watch 使你能监视一个变量的值而不管它何时被改变gdb基本命令 display 在断点的停止的地方,显示指定的表达式的值。(显示变量) undisplay 删除一个display设置的变量显示。这个命令
17、需要将display list中的索引做参数 quit 退出gdb.gdb基本命令gdb远程调试模型gdb远程调试环境模型图建立gdb和gdbserver 之间的连接l在目标板上运行gdbserverrootvm/root# ./gdbserver 192.168.1.1:2345 helloProcess hello created; pid=1000Listening on port 2345l将hello程序复制到主机的相应目录,执行arm-linux-gdb:./arm-linux-gdb hellol连接到开发板(gdb) target remote 192.168.1.1:2345
18、何谓何谓 core? 在使用半导体作为内存的材料前,人类是利用线圈当作内存的材料(发明 者为王安),线圈就叫作 core ,用线圈做的内存就叫作 core memory。如今 ,半导体工业澎勃发展,已经没有人用 core memory 了,不过,在许多情况下, 人们还是把记忆体叫作 core 。 何谓何谓 core dump? 我们在开发(或使用)一个程序时,最怕的就是程序莫明其妙地当掉。虽然系 统没事,但我们下次仍可能遇到相同的问题。于是这时操作系统就会把程序当掉 时的内存内容 dump 出来(现在通常是写在一个叫 core 的 file 里面),让 我们或是 debugger 做为参考。这
19、个动作就叫作 core dump。 core-dump为何会发生为何会发生 core dump? 前面说过,在程序当掉时出错。在 C/C+语言中,最常发生错误的地方就是指 针有问题。您可以利用 core 文件和 debugger 把错误找出来(要怎麽在 debugger 中使用 core 文件?man 一下 gdb 吧!)。 要怎麽才不会让要怎麽才不会让 core 文件出现?文件出现?如果用的是bash的话, 在/etc/profile里加上(或者修改)一条: ulimit -c 0core-dump如何让操作系统生产如何让操作系统生产core文件?文件? 再看看默认的一些参数,注意core
20、file size是个0,程序出错时不会产生core文件了。 $ ulimit -acore file size (blocks, -c) 0data seg size (kbytes, -d) unlimitedfile size (blocks, -f) unlimitedmax locked memory (kbytes, -l) 4max memory size (kbytes, -m) unlimitedopen files (-n) 2048pipe size (512 bytes, -p) 8stack size (kbytes, -s) 10240cpu time (secon
21、ds, -t) unlimitedmax user processes (-u) 7168virtual memory (kbytes, -v) unlimitedcore-dump $ ulimit -c 1024 $ ulimit c unlimited(使用-c unlimited不限制core文件大小) core文件命名 core.$PID 例如: core.234、core.9618等core-dumpcore-dump:Example$ more foo.c#include static void sub(void);int main(void) sub(); return 0;s
22、tatic void sub(void) int *p = NULL; /* derefernce a null pointer, expect core dump. */ printf(%d, *p);$ gcc -Wall -g foo.c$ ./a.outSegmentation fault (core dumped)$ ls -l core.*-rw- 1 uniware uniware 53248 Jun 30 17:10 core.9128注意看上述的输出信息,多了个(core dumped)。确实产生了一个core文件,9128是该进程的PID。我们用GDB来看看这个core。c
23、ore-dump:Example$ gdb -core=core.9128GNU gdb Asianux (6.0post-0.20040223.17.1AX)Copyright 2004 Free Software Foundation, Inc.GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions.Type show copying to
24、see the conditions.There is absolutely no warranty for GDB. Type show warranty for details.This GDB was configured as i386-asianux-linux-gnu.Core was generated by ./a.out.Program terminated with signal 11, Segmentation fault.#0 0 x08048373 in ? ()(gdb) bt#0 0 x08048373 in ? ()#1 0 xbfffd8f8 in ? ()#
25、2 0 x0804839e in ? ()#3 0 xb74cc6b3 in ? ()#4 0 x00000000 in ? ()此时用bt看不到backtrace,也就是调用堆栈,原来GDB还不知道符号信息在哪里。我们告诉它一下:(gdb) file ./a.outReading symbols from ./a.out.done.Using host libthread_db library /lib/tls/libthread_db.so.1.(gdb) bt#0 0 x08048373 in sub () at foo.c:17#1 0 x08048359 in main () at foo.c:8此时backtrace出来了。The End
侵权处理QQ:3464097650--上传资料QQ:3464097650
【声明】本站为“文档C2C交易模式”,即用户上传的文档直接卖给(下载)用户,本站只是网络空间服务平台,本站所有原创文档下载所得归上传人所有,如您发现上传作品侵犯了您的版权,请立刻联系我们并提供证据,我们将在3个工作日内予以改正。