1 問題:
一個C實現的32位多線程服務在啟動時core dump. 該服務運行了一年多,而此問題僅出現了一次,是一個比較難復現的問題。 出core的位置在C庫的qsort函數,信號是signal 8(算術錯).
2 定位:
core的棧的結構如下:
#0 0x4202a801 in qsort () from /lib/i686/libc.so.6
#1 0x0804e74e in getFRes (databuf=0x406697fc) at fs.cpp:128
#2 0x0804ea74 in adjustOrder (databuf=0x406697fc) at fs.cpp:233
#3 0x0804e553 in Search (databuf=0x406697fc) at fs.cpp:66
#4 0x0804e4b8 in getBResponse (databuf=0x406697fc) at fs.cpp:29
#5 0x0804c559 in getResponse (databuf=0x406697fc) at fs.cpp:283
#6 0x0804c292 in thread_main (arg=0x1) at fs.cpp:222
#7 0x40020941 in pthread_start_thread () from /lib/i686/libpthread.so.0
可以先通過GDB的命令查看出core的匯編指令。當程序core時,$eip保存了當時程序運行指令地址。通過x /i 命令可以將內存以匯編指令的方式查看。 這樣就可以得到core dump時運行的匯編指令。如下:
(gdb) x /i $eip
0x4202a801 <qsort+65>: divl 0x724(%ebx)
按匯編看,應該是除法指令出問題,一般覺得會是除數為0之類的錯誤 由于glibc是開源的,可以查看一下qsort原代碼,此處要注意版本問題,要下相同的版本才能方便問題定位。不同linux發行版帶的glibc的版本可能是不同的,而且系統維護人員也可能升級過相關的庫。 通過以下幾個命令可以查看系統的glibc版本。
$ ldd ./bin/fras libpthread.so.0 => /lib/i686/libpthread.so.0 (0x4001a000)
libcrypto.so.2 => /lib/libcrypto.so.2 (0x4004b000) libc.so.6 => /lib/i686/libc.so.6 (0x42000000)
libdl.so.2 => /lib/libdl.so.2 (0x4011f000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
/lib/i686/libc.so.6 (0x42000000) libdl.so.2 => /lib/libdl.so.2 (0x4011f000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
$ /lib/i686/libc.so.6 -v GNU C Library development release version 2.2.93 , by Roland McGrath et al. Copyright (C) 1992-2001, 2002 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. Compiled by GNU CC version 3.2 20020903 (Red Hat Linux 8.0 3.2-7). Compiled on a Linux 2.4.9-9 system on 2002-09-05. Available extensions:
下載glibc 2.2.93版本,查看其qsort實現,以下為簡化版的原代碼
void qsort (void *b, size_t n, size_t s, __compar_fn_t cmp)
{
const size_t size = n * s;
if (size < 1024)
{
void *buf = __alloca (size);
/* The temporary array is small, so put it on the stack. */
msort_with_tmp (b, n, s, cmp, buf);
}
else
{
static long int phys_pages;
static int pagesize;
if (phys_pages == 0)
{
phys_pages = __sysconf (_SC_PHYS_PAGES);
if (phys_pages == -1)
phys_pages = (long int) (~0ul >> 1);
phys_pages /= 4;
pagesize = __sysconf (_SC_PAGESIZE);
}
if (<strong>size / pagesize > phys_pages</strong>)
_quicksort (b, n, s, cmp);
......
}
}
根據錯誤類型(除法錯),可以看到出問題的指令剛好對應代碼中黑體部分(size / pagesize > phys_pages); 接下來的問題就相對簡單了,程序中有兩個static變量如下
static long int phys_pages;
static int pagesize;
在多線程情況下,線程A先運行到第一處黑體代碼,靜態變量 phys_pages 的值將被修改,即不為0,而pagesize未被賦值. 若此時發生線程切換,線程B開始運行,到分支判斷處phys_pages已不為0,所以else條件將被執行,而此時由于pagesize變量尚未被賦 值,結果 (size / pagesize)這一步就發生除0錯誤了。所以,core dump時的表現就是signal 8即算術錯。
3 解決: 可見,該glibc版本的qsort函數不是多線程安全的。 在不修改glibc的基礎上,可以考慮繞過此問題的辦法:
- 在主程序(線程未啟動時)中先調用qsort一次,為static變量賦上值。
- 需要注意的:初始化時,n*s必須大于1024,否則pagesize沒進行初始化。
–EnD–