蚊章汇 - 调试
type: channel
link: https://linuxcn.bwsl.wang/tag/%E8%B0%83%E8%AF%95/
items: GDB 调试器如何通过调用帧信息来确定函数调用关系
items: 以调试 Rust 的方式来学习 Rust
items: GDB 的 7 个单步调试命令
items: 提高调试能力的一些方法
items: 从命令行使用 wget 调试网页错误
items: 学习使用 GDB 调试代码
items: 在 Linux 上创建并调试转储文件
items: 使用微软的 ProcDump 调试 Linux
items: 使用 Delve 代替 Println 来调试 Go 程序
items: 5 个鲜为人知 GNU 调试器（GDB）技巧

GDB 调试器如何通过调用帧信息来确定函数调用关系
type: item
date: 2023-07-26T14:26:18+08:00
description: 从调试器中获取函数调用关系。在我的 上一篇文章 中，我展示了如何使用 debuginfo 在当前指令指针（IP）和包含它的函数或行之间进行映射。该信息对于显示 CPU 当前正在执行的代码很有帮助。不过，如果能显示更多的有关当前函数调用栈及其正在执行语句的上下文对我们定位问题来说也是十分有助的。例如，将空指针作为参数传递到函数中而导致非法内存访问的问题，只需查看当前执行函数行，即可发现该错误是由尝试通过空指针进行访问而触发的。但是，你真正想知道的是导致空指针访问的函数调用的完整上下文，以便确定该空指针最初是如何传递到该函数中的。此上下文信息由回溯提供，可以让你确定哪些函数可能对空指针参数负责。有一点是肯定的：确定当前活动的函数调用栈不是一项简单的操作。函数激活记录现代编程语言具有局部变量，并允许函数可以调用自身的递归。此外，并发程序具有多个线程，这些线程可能同时运行相同的函数。在这些情况下，局部变量不能存储在全局位置。对于函数的每次调用，局部变量的位置必须是唯一的。它的工作原理如下：每次调用函数时，编译器都会生成函数激活记录，以将局部变量存储在唯一位置。为了提高效率，处理器堆栈用于存储函数激活记录。当函数被调用时，会在处理器堆栈的顶部为该函数创建一条新的函数激活记录。如果该函数调用另一个函数，则新的函数激活记录将放置在现有函数激活记录之上。每次函数返回时，其函数激活记录都会从堆栈中删除。函数激活记录的创建是由函数中称为“ 序言 prologue ”的代码创建的。函数激活记录的删除由函数“ 尾声 epilogue ”处理。函数体可以利用堆栈上为其预留的内存来存储临时值和局部变量。函数激活记录的大小可以是可变的。对于某些函数，不需要空间来存储局部变量。理想情况下，函数激活记录只需要存储调用 该 函数的函数的返回地址。对于其他函数，除了返回地址之外，可能还需要大量空间来存储函数的本地数据结构。帧大小的可变导致编译器使用帧指针来跟踪函数激活帧的开始。函数序言代码具有在为当前函数创建新帧指针之前存储旧帧指针的额外任务，并且函数尾声必须恢复旧帧指针值。函数激活记录的布局方式、调用函数的返回地址和旧帧指针是相对于当前帧指针的恒定偏移量。通过旧的帧指针，可以定位堆栈上下一个函数的激活帧。重复此过程，直到检查完所有函数激活记录为止。优化复杂性在代码中使用显式帧指针有几个缺点。在某些处理器上，可用的寄存器相对较少。具有显式帧指针会导致使用更多内存操作。生成的代码速度较慢，因为帧指针必须位于寄存器中。具有显式帧指针可能会限制编译器可以生成的代码，因为编译器可能不会将函数序言和尾声代码与函数体混合。编译器的目标是尽可能生成快速代码，因此编译器通常会从生成的代码中省略帧指针。正如 Phoronix 的基准测试 所示，保留帧指针会显着降低性能。不过省略帧指针也有缺点，查找前一个调用函数的激活帧和返回地址不再是相对于帧指针的简单偏移。调用帧信息为了帮助生成函数回溯，编译器包含 DWARF 调用帧信息（CFI）来重建帧指针并查找返回地址。此补充信息存储在执行的 .eh_frame 部分中。与传统的函数和行位置信息的 debuginfo 不同，即使生成的可执行文件没有调试信息，或者调试信息已从文件中删除，.eh_frame 部分也位于可执行文件中。 调用帧信息对于 C++ 中的 throw-catch 等语言结构的操作至关重要。CFI 的每个功能都有一个帧描述条目（FDE）。作为其步骤之一，回溯生成过程为当前正在检查的激活帧找到适当的 FDE。将 FDE 视为一张表，每一行代表一个或多个指令，并具有以下列：规范帧地址（CFA），帧指针指向的位置返回地址有关其他寄存器的信息FDE 的编码旨在最大限度地减少所需的空间量。FDE 描述了行之间的变化，而不是完全指定每一行。为了进一步压缩数据，多个 FDE 共有的起始信息被分解出来并放置在通用信息条目（CIE）中。 这使得 FDE 更加紧凑，但也需要更多的工作来计算实际的 CFA 并找到返回地址位置。该工具必须从未初始化状态启动。它逐步遍历 CIE 中的条目以获取函数条目的初始状态，然后从 FDE 的第一个条目开始继续处理 FDE，并处理操作，直到到达覆盖当前正在分析的指令指针的行。调用帧信息使用实例从一个简单的示例开始，其中包含将华氏温度转换为摄氏度的函数。 内联函数在 CFI 中没有条目，因此 f2c 函数的 __attribute__((noinline)) 确保编译器将 f2c 保留为真实函数。#include <stdio.h> int __attribute__ ((noinline)) f2c(int f) { int c; printf("converting\n"); c = (f-32.0) * 5.…

