【问题标题】:Modified stack in multi-threaded case多线程情况下的修改堆栈
【发布时间】: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


【解决方案1】:

第 8 帧和第 7 帧之间库句柄差异的可能原因是什么?

最可能的原因是ld-linux.solibdl.so 不匹配。正如this answer 中所述,ld-linuxlibdl 必须来自相同的 GLIBC 版本,否则会发生坏事。

不匹配可能来自 (A) 尝试通过 LD_LIBRARY_PATH 指向不同的 libc 构建,或 (B) 将 libdl.a 静态链接到程序中。

(gdb) info shared 应该显示当前加载了哪些库。如果您看到 已安装 系统 ld-linuxlibdl 以外的其他内容,那么 (A) 可能是您的问题。

对于 (B),您可能收到(并且忽略了)链接器警告,大意是您的程序在运行时需要您用来链接它的 相同 libc 版本。与普遍的看法相反,完全静态的二进制文件在 Linux 上的可移植性较少,而不是更多。

【讨论】:

  • 我们都没有这样做。我们正在使用系统提供的 ld-linux.so 和 libdl.so,并且不要链接到静态 libdl.a。但这确实是一个有趣且可怕的错误:使用的库句柄来自不同的来源或类似的东西:-)
猜你喜欢
  • 2018-03-29
  • 2010-09-08
  • 1970-01-01
  • 2012-09-05
  • 1970-01-01
  • 2014-03-13
  • 1970-01-01
  • 2011-06-23
  • 2012-04-23
相关资源
最近更新 更多