如何调试令人困惑的大代码
How to debug confusingly big code?
我不是c++程序员,但尝试调试一些复杂的代码。不是最好的先决条件,我知道。。。
所以我有一个openfoam求解器,它使用(包括)大量代码,我很难真正找到错误。我用编译
SOURCE=mySolver.C;g++-m64-Dlinux64-DWM_DP-Wall-Wextra-Wno未使用的参数-Wold style cast-O3-DNoRepository-ftemplate-depth-100-I/opt/software/openfoam/openfoam-2.0.5/src/dynamicMesh/lnInclude{more linking}-I-fPIC-c$SOURCE-o Make/linux64Gcc46DPOpt/mySolver.o
在使用适当的选项运行求解器后,它在我的返回语句之后(或同时)崩溃
BEFORE return 0
*** glibc detected *** /opt/software/openfoam/myLibs/applications/bin/linux64Gcc46DPOpt/mySolver: double free or corruption (!prev): 0x000000000d3b7c30 ***
======= Backtrace: =========
/lib64/libc.so.6[0x31c307230f]
/lib64/libc.so.6(cfree+0x4b)[0x31c307276b]
/opt/software/openfoam/ThirdParty-2.0.5/platforms/linux64/gcc-4.5.3/lib64/libstdc++.so.6(_ZNSsD1Ev+0x39)[0x2b34781ffff9]
/opt/software/openfoam/myLibs/applications/bin/linux64Gcc46DPOpt/mySolver(_ZN4Foam6stringD1Ev+0x18)[0x441e2e]
/opt/software/openfoam/myLibs/applications/bin/linux64Gcc46DPOpt/mySolver(_ZN4Foam4wordD2Ev+0x18)[0x442216]
/lib64/libc.so.6(__cxa_finalize+0x8e)[0x31c303368e]
/opt/software/openfoam/myLibs/lib/linux64Gcc46DPOpt/libTMP.so[0x2b347a17f866]
======= Memory map: ========
...
我的求解器看起来像(对不起,我不能发布所有部分):
#include "stuff1.H"
#include "stuff2.H"
int main(int argc, char *argv[])
{
#include "stuff3.H"
#include "stuffn.H"
while (runTime.run())
{
...
}
Info<< "BEFORE return 0n" << endl;
return(0);
}
使用gdb运行解算器并设置set environment MALLOC_CHECK_ 2
,结果为:
BEFORE return 0
Program received signal SIGABRT, Aborted.
0x00000031c3030265 in raise () from /lib64/libc.so.6
(gdb) bt
#0 0x00000031c3030265 in raise () from /lib64/libc.so.6
#1 0x00000031c3031d10 in abort () from /lib64/libc.so.6
#2 0x00000031c3075ebc in free_check () from /lib64/libc.so.6
#3 0x00000031c30727f1 in free () from /lib64/libc.so.6
#4 0x00002aaab0496ff9 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() ()
from /opt/software/openfoam/ThirdParty-2.0.5/platforms/linux64/gcc-4.5.3/lib64/libstdc++.so.6
#5 0x0000000000441e2e in Foam::string::~string (this=0x2aaaac0bd3c8, __in_chrg=<value optimized out>) at /opt/software/openfoam/OpenFOAM-2.0.5/src/OpenFOAM/lnInclude/string.H:78
#6 0x0000000000442216 in Foam::word::~word (this=0x2aaaac0bd3c8, __in_chrg=<value optimized out>) at /opt/software/openfoam/OpenFOAM-2.0.5/src/OpenFOAM/lnInclude/word.H:63
#7 0x00000031c303368e in __cxa_finalize () from /lib64/libc.so.6
#8 0x00002aaab2416866 in __do_global_dtors_aux () from /opt/software/openfoam/myLibs/lib/linux64Gcc46DPOpt/libTMP.so
#9 0x0000000000000000 in ?? ()
(gdb)
我应该如何找到错误的真正来源?
Btw。我看到了这一点,这一点很相似,但并不能解决我的问题。此外,valgrind对我来说也不正确。我知道这与一些错误的分配有关,但我不知道如何真正找到问题所在。
/编辑
我还没能找到我的问题。。。
我认为我在上面发布的回溯(位置#8)表明问题出在编译为libTMP.so
的代码中。在Make/options文件中,我添加了选项-DFULLDEBUG-g-O0。我当时以为可以跟踪这个bug,但我不知道怎么做。
非常感谢您的帮助!
如果您已经处理了所有编译器警告和valgrind错误,但问题仍然存在,那么分而治之。
截取一半的代码(使用#if
指令,从Makefile中删除文件,或者删除行,稍后使用源代码管理进行恢复)。
如果问题消失了,那么它很可能是由你刚刚删除的东西引起的。或者,如果问题仍然存在,那么它肯定存在于仍然存在的代码中。
递归地重复这个过程,直到找到问题所在。
这并不总是有效的,因为未定义的行为可能会在导致它的行之后的时间显现出来
但是,您可以努力生成一个仍然存在问题的最小程序。最终,您必须生成一个无法进一步减少的实际最小示例,或者揭示真正的原因。
如果您在使用gdb
和valgrind
后没有得到任何具体的东西,我认为您可以尝试使用objdump
来分解您的库,正如您在回溯中看到的,它已经给了您错误的地址,我在调试问题时在项目中很久以前就尝试过这种方法。在反汇编之后,将错误地址与库中的语句地址进行匹配,这可能会让您了解错误位置。拆卸objdump -dR <library.so>
的命令
您可以在此处找到有关objdump
的更多信息
valgrind
好吧,我可能会因为一个单词的答案而被击落,但请耐心等待。试试valgrind。构建仍然存在问题的最调试版本:
valgrind路径/到/编程
第一个报告的问题很可能是你的问题来源。您甚至可以让valgrind启动gdb服务器,并让您附加调试导致第一个内存问题的代码。参见:
http://tromey.com/blog/?s=valgrind
其他一些尚未列出的选项有:
您可以尝试gdb执行流记录功能:
$ gdb target_executable
(gdb) b main
(gdb) run
(gdb) target record-full
(gdb) set record full insn-number-max unlimited
然后,当程序崩溃时,您将能够使用reverse-next
和reverse-step
命令反向执行流。请注意,程序在这种模式下运行非常慢。
另一种可能的方法是在代码上尝试clang静态分析器或clang检查工具。有时分析器可以很好地提示代码中可能存在的问题
此外,您还可以将代码与jemalloc链接,并使用它的调试功能。选项"选择垃圾"、"选择隔离"、"选项valgrind"answers"选项redzone"可以使用。通常,它会让malloc分配一些额外的内存,用于监视缓冲区结束后的写入和读取、释放内存的读取等。请参阅手册页。此选项可通过mallctl
功能启用。
找到bug的另一种方法是在启用gcc或clang的清理程序的情况下构建代码。您可以使用-fsanitize="消毒液"打开它们,其中"消毒液"可以是以下其中之一:address
、thread
、leak
、undefined
。编译器将使用一些额外的代码来检测应用程序,这些代码将进行额外的检查并打印报告。例如:
#include <vector>
#include <iostream>
int main() {
std::vector<int> vect;
vect.resize(5);
std::cout << vect[10] << std::endl; // access the element after the end of vector internal buffer
}
在打开消毒程序的情况下编译并运行:
$ clang++ -fsanitize=address test.cpp
$ ./a.out
给出输出:
==29920==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000dff8 at pc 0x0000004bad10 bp 0x7fff16d63e10 sp 0x7fff16d63e08
READ of size 4 at 0x60400000dff8 thread T0
#0 0x4bad0f in main (/home/pablo/a.out+0x4bad0f)
#1 0x7f0b6ce43fdf in __libc_start_main (/lib64/libc.so.6+0x1ffdf)
#2 0x4baaac in _start (/home/pablo/a.out+0x4baaac)
0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8)
allocated by thread T0 here:
#0 0x435b9b in operator new(unsigned long) (/home/pablo/a.out+0x435b9b)
#1 0x4c1f49 in __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (/home/pablo/a.out+0x4c1f49)
#2 0x4c1d05 in __gnu_cxx::__alloc_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (/home/pablo/a.out+0x4c1d05)
#3 0x4bfd51 in std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (/home/pablo/a.out+0x4bfd51)
#4 0x4bdb2a in std::vector<int, std::allocator<int> >::_M_fill_insert(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, unsigned long, int const&) (/home/pablo/a.out+0x4bdb2a)
#5 0x4bbe49 in std::vector<int, std::allocator<int> >::insert(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, unsigned long, int const&) (/home/pablo/a.out+0x4bbe49)
#6 0x4bb358 in std::vector<int, std::allocator<int> >::resize(unsigned long, int) (/home/pablo/a.out+0x4bb358)
#7 0x4bacaa in main (/home/pablo/a.out+0x4bacaa)
#8 0x7f0b6ce43fdf in __libc_start_main (/lib64/libc.so.6+0x1ffdf)
我部分同意Matt的观点:分而治之。但我部分同意,因为我部分不同意:修改你试图调试的代码可能会导致你走上错误的轨道,如果你试图用你不掌握的语言调试一个庞大而复杂的代码,那就更糟了。
相反,遵循divide-et-impera方法,再加上自上而下的策略:首先在更高级别的代码中添加一些断点,比如说在main中,然后启动程序,看看哪些断点会被击中,哪些在崩溃之前没有被击中。现在您已经大致了解了bug的位置;在您刚刚找到的区域中,删除所有断点并添加更深一点的新断点,然后重复,直到您找到导致崩溃的例程。
我知道,这可能很乏味,但它是有效的,而且,在这样做的同时,它会让你更好地了解整个系统是如何工作的。我用这种方式修复了由数万行代码组成的未知应用程序中的错误,而且它总是有效的;也许这可能需要一整天的时间,但它是有效的。
- 为什么使用__LINE_的代码在发布模式下在MSVC下编译,而不是在调试模式下
- 如何在大型c++项目的可视化代码中设置调试
- 为什么我的 VS 代码调试器在我的C++代码周围弹跳?
- 如何在 WSL 上获取 VS 代码以调试我的C++代码?
- 如何在visual studio代码的调试中运行c++脚本
- 使用无GDB的VS代码进行调试GNU
- VS 代码忽略 C++ 调试中的断点
- 视觉工作室代码远程调试C 类似Netbeans的方式
- Vscode C++调试使用 cl.exe 和 / 链接构建的代码.exe - 调试器不附加
- 该代码在调试模式下工作,但在发布模式C 中不起作用
- 将 GDB 或 LLDB 嵌入程序C++并从代码启动调试器
- 代码块调试预处理器
- 代码::块调试:不要单步执行头文件
- 如何在性能和VMS上对C++进行代码审查/调试/编码/测试/版本控制
- 链表程序挂在代码块调试器下,但在其他情况下正常执行
- 我无法在代码中调试::块
- C++ - 代码在调试中编译,但不在发布中编译
- 为什么派生程序的退出代码在调试时会发生更改
- 代码:块调试模式:我的代码崩溃,如果构建和运行,但如果调试/继续
- 在代码中调试我的C++项目时"Program received signal SIGSEGV, Segmentation fault. In ?? () ()":块