【发布时间】:2017-03-05 21:19:30
【问题描述】:
我们在 GNU/Linux 下通过 dlsym() 从共享库中加载符号,显然会遇到某种竞争条件,从而导致分段错误。回溯看起来像这样:
(gdb) backtrace
#0 do_lookup_x at dl-lookup.c:366
#1 _dl_lookup_symbol_x at dl-lookup.c:829
#2 do_sym at dl-sym.c:168
#3 _dl_sym at dl-sym.c:273
#4 dlsym_doit at dlsym.c:50
#5 _dl_catch_error at dl-error.c:187
#6 _dlerror_run at dlerror.c:163
#7 __dlsym at dlsym.c:70
#8 ... (our code)
我的本地机器使用 glibc-2.23。
我发现,在第 7 帧中提供给 __dlsym() 的库句柄与传递给 _dlerror_run() 的句柄不同。它在dlsym.c 的以下几行中狂奔:
void *
__dlsym (void *handle, const char *name DL_CALLER_DECL)
{
# ifdef SHARED
if (__glibc_unlikely (_dlfcn_hook != NULL))
return _dlfcn_hook->dlsym (handle, name, DL_CALLER);
# endif
struct dlsym_args args;
args.who = DL_CALLER;
args.handle = handle; /* <------------------ this isn't my handle! */
args.name = name;
/* Protect against concurrent loads and unloads. */
__rtld_lock_lock_recursive (GL(dl_load_lock));
void *result = (_dlerror_run (dlsym_doit, &args) ? NULL : args.sym);
__rtld_lock_unlock_recursive (GL(dl_load_lock));
return result;
}
GDB 说
(gdb) frame 7
#7 __dlsym at dlsym.c:70
(gdb) p *(struct link_map *)args.handle
$36 = {l_addr= 140736951484536, l_name = 0x7fffe0000078 "\300\215\r\340\377\177", ...}
所以这显然是垃圾。同样的情况也发生在较高的帧中,例如在第 2 帧中:
(gdb) frame 2
#2 do_sym at dl-sym.c:168
(gdb) p handle
$38 = {l_addr= 140736951484536, l_name = 0x7fffe0000078 "\300\215\r\340\377\177", ...}
很遗憾,第 7 帧中的参数handle 无法显示:
(gdb) p handle
$37 = <optimized out>
但令人惊讶的是,在第 8 帧以及在我们代码的更下方,句柄是正确的:
(gdb) frame 8
#8 ...
(gdb) p *(struct link_map *)libHandle
$38 = {l_addr = 140737160646656, l_name = 0x7fffd8005b60 "/path/to/libfoo.so", ...}
现在我的结论是,变量args 必须在__dlsym() 内部执行期间修改,但我看不出在哪里以及为什么。
我不得不承认,这个问题还有第二个方面:它只发生在多线程环境中,而且只是偶尔发生。但是正如您所看到的,在__dlsym() 的实现中存在一些针对竞争条件的对策,因为它们正在调用__rtld_lock_(un)lock_recursive(),并且局部变量args 不是跨线程共享的。奇怪的是,如果我让第 8 帧在我的线程之间互斥,问题仍然存在。
问题:第 8 帧和第 7 帧之间库句柄差异的可能原因是什么?
问题 2:dlopen() 是否会为不同的线程产生不同的值?或者换一种说法:是否可以在不同线程之间共享dlopen()返回的句柄。
更新:我感谢大家对这个问题发表评论并试图回答它,尽管几乎没有任何可行的信息可以这样做。我找到了这个问题的解决方案。正如评论者所预见的,这与我提供的堆栈跟踪和其他信息完全无关。因此,我认为这个问题已经结束,并将其标记为删除。 久等了,感谢所有的鱼
【问题讨论】:
-
在优化的构建中,局部变量可能无法用于检查。在 valgrind 下运行您的应用程序。错误很可能出现在您未在此处显示的代码中,而不是在 glibc 中。
-
@Maxim:我几乎完全同意你的观点,但我还是觉得
__dlsym()中的args结构不包含正确的数据。我会给 valgrind 一个机会,但我怀疑我会陷入与我的问题无关的错误中。 -
也可以试试 gcc 和 clang 中的线程清理器。
-
调试优化代码可能会显示错误值...如果您没有更好的主意,请使用标志
-O0 -g重新编译 libc -
“回溯看起来像这样”——您应该提供(相关部分)实际回溯。在编程中,细节很重要,你已经省略了几乎所有相关的细节。
标签: c linux multithreading shared-libraries glibc