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 execution,Sourceware#19365,Ubuntu#1573786,...) - 最近的GDB快照,例如{{1} }没有改正 -
(我现在很想尝试避免那个GDB错误,可能是在我们面前使用watch
& gdb-7.11.50.20160514
memset
例程;但这看起来很顺利太远了)
对于它的价值,崩溃消息由以下代码给出(在memcpy
的第900行附近):
#pragma GCC optimize ("-Og")
我的猜测是,该错误可能是围绕转发“判别式”的一个困难的GC错误,(每个 MELT值中的某种“类型”或“类”或“元数据”字段)在极少数情况下,该判别式仍然存在于年轻一代中...添加一些代码以避免确实使得错误发生在以后,但我完全不确定。
欢迎调试与实际虚拟地址相关的heisenbug的任何线索或建议(因此对ASLR敏感!)。
我甚至添加了一些初始化代码,以便可选 melt-runtime.h
或static 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
调用)。那还没有帮助!
答案 0 :(得分:2)
我在Smalltalk垃圾收集器中使用的方法是在每个GC之前复制堆并在副本中执行GC,然后在副本崩溃时重复调试。如果像我的系统是用高级语言开发的话,这是相对微不足道的;复制堆只是复制包含VM模拟的对象图(在模拟中,堆在一个大字节数组中)。
在您的环境中应用此技术可能会更具挑战性,但不应该是不可能的。让我在这里草绘一下......
我将调用您正在尝试调试“master”的进程以及那些克隆用于为子进程尝试GC的进程。
在主服务器中的GC之前,执行fork并让子服务器执行GC,在子服务器中运行泄漏检查程序并退出退出状态,以反映GC是否成功。如果孩子成功,主人然后继续自己的GC。否则它会循环,产生重复失败的GC的孩子。然后调试孩子。
孩子需要在两个州启动。每个GC的初始启动只需运行GC并退出并成功状态。我们现在知道的后续分叉将失败,可以进入等待状态,以便您可以将gdb附加到子代。
我称之为“旅行调试”,因为在调试崩溃之前,人们需要尽可能多的克隆跳过悬崖。如果你有这个工作,请告诉我。