gcc的 “-fpack-struct” 編譯選項導致程序core dump的分析
最近team引入gcov
來做代碼分析。編譯好的程序在Solaris
上運行的好好的,結果在Linux
上一運行就會產生core dump
文件。這篇文章就介紹整個分析過程。
首先用gdb
分析core
文件,顯示是strlen
調用出了問題:
(gdb)
#0 0x00000034e433386f in __strlen_sse42 () from /lib64/libc.so.6
#1 0x000000000053c57a in __gcov_init ()
#2 0x000000000053c4b9 in _GLOBAL__I_65535_0_g_cmd_param () at source/rerun/aicent_ara_rerun.c:963
#3 0x000000000053dc26 in __do_global_ctors_aux ()
#4 0x0000000000403743 in _init ()
#5 0x00007fff6d6b3ce8 in ?? ()
#6 0x000000000053db55 in __libc_csu_init ()
#7 0x00000034e421ecb0 in __libc_start_main () from /lib64/libc.so.6
#8 0x0000000000404449 in _start ()
由於我們使用的gcc
是用安裝包形式安裝的,沒有源碼。所以就從github
上找了相應版本的gcc
源代碼,希望能有所幫助。以下是__gcov_init
函數的代碼(https://github.com/gcc-mirror/gcc/blob/gcc-4_4_7-release/gcc/libgcov.c):
void
__gcov_init (struct gcov_info *info)
{
if (!info->version)
return;
if (gcov_version (info, info->version, 0))
{
const char *ptr = info->filename;
gcov_unsigned_t crc32 = gcov_crc32;
size_t filename_length = strlen(info->filename);
/* Refresh the longest file name information */
if (filename_length > gcov_max_filename)
gcov_max_filename = filename_length;
do
{
unsigned ix;
gcov_unsigned_t value = *ptr << 24;
for (ix = 8; ix--; value <<= 1)
{
gcov_unsigned_t feedback;
feedback = (value ^ crc32) & 0x80000000 ? 0x04c11db7 : 0;
crc32 <<= 1;
crc32 ^= feedback;
}
}
while (*ptr++);
gcov_crc32 = crc32;
if (!gcov_list)
atexit (gcov_exit);
info->next = gcov_list;
gcov_list = info;
}
info->version = 0;
}
結合源代碼和core
文件可以看出,應該是“size_t filename_length = strlen(info->filename);
”這一行出了問題。再結合匯編程序:
(gdb) disassemble __strlen_sse42
Dump of assembler code for function __strlen_sse42:
0x00000034e4333860 <+0>: pxor %xmm1,%xmm1
0x00000034e4333864 <+4>: mov %edi,%ecx
0x00000034e4333866 <+6>: mov %rdi,%r8
0x00000034e4333869 <+9>: and $0xfffffffffffffff0,%rdi
0x00000034e433386d <+13>: xor %edi,%ecx
=> 0x00000034e433386f <+15>: pcmpeqb (%rdi),%xmm1
是訪問rdi
寄存器出了問題,而rdi
保存的應該是strlen
的參數,也就是“info->filename
”。試著訪問一下rdi
寄存器保存的地址:
(gdb) i registers rdi
rdi 0x57c4ac00000000 24704565987246080
(gdb) x/16xb 0x57c4ac00000000
0x57c4ac00000000: Cannot access memory at address 0x57c4ac00000000
可以看到rdi
寄存器保存的地址的確是個無效的地址。
接下來,就要分析一下為什麼傳入__gcov_init
的info
結構體的filename
是一個無效指針。首先看一下gcov_info
結構體的定義(https://github.com/gcc-mirror/gcc/blob/gcc-4_4_7-release/gcc/gcov-io.h):
/* Information about a single object file. */
struct gcov_info
{
gcov_unsigned_t version; /* expected version number */
struct gcov_info *next; /* link to next, used by libgcov */
gcov_unsigned_t stamp; /* uniquifying time stamp */
const char *filename; /* output file name */
unsigned n_functions; /* number of functions */
const struct gcov_fn_info *functions; /* table of functions */
unsigned ctr_mask; /* mask of counters instrumented. */
struct gcov_ctr_info counts[0]; /* count data. The number of bits
set in the ctr_mask field
determines how big this array
is. */
};
查看調用__gcov_init
的_GLOBAL__I_65535_0_g_cmd_param
函數的匯編代碼:
(gdb) disassemble _GLOBAL__I_65535_0_g_cmd_param
Dump of assembler code for function _GLOBAL__I_65535_0_g_cmd_param:
0x000000000053c4ab <+0>: push %rbp
0x000000000053c4ac <+1>: mov %rsp,%rbp
0x000000000053c4af <+4>: mov $0x78d4a0,%edi
0x000000000053c4b4 <+9>: callq 0x53c4c0 <__gcov_init>
0x000000000053c4b9 <+14>: leaveq
0x000000000053c4ba <+15>: retq
End of assembler dump.
可以看到傳入__gcov_init
的參數為0x78d4a0
,也就是指向gcov_info
結構體的地址,查看這個地址的內容:
(gdb) x/64xb 0x78d4a0
0x78d4a0: 0x52 0x34 0x30 0x34 0x00 0x00 0x00 0x00
0x78d4a8: 0x00 0x00 0x00 0x00 0x82 0xf0 0xc7 0xa5
0x78d4b0: 0x60 0xc4 0x57 0x00 0x00 0x00 0x00 0x00
0x78d4b8: 0x0b 0x00 0x00 0x00 0xac 0xc4 0x57 0x00
0x78d4c0: 0x00 0x00 0x00 0x00 0x01 0x00 0x00 0x00
0x78d4c8: 0x39 0x01 0x00 0x00 0xc0 0xa4 0x47 0x03
0x78d4d0: 0x00 0x00 0x00 0x00 0xe0 0xd8 0x53 0x00
0x78d4d8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
可以看到對應filename
成員的值應該為0x57c4ac0000000b
,的確是個無效地址。問題分析到這裏,沒了思路。後來,在gcc
的bugzilla
裏找到這個問題:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=43341,才搞清楚是“-fpack-struct=4
”這個編譯選項導致的。
我們使用的是64位Linux,默認編譯生成的可執行文件是64位的。所以gcov_info
的默認內存布局應該是(gcov_unsigned_t
類型占4個字節,指針類型占8個字節):
Offset | 4 bytes | 4 bytes |
---|---|---|
0 | version | 填充成員 |
8 | next | next |
16 | stamp | 填充成員 |
24 | filename | filename |
… | … | … |
當使用“-fpack-struct=4
”這個編譯選項後,gcov_info
的內存布局變為:
Offset | 4 bytes | 4 bytes |
---|---|---|
0 | version | next |
8 | next | stamp |
16 | filename | filename |
… | … | … |
經過推算,filename
成員的值應該為0x57c460
,驗證一下:
(gdb) p (char*)0x57c460
$1 = 0x57c460 "/home/.../.....gcda"
打印出的是正確的值。在Solaris
上沒問題的原因是因為64位Solaris
默認編譯出來的程序是32位的。
看了一下gcc
網站對-fpack-struct
的介紹(https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html),使用這個編譯選項會導致ABI(Application Binary Interface)
的改變,所以使用時一定要謹慎。
本文轉載自我在hellogcc上文章:https://www.hellogcc.org/?p=34087。
最後更新:2017-05-23 15:36:57