猎杀(记忆,与GC有关)heisenbug在没有ASLR的情况下消失

时间:2016-05-14 16:14:42

标签: c++ linux debugging heisenbug

OS:Linux / Debian / Sid / x86_64(和Linux / Debian / Testing / x86_64);我用于编译的系统GCC是6.1.1(和Debian / Testing的5.3)。 Gnu libc是2.22; Linux内核是4.5; GDB是系统7.10或我自己的系统,由FSF源构建,7.11

我正在打猎(近两周以来)一个记忆& GCC的garbage collection实验分支中heisenbug相关MELT (MELT严格来说是一种类似Lisp的域特定语言,用于自定义GCC编译器; MELT方言被翻译为使用MELT本身的C ++,可以用

检索
svn co -r236207 svn://gcc.gnu.org/svn/gcc/branches/melt-branch gcc-melt

然后(对于每个GCC变体或分支)在外部树中构建它,例如

mkdir _ObjMelt
cd _ObjMelt
../gcc-melt/configure  --disable-bootstrap --enable-checks=gc \
 --enable-plugins --disable-multilib --enable-languages=c,c++,lto

(如果您愿意,可以将其他选项传递给../gcc-melt/configure,例如CXXFLAGS='-g3 -O0 -DMELT_HAVE_RUNTIME_DEBUG=1';您可以删除--enable-checks=gc选项

当然还有make(或make -j4);这种构建可能需要半个多小时(并且可能会因ASLR而失败,见下文)

MELT有一个世代的复制垃圾收集器(我怀疑这个错误是其中的一个角落)并且使用了很多metaprogramming(特别是大多数扫描和复制GC的转发代码由MELT生成。

valgrind在这里没有帮助:我们实施复制GC,而GCC本身就是 - 即使没有MELT泄漏内存)

MELT是自助式的。通常的构建过程是从MELT源代码重新生成两次发出的C ++代码。通常的方法是发出一些C ++代码,派一些make来获取一个共享对象,然后再dlopen共享对象。

没有 ASLR,构建总是成功的(它正在运行一个重要的测试:MELT的引导程序,以及MELT扩展的编译对MELT运行时的分析) 。我甚至可以使用make upgrade-warmelt重新生成运行时代码。

但是启用了ASLR ,构建失败,崩溃总是以相同的方式(请注意cc1plus是MELT):

cc1plus: note: MELT got fatal failure from ../../gcc-melt/gcc/melt-runtime.h:900
cc1plus: fatal error: corrupted memory heap with null magic discriminant
                      in 0x2bab6a8; GC#11
compilation terminated.
MELT BUILD SCRIPT FAILURE: 
  melt-build-script.tpl:382/307-melt-build-script.tpl:459/382 failed 
  with arguments @meltbuild-stage2/warmelt-normatch.args

我正在禁用ASLR,例如与exec setarch $(uname -m) -R /bin/bash;当然,运行uder gdb默认情况下禁用ASLR(除非我{G}命令执行set disable-randomization 0。)

我的同事FranckVédrine建议我使用gdb的{​​{3}}设施;原则上,它应该像在我的GC中设置断点一样简单(以及fatal_error宏调用的melt_fatal_info& melt_fatal_error ...),到达{{1}状态,执行GC#11以执行后续执行,运行故障情况(使用record禁用ASLR)直到“崩溃”,然后set disable-randomization 0直到GC中的断点,并且明智地使用reverse-cont。遗憾的是,这会引发广泛已知的GDB错误reverse executionSourceware#19365Ubuntu#1573786,...) - 最近的GDB快照,例如{{1} }没有改正 -

(我现在很想尝试避免那个GDB错误,可能是在我们面前使用watch& gdb-7.11.50.20160514 memset例程;但这看起来很顺利太远了)

对于它的价值,崩溃消息由以下代码给出(在memcpy的第900行附近):

#pragma GCC optimize ("-Og")

我的猜测是,该错误可能是围绕转发“判别式”的一个困难的GC错误,(每个 MELT值中的某种“类型”或“类”或“元数据”字段)在极少数情况下,该判别式仍然存在于年轻一代中...添加一些代码以避免确实使得错误发生在以后,但我完全不确定。

欢迎调试与实际虚拟地址相关的heisenbug的任何线索或建议(因此对ASLR敏感!)。

我甚至添加了一些初始化代码,以便可选 melt-runtime.hstatic inline int melt_magic_discr (melt_ptr_t p) { if (!p) return 0; #if MELT_HAVE_DEBUG > 0 || MELT_HAVE_RUNTIME_DEBUG > 0 if (MELT_UNLIKELY(!p->u_discr)) { /* This should never happen, we are asking the discriminant of a not yet filled, since cleared, memory zone. */ melt_fatal_error ("corrupted memory heap with null discriminant in %p; GC#%ld", (void*) p, melt_nb_garbcoll); } #endif /*MELT_HAVE_DEBUG or MELT_HAVE_RUNTIME_DEBUG */ gcc_assert (p->u_discr != NULL); return p->u_discr->meltobj_magic; } 几个无用的兆字节,希望“重现”{{1}给出的随机地址}(由MELT及其GC使用的mmap调用)。那还没有帮助!

1 个答案:

答案 0 :(得分:2)

我在Smalltalk垃圾收集器中使用的方法是在每个GC之前复制堆并在副本中执行GC,然后在副本崩溃时重复调试。如果像我的系统是用高级语言开发的话,这是相对微不足道的;复制堆只是复制包含VM模拟的对象图(在模拟中,堆在一个大字节数组中)。

在您的环境中应用此技术可能会更具挑战性,但不应该是不可能的。让我在这里草绘一下......

我将调用您正在尝试调试“master”的进程以及那些克隆用于为子进程尝试GC的进程。

在主服务器中的GC之前,执行fork并让子服务器执行GC,在子服务器中运行泄漏检查程序并退出退出状态,以反映GC是否成功。如果孩子成功,主人然后继续自己的GC。否则它会循环,产生重复失败的GC的孩子。然后调试孩子。

孩子需要在两个州启动。每个GC的初始启动只需运行GC并退出并成功状态。我们现在知道的后续分叉将失败,可以进入等待状态,以便您可以将gdb附加到子代。

我称之为“旅行调试”,因为在调试崩溃之前,人们需要尽可能多的克隆跳过悬崖。如果你有这个工作,请告诉我。