如何调试令人困惑的大代码

How to debug confusingly big code?

本文关键字:代码 何调试 调试      更新时间:2023-10-16

我不是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中删除文件,或者删除行,稍后使用源代码管理进行恢复)。

如果问题消失了,那么它很可能是由你刚刚删除的东西引起的。或者,如果问题仍然存在,那么它肯定存在于仍然存在的代码中。

递归地重复这个过程,直到找到问题所在。

这并不总是有效的,因为未定义的行为可能会在导致它的行之后的时间显现出来

但是,您可以努力生成一个仍然存在问题的最小程序。最终,您必须生成一个无法进一步减少的实际最小示例,或者揭示真正的原因。

如果您在使用gdbvalgrind后没有得到任何具体的东西,我认为您可以尝试使用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-nextreverse-step命令反向执行流。请注意,程序在这种模式下运行非常慢。

另一种可能的方法是在代码上尝试clang静态分析器或clang检查工具。有时分析器可以很好地提示代码中可能存在的问题

此外,您还可以将代码与jemalloc链接,并使用它的调试功能。选项"选择垃圾"、"选择隔离"、"选项valgrind"answers"选项redzone"可以使用。通常,它会让malloc分配一些额外的内存,用于监视缓冲区结束后的写入和读取、释放内存的读取等。请参阅手册页。此选项可通过mallctl功能启用。

找到bug的另一种方法是在启用gcc或clang的清理程序的情况下构建代码。您可以使用-fsanitize="消毒液"打开它们,其中"消毒液"可以是以下其中之一:addressthreadleakundefined。编译器将使用一些额外的代码来检测应用程序,这些代码将进行额外的检查并打印报告。例如:

#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的位置;在您刚刚找到的区域中,删除所有断点并添加更深一点的新断点,然后重复,直到您找到导致崩溃的例程。

我知道,这可能很乏味,但它是有效的,而且,在这样做的同时,它会让你更好地了解整个系统是如何工作的。我用这种方式修复了由数万行代码组成的未知应用程序中的错误,而且它总是有效的;也许这可能需要一整天的时间,但它是有效的。