動態

詳情 返回 返回

記錄一次glibc版本過低導致的程序無法正常加載的問題 - 動態 詳情

記錄一次glibc版本過低導致的程序無法正常加載的問題

2023.11.27

問題現象

一個程序使用C語言編寫的,但由於某些原因,需要通過dlopen的方式調用go語言生成的so,在其它設備上可以正常運行,但在一個arm環境上運行的時候,發現無法正常運行,看到的現象是程序無任何響應,類似直接卡死了。私用gdb查看當前進程,線程信息及調用信息如下:

$3 = (void *) 0x7f98c31000
(gdb) info threads
  Id   Target Id         Frame
* 1    LWP 4658 "test"     get_func (ctx=0x200cd59d80,
    func_name=0x200cf63ae0 "CheckTest")
    at /root/codes/rechk.c:192
  2    LWP 4666 "test"     0x0000007f92cd3a98 in pthread_cond_wait ()
   from /lib/libpthread.so.0
(gdb) c
Thread 1 "dp" received signal SIGINT, Interrupt.
0x0000007fad50ca98 in pthread_cond_wait () from /lib/libpthread.so.0
(gdb) bt
#0  0x0000007fad50ca98 in pthread_cond_wait () from /lib/libpthread.so.0
#1  0x0000007f920031ac in _cgo_wait_runtime_init_done () at gcc_libinit.c:40
#2  0x0000007f92002d5c in CheckTest (
    buf=buf@entry=0x7f96400013 "371329199910103717\"}", bufLen=bufLen@entry=18)
    at _cgo_export.c:152
#3  0x0000007f940c2a54 in hit_cb (node=<optimized out>,
    priv=0x7fc8279118)
    at /root/codes/rule.c:1530

由於之前有類似的經驗,發現少了go的一些基礎線程,但不明白為什麼會沒有go的基礎線程。既然能調用到so裏面的函數,説明so應該是加載成功了。這也正是比較坑的地方,被逼無奈之下,只好從dlopen的函數就開始gdb,發現dlopen竟然報錯了,報錯信息如下:

(gdb) p dl_err_str
$4 = 0x7fb6a1dfc0 "dlopen: cannot load any more object with static TLS"

而且dlopen報錯後,並沒有執行裏面的CheckTest函數,因為判斷了dlopen失敗後,並不會再繼續後續流程。那為什麼會卡在CheckTest函數中呢?於是繼續運行程序,發現是第二次再調用hit_cb函數的時候,才會出現卡住的問題。第二次調用hit_cb函數的時候,由於上次調用dlopen失敗,所以依然會再次調用dlopen函數打開so,而此次調用dlopen竟然成功了,但是並沒有啓動go的那些基礎線程。於是上網搜索關於dlopen: cannot load any more object with static TLS的問題,發現網上有人貼出來是因為glibc版本問題導致的,原文鏈接是https://blog.csdn.net/jingyi130705008/article/details/117623889:

這是一個低版本glibc (< 2.23)的已知bug,通過dlopen加載一個動態鏈接庫(DSO),並依次將其依賴的DSO也加載進來的時候。具體產生條件是:

glibc < 2.23
已經加載了超過14個含TLS的DSO
當前加載的DSO使用了static TLS
注意條件2,3。如果能夠在加載14個含TLS的DSO前,提前加載含有static TLS的DSO,即可繞過這個問題。

具體做法:找到報錯模塊(比如paddle)如果可以單獨import成功的話,調整import包的順序,把出問題的包放在最前面import

查看當前系統的glibc版本:

# ldd --version
ldd (Ubuntu GLIBC 2.19kord) 2.19
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

通過readelf命令查看自己的so是否有TLS,

[root@test]# readelf -l libdi_test.so|grep TLS
  TLS            0x00000000000c2e00 0x00000000002c2e00 0x00000000002c2e00

果然有TLS,於是去掉其它so中的__thread定義,再次加載此so,發現可以加載成功了,go的基礎線程已經起來了,如下所示:

(gdb) info threads
  Id   Target Id         Frame
* 1    LWP 4639 "dp"     0x0000007f7c50bf34 in nanosleep () from /lib/libc.so.6
  2    LWP 4647 "dp"     0x0000007f801aca98 in pthread_cond_wait ()
   from /lib/libpthread.so.0
  3    LWP 1006 "dp"     runtime.futex ()
    at /root/go/src/runtime/sys_linux_arm64.s:662
  4    LWP 1007 "dp"     runtime.futex ()
    at /root/go/src/runtime/sys_linux_arm64.s:662
  5    LWP 1008 "dp"     runtime.futex ()
    at /root/go/src/runtime/sys_linux_arm64.s:662
  6    LWP 1009 "dp"     runtime.futex ()
    at /root/go/src/runtime/sys_linux_arm64.s:662
  7    LWP 1070 "dp"     runtime.futex ()
    at /root/go/src/runtime/sys_linux_arm64.s:662
  8    LWP 1148 "ZMQbg/Reaper" 0x0000007f7c53759c in epoll_pwait ()
   from /lib/libc.so.6
  9    LWP 1149 "ZMQbg/IO/0" 0x0000007f7c53759c in epoll_pwait ()
   from /lib/libc.so.6

而且功能也好用了,由此確定是TLS問題導致的。

解決方法

長期的解決辦法是更新glibc的版本,但如果不能更新glibc庫,那就只能把__thread去掉。當然,去掉__thread標識後導致的併發問題,還是需要通過別的手段去解決。

其它

為什麼第二次dlopen能成功

這個問題暫時還未深入去研究。

TLS是什麼

在這裏,TLS是線程局部存儲(Thread Local Storage)的縮寫,而不是我們常説的安全協議中中的TLS。

參考資料

dlopen: cannot load any more object with static TLS問題解決-CSDN博客

dlopen:cannot load any more object with static TLS:_jingyi130705008的博客-CSDN博客

c++ - Cannot load any more object with static TLS - Stack Overflow

user avatar sherlocked93 頭像 lyflexi 頭像 yjdmx 頭像 shu_jshu_jiashu_jianshu_jiang 頭像
點贊 4 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.