以调试 Rust 的方式来学习 Rust
type: item
date: 2022-12-30T18:34:00+08:00
description: Rustlings 是由 Rust 团队维护的开源项目，旨在帮助你通过调试代码的方式来学习 Rust。在我上一篇 关于 Rustup 的文章 中，我向你们展示了如何安装 Rust 工具链。但是，如果不能上手操作一下 Rust 的话下载工具链又有什么用？学习任何语言都包括阅读现有的代码和写很多的示例程序，这是精通一门语言的好方法。然而，我们还可以走第三条路：调试代码。通过调试来学习牵扯到尝试去编译一个已经写好的（满是漏洞的）示例程序，理解编译器生成的错误信息，修复示例代码，然后再重新编译。重复这个过程直到代码能够成功被编译并运行。Rustlings 是一个由 Rust 团队维护的开源项目，旨在帮助你通过调试代码来学习 Rust。它也会一路为你提供提示。如果你是一名 Rust 初学者，并且刚开始阅读或已经读完了 Rust 书籍，那么 Rustlings 就是理想的下一步。Rustllings 帮助你将运用书中所学，并转向开发更大的项目。安装 Rustlings我使用（并推荐）Fedora 电脑来体验 Rustlings，但是任何 Linux 发行版都可以。要安装 Rustlings，你必须下载并运行它的安装脚本。通常建议你以不具备任何特别权限的普通用户（非 root 用户）来运行脚本。记住，你需要 Rust 工具链来使用 Rustlings。如果你还没有这些工具链，请参考我 关于 Rustup 的文章。当你准备好时，下载这个安装脚本：$ curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh > rustlings_install.sh $ file rustlings_install.sh rustlings_install.sh: Bourne-Again shell script, ASCII text executable 阅读脚本以了解它会做什么：$ less rustlings_install.sh 然后运行安装：$ bash rustlings_install.sh [...] Installing /home/tux/.cargo/bin/rustlings Installed package `rustlings v4.8.0 (/home/tux/rustlings)` (executable `rustlings`) All done! 运行 rustlings 以开始。Rustlings 练习你现在可以使用命令 rustlings。与标志 --help 一起执行来查看可选的选项。$ rustlings --help 这个安装脚本也克隆了 Rustlings 的 Git 仓库，并安装了运行示例程序所需的依赖。你可以在 ruslings 下的 exercises 目录查阅这些示例程序。$ cd rustlings $ pwd /home/tux/rustlings $ ls AUTHORS.md Cargo.toml CONTRIBUTING.md info.toml install.sh README.md target Cargo.lock CHANGELOG.md exercises install.ps1 LICENSE src tests $ ls -m exercises/ advanced_errors, clippy, collections, conversions, enums, error_handling, functions, generics, if, intro, macros, mod.rs, modules, move_semantics, option, primitive_types, quiz1.rs, quiz2.rs, quiz3.rs, quiz4.rs, README.md, standard_library_types, strings, structs, tests, threads, traits, variables 从命令行列出所有练习命令 ruslings 提供给你一个 list 命令用以展示每个示例程序，它的完整路径，以及状态 （默认为 “待定”）。$ rustlings list Name Path Status intro1 exercises/intro/intro1.rs Pending intro2 exercises/intro/intro2.rs Pending variables1 exercises/variables/variables1.rs Pending variables2 exercises/variables/varia…

