Recently, the team introduced gcov for code analysis. The compiled program runs well on Solaris. If the result is run on Linux, the core dump file is generated. This article introduces the entire analysis process.
First, use gdb to analyze the core file. It is displayed that the strlen call has a problem:
(Gdb) bt
#0 0x00000034e4330000f in _ strlen_sse42 () from/lib64/libc. so.6
#1 0x00000000000000530000a in _ gcov_init ()
#2 0x0000000000000053c4b9 in _ global0000i_65535_0 _ g_0000_param () at source/rerun/aicent_ara_rerun.c: 963
#3 0x0000000000000053dc26 in _ do_global_ctors_aux ()
#4 0x0000000000403743 in _ init ()
#5 0x00007fff6d6b3ce8 in ?? ()
#6 0x0000000000000053db55 in _ libc_csu_init ()
#7 0x00000034e421ecb0 in _ libc_start_main () from/lib64/libc. so.6
#8 0x0000000000404449 in _ start ()
Since the gcc we use is installed in the form of an installation package, there is no source code. So I found the gcc source code of the corresponding version from github, hoping to help. The following is the code for the _ gcov_init function (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;
}
It can be seen from the source code and core files that the line "size_t filename_length = strlen (info-> filename);" has a problem. Combined with Assembler:
(Gdb) disassemble _ strlen_sse42
Dump of javaser code for function _ strlen_sse42:
0x00000034e4333860 <+ 0>: pxor % xmm1, % xmm1
0x00000034e4333864 <+ 4>: mov % edi, % ecx
0x00000034e4333866 <+ 6>: mov % rdi, % r8
0x00000034e4333869 <+ 9>: and $0xfffffffffffff0, % rdi
0x00000034e4330000d <+ 13>: xor % edi, % ecx
=> 0x00000034e4330000f <+ 15>: pcmpeqb (% rdi), % xmm1
An error occurred while accessing the rdi register. rdi should save the strlen parameter, that is, "info-> filename ". Try to access the address saved in the rdi register:
(Gdb) I registers rdi
Rdi 0x57c4ac00000000 24704565987246080
(Gdb) x/16xb 0x57c4ac00000000
0x57c4ac00000000: Cannot access memory at address 0x57c4ac00000000
The address saved in the rdi register is indeed invalid.
Next, we will analyze why the filename of the info struct passed in _ gcov_init is an invalid pointer. First take a look at the definition of gcov_info struct (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 .*/
};
View the assembly code of the _ gcov_init _ globalcompui_65535_0 _ g_compute_param function:
(Gdb) disassemble _ globalpolici_65535_0 _ g_pai_param
Dump of worker Er code for function _ globalpolici_65535_0 _ g_0000_param:
0x000000000053c4ab <+ 0>: push % rbp
0x000000000053c4ac <+ 1>: mov % rsp, % rbp
0x000000000053c4af <+ 4>: mov $0x78d4a0, % edi
0x0000000000000053c4b4 <+ 9>: callq 0x53c4c0 <__gcov_init>
0x0000000000000053c4b9 <+ 14>: leaveq
0x0000000000000053c4ba <+ 15>: retq
End of worker Er dump.
You can see that the input _ gcov_init parameter is 0x78d4a0, that is, the address pointing to the gcov_info struct, and view the content of this address:
(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 0x00x00
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
The value of the corresponding filename member should be 0x57c4ac0000000b, which is indeed an invalid address. The problem is analyzed here and there is no idea. Later, in the gcc bugzilla found this problem: https://gcc.gnu.org/bugzilla/show_bug.cgi? Id = 43341, it is clear that it is caused by the "-fpack-struct = 4" compilation option.
We use 64-bit Linux. By default, the executable files generated by compilation are 64-bit. Therefore, the default memory layout of gcov_info should be (gcov_unsigned_t occupies 4 bytes, and pointer type occupies 8 bytes ):
Offset 4 bytes 4 bytes
0 version fill members
8 next
16 stamp filling members
24 filename
... ... ...
After the "-fpack-struct = 4" compilation option is used, the memory layout of gcov_info is changed:
Offset 4 bytes 4 bytes
0 version next
8 next stamp
16 filename
... ... ...
After calculation, the filename value should be 0x57c460. Verify that:
(Gdb) p (char *) 0x57c460
$1 = 0x57c460 "/home/... gcda"
The correct value is printed. There is no problem on Solaris because the 64-bit Solaris program compiled by default is 32-bit.
Take a look at the gcc website's introduction to-fpack-struct (https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fpack-struct-2675), using this compilation option will lead to changes in ABI (Application Binary Interface), so be careful when using it.