首页 > 编程语言 >读书笔记:高效C/C++调试

读书笔记:高效C/C++调试

时间:2024-09-03 09:29:20浏览次数:15  
标签:字节 读书笔记 C++ 6.3 内存 Yes 小结 调试

高效C/C++调试(美)严琦、卢宪廷

目 录

第1章 调试符号和调试器 1

1.1 调试符号 1

1.1.1 调试符号概览 2

  • 全局变量
  • 文件行号
  • 数据类型
    1.1.2 DWARF格式 3
    1.2 实战故事1:数据类型的不一致 14
    1.3 调试器的内部结构 16
    1.3.1 用户界面 16
    1.3.2 符号管理模块 16
    1.3.3 目标管理模块 17

1.4 技巧和注意事项 21

1.4.1 特殊的调试符号 21

库的符号可能被部分剥离,重新编译进符号表

1.4.2 改变执行及其副作用 24

set var gFlags = 5;

1.4.3 符号匹配的自动化 25
1.4.4 后期分析 26
1.4.5 内存保护 27

1.4.6 断点不工作 27

断点不工作场景:
(1)调试符号不匹配
(2)共享库未加载
(3)启用了优化
1.5 本章小结 28

第2章 堆数据结构 29

2.1 理解内存管理器 30

2.1.1 ptmalloc 31

边界标签和盒子

2.1.2 TCMalloc 34

google开发
2.1.3 多个堆 38
2.2 利用堆元数据 39
2.3 本章小结 42

第3章 内存损坏 43

3.1 内存是怎么损坏的 44

3.1.1 内存溢出与下溢 44

用户代码写入超过分盘内存块,会覆写下一个内存块的标签

3.1.2 访问释放的内存 45

用户代码持有指向已释放指针

3.1.3 使用未初始化的值 46

info symbol 0x002ab

3.2 调试内存损坏 47
3.2.1 初始调查 49
3.2.2 内存调试工具 53
3.2.3 堆与栈内存损坏对比 53
3.2.4 工具箱 54
3.3 实战故事2:神秘的字节序转换 55
3.3.1 症状 55
3.3.2 分析和调试 56
3.3.3 错误和有价值的点 64
3.4 实战故事3:覆写栈变量 65
3.4.1 症状 65
3.4.2 分析和调试 65
3.5 本章小结 68

第4章 C++对象布局 69

4.1 对齐和大小端 69

4.1.1 对齐 69

字节:Byte,字:word
4.1.2 大小端 70
4.2 C++对象布局 71
4.3 实战故事4:访问已经释放的数据 94
4.3.1 症状 94
4.3.2 分析和调试 94
4.4 搜索引用树 95
4.5 本章小结 101

第5章 优化后的二进制 102

5.1 调试版和发行版的区别 102
5.2 调试优化代码的挑战 106
5.3 汇编代码介绍 108
5.3.1 寄存器 109
5.3.2 指令集 111
5.3.3 程序汇编的结构 113
5.3.4 函数调用习惯 116
5.4 分析优化后的代码 127
5.5 调试优化后的代码示例 130
5.6 本章小结 141

第6章 进程镜像 142

进程映射:这个视图提供了从内核角度观察进程地址空间的视角。

# cat /proc/<pid>/maps
Address                   perm  offset   device inode   pathname
55bc80a11000-55bc80a32000 r--p  00000000 fd:00  1054763 /usr/sbin/nginx

说明:

  • Address: 显示内存区域的地址范围。在linux内核中,这也被称为虚拟内存区域(VMA)
  • perm: 展示权限位。其中,rwx分别代表读、写和执行权限。“-”表示该权限被禁止。这一列的最后一个字符要么是s要么是p,分别代表该内存区域是共享的还是私有的。
  • offset: 表示该内存区域关联的磁盘文件的偏移量。
  • device: 展示以marjor:minor格式表示的设备号。
  • inode: 给出设备的inode号。
  • pathname: 显示相关文件的路径。
    6.1 二进制文件格式 144
    6.2 运行期加载和链接 148

6.3 进程映射表 153

6.3.1 可执行文件 154
6.3.2 共享库 156
6.3.3 线程栈 157
6.3.4 无名区域 157
6.3.5 拦截 158
6.3.6 链接时替换 158
6.3.7 预先加载代理函数 159
6.3.8 修改导入和导出表 159
6.3.9 对目标函数进行手术改变 164
6.3.10 核心转储文件格式 166
6.3.11 核心转储文件分析工具 169
6.4 本章小结 170

第7章 调试多线程程序 171

7.1 竞争条件 171
7.2 它是竞争条件吗 172
7.3 调试竞争条件 174
7.4 实战故事5:记录重要区域 175
7.4.1 症状 175
7.4.2 分析调试 175
7.5 死锁 177
7.6 本章小结 179

第8章 更多调试方法 180

8.1 重现错误 180

8.1.1 归因 181
8.1.2 收集环境信息 182
8.1.3 重建环境 184

8.2 防止未来的bug 184

