电子产业一站式赋能平台

PCB联盟网

搜索
查看: 47|回复: 0
收起左侧

新书上市 | 谈一谈程序崩溃与异常该如何快速定位

[复制链接]

864

主题

864

帖子

8156

积分

高级会员

Rank: 5Rank: 5

积分
8156
发表于 2024-2-2 08:30:00 | 显示全部楼层 |阅读模式
大家好,我是飞宇。
* d6 ?2 U- P; O1 O6 C前段时间清华大学出版社送了一本新书给我《高效C/C++调试》,翻看了几眼觉得很不错哦,今天给大家分享一下,文末也有8 g" a( J( c3 x2 q& u
,欢迎参加!! k& a2 O% o  F: z9 |2 V% Z
* L9 W) b; f7 a5 R. @/ A9 G: R0 k
作为开发人员,我们都知道C/C++程序中不可避免的挑战之一就是程序崩溃。当面对这样的问题时,核心转储(core dump)成为了我们不可或缺的调试工具。但是,如何高效地使用这些工具进行故障排除呢?
# z( b  b( n8 Q在我们的实际开发经验中,就遇到过一个棘手的问题,我们的服务器程序在测试阶段出现了随机崩溃。经过一段时间的调试,我们怀疑这可能是由于内存越界错误引起的,问题似乎出在一个特定的数据对象上。当程序尝试更新该对象的一个数据成员时,紧随其后的数据对象就会被损坏。8 \) Q1 |9 f" k8 L$ t" V+ n

) r! z; V% s3 l% C
& q# x9 c  L5 c5 e8 l' y8 y7 z然而,这段代码看似是无辜的,因为它只是在访问自己的数据成员。令人困惑的是,这个简单操作如何会损坏另一个数据对象。通过深入调查,我们发现这个问题对象是在一个模块中创建的,然后传递到另一个模块,在那里进行数据成员的更新。
! k, ?1 }0 t& c. J" W在一番探索后,我们发现两个模块对同一个数据对象的大小存在不一致的看法。调试器在第一个模块环境中显示一个尺寸,而在第二个模块中打印出另一个更大的尺寸。这令人大为惊讶,因为这个对象是在一个头文件中声明的,且这个头文件被两个项目共享。通过更深入地打印并比较每个模块内的对象布局以及其数据成员的偏移量(对象的类型调试符号),明显看出编译器对对象的布局有所不同:一个模块中的所有数据成员都适当地对齐,而另一个模块并没有,而是将所有的数据成员打包在一起。数据对象以较小的尺寸被打包创建。
. Q' [) g1 ~1 n$ i$ B5 _/ v+ x: [这种情况也得到了底层内存管理器分配的内存块大小的证实。当对象从打包对齐的模块被传入未打包布局的模块时,更新数据成员的操作就会覆盖内存并损坏附近的对象。图1-3以更简洁的方式描述了这个bug。一个T类型的结构体对象在模块A中被创建为打包格式,然后传到模块B,模块B却认为它是未打包的格式。模块B中灰色的数据成员data3覆盖了已分配的内存块。4 J3 x' d( o: ^
你可能很想知道这种情况是如何发生的。结果表明,尽管对象在头文件中被正确地声明,但bug却来自另一个头文件,其中使用了以下编译指令:
$ N' d% X. t, @#pragma pack(4)) J4 E& c5 }9 n7 L  o
...% K& U& \( f6 j2 y/ S& W7 h
#pragma pack()
! n- z7 J+ T& M/ u# U* `开发工程师打算将编译指令中间的结构体打包为4字节边界。这个指令被微软的Visual Studio编译器正确解析。然而,当同一份代码被AIX的Visual Age C++编译器编译时,问题就出现了。该编译器有一个类似但略有不同的编译指令语法来结束打包作用域:
0 ~# k8 K9 L! ?+ |4 ]7 V#pragma pack(4)1 @- Q' D. d5 }% Y& R- \8 x% n. h
...# r0 k0 c6 t" P3 x- \( j1 ?
#pragma pack(nopack)
3 V% d7 I8 s5 |$ y6 V由于这个语法差异,Visual Age C++编译器只识别了打包的开始编译指令(第一行),却忽略了结束打包的编译指令(最后一行)。在程序员试图结束数据打包的地方,编译器仍然在继续打包数据结构。在模块A中,我们的受害对象在引入包含上述编译指令的头文件之后声明。在模块B中,问题头文件没有被引入,所以对象没有被打包。这就是不一致性的产生原因。
8 @5 d0 ?( L7 t7 y数据类型的调试符号准确地反映了编译器如何解析数据类型,生成的机器指令也根据这个解析结果来操作数据对象。具体来说,当创建新对象时,编译器会申请与结构体大小相等的内存块;数据成员的访问地址是通过从内存块开头的偏移量来计算的。7 n8 e2 W9 Y! B3 m1 B
Core Dump:程序的“犯罪现场”在Linux、Windows或Mac系统中,当程序异常终止时,操作系统会生成一份内存快照,即core dump。它记录了程序崩溃时的详细状态,就像留下了犯罪现场的线索。
0 p# m- N) g  t, t高效调试的首步:日志与GDB一般情况下,我们首先检查程序日志。如果日志无法提供有效信息,那么就需要借助GNU调试器(GDB)来加载core dump,查找程序崩溃的具体位置。这对于新手程序员而言,通常是寻找bug的“终点”。
* {- _8 D# k# O( H迈向深度调试:Google Address Sanitizer (GAS)然而,C/C++程序中常见的内存破坏类错误并非总是能通过GDB轻易定位。这时,Google Address Sanitizer(GAS)成为了一个强大的工具。它像在程序中安装了监控摄像头,帮助我们捕捉到内存破坏的瞬间,极大地简化了调试过程。+ \* y3 u7 a/ H& Q9 U6 Y5 x  y: D
全面解析:Core Analyzer当GDB和GAS无法完全解决问题时,Core Analyzer这款开源工具展现了其独特优势。它能遍历整个内存,识别出内存破坏的具体位置,甚至能够追踪多线程共享变量的问题,为深入调试提供了强大的支持。/ y# g. {. ~$ i/ n6 y6 X6 i% W6 ?
在面对复杂和顽固的bug时,掌握高效的调试技巧至关重要。但关键在于,当常规方法无法解决问题时,我们应该如何行动呢?
: g  R6 Q- P/ C) C! I8 B这里推荐一本清华大学出版社的新书《高效C/C++调试》,书里深入探讨了C/C++软件调试的各种方法和技巧。作者Michael和卢宪廷凭借他们丰富的实战经验,不仅提供了对调试工具(GDB、Core Analyzer等等)的深入指南,还教导读者如何从宏观和微观视角审视问题,策略性地选择解决问题的最佳路径。这本书的核心在于,它不仅聚焦于C/C++,提出的多数策略具有广泛的通用性,适用于多种编程语言和平台。) v1 G/ W2 o+ X% r
本书的示例包含了大量的代码片段和实际案例。此外,本书还专门介绍了调试器插件和实用工具的开发。这些工具能够增强现有的调试器,拓宽我们的视野,要么提供新的角度审视问题,要么帮助我们更深入地研究问题。尽管本书主要探讨的是C/C++,但书中所介绍的方法和策略是通用的,独立于特定语言。
. A. g/ J7 F4 v1 G5 O3 [这是一套经过实战检验的、系统化的调试方法,旨在帮助具有编程背景的读者更高效地解决软件问题。无论您是编程新手还是经验丰富的开发者,《高效C/C++调试》都十分值得参考。* \+ x9 v- i& ]- n; ?, |

* T6 h: Y& S4 W% W# \: M6 M; h4 U# i* F3 q; w1 q
有打算入手的可以上手一本看看了,书籍质量和印刷质量都很不错。$ o$ {7 G0 F  ^$ N( R- p% m8 o

: \! M; l% K0 u3 x$ r# \读者福利本来打算采用留言点赞送书的方式,可飞宇听说貌似又一些刷赞团体专门组团刷赞来白嫖书籍的,所以为了让这些有价值的书被真心爱学习的小伙伴抽中,现在决定采用朋友圈点赞送书的形式。  y3 ?* o+ L# v2 |" B
稍后我会在自己的朋友圈发布一条动态,点赞即可,今晚六点开奖,如果你没有我的好友欢迎你扫描下方二维码添加我,我也会经常在朋友圈分享一些技术和个人感悟,欢迎围观。
' P1 U5 e* A0 `9 ^* ~- D& H* ~( }: A" |& V/ k  N: W) g! t
对了,本次一共包邮送出五本,下一个幸运儿就是你~
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


联系客服 关注微信 下载APP 返回顶部 返回列表