青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

loop_in_codes

低調做技術__歡迎移步我的獨立博客 codemaro.com 微博 kevinlynx

淺析glibc中thread tls的一處bug

最早的時候是在程序初始化過程中開啟了一個timer(timer_create),這個timer第一次觸發的時間較短時就會引起程序core掉,core的位置也是不定的。使用valgrind可以發現有錯誤的內存寫入:

==31676== Invalid write of size 8
==31676==    at 0x37A540F852: _dl_allocate_tls_init (in /lib64/ld-2.5.so)
==31676==    by 0x4E26BD3: pthread_create@@GLIBC_2.2.5 (in /lib64/libpthread-2.5.so)
==31676==    by 0x76E0B00: timer_helper_thread (in /lib64/librt-2.5.so)
==31676==    by 0x4E2673C: start_thread (in /lib64/libpthread-2.5.so)
==31676==    by 0x58974BC: clone (in /lib64/libc-2.5.so)
==31676==  Address 0xf84dbd0 is 0 bytes after a block of size 336 alloc'd
==31676==    at 0x4A05430: calloc (vg_replace_malloc.c:418)
==31676==    by 0x37A5410082: _dl_allocate_tls (in /lib64/ld-2.5.so)
==31676==    by 0x4E26EB8: pthread_create@@GLIBC_2.2.5 (in /lib64/libpthread-2.5.so)
==31676==    by 0x76E0B00: timer_helper_thread (in /lib64/librt-2.5.so)
==31676==    by 0x4E2673C: start_thread (in /lib64/libpthread-2.5.so)
==31676==    by 0x58974BC: clone (in /lib64/libc-2.5.so)

google _dl_allocate_tls_init 相關發現一個glibc的bug Bug 13862 和我的情況有點類似。本文就此bug及tls相關實現做一定闡述。

需要查看glibc的源碼,如何確認使用的glibc的版本,可以這樣:

$ /lib/libc.so.6
GNU C Library stable release version 2.5, by Roland McGrath et al.
...

