提问人:jyelon 提问时间:2/18/2022 最后编辑:jyelon 更新时间:11/11/2023 访问量:159
如何摆脱 C++ 中的非确定性。尝试ADDR_NO_RANDOMIZE,其他事情
How to get rid of nondeterminism in C++. Tried ADDR_NO_RANDOMIZE, other things
问:
我有一个不确定的 C++ 程序。我使用输入文件运行它,它运行正常。我第二次使用相同的输入文件运行它,它崩溃了。我想摆脱非确定性,以使崩溃可重现。
我扔了一些 print-statements 来打印某些数据结构的地址。每次执行时,数据结构都位于不同的地址(在 Linux 下)。
malloc 返回不可预测地址的一个明显原因是 ASLR。我把它关掉了。我可以验证它是否关闭 - 共享库始终在相同的地址加载,堆栈始终在同一地址,依此类推。但即使关闭了 ASLR,malloc 仍然不可重现 - 它会在不同的连续运行中返回不同的地址。
我正在绞尽脑汁寻找非确定论的可能来源:
我已经使用“strace”运行了该程序。我可以“比较”两次连续执行的跟踪,除了打印我的数据结构地址(位于不同地址)的 print-statements 之外,没有其他差异。
据我所知,它没有使用线程,除非glibc或C++在幕后使用线程。我确实注意到 ptmalloc 使用了__thread变量......这是否相关?
除默认信号处理程序外,没有其他信号处理程序。我不是故意使用信号。
从理论上讲,glibc 中的某些东西可能会获得一个 CPU 性能计数器,并将其用作非确定性的来源。但我对此持怀疑态度。
有谁知道 malloc 在连续执行时返回不同地址的原因是什么?
更新:
这是我发现的最小的程序,即使关闭了 ASLR,它也表现出非确定性:
int main(int argc, char **argv) {
// Turn off ASLR address space layout randomization.
const int old_personality = personality(ADDR_NO_RANDOMIZE);
if (!(old_personality & ADDR_NO_RANDOMIZE)) {
const int new_personality = personality(ADDR_NO_RANDOMIZE);
if (new_personality & ADDR_NO_RANDOMIZE) {
execv(argv[0], argv);
}
}
// Create a lua engine, then free it.
lua_State *L = luaL_newstate();
lua_close(L);
// Allocate a big block of RAM.
malloc(4*1024*1024);
// Now print the hash of some mallocs.
int hash = 0;
for (int i = 0; i < 100; i++) {
int n = (int)(ptrdiff_t)malloc(1);
hash = (hash * 17) + n;
}
fprintf(stderr, "%08x\n", hash);
}
下面是三次运行的输出:
$ ./foo
c75ba620
$ ./foo
0e2e5210
$ ./foo
7c38ba10
我不知道为什么 lua 分配是相关的,但如果没有 luaL_newstate 和 lua_close,它就无法做到这一点。如果没有中间的 4 兆字节 malloc,它也无法做到这一点。
更新2:
我找到了非决定论的根源。lua 库调用 time(0) 来获取当前时间,然后将其用作随机种子,影响它所做的内存分配。花了这么长时间才发现这个问题的原因是“strace”没有向“time(0)”报告系统调用。我假设所有系统调用都是由 strace 报告的。
答:
您可能是内存泄漏问题的受害者。您的程序使用不是由您分配的内存。如果此内存地址位于您分配了某些内容的同一内存块中,则程序不会崩溃。但是,如果程序访问了块中的内存,您没有正确读取/写入,操作系统就会杀死您的程序 - 您会将其视为崩溃。
评论
有谁知道 malloc 在连续执行时返回不同地址的原因是什么?
假设该过程实际上是单线程的,并且没有块地址随机化完成,我猜:malloc
您的程序可能在某个时间点分配了随机数量的内存,例如,在计算要分配的某个块的大小时使用不可靠的垃圾值。所有后续分配返回的地址都可能受其影响(因此是随机的)。
当然,这将取决于分配的实现和实际大小:如果它作为每线程存储桶分配器实现,并且随机块大小不超过存储桶大小,那么对后续分配的影响几乎为零。malloc
评论
在这种情况下,非确定性来自 Lua 运行时内部。Lua 使用 'time(0)' 生成一个随机种子,然后影响 Lua 发出的 malloc 调用。
这种非确定性的来源对我隐藏的原因是 linux 'strace' 没有报告对 “time(0)” 的系统调用。我假设 strace 会向我显示任何在连续执行时返回不同值的系统调用。
评论
如果您使用的是 Linux,则需要做两件事:
- 按照您所做的方式进行设置。
ADDR_NO_RANDOMIZATION
- 使 malloc 实现具有确定性。例如,对于 dlmalloc,这与重新编译一样简单。
wasi-libc
就是这样做的。如果已定义,dlmalloc 将只使用固定的堆栈地址进行种子设定,而不是 time(0) 或 /dev/urandom: https://github.com/WebAssembly/wasi-libc/blob/main/dlmalloc/src/malloc.c#L3181#define LACKS_TIME_H 1
LACKS_TIME_H
评论