在没有ASLR的情况下猎杀一只消失的(记忆,GC相关)黑森博格
hunting a (memory, GC related) heisenbug disappearing without ASLR
OS:Linux/Debian/Sid/x86_64(和Linux/Debian/Testing/x86_66);我用于编译的系统GCC是6.1.1(Debian/Testing是5.3)。Gnu-libc为2.22;Linux内核为4.5;GDB是7.10系统或我自己的系统,由FSF源代码7.11构建
我正在寻找(从近两周)一个记忆&垃圾收集GCC的MELT实验分支中的相关heisenbug
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有一个世代复制垃圾收集器(我怀疑这个bug是其中的一个角落),并使用了大量元编程(特别是,复制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(除非我将set disable-randomization 0
作为GDB命令执行)。
我的同事Franck Védrine建议我使用gdb
的反向执行功能;原则上,它应该像在我的GC中设置断点一样简单(以及在melt_fatal_error
宏调用的fatal_error
&melt_fatal_info
中…),达到GC#11
状态,做一个record
用于后续的向后执行,运行故障案例(使用set disable-randomization 0
禁用ASLR)直到"崩溃",然后reverse-cont
直到GC中的断点,并明智地使用watch
。遗憾的是,这触发了一个众所周知的GDB错误(Sourceware#19365,Ubuntu#1573786,Redhat#1136403,…)-最近的GDB快照(如gdb-7.11.50.20160514
)不正确-
(我现在很想避免GDB错误,也许是通过在#pragma GCC optimize ("-Og")
之前有我自己的memset
和memcpy
例程;但这看起来太过分了)
值得一提的是,崩溃消息由以下代码给出(在我的melt-runtime.h
的第900行附近):
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;
}
我的猜测是,该漏洞可能是一个围绕"判别式"(每个MELT值中的一种"类型"、"类"或"元数据"字段)转发的GC漏洞,在这种判别式仍在年轻一代的罕见情况下。。。添加一些代码来避免这种情况确实会使错误稍后发生,但我一点也不确定。
欢迎任何与实际虚拟地址相关的调试heisenbug的线索或建议(因此对ASLR来说是明智的!)
我甚至添加了一些初始化代码,以便能够可选地mmap
或sbrk
几个无用的兆字节,希望"重现"mmap
(由MELT及其GC使用的calloc
调用)给出的随机地址。这还没用!
我在Smalltalk垃圾收集器中使用的方法是在每个GC之前复制堆,并在副本中执行GC,然后在副本崩溃时重复进行调试。如果像我这样的系统是用高级oo语言开发的,那么这相对来说是微不足道的;复制堆只是复制包括VM模拟的对象的图(并且在模拟中堆在单个大字节数组中)。
在你的环境中应用这项技术可能会更具挑战性,但也不是不可能。让我在这里画一下。。。
我将把您尝试调试的进程称为"master",并将那些被克隆以尝试GC for it的进程称之为子进程。
在master中执行GC之前,执行fork并让子级执行GC,在子级中运行泄漏检查程序并退出,退出状态反映GC是否成功。如果子级成功,则主级继续执行其自己的GC。否则它会循环,生成重复失败GC的子级。然后调试子级。
孩子需要在两个州出生。每个GC中的初始启动只是运行GC并以成功状态退出。我们现在知道会失败的后续fork可以进入等待状态,这样您就可以将gdb附加到子级。
我称之为"旅鼠调试",因为在调试崩溃之前,可以让任意数量的克隆跳过悬崖。如果你能做到这一点,请告诉我。
- 松弛原子与无同步情况下的记忆连贯性
- Visual C++GC接口如何启用它以及要包含哪个库
- 递归函数有效,但无法记忆
- 如何将记忆应用于此递归函数?
- 共享记忆:让我们谈谈它的特殊性
- 为什么nlohmann不释放记忆
- CPP 中的瓦尔格林德和记忆泄漏:"Conditional jump or move depends on uninitialised values"
- C++17 多态记忆资源不起作用
- 我是否漏了记忆?
- 关于记忆后这种递归关系的时间复杂度
- 记忆栅栏和记忆屏障是一样的吗
- 使用记忆在 C++ 中实现 Knapstack
- 如何正确链接到CMake中的库(使用Boehm GC)?
- V8垃圾收集器回调,用于测量GC活动
- 无论如何可以将webm / mp4文件编译/记忆为.exe程序吗?(C++)
- 如何在硬币兑换中添加记忆
- 动态记忆的删除是如何真正起作用的
- C++遗传,记忆问题
- 使用未声明的标识符"nothrow";你是说"扔"吗?记忆
- 在没有ASLR的情况下猎杀一只消失的(记忆,GC相关)黑森博格