GDB 的 7 个单步调试命令
type: item
date: 2022-12-19T17:38:32+08:00
description: 即使是复杂的函数，也有几种方法可以单步调试，所以下次在排除代码故障时，可以尝试一下这些 GDB 技术。调试器 是一个可以运行你的代码并检查问题的软件。GNU Debugger（GBD）是最流行的调试器之一，在这篇文章中，我研究了 GDB 的 step 命令和其他几种常见情况的相关命令。step 是一个被广泛使用的命令，但它有一些人们不太了解的地方，可能会使得他们十分困惑。此外，还有一些方法可以在不使用 step 命令的情况下进入一个函数，比如使用不太知名的 advance 命令。1、无调试符号考虑以下这个简单的示例程序：#include <stdio.h> int num() { return 2; } void bar(int i) { printf("i = %d\n", i); } int main() { bar(num()); return 0; } 如果你在没有 调试符号 debugging sysbols 的情况下进行编译（LCTT 译注：即在使用 gcc 编译程序时没有写 -g 选项），然后在 bar 上设置一个断点，然后尝试在这个函数内使用 step 来单步执行语句。GDB 会给出一个 没有行号信息 no line number information 的错误信息。gcc exmp.c -o exmp gdb ./exmp (gdb) b bar Breakpoint 1 at 0x401135 (gdb) r Starting program: /home/ahajkova/exmp Breakpoint 1, 0x0000000000401135 in bar () (gdb) step Single stepping until exit from function bar, which has no line number information. i = 2 0x0000000000401168 in main () 2、stepi 命令但是你仍然可以在没有行号信息的函数内部单步执行语句，但要使用 stepi 命令来代替 step。stepi 一次只执行一条指令。当使用 GDB 的 stepi 命令时，先做 display/i $pc 通常很有用，这会在每一步之后显示 程序计数器 program counter 的值和相应的 机器指令 machine instruction ：(gdb) b bar Breakpoint 1 at 0x401135 (gdb) r Starting program: /home/ahajkova/exmp Breakpoint 1, 0x0000000000401135 in bar () (gdb) display/i $pc 1: x/i $pc => 0x401135 <bar+4>: sub $0x10,%rsp 在上述的 display 命令中，i 代表机器指令，$pc 表示程序计数器寄存器（即 PC 寄存器）。使用 info registers 命令，来打印寄存器的内容，也是十分有用的。(gdb) info registers rax 0x2 2 rbx 0x7fffffffdbc8 140737488346056 rcx 0x403e18 4210200 (gdb) print $rax $1 = 2 (gdb) stepi 0x0000000000401139 in bar () 1: x/i $pc => 0x401139 <bar+8>: mov %edi,-0x4(%rbp) 3、复杂的函数调用在带调试符号的 -g 选项，重新编译示例程序后，你可以使用行号在 main 中 bar 调用上设置断点，然后再单步执行 bar 函数的语句：gcc -g exmp.c -o exmp gdb ./exmp (gdb) b exmp.c:14 Breakpoint 1 at 0x401157: file exmp.c, line 14. (gdb) r Starting program: /home/ahajkova/exmp Breakpoint 1, main () at exmp.c:14 14 bar(num()); 接下来，用 step，来单步执行 bar() 函数的语句：(gdb) step num () at exmp.c:4 4 return 2; 函数调用的参数需要在实际的函数调用之前进行处理，bar() 函数的参数是 num() 函数，所以 num() 会在 bar() 被调用之前执行。但是，通过 GDB 调试，你怎么才能如愿以偿地进入 bar() 函数呢？你可以使用 finish 命令，并再次使用 step 命令。(gdb) fini…