8.2.1 知识保留和传递 185
8.2.2 增强提前检查 185
8.2.3 编写更好调试的代码 185

8.3 不要忘记这些调试规则 189

8.3.1 分治法 189
8.3.2 退一步,获取新的观点 189
8.3.3 保留调试历史 190

8.4 逆向调试 190

reverse-step: 反向逐步执行

8.4.1 rr:Record and Replay 191

8.4.2 rr注意事项 191
8.5 本章小结 192

第9章 拓展调试器能力 193

9.1 使用Python拓展GDB 193
9.1.1 美化输出 194
9.1.2 编写自己的美观打印器 195
9.1.3 将重复的工作变成一个命令 197
9.1.4 更快地调试bug 198
9.1.5 使用Python设置断点 200
9.1.6 通过命令行来启动程序和设置断点 203
9.2 GDB自定义命令 203
9.3 本章小结 206

第10章 内存调试工具 207

内存调试工具底层算法分为3种类型:
(1)填充字节方法:最常用的是在每个内存块的开头和末尾添加额外的填充字节。有缺陷的代码可能会越过分配的内存块的界限,修改这些填充字节。调试工具在内存API的入口malloc, free检查这些 填充字节。如果发现填充字节被修改,就表示内存损坏。工具报告错误的上下文。
(2)系统保护页方法:工具在可能越界的内存块前后设置一个不可访问的系统保护页。程序非法访问时,系统通过硬件检测到。这种方法可以立即捕获无效的内存访问。但是频繁的设置系统保护页会造成内存和CPU开销大。
(3)动态二进制分析:valgrind可以运行任何现有程序而无须重新编译。在内部使用影子内存跟踪程序内存使用情况,每次内存访问都会更新影子内存。缺点:细粒度和软件模式检查性能下降。google address sanitizer通过编译器在生成的二进制文件中插入诊断代码,不需要二进制检测框架。
对于复杂问题,常见的方式是根据收集到的信息尝试在受控环境中重现问题。然而,如果问题的重现具有很强的时序相关性。或者每次运行时内存块的地址可能会改变。这时我们可以通过各种工具尽早地检测到内存。
有时错误可能因为分配算法改变而掩盖。但并不意味着修复。

不同内存调试工具的对比

调试特性 ptmalloc MALLOC_CHECK _ Asan AccuTrak Valgrind/Memcheck
实现原理 软件填充 软件影子内存 硬件和软件填充 软件影子内存
检测上溢出 Yes Yes Yes Yes
检测下溢出 No Yes Yes Yes
检测重复释放 Yes Yes Yes Yes
检测释放后使用 No Yes Yes Yes
检测使用未初始化内存 No No No Yes
粒度 字节 字节 字节
变慢程度
空间开销(每个用户块) 1-16字节 1字节 8字节 or 1系统页 与块大小相同
配置 No No Yes No
代码开源 Yes Yes Yes Yes
重新编译 No Yes No No

10.1 ptmalloc’s MALLOC_CHECK_ 208
10.2 Google Address Sanitizer 212
10.3 AccuTrak 213
10.4 有效地调试内存损坏 225
10.5 实战故事6:内存管理器的崩溃问题 228
10.5.1 症状 229
10.5.2 分析和调试 229
10.6 本章小结 235

第11章 Core Analyzer 236

解析进程的核心转储文件或内存映像
11.1 使用示例 237
11.2 主要功能 239
11.2.1 搜索引用的对象(水平搜索) 239
11.2.2 查询地址及其底层对象(垂直搜索) 240
11.2.3 内存模式分析 241
11.2.4 查询堆内存块 242
11.2.5 堆遍历(检查整个堆以发现损坏并获取内存使用统计) 242
11.3 本章小结 246

第12章 更多调试工具 247

12.1 strace 247

作用:程序和操作系统如何交流
12.1.1 常用功能 247
12.1.2 常用附加选项 248

12.2 实战故事7:僵尸进程 248

strace -o debug.txt -f -e trace=signal <program>

12.2.1 遇到难题 248
12.2.2 揭示bug的真相 249

12.3 Perf 249

作用:分析系统性能瓶颈
收集性能数据
(1)CPU周期
(2)指令计数
(3)缓存未命中
(4)分支预测错误
(5)内存访问

12.4 eBPF 250

作用:高度定制和细致分析
12.4.1 准备环境 251
12.4.2 编写代码 251
12.4.3 编译程序 252
12.4.4 加载和运行程序 254
12.5 实战故事8:链接问题 255
12.5.1 切入 255
12.5.2 更奇怪的事情 258
12.5.3 柳暗花明 259
12.5.4 补充 260
12.5.5 结论 261
12.6 实战故事9:临时变量的生命周期 261
12.7 本章小结 264

第13章 崩溃发送机制 265

崩溃报告
13.1 客户端 266
13.2 远程报告收集服务器 267
13.3 终端集成器 268
13.4 本章小结 268

第14章 内存泄漏 269

g++ -fsanitize=address

