序言
近来更新了几篇关于编译器、性能优化等相对比较底层的文章,有童鞋发私信给我,希望能写几篇介绍程序调试方法的文章。
想了下,就从C语言的宏定义入手吧。这个是调试C语言程序时常常会碰到的问题,在我刚入门时也以前疑惑过我。
关于C语言的宏
C语言,便于入门,无法精通。这样说,我想应当不会有人反对吧?
它的易,在于其精简的句型,简单的数据类型。它的难,在于它的灵活。
说起它的难,你们第一反应肯定是表针。作为C语言的真谛,表针的存在,致使C语言足够灵活,异常强悍。但同时,它也促使C语言显得无法掌控,程序显得无法理解。
这么,不仅表针呢?还有宏。
C语言中,宏是和表针一样强悍的存在。毫不夸张的说,通过C语言的宏定义,甚至可以发展出一门全新的编程语言。
恰当的使用宏linux 内核调试,才能促使代码愈发简练精炼,易读,能够提升程序的运行效率。真正的C语言前辈,都十分擅于用宏来实现一些中级的编程方法。
Linux内核就是一个典型的案例。作为全球顶级黑客大牛们通力合作的产物,Linux内核把C语言的宏发挥到了极至,各类精妙、有趣的宏方法在Linux内核代码中随处可见。
然而,和表针一样,宏也是一把双刃剑。大量的运用宏,尤其是一些中级、精妙的方法,会促使代码显得无法理解,对于理解代码的实现细节和调试带来不小的难度。对于这点,我想研究过Linux内核代码的童鞋都深有感悟。
GDB调试时遇见宏如何办?
我们晓得linux设置环境变量,在C语言程序预编译阶段,所有的宏定义就会被展开在C源码文件中引用宏的地方。因而,通常编译之后的目标文件中,不存在关于宏定义的任何信息。如下边这段代码:
先编译一下:
gcc -g test.c -o test
之后用GDB调试:
虽然我们编译时加了“-g”选项,但当我们在GDB中查看MONDAY的值和使用MAX宏时,依然会提示当前上行文中找不到这两个符号。
如何解决呢?虽然GCC早已给我们提供了解决方案。
GCC的调试选项-g
我们晓得,要想用GDB进行调试,必须在用GCC编译时加上“-g”选项。但好多童鞋可能不晓得的是,和优化选项“-Ox”一样,调试选项“-g”也有几个等级可选:
可见,我们编译时加的“-g”选项,虽然等同于“-g2”,它形成了足够多的调试信息中文linux操作系统,我们可以用gdb查看调用栈、查看局部变量等。并且,要想查看宏定义,则必需要使用“-g3”选项。
使用-g3调试选项
重新编译一下:
gcc -g3 test.c -o test
用gdb重新调试:
用“-g3”选项重新编译以后,就可以在gdb中查看宏定义了。
原本,到这儿就该结束了,而且为了满足好奇心linux 内核调试,我们看一下“-g2”和“-g3”对目标文件形成了哪些影响。
“-g2”和“-g3”对目标文件的影响
我们先看一下“-g2”,重新编译一下,之后用readelf命令查看一下目标文件中的节区颈部(sectionheader)表:
gcc -g2 test.c -o test
readelf -S test
主要看一下和调试信息相关的节区:
-g2目标文件调试信息
GCC默认使用的调试信息储存格式称作DWARF,gdb就是基于这个格式的规范,实现的源码级调试。
稍为解释一下:
之后,再用“-g3”选项重新编译一下:
gcc -g3 test.c -o test
readelf -S test
再看下节区表:
-g3目标文件调试信息
跟“-g2”相比,“-g3”多了一个.debug_macro,这个节区就是主要拿来储存宏定义的信息。
我们用下边这个命令来查看一下这个节区上面都储存了什么信息:
readelf --debug-dump test
输出信息比较多,和“.debug_macro”有关的如右图所示:
测试程序中定义的两个宏MONDAY和MAX的信息都包含在这个节区中,包括文件信息、行号信息、以及宏定义具体内容等。
结语
作为程序员,我们每天都在和各类Bug斗智斗勇,程序调试和问题定位的能力,对我们来说是至关重要的一项基本技能。
工欲善其事,必先利其器。
接出来,我会更新一系列文章,深入讲解程序调试和问题定位的方法,以及其背后的实现原理。主要内容包括:
感兴趣的童鞋欢迎欢迎右上角关注!
假如认为有用的话,别忘了点赞!把知识分享给更多志同道合的人!感谢!
也欢迎围观我另外一个正在更新中的系列专题文章:
你真的理解"Helloworld"吗?从编译链接到OS内核系列专题(已更新三篇)
以及其他几篇关于性能优化的文章。