提高调试能力的一些方法
type: item
date: 2022-09-02T06:28:56+08:00
description: 你们好！我一直在编写一本关于调试的杂志（这是 目录的初稿）。作为其中的一部分，我认为阅读一些关于调试的学术论文可能会很有趣，上周 Greg Wilson 给我发了一些关于调试学术研究的论文。其中一篇论文（《[建立一个调试教学的框架[付费墙]](https://dl.acm.org/doi/abs/10.1145/3286960.3286970)》）对我们有效调试所需的不同种类的知识/技能进行了分类，我非常喜欢。它来自另一篇关于故障排除的更一般性的论文：《学会排错：一个新的基于理论的设计架构》。我认为这个分类对于思考如何更好地进行调试是一个非常有用的结构，所以我把论文中的五个类别重新规划为你可以采取的行动，以提高调试的效率。以下是这些行动：1、学习代码库要调试一些代码，你需要了解你正在使用的代码库。这似乎有点显而易见（当然，不了解代码的工作原理，你就无法调试代码！）这种学习随着时间的推移会很自然地发生，而且实际上调试也是 学习 一个新的代码库如何工作的最好方法之一—— 看到一些代码是如何崩溃的，有助于你了解它是如何工作的。该论文将此称为“系统知识”。2、学习系统论文中提到，你需要了解编程语言，但我认为不止于此 —— 为了修复 bug，往往你需要学习很多更广泛的环境，而不仅仅是语言。举个例子，如果你是后端 Web 开发者，你可能需要的一些“系统”知识包括：HTTP 缓存如何工作CORS数据库事务是如何工作的我发现我经常需要更有意识地去学习像这样的系统性的东西 —— 我需要真正花时间去查找和阅读它们。该论文将此称为“领域知识”。3、学习你的工具现在有很多工具，例如：调试器（GDB 等）浏览器开发工具 剖析器 profiler strace / ltracetcpdump / wireshark核心转储甚至像错误信息这样的基本东西（如何正确阅读它们）我在这个博客上写了很多关于调试工具的文章，并且肯定学习这些工具给我带来了巨大的变化。该论文将此称为“处理性知识”。4、学习策略这是最模糊的一类，在如何高效调试的过程中，我们都有很多策略和启发式方法。比如说：写一个单元测试写一个小的独立程序来重现这个错误找到一个能工作的版本的代码，看看有什么变化打印出无数的东西增加额外的日志记录休息一下向朋友解释这个错误，然后在中途发现问题所在查看 GitHub 上的问题，看看是否有匹配的问题在写这本杂志的时候，我一直在思考这个类别，但我想让这篇文章简短，所以我不会在这里多说。该论文将此称为“战略知识”。5、获得经验最后一个类别是“经验”。这篇论文对此有一个非常有趣的评论：他们的研究结果并没有显示出新手和专家所采用的策略有什么明显的区别。专家只是形成了更多正确的假设，并且在寻找故障方面更有效率。作者怀疑这个结果是由于新手和专家之间的编程经验不同造成的。这真的引起了我的共鸣 —— 我遇到过很多第一次遇到时非常令人沮丧和困难的 bug，而在第五次、第十次或第二十次时就非常简单了。对我来说，这也是最直接的知识类别之一 —— 你需要做的就是调查一百万个 bug，反正这就是我们作为程序员的全部生活 : ) 。这需要很长的时间，但我觉得它发生得很自然。本文将此称为“经验知识”。就这样吧！我打算把这篇文章写得很短，我只是非常喜欢这个分类，想把它分享出来。via: https://jvns.ca/blog/2022/08/30/a-way-to-categorize-debugging-skills/作者：Julia Evans 选题：lkxed 译者：aftermath0703 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出

从命令行使用 wget 调试网页错误
type: item
date: 2021-09-18T06:50:23+08:00
description: 调试 Web 服务器的一种方法是使用 wget 命令行程序。有时在管理一个网站时，事情会被搞得一团糟。你可能会删除一些陈旧的内容，用重定向到其他页面来代替。后来，在做了其他改动后，你发现一些网页变得完全无法访问了。你可能会在浏览器中看到一个错误：“该页面没有正确重定向”，并建议你检查你的 cookie。调试这种情况的一个方法是使用 wget 命令行程序，使用 -S 选项来显示所有的服务器响应。当使用 wget 进行调试时，我也喜欢使用 -O 选项将输出保存到一些临时文件中，以备以后需要查看其内容。$ wget -O /tmp/test.html -S http://10.0.0.11/announce/ --2021-08-24 17:09:49-- http://10.0.0.11/announce/ Connecting to 10.0.0.11:80... connected. HTTP request sent, awaiting response... HTTP/1.1 302 Found Date: Tue, 24 Aug 2021 22:09:49 GMT Server: Apache/2.4.48 (Fedora) X-Powered-By: PHP/7.4.21 Location: http://10.0.0.11/assets/ Content-Length: 0 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 Location: http://10.0.0.11/assets/ [following] --2021-08-24 17:09:49-- http://10.0.0.11/assets/ Reusing existing connection to 10.0.0.11:80. HTTP request sent, awaiting response... HTTP/1.1 302 Found Date: Tue, 24 Aug 2021 22:09:49 GMT Server: Apache/2.4.48 (Fedora) X-Powered-By: PHP/7.4.21 Location: http://10.0.0.11/announce/ Content-Length: 0 Keep-Alive: timeout=5, max=99 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 Location: http://10.0.0.11/announce/ [following] --2021-08-24 17:09:49-- http://10.0.0.11/announce/ Reusing existing connection to 10.0.0.11:80. . . . 20 redirections exceeded. 我在这个输出中省略了很多重复的内容。通过阅读服务器的响应，你可以看到 http://10.0.0.11/announce/ 立即重定向到 http://10.0.0.11/assets/，然后又重定向到 http://10.0.0.11/announce/。以此类推。这是一个无休止的循环，wget 将在 20 次重定向后退出。但有了这些调试信息，你可以修复重定向，避免循环。via: https://opensource.com/article/21/9/wget-debug-web-server作者：Jim Hall 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出

学习使用 GDB 调试代码
type: item
date: 2021-03-15T05:06:00+08:00
description: 使用 GNU 调试器来解决你的代码问题。GNU 调试器常以它的命令 gdb 称呼它，它是一个交互式的控制台，可以帮助你浏览源代码、分析执行的内容，其本质上是对错误的应用程序中出现的问题进行逆向工程。故障排除的麻烦在于它很复杂。GNU 调试器 并不是一个特别复杂的应用程序，但如果你不知道从哪里开始，甚至不知道何时和为何你可能需要求助于 GDB 来进行故障排除，那么它可能会让人不知所措。如果你一直使用 print、echo 或 printf 语句来调试你的代码，当你开始思考是不是还有更强大的东西时，那么本教程就是为你准备的。有错误的代码要开始使用 GDB，你需要一些代码。这里有一个用 C++ 写的示例应用程序（如果你一般不使用 C++ 编写程序也没关系，在所有语言中原理都是一样的），其来源于 猜谜游戏系列 中的一个例子。#include <iostream> #include <stdlib.h> //srand #include <stdio.h> //printf using namespace std; int main () { srand (time(NULL)); int alpha = rand() % 8; cout << "Hello world." << endl; int beta = 2; printf("alpha is set to is %s\n", alpha); printf("kiwi is set to is %s\n", beta); return 0; } // main 这个代码示例中有一个 bug，但它确实可以编译（至少在 GCC 5 的时候）。如果你熟悉 C++，你可能已经看到了，但这是一个简单的问题，可以帮助新的 GDB 用户了解调试过程。编译并运行它就可以看到错误：$ g++ -o buggy example.cpp $ ./buggy Hello world. Segmentation fault 排除段故障从这个输出中，你可以推测变量 alpha 的设置是正确的，因为否则的话，你就不会看到它后面的那行代码执行。当然，这并不总是正确的，但这是一个很好的工作理论，如果你使用 printf 作为日志和调试器，基本上也会得出同样的结论。从这里，你可以假设 bug 在于成功打印的那一行之后的某行。然而，不清楚错误是在下一行还是在几行之后。GNU 调试器是一个交互式的故障排除工具，所以你可以使用 gdb 命令来运行错误的代码。为了得到更好的结果，你应该从包含有调试符号的源代码中重新编译你的错误应用程序。首先，看看 GDB 在不重新编译的情况下能提供哪些信息：$ gdb ./buggy Reading symbols from ./buggy...done. (gdb) start Temporary breakpoint 1 at 0x400a44 Starting program: /home/seth/demo/buggy Temporary breakpoint 1, 0x0000000000400a44 in main () (gdb) 当你以一个二进制可执行文件作为参数启动 GDB 时，GDB 会加载该应用程序，然后等待你的指令。因为这是你第一次在这个可执行文件上运行 GDB，所以尝试重复这个错误是有意义的，希望 GDB 能够提供进一步的见解。很直观，GDB 用来启动它所加载的应用程序的命令就是 start。默认情况下，GDB 内置了一个断点，所以当它遇到你的应用程序的 main 函数时，它会暂停执行。要让 GDB 继续执行，使用命令 continue：(gdb) continue Continuing. Hello world. Program received signal SIGSEGV, Segmentation fault. 0x00007ffff71c0c0b in vfprintf () from /lib64/libc.so.6 (gdb) 毫不意外：应用程序在打印 “Hello world” 后不久就崩溃了，但 GDB 可以提供崩溃发生时正在发生的函数调用。这有可能就足够你找到导致崩溃的 bug，但为了更好地了解 GDB 的功能和一般的调试过程，想象一下，如果问题还没有变得清晰，你想更深入地挖掘这段代码发生了什么。用调试符号编译代码要充分利用 GDB，你需要将调试符号编译到你的可执行文件中。你可以用 GCC 中的 -g 选项来生成这个符号：$ g++ -g -o debuggy example.cpp $ ./debuggy Hello world. Segmentation fault 将调试符号编译到可执行文件中的结果是得到一个大得多的文件，所以通常不会分…

在 Linux 上创建并调试转储文件
type: item
date: 2020-08-27T07:46:08+08:00
description: 了解如何处理转储文件将帮你找到应用中难以重现的 bug。崩溃转储、内存转储、核心转储、系统转储……这些全都会产生同样的产物：一个包含了当应用崩溃时，在那个特定时刻应用的内存状态的文件。这是一篇指导文章，你可以通过克隆示例的应用仓库来跟随学习：git clone https://github.com/hANSIc99/core_dump_example.git 信号如何关联到转储信号是操作系统和用户应用之间的进程间通讯。Linux 使用 POSIX 标准中定义的信号。在你的系统上，你可以在 /usr/include/bits/signum-generic.h 找到标准信号的定义。如果你想知道更多关于在你的应用程序中使用信号的信息，这有一个信息丰富的 signal 手册页。简单地说，Linux 基于预期的或意外的信号来触发进一步的活动。当你退出一个正在运行的应用程序时，应用程序通常会收到 SIGTERM 信号。因为这种类型的退出信号是预期的，所以这个操作不会创建一个内存转储。以下信号将导致创建一个转储文件（来源：GNU C库）:SIGFPE：错误的算术操作SIGILL：非法指令SIGSEGV：对存储的无效访问SIGBUS：总线错误SIGABRT：程序检测到的错误，并通过调用 abort() 来报告SIGIOT：这个信号在 Fedora 上已经过时，过去在 PDP-11 上用 abort() 时触发，现在映射到 SIGABRT创建转储文件导航到 core_dump_example 目录，运行 make，并使用 -c1 开关执行该示例二进制：./coredump -c1 该应用将以状态 4 退出，带有如下错误：“Abgebrochen (Speicherabzug geschrieben) ”（LCTT 译注：这是德语，应该是因为本文作者系统是德语环境）大致翻译为“分段故障（核心转储）”。是否创建核心转储是由运行该进程的用户的资源限制决定的。你可以用 ulimit 命令修改资源限制。检查当前创建核心转储的设置：ulimit -c 如果它输出 unlimited，那么它使用的是（建议的）默认值。否则，用以下方法纠正限制：ulimit -c unlimited 要禁用创建核心转储，可以设置其大小为 0：ulimit -c 0 这个数字指定了核心转储文件的大小，单位是块。什么是核心转储？内核处理核心转储的方式定义在：/proc/sys/kernel/core_pattern 我运行的是 Fedora 31，在我的系统上，该文件包含的内容是：/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h 这表明核心转储被转发到 systemd-coredump 工具。在不同的 Linux 发行版中，core_pattern 的内容会有很大的不同。当使用 systemd-coredump 时，转储文件被压缩保存在 /var/lib/systemd/coredump 下。你不需要直接接触这些文件，你可以使用 coredumpctl。比如说：coredumpctl list 会显示系统中保存的所有可用的转储文件。使用 coredumpctl dump，你可以从最后保存的转储文件中检索信息：[stephan@localhost core_dump_example]$ ./coredump Application started… (…….) Message: Process 4598 (coredump) of user 1000 dumped core. Stack trace of thread 4598: #0 0x00007f4bbaf22625 __GI_raise (libc.so.6) #1 0x00007f4bbaf0b8d9 __GI_abort (libc.so.6) #2 0x00007f4bbaf664af __libc_message (libc.so.6) #3 0x00007f4bbaf6da9c malloc_printerr (libc.so.6) #4 0x00007f4bbaf6f49c _int_free (libc.so.6) #5 0x000000000040120e n/a (/home/stephan/Dokumente/core_dump_example/coredump) #6 0x00000000004013b1 n/a (/home/stephan/Dokumente/core_dump_example/coredump) #7 0x00007f4bbaf0d1a3 __libc_start_main (libc.so.6) #8 0x0000000…

使用微软的 ProcDump 调试 Linux
type: item
date: 2020-08-20T17:57:30+08:00
description: 用这个微软的开源工具，获取进程信息。微软越来越心仪 Linux 和开源，这并不是什么秘密。在过去几年中，该公司稳步地增加了对开源的贡献，包括将其部分软件和工具移植到 Linux。2018 年底，微软宣布将其 Sysinternals 的部分工具以开源的方式移植到 Linux，Linux 版的 ProcDump是其中的第一个。如果你在 Windows 上从事过调试或故障排除工作，你可能听说过 Sysinternals，它是一个“瑞士军刀”工具集，可以帮助系统管理员、开发人员和 IT 安全专家监控和排除 Windows 环境的故障。Sysinternals 最受欢迎的工具之一是 ProcDump。顾名思义，它用于将正在运行的进程的内存转储到磁盘上的一个核心文件中。然后可以用调试器对这个核心文件进行分析，了解转储时进程的状态。因为之前用过 Sysinternals，所以我很想试试 ProcDump 的 Linux 移植版。开始使用 Linux 上的 ProcDump要试用 Linux 上的 ProcDump，你需要下载该工具并编译它。（我使用的是 Red Hat Enterprise Linux，尽管这些步骤在其他 Linux 发行版上应该是一样的）：$ cat /etc/redhat-release Red Hat Enterprise Linux release 8.2 (Ootpa) $ $ uname -r 4.18.0-193.el8.x86_64 $ 首先，克隆 Linux 版 ProcDump 的版本库。$ git clone https://github.com/microsoft/ProcDump-for-Linux.git Cloning into 'ProcDump-for-Linux'... remote: Enumerating objects: 40, done. remote: Counting objects: 100% (40/40), done. remote: Compressing objects: 100% (33/33), done. remote: Total 414 (delta 14), reused 14 (delta 6), pack-reused 374 Receiving objects: 100% (414/414), 335.28 KiB | 265.00 KiB/s, done. Resolving deltas: 100% (232/232), done. $ $ cd ProcDump-for-Linux/ $ $ ls azure-pipelines.yml CONTRIBUTING.md docs INSTALL.md Makefile procdump.gif src CODE_OF_CONDUCT.md dist include LICENSE procdump.1 README.md tests $ 接下来，使用 make 构建程序。它能准确地输出编译源文件所需的 GCC 命令行参数。$ make rm -rf obj rm -rf bin rm -rf /root/ProcDump-for-Linux/pkgbuild gcc -c -g -o obj/Logging.o src/Logging.c -Wall -I ./include -pthread -std=gnu99 gcc -c -g -o obj/Events.o src/Events.c -Wall -I ./include -pthread -std=gnu99 gcc -c -g -o obj/ProcDumpConfiguration.o src/ProcDumpConfiguration.c -Wall -I ./include -pthread -std=gnu99 gcc -c -g -o obj/Handle.o src/Handle.c -Wall -I ./include -pthread -std=gnu99 gcc -c -g -o obj/Process.o src/Process.c -Wall -I ./include -pthread -std=gnu99 gcc -c -g -o obj/Procdump.o src/Procdump.c -Wall -I ./include -pthread -std=gnu99 gcc -c -g -o obj/TriggerThreadProcs.o src/TriggerThreadProcs.c -Wall -I ./include -pthread -std=gnu99 gcc -c -g -o obj/Core…

使用 Delve 代替 Println 来调试 Go 程序
type: item
date: 2020-07-10T07:44:18+08:00
description: Delve 是能让调试变成轻而易举的事的万能工具包。你上次尝试去学习一种新的编程语言时什么时候？你有没有持之以恒，你是那些在新事物发布的第一时间就勇敢地去尝试的一员吗？不管怎样，学习一种新的语言也许非常有用，也会有很多乐趣。你尝试着写简单的 “Hello, world!”，然后写一些示例代码并执行，继续做一些小的修改，之后继续前进。我敢保证我们都有过这个经历，不论我们使用哪种技术。假如你尝试用一段时间一种语言，并且你希望能够精通它，那么有一些事物能在你的进取之路上帮助你。其中之一就是调试器。有些人喜欢在代码中用简单的 “print” 语句进行调试，这种方式很适合代码量少的简单程序；然而，如果你处理的是有多个开发者和几千行代码的大型项目，你应该使用调试器。最近我开始学习 Go 编程语言了，在本文中，我们将探讨一种名为 Delve 的调试器。Delve 是专门用来调试 Go 程序的工具，我们会借助一些 Go 示例代码来了解下它的一些功能。不要担心这里展示的 Go 示例代码；即使你之前没有写过 Go 代码也能看懂。Go 的目标之一是简单，因此代码是始终如一的，理解和解释起来都很容易。Delve 介绍Delve 是托管在 GitHub 上的一个开源项目。它自己的文档中写道：Delve 是 Go 编程语言的调试器。该项目的目标是为 Go 提供一个简单、全功能的调试工具。Delve 应该是易于调用和易于使用的。当你使用调试器时，事情可能不会按你的思路运行。如果你这样想，那么你不适合用 Delve。让我们来近距离看一下。我的测试系统是运行着 Fedora Linux 的笔记本电脑，Go 编译器版本如下：$ cat /etc/fedora-release Fedora release 30 (Thirty) $ $ go version go version go1.12.17 linux/amd64 $ Golang 安装如果你没有安装 Go，你可以运行下面的命令，很轻松地就可以从配置的仓库中获取。$ dnf install golang.x86_64 或者，你可以在安装页面找到适合你的操作系统的其他安装版本。在开始之前，请先确认已经设置好了 Go 工具依赖的下列各个路径。如果这些路径没有设置，有些示例可能不能正常运行。你可以在 SHELL 的 RC 文件中轻松设置这些环境变量，我的机器上是在 $HOME/bashrc 文件中设置的。$ go env | grep GOPATH GOPATH="/home/user/go" $ $ go env | grep GOBIN GOBIN="/home/user/go/gobin" $ Delve 安装你可以像下面那样，通过运行一个简单的 go get 命令来安装 Delve。go get 是 Golang 从外部源下载和安装需要的包的方式。如果你安装过程中遇到了问题，可以查看 Delve 安装教程。$ go get -u github.com/go-delve/delve/cmd/dlv $ 运行上面的命令，就会把 Delve 下载到你的 $GOPATH 的位置，如果你没有把 $GOPATH 设置成其他值，那么默认情况下 $GOPATH 和 $HOME/go 是同一个路径。你可以进入 go/ 目录，你可以在 bin/ 目录下看到 dlv。$ ls -l $HOME/go total 8 drwxrwxr-x. 2 user user 4096 May 25 19:11 bin drwxrwxr-x. 4 user user 4096 May 25 19:21 src $ $ ls -l ~/go/bin/ total 19596 -rwxrwxr-x. 1 user user 20062654 May 25 19:17 dlv $ 因为你把 Delve 安装到了 $GOPATH，所以你可以像运行普通的 shell 命令一样运行它，即每次运行时你不必先进入它所在的目录。你可以通过 version 选项来验证 dlv 是否正确安装。示例中安装的版本是 1.4.1。$ which dlv ~/go/bin/dlv $ $ dlv version Delve Debugger Version: 1.4.1 Build: $Id: bda606147ff48b58bde39e20b9e11378eaa4db46 $ $ 现在，我们一起在 Go 程序中使用 Delve 来理解下它的功能以及如何使用它们。我们先来写一个 hello.go，简单地打印一条 Hello, world! 信息。记着，我把这些示例程序放到了 $GOBIN 目录下。$ pwd /home/user/go/gobin $ $ cat he…

5 个鲜为人知 GNU 调试器（GDB）技巧
type: item
date: 2019-12-06T04:37:12+08:00
description: 了解如何使用 gdb 的一些鲜为人知的功能来检查和修复代码。GNU 调试器（gdb）是一种宝贵的工具，可用于在开发程序时检查正在运行的进程并解决问题。你可以在特定位置（按函数名称、行号等）设置断点、启用和禁用这些断点、显示和更改变量值，并执行所有调试器希望执行的所有标准操作。但是它还有许多其它你可能没有尝试过的功能。这里有五个你可以尝试一下。条件断点设置断点是学习使用 GNU 调试器的第一步。程序在达到断点时停止，你可以运行 gdb 的命令对其进行检查或更改变量，然后再允许该程序继续运行。例如，你可能知道一个经常调用的函数有时会崩溃，但仅当它获得某个参数值时才会崩溃。你可以在该函数的开始处设置一个断点并运行程序。每次碰到该断点时都会显示函数参数，并且如果未提供触发崩溃的参数值，则可以继续操作，直到再次调用该函数为止。当这个惹了麻烦的参数触发崩溃时，你可以单步执行代码以查看问题所在。(gdb) break sometimes_crashes Breakpoint 1 at 0x40110e: file prog.c, line 5. (gdb) run [...] Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5 5 fprintf(stderr, (gdb) continue Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5 5 fprintf(stderr, (gdb) continue为了使此方法更具可重复性，你可以在你感兴趣的特定调用之前计算该函数被调用的次数，并在该断点处设置一个计数器（例如，continue 30 以使其在接下来的 29 次到达该断点时忽略它）。但是断点真正强大的地方在于它们在运行时评估表达式的能力，这使你可以自动化这种测试。break [LOCATION] if CONDITION (gdb) break sometimes_crashes if !f Breakpoint 1 at 0x401132: file prog.c, line 5. (gdb) run [...] Breakpoint 1, sometimes_crashes (f=0x0) at prog.c:5 5 fprintf(stderr, (gdb)条件断点使你不必让 gdb 每次调用该函数时都去问你要做什么，而是让条件断点仅在特定表达式的值为 true 时才使 gdb 停止在该位置。如果执行到达条件断点的位置，但表达式的计算结果为 false，调试器会自动使程序继续运行，而无需询问用户该怎么做。断点命令GNU 调试器中断点的一个甚至更复杂的功能是能够编写对到达断点的响应的脚本。断点命令使你可以编写一系列 GNU 调试器命令，以在到达该断点时运行。我们可以使用它来规避在 sometimes_crashes 函数中我们已知的错误，并在它提供空指针时使其无害地从该函数返回。我们可以使用 silent 作为第一行，以更好地控制输出。否则，每次命中断点时，即使在运行断点命令之前，也会显示堆栈帧。(gdb) break sometimes_crashes Breakpoint 1 at 0x401132: file prog.c, line 5. (gdb) commands 1 Type commands for breakpoint(s) 1, one per line. End with a line saying just "end". >silent >if !f >frame >printf "Skipping call\n" >return 0 >continue >end >printf "Continuing\n" >continue >end (gdb) run Starting program: /home/twaugh/Documents/GDB/prog warning: Loadable section ".note.gnu.property" outside of ELF segments Continuing Continuing Continuing #0 sometimes_crashes (f=0x0) at prog.c:5 5 fprintf(stderr, Skipping call [Inferior 1 (process 9373) exited normally] (gdb)转储二进制内存GNU 调试器内置支持使用 x 命令以各种格式检查内存，包括八进制、十六进制等。但是我喜欢并排看到两种格式：左侧为十六进制字节，右侧为相同字节表示的 …