14.1 为什么RAII是基石 269
14.2 分析 270
14.3 调试内存泄漏 273
14.4 本章小结 275
第15章 协程 276
15.1 C++协程 277
15.2 协程的切分点 279
15.3 协程之诺 281
15.4 本章小结 283
第16章 远程调试 284
16.1 GDB远程调试 285
16.2 Visual Studio远程调试 286
16.3 本章小结 287
第17章 容器世界 288
17.1 容器示例 288
17.2 容器应用 289
17.3 C/C++容器调试 291
17.4 实战故事10:CrashLoopBackOff 292
17.5 实战故事11:liveness failure 292
17.6 本章小结 294

第18章 尽量不要调试程序 295

18.1 借助编译器来提前发现错误 295
18.2 编写简短的实验代码 295

18.3 日志和监控 296

18.3.1 日志 296

要素:

  • 日志级别
  • 时间戳
  • Json格式化,方便日志解析

18.3.2 监控 297

  • metrics
  • alerts
  • dashboards
    18.4 遵循最佳编码实践 297
    18.5 本章小结 298
    附录A 调试混合语言 299
    附录B 在Windows/x86环境下进行程序调试 301
    B.1 PE文件格式 301
    B.2 Windows Minidump格式 306
    附录C 一个简单的C++ coroutine程序 309

资料:
core analyzer https://cloud.tencent.com/developer/article/2408828
作者博客:高效C/C++调试 - CrackingOysters的文章 - 知乎
https://zhuanlan.zhihu.com/p/675726977

标签:字节,读书笔记,C++,6.3,内存,Yes,小结,调试
From: https://www.cnblogs.com/liqinglucky/p/18393912/debug

相关文章

  • C++(static_cast)
    目录1.语法2.示例3.为什么选择static_cast总结static_cast是C++中的一种类型转换运算符,用于在不同的数据类型之间进行安全转换。与C风格的强制类型转换不同,static_cast更加安全和明确。它主要用于进行类型转换时,确保转换是合法的,并且不会引入不必要的风险。1.语法......
  • A-计算机毕业设计定制:80891ssm大学校园慈善拍卖网站(免费领源码)可做计算机毕业设计JAV
    摘要信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题。针对大学校园慈善拍卖网站等问题,对大学校园慈善拍卖网站进行研究分析,然后开发设计出大学校园慈善......
  • 【Linux】Linux系统调试:如何选择strace和ltrace,全面对比
    在调试和诊断Linux程序时,strace和ltrace是两款常用的命令行工具。尽管它们都用于跟踪程序的行为,但它们的关注点和用途有所不同。本文将详细解析strace和ltrace的区别,帮助你选择适合的工具进行调试和诊断。......
  • C++STL之list容器:基本使用及模拟实现
    目录有了vector,为何还需listlist的使用1,push_back、push_front、pop_back、pop_front的使用2,正向、反向、const正向、const反向迭代器的使用正向、反向迭代器的使用const正向、const反向迭代器的使用3,operator=赋值4,insert、erase任意位置的插入、删除5,迭代器失效(......
  • C++内存管理
    感谢观看!!!文章目录一、C/C++内存分布二、C语言中动态内存管理方式三.C++中动态内存管理四.operatornew与operatordelete函数五.new和delete的实现原理六.定位new表达式(placement-new)七.常见面试题一.C/C++内存分布 我们先来看下面的一段代码和相关问题 ......
  • 【新】如何编写一个C++程序来整蛊你的好基友?
    【新版】如何编写一个C++程序来整蛊你的好基友呢?如何编写一个C++程序来整蛊你的好基友整蛊按照危险性来排序3星类1.一直输出,换行2.一直输出,不换行3.给控制台换一个颜色(较有威慑力)颜色代码4.扫盘(配上第三个效果更好,可以用来装B)4星类(含部分解药)弹窗类弹窗代码按下反......
  • 使用C++,仿照string类,实现myString
    类由结构体演化而来,只需要将struct改成关键字class,就定义了一个类C++中类和结构体的区别:默认的权限不同,结构体中默认权限为public,类中默认权限为private默认的继承方式不同,结构体的默认继承方式为public,类的默认继承方式为private//定义格式class类名{public:......
  • 使用C++手动封装一个顺序表,包含成员数组一个,成员变量N个
    实现顺序表的判空,判满,添加数据,求实际长度,任意位置的插入/删除,访问数组中的任意一个元素,以及让顺序表自动扩容。首先需要实现一个顺序表需要使用结构体构造其基本组成部分,以及基本函数接口,采用内部声明外部定义的方式。//使用C++手动封装一个顺序表,包含成员数组一个,成员变量N......
  • 使用C++编写程序,提示并输入一个字符串,统计其中的英文字符,数字,空格以及其他字符的数量
    由于c++兼容c语言的程序,所以子函数使用了c语言的内容#include<iostream>#include<string.h>usingnamespacestd;voidCount(constcharstr[]){intletter=0,num=0,space=0,etc=0;while(*str!='\0'){if((*str>='a'&&*......