為了方便,還可以直接在(glibc Cross Reference)[http://osxr.org/glibc/source/?v=glibc-2.17]網頁上進行查看,版本不同,但影響不大。

BUG描述

要重現13862 BUG作者提到要滿足以下條件:

The use of a relatively large number of dynamic libraries, loaded at runtime using dlopen.

The use of thread-local-storage within those libraries.

A thread exiting prior to the number of loaded libraries increasing a significant amount, followed by a new thread being created after the number of libraries has increased.

簡單來說,就是在加載一大堆包含TLS變量的動態庫的過程中,開啟了一個線程,這個線程退出后又開啟了另一個線程。

這和我們的問題場景很相似。不同的是我們使用的是timer,但timer在觸發時也是開啟新的線程,并且這個線程會立刻退出:

/nptl/sysdeps/unix/sysv/linux/timer_routines.c

timer_helper_thread(...)  // 用于檢測定時器觸發的輔助線程
{
    ...
      pthread_t th;
      (void) pthread_create (&th, &tk->attr, timer_sigev_thread, // 開啟一個新線程調用用戶注冊的定時器函數
                 td);
    ...
}

要重現此BUG可以使用我的實驗代碼 thread-tls,或者使用Bug 13862 中的附件

TLS相關實現

可以順著_dl_allocate_tls_init函數的實現查看相關聯的部分代碼。該函數遍歷所有加載的包含TLS變量的模塊,初始化一個線程的TLS數據結構。

每一個線程都有自己的堆棧空間,其中單獨存儲了各個模塊的TLS變量,從而實現TLS變量在每一個線程中都有單獨的拷貝。TLS與線程的關聯關系可以查看下圖:

應用層使用的pthread_t實際是個pthread對象的地址。創建線程時線程的堆棧空間和pthread結構是一塊連續的內存。但這個地址并不指向這塊內存的首地址。相關代碼:/nptl/allocatestack.c allocate_stack,該函數分配線程的堆棧內存。

pthread第一個成員是tcbhead_ttcbhead_tdtv指向了一個dtv_t數組,該數組的大小隨著當前程序載入的模塊多少而動態變化。每一個模塊被載入時,都有一個l_tls_modid,其直接作為dtv_t數組的下標索引。tcbhead_t中的dtv實際指向的是dtv_t第二個元素,第一個元素用于記錄整個dtv_t數組有多少元素,第二個元素也做特殊使用,從第三個元素開始,才是用于存儲TLS變量。

一個dtv_t存儲的是一個模塊中所有TLS變量的地址,當然這些TLS變量都會被放在連續的內存空間里。dtv_t::pointer::val正是用于指向這塊內存的指針。對于非動態加載的模塊它指向的是線程堆棧的位置;否則指向動態分配的內存位置。

以上結構用代碼描述為,

union dtv_t {
    size_t counter;
    struct {
        void *val; /* point to tls variable memory */
        bool is_static;
    } pointer;
};
 
struct tcbhead_t {
    void *tcb;
    dtv_t *dtv; /* point to a dtv_t array */
    void *padding[22]; /* other members i don't care */
};

struct pthread {
    tcbhead_t tcb;
    /* more members i don't care */
};

dtv是一個用于以模塊為單位存儲TLS變量的數組

實際代碼參看 /nptl/descr.h 及 nptl/sysdeps/x86_64/tls.h。

實驗

使用g++ -o thread -g -Wall -lpthread -ldl thread.cpp編譯代碼,即在創建線程前加載了一個.so:

Breakpoint 1, dump_pthread (id=1084229952) at thread.cpp:40
40          printf("pthread %p, dtv %p\n", pd, dtv);
(gdb) set $dtv=pd->tcb.dtv
(gdb) p $dtv[-1]
$1 = {counter = 17, pointer = {val = 0x11, is_static = false}}
(gdb) p $dtv[3]
$2 = {counter = 18446744073709551615, pointer = {val = 0xffffffffffffffff, is_static = false}}

dtv[3]對應著動態加載的模塊,is_static=falseval被初始化為-1:

/elf/dl-tls.c _dl_allocate_tls_init

if (map->l_tls_offset == NO_TLS_OFFSET
   || map->l_tls_offset == FORCED_DYNAMIC_TLS_OFFSET)
 {
   /* For dynamically loaded modules we simply store
      the value indicating deferred allocation.  */
   dtv[map->l_tls_modid].pointer.val = TLS_DTV_UNALLOCATED;
   dtv[map->l_tls_modid].pointer.is_static = false;
   continue;
 }

dtv數組大小之所以為17,可以參看代碼 /elf/dl-tls.c allocate_dtv

// dl_tls_max_dtv_idx 隨著載入模塊的增加而增加,載入1個.so則是1 

dtv_length = GL(dl_tls_max_dtv_idx) + DTV_SURPLUS; // DTV_SURPLUS 14
dtv = calloc (dtv_length + 2, sizeof (dtv_t));
if (dtv != NULL)
 {
   /* This is the initial length of the dtv.  */
   dtv[0].counter = dtv_length;

繼續上面的實驗,當調用到.so中的function時,其TLS被初始化,此時dtv[3]val指向初始化后的TLS變量地址:

68          fn();
(gdb)
0x601808, 0x601804, 0x601800
72          return 0;
(gdb) p $dtv[3]
$3 = {counter = 6297600, pointer = {val = 0x601800, is_static = false}}
(gdb) x/3xw 0x601800
0x601800:       0x55667788      0xaabbccdd      0x11223344

這個時候還可以看看dtv[1]中的內容,正是指向了pthread前面的內存位置:

(gdb) p $dtv[1]
$5 = {counter = 1084229936, pointer = {val = 0x40a00930, is_static = true}}
(gdb) p/x tid
$7 = 0x40a00940

結論:

  • 線程中TLS變量的存儲是以模塊為單位的

so模塊加載

這里也并不太需要查看dlopen等具體實現,由于使用__thread來定義TLS變量,整個實現涉及到ELF加載器的一些細節,深入下去內容較多。這里直接通過實驗的手段來了解一些實現即可。

上文已經看到,在創建線程前如果動態加載了.so,dtv數組的大小是會隨之增加的。如果是在線程創建后再載入.so呢?

使用g++ -o thread -g -Wall -lpthread -ldl thread.cpp -DTEST_DTV_EXPAND -DSO_CNT=1編譯程序,調試得到:

73          load_sos();
(gdb)
0x601e78, 0x601e74, 0x601e70

Breakpoint 1, dump_pthread (id=1084229952) at thread.cpp:44
44          printf("pthread %p, dtv %p\n", pd, dtv);
(gdb) p $dtv[-1]
$3 = {counter = 17, pointer = {val = 0x11, is_static = false}}
(gdb) p $dtv[4]
$4 = {counter = 6299248, pointer = {val = 0x601e70, is_static = false}}

在新載入了.so時,dtv數組大小并沒有新增,dtv[4]直接被拿來使用。

因為dtv初始大小為16,那么當載入的.so超過這個數字的時候會怎樣?

使用g++ -o thread -g -Wall -lpthread -ldl thread.cpp -DTEST_DTV_EXPAND編譯程序:

...
pthread 0x40a00940, dtv 0x6016a0
...
Breakpoint 1, dump_pthread (id=1084229952) at thread.cpp:44
44          printf("pthread %p, dtv %p\n", pd, dtv);
(gdb) p dtv
$2 = (dtv_t *) 0x6078a0
(gdb) p dtv[-1]
$3 = {counter = 32, pointer = {val = 0x20, is_static = false}}
(gdb) p dtv[5]
$4 = {counter = 6300896, pointer = {val = 0x6024e0, is_static = false}}

可以看出,dtv被重新分配了內存(0x6016a0 -> 0x6078a0)并做了擴大。

以上得出結論:

  • 創建線程前dtv的大小會根據載入模塊數量決定
  • 創建線程后新載入的模塊會動態擴展dtv的大小(必要的時候)

pthread堆棧重用

allocate_stack中分配線程堆棧時,有一個從緩存中取的操作:

allocate_stack(..) {
    ...
    pd = get_cached_stack (&size, &mem);
    ...
}
/* Get a stack frame from the cache.  We have to match by size since
   some blocks might be too small or far too large.  */
get_cached_stack(...) {
    ...
    list_for_each (entry, &stack_cache) // 根據size從stack_cache中取
    { ... }
    ...
    /* Clear the DTV.  */
    dtv_t *dtv = GET_DTV (TLS_TPADJ (result));
    for (size_t cnt = 0; cnt < dtv[-1].counter; ++cnt)
        if (! dtv[1 + cnt].pointer.is_static
                && dtv[1 + cnt].pointer.val != TLS_DTV_UNALLOCATED)
            free (dtv[1 + cnt].pointer.val);
    memset (dtv, '\0', (dtv[-1].counter + 1) * sizeof (dtv_t));

    /* Re-initialize the TLS.  */
    _dl_allocate_tls_init (TLS_TPADJ (result));
}

get_cached_stack會把取出的pthread中的dtv重新初始化。注意 _dl_allocate_tls_init 中是根據模塊列表來初始化dtv數組的。

實驗

當一個線程退出后,它就可能被當做cache被get_cached_stack取出復用。

使用g++ -o thread -g -Wall -lpthread -ldl thread.cpp -DTEST_CACHE_STACK編譯程序,運行:

$ ./thread
..
pthread 0x413c9940, dtv 0x1be46a0
... 
pthread 0x413c9940, dtv 0x1be46a0

回顧BUG

當新創建的線程復用了之前退出的線程堆棧時,由于在_dl_allocate_tls_init中初始化dtv數組時是根據當前載入的模塊數量而定。如果在這個時候模塊數已經超過了這個復用的dtv數組大小,那么就會出現寫入非法的內存。使用valgrind檢測就會得到本文開頭提到的結果。

由于dtv數組大小通常會稍微大點,所以在新加載的模塊數量不夠多時程序還不會有問題。可以通過控制測試程序中SO_CNT的大小看看dtv中內容的變化。

另外,我查看了下glibc的更新歷史,到目前為止(2.20)這個BUG還沒有修復。

參考文檔

posted on 2014-10-07 21:38 Kevin Lynx 閱讀(3658) 評論(1)  編輯 收藏 引用 所屬分類: c/c++

評論

# re: 淺析glibc中thread tls的一處bug 2014-10-08 09:37 Enic

linux各種坑  回復  更多評論   

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲天堂网站在线观看视频| 亚洲国产精品va| 亚洲天堂偷拍| 一区二区三区高清在线观看| 欧美视频不卡| 欧美综合77777色婷婷| 久久激情视频久久| 玉米视频成人免费看| 亚洲大片av| 可以免费看不卡的av网站| 亚洲国产小视频在线观看| 亚洲免费观看高清完整版在线观看| 欧美日韩网站| 久久国产日韩| 免费观看一区| 欧美亚洲网站| 男人的天堂成人在线| 亚洲一区二区三区四区五区黄| 午夜精品久久久久久| 亚洲精品乱码久久久久久蜜桃麻豆 | 美女视频黄a大片欧美| 亚洲欧洲视频在线| 亚洲天堂av综合网| 亚洲黄一区二区| 亚洲午夜精品一区二区三区他趣| 国产婷婷97碰碰久久人人蜜臀| 欧美成人午夜影院| 国产精品网站在线观看| 亚洲国产精品www| 男女av一区三区二区色多| 欧美大片专区| 久久九九热免费视频| 欧美日韩八区| 老司机免费视频久久| 国产精品青草久久久久福利99| 欧美成人精品| 国产日韩欧美一区| 一本色道久久综合亚洲精品按摩 | 久久精彩免费视频| 一本色道久久综合狠狠躁篇的优点| 亚洲欧美韩国| 亚洲婷婷在线| 欧美v日韩v国产v| 久久精品中文字幕一区二区三区| 欧美精品九九| 欧美v亚洲v综合ⅴ国产v| 国产伦精品一区二区三区免费迷| 亚洲国产精品专区久久| 国产一区日韩一区| 小黄鸭视频精品导航| 一区二区三区视频在线观看| 欧美mv日韩mv国产网站app| 久久香蕉国产线看观看网| 国产欧美一区二区三区久久人妖| 日韩亚洲精品电影| 日韩视频在线观看一区二区| 嫩草影视亚洲| 久久夜色精品国产噜噜av| 国产综合香蕉五月婷在线| 午夜欧美大尺度福利影院在线看| 一本色道精品久久一区二区三区| 久久亚洲春色中文字幕久久久| 久久久国产午夜精品| 国产精品自拍视频| 性欧美大战久久久久久久免费观看| 亚洲欧美韩国| 国产精品社区| 欧美一级成年大片在线观看| 欧美一区二区三区在线| 国产精品亚洲欧美| 亚洲欧美一区二区三区极速播放| 午夜精品一区二区三区四区 | 国产婷婷色一区二区三区在线 | 久久久久一区二区三区| 欧美在线免费视屏| 国产一级一区二区| 久久久久国产一区二区| 欧美成人日韩| 中日韩美女免费视频网站在线观看| 欧美视频在线观看一区| 亚洲免费在线观看| 久久天天狠狠| 亚洲经典自拍| 欧美三级日韩三级国产三级| 午夜精品视频在线观看| 蜜桃av噜噜一区| 亚洲美女视频在线免费观看| 欧美性色综合| 欧美在线观看你懂的| 欧美成人精品1314www| 中文久久精品| 国产精品免费在线| 久久久国产一区二区三区| 亚洲黄色一区| 久久久国产精品一区二区中文| 亚洲福利精品| 欧美性一区二区| 久久久久久一区| 亚洲精品视频在线观看免费| 欧美一区二区三区久久精品茉莉花 | 亚洲人午夜精品免费| 欧美日韩中国免费专区在线看| 午夜久久久久久| 亚洲第一在线视频| 欧美一区综合| 99在线精品视频在线观看| 国产日本欧美一区二区三区在线| 美女任你摸久久| 小嫩嫩精品导航| 99国产精品99久久久久久粉嫩| 久久久久久久久久久久久女国产乱 | 欧美大尺度在线| 亚洲一区三区视频在线观看| 亚洲国产你懂的| 合欧美一区二区三区| 欧美日韩高清一区| 久久夜色精品| 欧美一级播放| 亚洲自拍电影| 一本一道久久综合狠狠老精东影业| 久久婷婷国产综合精品青草 | 99re视频这里只有精品| 在线观看视频一区二区欧美日韩| 国产精品日本欧美一区二区三区| 欧美成在线视频| 久久一区二区三区四区| 欧美一区二区三区在线观看| 一区二区三区www| 亚洲精品久久| 亚洲国产精品成人va在线观看| 久久久av毛片精品| 欧美一区二区三区在线观看| 亚洲综合色视频| 在线一区二区三区做爰视频网站| 亚洲激情专区| 亚洲精品免费一二三区| 亚洲国产成人精品女人久久久 | 99在线精品视频| 亚洲久色影视| 亚洲人成网站777色婷婷| 亚洲大胆av| 欧美成人免费全部观看天天性色| 久久噜噜亚洲综合| 久久久久99| 久久天天狠狠| 久热成人在线视频| 欧美高清视频一区二区三区在线观看| 久久夜色精品国产| 免费欧美在线视频| 久久亚洲精品伦理| 蜜臀久久99精品久久久画质超高清| 亚洲午夜久久久久久久久电影院| 亚洲午夜影视影院在线观看| 亚洲一线二线三线久久久| 亚洲一区二区三区免费观看| 亚洲欧美日本国产有色| 久久激情网站| 亚洲第一精品久久忘忧草社区| 亚洲国产精品成人综合| 999在线观看精品免费不卡网站| 亚洲精品乱码视频| 亚洲一区精彩视频| 久久久久久久久久久久久女国产乱| 欧美在线www| 免费一级欧美在线大片| 欧美日韩不卡视频| 国产欧美日韩另类视频免费观看| 狠狠色丁香婷综合久久| 在线播放国产一区中文字幕剧情欧美 | 欧美激情一二三区| 国产精品久久婷婷六月丁香| 狠狠综合久久| 一本到12不卡视频在线dvd| 午夜在线a亚洲v天堂网2018| 久久人人看视频| 99爱精品视频| 久久av老司机精品网站导航| 欧美国产日韩在线| 国产一区二区三区的电影 | 国产日产欧产精品推荐色 | 香蕉久久夜色精品| 欧美激情五月| 亚洲欧美日韩国产中文在线| 欧美v国产在线一区二区三区| 国产精品日产欧美久久久久| 亚洲国产福利在线| 欧美亚洲在线观看| 91久久黄色| 欧美在线中文字幕| 欧美日韩一区高清| 亚洲国产cao| 久久精品国产清自在天天线| 亚洲美女中出| 老司机aⅴ在线精品导航| 国产精品视频区| 亚洲先锋成人| 亚洲精品123区| 蜜臀久久久99精品久久久久久| 国产乱理伦片在线观看夜一区 | 亚洲第一黄色网|