cpp好用的编译调试命令

概述:cpp在编译链接过程中,会产生很多种类的中间文件和结果文件,这些个文件是否达到预期目标,都是要进行测试的,还有当运行时出现问题,也是需要进行检测的。这里就是一些linux下帮助测试和调试的命令。除了linux系统,在windows中也可以通过安装mingw来获取各种需要的命令,它们的行为是类似的。

准备

针对一个c/cpp代码的可执行文件的生成,通常经历预编译、编译、汇编和链接四步,期中分别使用以下命令获得特定步骤的目标文件:

g++ -E hello.cpp -o hello.i
g++ -S hello.i -o hello.s
g++ -c hello.s -o hello.o
g++ hello.o -o hello
./hello

如下是一个简单的示例:

#include <iostream>
using std::cout;
int main(){
cout << "Hello, world.\n";
return 0;
}

如上所示,就这么一个简单的hello,world例子,经历预编译、编译、汇编和链接四步就已经产生hello.i、hello.s、hello.o这些个中间文件了,如果确定索要目标已达预期?这就需要检查这些个文件的状态。

好用的命令

在linux当中编程,使用命令来获取文件信息是必不可少的环节,但这里只介绍具体命令的简单使用,其他的一些额外的知识诸如线程进程的都不去过多介绍。

size命令

从汇编步骤开始,目标文件都已经可以算是成型的linux多段文件,为了查看这些短信息,可以使用size命令查看。针对目标文件,size命令会输出它的段大小和总量大小。

PS C:\Users\penta> size -h
Usage: D:\software\mingw-w64\bin\size.exe [option(s)] [file(s)]
Displays the sizes of sections inside binary files
If no input file(s) are specified, a.out is assumed
The options are:
-A|-B --format={sysv|berkeley} Select output style (default is berkeley)
-o|-d|-x --radix={8|10|16} Display numbers in octal, decimal or hex
-t --totals Display the total sizes (Berkeley only)
--common Display total size for *COM* syms
--target=<bfdname> Set the binary file format
@<file> Read options from <file>
-h --help Display this information
-v --version Display the program's version

上面是安装了mingw以后在windows的powershell下的help结果,可以知道它专门针对[binary files]二进制文件,主要是展示它的段大小,如果没有给出目标文件,它就会自动查找当前目录是否有a.out文件。来看一下使用:

PS D:\Desktop> g++ hello.cpp -o hello
PS D:\Desktop> size hello.exe
text data bss dec hex filename
10004 2460 2432 14896 3a30 hello.exe
PS D:\Desktop> g++ -c hello.cpp -o hello.o
PS D:\Desktop> size hello.o
text data bss dec hex filename
352 8 16 376 178 hello.o
PS D:\Desktop>

如上,在汇编步骤得到上面简单例子的目标文件以后,可以看到指令就那么点,非常简单,而再链接一下cpp标准库后生成的可执行文件却是它的几十倍。text表示正文段大小(应当是机器指令集),data表示包含静态变量和已经初始化(可执行文件包含了初始化的值)的全局变量的数据段大小,bss由可执行文件中不含其初始化值的全局变量组成。它默认伯克利方式的输出。不过这个命令只是简单查看段大小,信息还是太少了。

另外它支持的系统架构也非常多,如下:

D:\software\mingw-w64\bin\size.exe: supported targets: pe-x86-64 pei-x86-64 pe-bigobj-x86-64 elf64-x86-64 elf64-l1om elf64-k1om pe-i386 pei-i386 elf32-i386 elf32-iamcu elf64-little elf64-big elf32-little elf32-big plugin srec symbolsrec verilog tekhex binary ihex

nm命令

这个命令是用来检查文件中符号的,比如函数和全局变量等,比如尚未链接标准库的hello.o文件:

PS D:\Desktop> nm hello.o
0000000000000000 b .bss
0000000000000000 d .ctors
0000000000000000 d .data
0000000000000000 p .pdata
0000000000000000 r .rdata
0000000000000000 r .rdata$.refptr._ZSt4cout
0000000000000000 r .rdata$zzz
0000000000000000 R .refptr._ZSt4cout
0000000000000000 t .text
0000000000000000 r .xdata
U __main
000000000000002b t __tcf_0
0000000000000082 t _GLOBAL__sub_I_main
0000000000000046 t _Z41__static_initialization_and_destruction_0ii
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
0000000000000000 r _ZStL19piecewise_construct
0000000000000000 b _ZStL8__ioinit
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
U atexit
0000000000000000 T main

如上, 信息很多,但是对于现在这里,要关注的其实就是main函数入口而已,这个函数主要是检测一些.o、.a和.so库文件和目标文件,看是否把目标函数或者目标类编译进去了。比如下面这个简单库

// test.h
#include <iostream>
using std::cout;
void display();
//test.cpp
#include "test.h"
void display(){
cout << "test.\n";
}

然后编写一个makefile来生成test的静态库,然后再把test静态库链接进test可执行文件中:

test: libtest.a main.o
g++ main.o -o test -ltest -L .
libtest.a: test.o
ar crs libtest.a test.o
main.o: main.cpp
g++ -c main.cpp -o main.o
test.o: test.h test.cpp
g++ -c test.cpp -o test.o
clean:
rm *.o

然后使用nm命令来检查这里的test静态库libtest.a是否包含了display这个符号:

PS D:\Desktop\test> nm .\libtest.a | findstr "display"
0000000000000079 t _GLOBAL__sub_I__Z7displayv
0000000000000000 T _Z7displayv
PS D:\Desktop\test>
PS D:\Desktop\test> nm test.o | findstr "display"
0000000000000079 t _GLOBAL__sub_I__Z7displayv
0000000000000000 T _Z7displayv
PS D:\Desktop\test> nm main.o | findstr "display"
U _Z7displayv
PS D:\Desktop\test> nm test.exe | findstr "display"
0000000000401669 t _GLOBAL__sub_I__Z7displayv
00000000004015f0 T _Z7displayv
PS D:\Desktop\test>

上面有四个命令执行输出,分别是静态库libtest.a、生成静态库的目标文件test.o和生成可执行文件test的main.o和最后可执行文件,分别查看display符号信息。需要说明的是T代表代码段中全局非静态函数,U代表未定义过需要从其他对象文件中链接进来。这也展示了display在库函数中被声明被定义,然后在目标文件中被引入,然后静态文件整体链接到可执行文件中的过程。稍微修改一下,把库函数的简单函数换成一个包含简单函数和虚函数的类:

// test.h
#include <iostream>
using std::cout;
class Base{
int a;
void fun_a(){a=1;}
public:
void fun_b();
virtual void fun_c();
};
//test.cpp
#include "test.h"
void Base::fun_b(){
cout << "aaa\n";
}
void Base::fun_c(){
cout << "virtual.\n";
}

重新make一下,来看看结果:

PS D:\Desktop\test> nm test.o | findstr "Base"
0000000000000000 r .rdata$_ZTI4Base
0000000000000000 r .rdata$_ZTS4Base
0000000000000000 r .rdata$_ZTV4Base
00000000000000a3 t _GLOBAL__sub_I__ZN4Base5fun_bEv
0000000000000000 T _ZN4Base5fun_bEv
0000000000000026 T _ZN4Base5fun_cEv
0000000000000000 R _ZTI4Base
0000000000000000 R _ZTS4Base
0000000000000000 R _ZTV4Base
PS D:\Desktop\test> nm .\libtest.a | findstr "Base"
0000000000000000 r .rdata$_ZTI4Base
0000000000000000 r .rdata$_ZTS4Base
0000000000000000 r .rdata$_ZTV4Base
00000000000000a3 t _GLOBAL__sub_I__ZN4Base5fun_bEv
0000000000000000 T _ZN4Base5fun_bEv
0000000000000026 T _ZN4Base5fun_cEv
0000000000000000 R _ZTI4Base
0000000000000000 R _ZTS4Base
0000000000000000 R _ZTV4Base
PS D:\Desktop\test> nm main.o | findstr "Base"
0000000000000000 p .pdata$_ZN4BaseC1Ev
0000000000000000 r .rdata$.refptr._ZTV4Base
0000000000000000 R .refptr._ZTV4Base
0000000000000000 t .text$_ZN4BaseC1Ev
0000000000000000 r .xdata$_ZN4BaseC1Ev
U _ZN4Base5fun_bEv
0000000000000000 T _ZN4BaseC1Ev
U _ZTV4Base
PS D:\Desktop\test> nm test.exe | findstr "Base"
000000000040506c p .pdata$_ZN4BaseC1Ev
0000000000404340 r .rdata$.refptr._ZTV4Base
00000000004044e0 r .rdata$_ZTI4Base
00000000004044f0 r .rdata$_ZTS4Base
0000000000404500 r .rdata$_ZTV4Base
0000000000404340 R .refptr._ZTV4Base
0000000000402d90 t .text$_ZN4BaseC1Ev
0000000000406078 r .xdata$_ZN4BaseC1Ev
0000000000400000 A __ImageBase
00000000004029e0 T _GetPEImageBase
00000000004016a3 t _GLOBAL__sub_I__ZN4Base5fun_bEv
00000000004027b0 T _ValidateImageBase
0000000000402790 t _ValidateImageBase.part.0
0000000000401600 T _ZN4Base5fun_bEv
0000000000401626 T _ZN4Base5fun_cEv
0000000000402d90 T _ZN4BaseC1Ev
00000000004044e0 R _ZTI4Base
00000000004044f0 R _ZTS4Base
0000000000404500 R _ZTV4Base
PS D:\Desktop\test>

使用nm命令来研究,对于了解其内存结构很有帮助。另外,不同系统,它的结构也有所区别,对应到nm进行查看的数据也会有区别,比如在ubuntu中:

jam@jam-S1-Pro-Series:~/Desktop/test$ nm test.o
U __cxa_atexit
U __dso_handle
U _GLOBAL_OFFSET_TABLE_
00000000000000ae t _GLOBAL__sub_I__ZN4Base5fun_bEv
0000000000000058 t _Z41__static_initialization_and_destruction_0ii
0000000000000000 T _ZN4Base5fun_bEv
000000000000002c T _ZN4Base5fun_cEv
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
0000000000000000 b _ZStL8__ioinit
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
0000000000000000 V _ZTI4Base
0000000000000000 V _ZTS4Base
0000000000000000 V _ZTV4Base
U _ZTVN10__cxxabiv117__class_type_infoE
jam@jam-S1-Pro-Series:~/Desktop/test$ nm libtest.a
test.o:
U __cxa_atexit
U __dso_handle
U _GLOBAL_OFFSET_TABLE_
00000000000000ae t _GLOBAL__sub_I__ZN4Base5fun_bEv
0000000000000058 t _Z41__static_initialization_and_destruction_0ii
0000000000000000 T _ZN4Base5fun_bEv
000000000000002c T _ZN4Base5fun_cEv
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
0000000000000000 b _ZStL8__ioinit
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
0000000000000000 V _ZTI4Base
0000000000000000 V _ZTS4Base
0000000000000000 V _ZTV4Base
U _ZTVN10__cxxabiv117__class_type_infoE
jam@jam-S1-Pro-Series:~/Desktop/test$ nm main.o
U __cxa_atexit
U __dso_handle
U _GLOBAL_OFFSET_TABLE_
00000000000000a4 t _GLOBAL__sub_I_main
0000000000000000 T main
U __stack_chk_fail
000000000000004e t _Z41__static_initialization_and_destruction_0ii
U _ZN4Base5fun_bEv
0000000000000000 W _ZN4BaseC1Ev
0000000000000000 W _ZN4BaseC2Ev
0000000000000000 n _ZN4BaseC5Ev
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
0000000000000000 b _ZStL8__ioinit
U _ZTV4Base
jam@jam-S1-Pro-Series:~/Desktop/test$ nm test
000000000000038c r __abi_tag
0000000000004010 B __bss_start
0000000000004150 b completed.0
U __cxa_atexit@GLIBC_2.2.5
w __cxa_finalize@GLIBC_2.2.5
0000000000004000 D __data_start
0000000000004000 W data_start
00000000000010f0 t deregister_tm_clones
0000000000001160 t __do_global_dtors_aux
0000000000003d68 d __do_global_dtors_aux_fini_array_entry
0000000000004008 D __dso_handle
0000000000003d98 d _DYNAMIC
0000000000004010 D _edata
0000000000004158 B _end
000000000000134c T _fini
00000000000011a0 t frame_dummy
0000000000003d50 d __frame_dummy_init_array_entry
0000000000002210 r __FRAME_END__
0000000000003f98 d _GLOBAL_OFFSET_TABLE_
000000000000124d t _GLOBAL__sub_I_main
0000000000001332 t _GLOBAL__sub_I__ZN4Base5fun_bEv
w __gmon_start__
000000000000201c r __GNU_EH_FRAME_HDR
0000000000001000 T _init
0000000000002000 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U __libc_start_main@GLIBC_2.34
00000000000011a9 T main
0000000000001120 t register_tm_clones
U __stack_chk_fail@GLIBC_2.4
00000000000010c0 T _start
0000000000004010 D __TMC_END__
00000000000011f7 t _Z41__static_initialization_and_destruction_0ii
00000000000012dc t _Z41__static_initialization_and_destruction_0ii
0000000000001284 T _ZN4Base5fun_bEv
00000000000012b0 T _ZN4Base5fun_cEv
0000000000001266 W _ZN4BaseC1Ev
0000000000001266 W _ZN4BaseC2Ev
U _ZNSt8ios_base4InitC1Ev@GLIBCXX_3.4
U _ZNSt8ios_base4InitD1Ev@GLIBCXX_3.4
0000000000004040 B _ZSt4cout@GLIBCXX_3.4
0000000000004151 b _ZStL8__ioinit
0000000000004152 b _ZStL8__ioinit
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@GLIBCXX_3.4
0000000000003d88 V _ZTI4Base
0000000000002013 V _ZTS4Base
0000000000003d70 V _ZTV4Base
U _ZTVN10__cxxabiv117__class_type_infoE@CXXABI_1.3
jam@jam-S1-Pro-Series:~/Desktop/test$

readelf命令

这个命令使用在linux环境,因为linux中的可执行文件才是elf文件,而windows下的是exe文件,所以是有系统限制的。不过,好像这两个都起源于coff文件,所以是有亲缘关系的。相对于了解简单符号的nm命令,readelf可以说是详细版的nm了:

jam@jam-S1-Pro-Series:~/Desktop/test$ readelf -a test.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 2176 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 24
Section header string table index: 23
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .group GROUP 0000000000000000 00000040
000000000000000c 0000000000000004 21 12 4
[ 2] .group GROUP 0000000000000000 0000004c
000000000000000c 0000000000000004 21 13 4
[ 3] .group GROUP 0000000000000000 00000058
0000000000000008 0000000000000004 21 15 4
[ 4] .text PROGBITS 0000000000000000 00000060
00000000000000c7 0000000000000000 AX 0 0 2
[ 5] .rela.text RELA 0000000000000000 000005a8
0000000000000120 0000000000000018 I 21 4 8
[ 6] .data PROGBITS 0000000000000000 00000127
0000000000000000 0000000000000000 WA 0 0 1
[ 7] .bss NOBITS 0000000000000000 00000127
0000000000000001 0000000000000000 WA 0 0 1
[ 8] .rodata PROGBITS 0000000000000000 00000127
000000000000000f 0000000000000000 A 0 0 1
[ 9] .data.rel.ro[...] PROGBITS 0000000000000000 00000138
0000000000000018 0000000000000000 WAG 0 0 8
[10] .rela.data.r[...] RELA 0000000000000000 000006c8
0000000000000030 0000000000000018 IG 21 9 8
[11] .data.rel.ro[...] PROGBITS 0000000000000000 00000150
0000000000000010 0000000000000000 WAG 0 0 8
[12] .rela.data.r[...] RELA 0000000000000000 000006f8
0000000000000030 0000000000000018 IG 21 11 8
[13] .rodata._ZTS4Base PROGBITS 0000000000000000 00000160
0000000000000006 0000000000000000 AG 0 0 1
[14] .init_array INIT_ARRAY 0000000000000000 00000168
0000000000000008 0000000000000008 WA 0 0 8
[15] .rela.init_array RELA 0000000000000000 00000728
0000000000000018 0000000000000018 I 21 14 8
[16] .comment PROGBITS 0000000000000000 00000170
0000000000000026 0000000000000001 MS 0 0 1
[17] .note.GNU-stack PROGBITS 0000000000000000 00000196
0000000000000000 0000000000000000 0 0 1
[18] .note.gnu.pr[...] NOTE 0000000000000000 00000198
0000000000000020 0000000000000000 A 0 0 8
[19] .eh_frame PROGBITS 0000000000000000 000001b8
0000000000000098 0000000000000000 A 0 0 8
[20] .rela.eh_frame RELA 0000000000000000 00000740
0000000000000060 0000000000000018 I 21 19 8
[21] .symtab SYMTAB 0000000000000000 00000250
00000000000001f8 0000000000000018 22 8 8
[22] .strtab STRTAB 0000000000000000 00000448
0000000000000160 0000000000000000 0 0 1
[23] .shstrtab STRTAB 0000000000000000 000007a0
00000000000000dc 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)
COMDAT group section [ 1] `.group' [_ZTV4Base] contains 2 sections:
[Index] Name
[ 9] .data.rel.ro.local._ZTV4Base
[ 10] .rela.data.rel.ro.local._ZTV4Base
COMDAT group section [ 2] `.group' [_ZTI4Base] contains 2 sections:
[Index] Name
[ 11] .data.rel.ro._ZTI4Base
[ 12] .rela.data.rel.ro._ZTI4Base
COMDAT group section [ 3] `.group' [_ZTS4Base] contains 1 sections:
[Index] Name
[ 13] .rodata._ZTS4Base
There are no program headers in this file.
There is no dynamic section in this file.
Relocation section '.rela.text' at offset 0x5a8 contains 12 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000013 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4
00000000001d 000900000002 R_X86_64_PC32 0000000000000000 _ZSt4cout - 4
000000000025 000a00000004 R_X86_64_PLT32 0000000000000000 _ZStlsISt11char_t[...] - 4
00000000003f 000500000002 R_X86_64_PC32 0000000000000000 .rodata + 1
000000000049 000900000002 R_X86_64_PC32 0000000000000000 _ZSt4cout - 4
000000000051 000a00000004 R_X86_64_PLT32 0000000000000000 _ZStlsISt11char_t[...] - 4
00000000007c 000300000002 R_X86_64_PC32 0000000000000000 .bss - 4
000000000084 001000000004 R_X86_64_PLT32 0000000000000000 _ZNSt8ios_base4In[...] - 4
00000000008b 001100000002 R_X86_64_PC32 0000000000000000 __dso_handle - 4
000000000095 000300000002 R_X86_64_PC32 0000000000000000 .bss - 4
00000000009f 00130000002a R_X86_64_REX_GOTP 0000000000000000 _ZNSt8ios_base4In[...] - 4
0000000000a7 001400000004 R_X86_64_PLT32 0000000000000000 __cxa_atexit - 4
Relocation section '.rela.data.rel.ro.local._ZTV4Base' at offset 0x6c8 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000008 000d00000001 R_X86_64_64 0000000000000000 _ZTI4Base + 0
000000000010 000b00000001 R_X86_64_64 000000000000002c _ZN4Base5fun_cEv + 0
Relocation section '.rela.data.rel.ro._ZTI4Base' at offset 0x6f8 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000000 000e00000001 R_X86_64_64 0000000000000000 _ZTVN10__cxxabiv1[...] + 10
000000000008 000f00000001 R_X86_64_64 0000000000000000 _ZTS4Base + 0
Relocation section '.rela.init_array' at offset 0x728 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000000000 000200000001 R_X86_64_64 0000000000000000 .text + ae
Relocation section '.rela.eh_frame' at offset 0x740 contains 4 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
000000000040 000200000002 R_X86_64_PC32 0000000000000000 .text + 2c
000000000060 000200000002 R_X86_64_PC32 0000000000000000 .text + 58
000000000080 000200000002 R_X86_64_PC32 0000000000000000 .text + ae
No processor specific unwind information to decode
Symbol table '.symtab' contains 21 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.cpp
2: 0000000000000000 0 SECTION LOCAL DEFAULT 4 .text
3: 0000000000000000 0 SECTION LOCAL DEFAULT 7 .bss
4: 0000000000000000 1 OBJECT LOCAL DEFAULT 7 _ZStL8__ioinit
5: 0000000000000000 0 SECTION LOCAL DEFAULT 8 .rodata
6: 0000000000000058 86 FUNC LOCAL DEFAULT 4 _Z41__static_ini[...]
7: 00000000000000ae 25 FUNC LOCAL DEFAULT 4 _GLOBAL__sub_I__[...]
8: 0000000000000000 44 FUNC GLOBAL DEFAULT 4 _ZN4Base5fun_bEv
9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4cout
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZStlsISt11char_[...]
11: 000000000000002c 44 FUNC GLOBAL DEFAULT 4 _ZN4Base5fun_cEv
12: 0000000000000000 24 OBJECT WEAK DEFAULT 9 _ZTV4Base
13: 0000000000000000 16 OBJECT WEAK DEFAULT 11 _ZTI4Base
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZTVN10__cxxabiv[...]
15: 0000000000000000 6 OBJECT WEAK DEFAULT 13 _ZTS4Base
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4I[...]
17: 0000000000000000 0 NOTYPE GLOBAL HIDDEN UND __dso_handle
18: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
19: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4I[...]
20: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __cxa_atexit
No version information found in this file.
Displaying notes found in: .note.gnu.property
Owner Data size Description
GNU 0x00000010 NT_GNU_PROPERTY_TYPE_0
Properties: x86 feature: IBT, SHSTK

-a参数表示所有信息,这里仅是针对test.o这一个目标文件,就已经信息庞大至此(斗宗强者恐怖如斯),太多信息了,回到readelf的参数使用,然后来了解一下如何阅读它的信息:

jam@jam-S1-Pro-Series:~/Desktop/test$ readelf test.o
Usage: readelf <option(s)> elf-file(s)
Display information about the contents of ELF format files
Options are:
-a --all Equivalent to: -h -l -S -s -r -d -V -A -I
-h --file-header Display the ELF file header
-l --program-headers Display the program headers
--segments An alias for --program-headers
-S --section-headers Display the sections' header
--sections An alias for --section-headers
-g --section-groups Display the section groups
-t --section-details Display the section details
-e --headers Equivalent to: -h -l -S
-s --syms Display the symbol table
--symbols An alias for --syms
--dyn-syms Display the dynamic symbol table
--lto-syms Display LTO symbol tables
--sym-base=[0|8|10|16]
Force base for symbol sizes. The options are
mixed (the default), octal, decimal, hexadecimal.
-C --demangle[=STYLE] Decode mangled/processed symbol names
STYLE can be "none", "auto", "gnu-v3", "java",
"gnat", "dlang", "rust"
--no-demangle Do not demangle low-level symbol names. (default)
--recurse-limit Enable a demangling recursion limit. (default)
--no-recurse-limit Disable a demangling recursion limit
-U[dlexhi] --unicode=[default|locale|escape|hex|highlight|invalid]
Display unicode characters as determined by the current locale
(default), escape sequences, "<hex sequences>", highlighted
escape sequences, or treat them as invalid and display as
"{hex sequences}"
-n --notes Display the core notes (if present)
-r --relocs Display the relocations (if present)
-u --unwind Display the unwind info (if present)
-d --dynamic Display the dynamic section (if present)
-V --version-info Display the version sections (if present)
-A --arch-specific Display architecture specific information (if any)
-c --archive-index Display the symbol/file index in an archive
-D --use-dynamic Use the dynamic section info when displaying symbols
-L --lint|--enable-checks
Display warning messages for possible problems
-x --hex-dump=<number|name>
Dump the contents of section <number|name> as bytes
-p --string-dump=<number|name>
Dump the contents of section <number|name> as strings
-R --relocated-dump=<number|name>
Dump the relocated contents of section <number|name>
-z --decompress Decompress section before dumping it
-w --debug-dump[a/=abbrev, A/=addr, r/=aranges, c/=cu_index, L/=decodedline,
f/=frames, F/=frames-interp, g/=gdb_index, i/=info, o/=loc,
m/=macro, p/=pubnames, t/=pubtypes, R/=Ranges, l/=rawline,
s/=str, O/=str-offsets, u/=trace_abbrev, T/=trace_aranges,
U/=trace_info]
Display the contents of DWARF debug sections
-wk --debug-dump=links Display the contents of sections that link to separate
debuginfo files
-P --process-links Display the contents of non-debug sections in separate
debuginfo files. (Implies -wK)
-wK --debug-dump=follow-links
Follow links to separate debug info files (default)
-wN --debug-dump=no-follow-links
Do not follow links to separate debug info files
--dwarf-depth=N Do not display DIEs at depth N or greater
--dwarf-start=N Display DIEs starting at offset N
--ctf=<number|name> Display CTF info from section <number|name>
--ctf-parent=<name> Use CTF archive member <name> as the CTF parent
--ctf-symbols=<number|name>
Use section <number|name> as the CTF external symtab
--ctf-strings=<number|name>
Use section <number|name> as the CTF external strtab
-I --histogram Display histogram of bucket list lengths
-W --wide Allow output width to exceed 80 characters
-T --silent-truncation If a symbol name is truncated, do not add [...] suffix
@<file> Read options from <file>
-H --help Display this information
-v --version Display the version number of readelf

参数太多,所以首选几个通用的适用性强的:a参数表示全部展示,上面已经有例子了,接下来进行细分解析一下。一个elf文件有其独特的header,细分出来它还有着program和section部分,它们都有着自己独特的header,书是记录信息的载体,把一个elf看成一本书的话,header就是重要的目录部分。elf header描述的是文件类型、abi还有着到program header表和section header表的索引:

jam@jam-S1-Pro-Series:~/Desktop/test$ readelf -h test
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Position-Independent Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x10c0
Start of program headers: 64 (bytes into file)
Start of section headers: 14904 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 32
Section header string table index: 31
jam@jam-S1-Pro-Series:~/Desktop/test$ readelf -h test.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 2176 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 24
Section header string table index: 23
jam@jam-S1-Pro-Series:~/Desktop/test$

其后便是关系到执行时信息的program header,它只存在于可执行文件和共享库,所以上面test.o的信息中就没有program header和dynamic header,这部分信息,可以l参数进行查看:

jam@jam-S1-Pro-Series:~/Desktop/test$ readelf -l test
Elf file type is DYN (Position-Independent Executable file)
Entry point 0x10c0
There are 13 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000002d8 0x00000000000002d8 R 0x8
INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000000008e8 0x00000000000008e8 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x0000000000000359 0x0000000000000359 R E 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
0x0000000000000214 0x0000000000000214 R 0x1000
LOAD 0x0000000000002d50 0x0000000000003d50 0x0000000000003d50
0x00000000000002c0 0x0000000000000408 RW 0x1000
DYNAMIC 0x0000000000002d98 0x0000000000003d98 0x0000000000003d98
0x0000000000000200 0x0000000000000200 RW 0x8
NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000030 0x0000000000000030 R 0x8
NOTE 0x0000000000000368 0x0000000000000368 0x0000000000000368
0x0000000000000044 0x0000000000000044 R 0x4
GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000030 0x0000000000000030 R 0x8
GNU_EH_FRAME 0x000000000000201c 0x000000000000201c 0x000000000000201c
0x000000000000006c 0x000000000000006c R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002d50 0x0000000000003d50 0x0000000000003d50
0x00000000000002b0 0x00000000000002b0 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .plt.got .plt.sec .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .data.rel.ro .dynamic .got .data .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .note.gnu.property
10 .eh_frame_hdr
11
12 .init_array .fini_array .data.rel.ro .dynamic .got
jam@jam-S1-Pro-Series:~/Desktop/test$ readelf -l test.o
There are no program headers in this file.
jam@jam-S1-Pro-Series:~/Desktop/test$

还有关系链接阶段的section header,它的表中有着各个section的位置索引,用大写S参数查看。这些个信息非常繁杂,所以一般来说,看这个的都是很习以为常的或者说真的高手了。

查看依赖的ldd

在linux中,一份可执行文件链接了什么库,可以通过ldd进行查看,有时候运行时显示找不到某某依赖,就可以通过这个命令去查看对应信息:

jam@jam-S1-Pro-Series:~/Desktop/test$ ldd test
linux-vdso.so.1 (0x00007ffc4c1ab000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f3ace200000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3acde00000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f3ace505000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3ace608000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f3ace4e5000)
jam@jam-S1-Pro-Series:~/Desktop/test$ ldd main.o
not a dynamic executable
jam@jam-S1-Pro-Series:~/Desktop/test$ ldd libtest.a
not a dynamic executable
jam@jam-S1-Pro-Series:~/Desktop/test$

如上,可以明白地看出,它主要针对包含了[dynamic executable]的可执行文件和动态库文件,所以main.cpp的目标文件和test库的静态库都对ldd无感,而可执行文件test则可以看到关于linux动态库、c++动态库和c动态库等等信息,而且上面是缺失无漏的,所以test可执行文件可以正常执行。下面修改一下上面的makefile,把静态库修改成动态库:

test: libtest.so main.o
g++ main.o libtest.so -o test
libtest.so: test.o
g++ -shared test.o -o libtest.so
main.o: main.cpp
g++ -c main.cpp -o main.o
test.o: test.h test.cpp
g++ -c -fPIC test.cpp -o test.o
clean:
rm *.o

然后用ldd查看一下test可执行文件的情况:

jam@jam-S1-Pro-Series:~/Desktop/test$ ldd test
linux-vdso.so.1 (0x00007fffd747d000)
libtest.so => not found
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007faa39400000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007faa39000000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007faa396d7000)
/lib64/ld-linux-x86-64.so.2 (0x00007faa397da000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007faa396b7000)
jam@jam-S1-Pro-Series:~/Desktop/test$ ./test
./test: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
jam@jam-S1-Pro-Series:~/Desktop/test$ nm libtest.so | grep Base
000000000000115a T _ZN4Base5fun_bEv
0000000000001186 T _ZN4Base5fun_cEv
0000000000003dd8 V _ZTI4Base
000000000000200f V _ZTS4Base
0000000000003dc0 V _ZTV4Base
jam@jam-S1-Pro-Series:~/Desktop/test$ ldd libtest.so
linux-vdso.so.1 (0x00007fff107ae000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fe041e00000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe041a00000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fe04206d000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe042170000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fe04204d000)

重点关注刚丢进makefile的libtest.so动态库的实现,在ldd输出中,可以明显看到它没有找到对应刚刚make生成的libtest.so动态库文件,但在当前目录下也是能够明显地看出libtest.so的存在的,所以不出意料的,test可执行文件的执行时报错了的,因为它没有成功加载需要的动态库到内存中,这个缺失对象就是libtest.so,那这是为什么呢?是因为没有成功生成动态库或者动态库内部链接有问题吗?使用nm和ldd查看这个动态库文件,也是没看到有问题,所以是为何?

问题在于linux下可执行文件的运行,是根据LD_LIBRARY_PATH环境变量去寻找需要的依赖的,但这个变量并不包括当前项目路径,所以还是需要添加环境变量:

...
test: libtest.so main.o
g++ main.o libtest.so -o test -Wl,-rpath=/home/jam/Desktop/test
...

如上,编译的时候,添加动态库路径到可执行文件中,本来是想在编译动态库的时候修改静态变量的,但发现export的修改,只是针对执行的bash,所以在make执行以后,LD_LIBRARY_PATH又回归原样了,不过本身也不想进行永久修改,所以使用添加编译参数的办法。另外就是在执行make的时候出现,make: 'test' is up to date的提示,这时候需要进行make clean操作,然后重新make就可以了:

jam@jam-S1-Pro-Series:~/Desktop/test$ ldd test
linux-vdso.so.1 (0x00007ffc7b97e000)
libtest.so => /home/jam/Desktop/test/libtest.so (0x00007f27313b5000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f2731000000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2730c00000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f27312b9000)
/lib64/ld-linux-x86-64.so.2 (0x00007f27313c1000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f2731299000)
jam@jam-S1-Pro-Series:~/Desktop/test$ ./test
aaa
jam@jam-S1-Pro-Series:~/Desktop/test$

好的,ok。

linux中的打断点--gdb调试

在windows中,可视化IDE把各种功能都做的很好,比如打断点进行调试,这样可以逐步去查看可能代码运行可能出现的问题,而在linux中,进行端点调试的办法就是gdb了。在ubuntu中,如果没有安装,可以使用apt进行安装:

sudo apt update
sudo apt install gdb

而在windows中,要使用gdb,则是要安装了mingw并添加了环境变量以后,才能使用。

简单尝试

使用gdb,首要就是在编译的时候,指定-g参数,这样它就会保留debug info了:

g++ test.cpp main.cpp -o test -g

然后进入gdb的调试界面(还是使用的上面的例子):

PS D:\Desktop\test> gdb test
GNU gdb (GDB) 8.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-w64-mingw32".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from test...done.
(gdb)

好,成功进入,然后设置断点,开始调试:

(gdb) b main.cpp:6
Breakpoint 1 at 0x401639: file main.cpp, line 6.
(gdb) r
Starting program: D:\Desktop\test\test.exe
[New Thread 6392.0x5bd0]
[New Thread 6392.0x2630]
[New Thread 6392.0x2330]
[New Thread 6392.0x418c]
Thread 1 hit Breakpoint 1, main () at main.cpp:6
6 a.fun_b();
(gdb)

如上,设置断点在main.cpp的第6行,然后r一下,开始运行到断点处,Base类实体变量a准备执行fun_b成员函数。现在可以进行的动作就是next简单执行a.fun_b()语句;或者step进入执行函数中进行调试;或者continue继续执行下去直到下一个断点,没有断点就运行到退出。分别如下:

(gdb) break main.cpp:6
Breakpoint 1 at 0x401639: file main.cpp, line 6.
(gdb) r
Starting program: D:\Desktop\test\test.exe
[New Thread 20248.0x3174]
[New Thread 20248.0x59ac]
[New Thread 20248.0xa00]
[New Thread 20248.0x4ae0]
Thread 1 hit Breakpoint 1, main () at main.cpp:6
6 a.fun_b();
(gdb) next
aaa
8 return 0;
(gdb)
(gdb) r
Starting program: D:\Desktop\test\test.exe
[New Thread 9768.0x3590]
[New Thread 9768.0x4bd0]
[New Thread 9768.0x3254]
[New Thread 9768.0xea4]
Thread 1 hit Breakpoint 1, main () at main.cpp:6
6 a.fun_b();
(gdb) step
Base::fun_b (this=0x61fe10) at test.cpp:4
4 cout << "aaa\n";
(gdb)
(gdb) continue
Continuing.
[New Thread 6392.0x19fc]
aaa
[Thread 6392.0x19fc exited with code 0]
[Thread 6392.0x418c exited with code 0]
[Thread 6392.0x2330 exited with code 0]
[Thread 6392.0x2630 exited with code 0]
[Inferior 1 (process 6392) exited normally]
(gdb) next
The program is not being run.
(gdb) quit
PS D:\Desktop\test>

如上,quit操作,可以退出当前gdb调试:

(gdb) quit
A debugging session is active.
Inferior 1 [process 9768] will be killed.
Quit anyway? (y or n) y
PS D:\Desktop\test>

当然,也可以重新打断点再r跑起来继续调试,不过这样前面添加的和后面不同的断点机会一直存在。另外,在上面的命令中,一个命令可以显式发起也可以简略发起:

  • break--b
  • continue--c
  • next--n
  • run--r

除了上面的命令,还有着重要的backtrace、print、list和info等命令。简单演示一下:

(gdb) break main.cpp:6
Breakpoint 1 at 0x401639: file main.cpp, line 6.
(gdb) r
Starting program: D:\Desktop\test\test.exe
[New Thread 20248.0x3174]
[New Thread 20248.0x59ac]
[New Thread 20248.0xa00]
[New Thread 20248.0x4ae0]
Thread 1 hit Breakpoint 1, main () at main.cpp:6
6 a.fun_b();
(gdb) backtrace
#0 main () at main.cpp:6
(gdb) step
Base::fun_b (this=0x61fe10) at test.cpp:4
4 cout << "aaa\n";
(gdb) backtrace
#0 Base::fun_b (this=0x61fe10) at test.cpp:4
#1 0x0000000000401645 in main () at main.cpp:6
(gdb)
(gdb) r
Starting program: D:\Desktop\test\test.exe
[New Thread 21976.0x2038]
[New Thread 21976.0x13c8]
[New Thread 21976.0x41e4]
[New Thread 21976.0x5a04]
Thread 1 hit Breakpoint 2, main () at main.cpp:5
5 Base a;
(gdb) print a
$2 = {_vptr.Base = 0x10b19a0, a = 16}

打印变量或寄存器值的是print,info则是查看断点信息,下面可以添加了那么些个断点,然后使用delet来永久删除。如果print要打印的信息过长,命令默认显示不全,那就可以对其进行set print element 0设置。下面展示info使用:

(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000401639 in main() at main.cpp:6
breakpoint already hit 1 time
(gdb) break main.cpp:5
Breakpoint 2 at 0x40162d: file main.cpp, line 5.
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000401639 in main() at main.cpp:6
breakpoint already hit 1 time
2 breakpoint keep y 0x000000000040162d in main() at main.cpp:5
(gdb) delet breakpoint 1
(gdb) info break
Num Type Disp Enb Address What
2 breakpoint keep y 0x000000000040162d in main() at main.cpp:5
(gdb)

list是查看源码的:

(gdb) r
Starting program: D:\Desktop\test\test.exe
[New Thread 15204.0x58ac]
[New Thread 15204.0x5690]
[New Thread 15204.0x4e90]
[New Thread 15204.0x5390]
Thread 1 hit Breakpoint 1, main () at main.cpp:6
6 a.fun_b();
(gdb) list
1 #include "test.h"
2
3 int main(){
4
5 Base a;
6 a.fun_b();
7
8 return 0;
9 }(gdb)

如上就够进行简单使用了,但实际上,gdb断点调试,还是更多用来进行coredump的检查,也就是内存泄露问题。还有一件事,使用gdb进行stl容器的调试的使用,print会有点问题,可以使用stl-views的插件来提供帮助,这个插件可以在GDB官网上去查找并下载安装,也可以到sourceware中进行下载。下面是使用:

(gdb) source ~/stl-views-1.0.3.gdb
(gdb) help pset
(gdb) help pmap

gdb读取core-dump文件进行调试

core dump是运行程序在奔溃宕机,系统进行清理后留下的一份内存快照,但这个在系统中是默认关闭的,所以需要手动开启,比如在linux中:

# 查看core dump是否开启,如果得到0那就是关闭了
ulimit -c
# 当前终端开启core dump
ulimit -c unlimited
sudo bash -c 'echo core > /proc/sys/kernal/core_pattern'

core文件默认和可执行文件同一路径,并且都命名为core,扩展名为pid,不添加扩展的话就简单的core,后面生成的会把前面的进行覆盖。设计一个内存泄露的例子:

#include <iostream>
void test(){
char *p = "test";
delete(p);
}
int main(){
test();
return 0;
}

然后编译运行一下,core就生成在当前路径了:

jam@jam-S1-Pro-Series:~/Desktop/test$ g++ coredump.cpp -o test
coredump.cpp: In function ‘void test()’:
coredump.cpp:4:15: warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
4 | char *p = "abaaba";
| ^~~~~~~~
coredump.cpp:5:13: warning: ‘void operator delete(void*, std::size_t)’ called on a pointer to an unallocated object ‘"abaaba"’ [-Wfree-nonheap-object]
5 | delete(p);
| ^
coredump.cpp:5:12: note: assigned here
5 | delete(p);
| ~^~
jam@jam-S1-Pro-Series:~/Desktop/test$ ./test
free(): invalid pointer
Aborted (core dumped)
jam@jam-S1-Pro-Series:~/Desktop/test$ ls
core coredump.cpp test
jam@jam-S1-Pro-Series:~/Desktop/test$

如上,core文件就得到了,然后输入gdb test core进行调试:

jam@jam-S1-Pro-Series:~/Desktop/test$ gdb test core
GNU gdb (Ubuntu 12.1-3ubuntu2) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from test...
[New LWP 9563]
This GDB supports auto-downloading debuginfo from the following URLs:
https://debuginfod.ubuntu.com
Enable debuginfod for this session? (y or [n]) y
Debuginfod has been enabled.
To make this setting permanent, add 'set debuginfod enabled on' to .gdbinit.
Downloading 6.01 MB separate debug info for /lib/x86_64-linux-gnu/libstdc++.so.6
Downloading 0.17 MB separate debug info for /lib/x86_64-linux-gnu/libgcc_s.so.1
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `./test'.
Program terminated with signal SIGABRT, Aborted.
#0 __pthread_kill_implementation (no_tid=0, signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:44
Download failed: Invalid argument. Continuing without source file ./nptl/./nptl/pthread_kill.c.
44 ./nptl/pthread_kill.c: No such file or directory.

这就算进入了core的调试界面了,然后输出一下当前堆栈:

(gdb) bt
#0 __pthread_kill_implementation (no_tid=0, signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:44
#1 __pthread_kill_internal (signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:78
#2 __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) at ./nptl/pthread_kill.c:89
#3 0x00007f4b2683bc46 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4 0x00007f4b268227fc in __GI_abort () at ./stdlib/abort.c:79
#5 0x00007f4b268850be in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7f4b269b95dc "%s\n") at ../sysdeps/posix/libc_fatal.c:155
#6 0x00007f4b2689c3fc in malloc_printerr (str=str@entry=0x7f4b269b7234 "free(): invalid pointer") at ./malloc/malloc.c:5660
#7 0x00007f4b2689e26c in _int_free (av=<optimized out>, p=<optimized out>, have_lock=have_lock@entry=0) at ./malloc/malloc.c:4435
#8 0x00007f4b268a09e3 in __GI___libc_free (mem=<optimized out>) at ./malloc/malloc.c:3385
#9 0x0000555e50af21b6 in test () at coredump.cpp:5
#10 0x0000555e50af21c6 in main () at coredump.cpp:9

可以看出main是最后出口,那问题应该就是test函数了,查看一下倒数第二项:

(gdb) frame 9
#9 0x0000555e50af21b6 in test () at coredump.cpp:5
5 delete(p);
(gdb)

这里显示在delete操作的时候出错,然后回去看代码逻辑就可以了,这就一个简单使用了。另外,在项目中,还可以修改core_pattern设置,使其定向生成,并且添加一些重要信息在core文件名上:

jam@jam-S1-Pro-Series:~/Desktop/test$ sudo bash -c 'echo "core-%e-%t" > /proc/sys/kernel/core_pattern'
jam@jam-S1-Pro-Series:~/Desktop/test$ ls
core coredump.cpp test
jam@jam-S1-Pro-Series:~/Desktop/test$ ./test
free(): invalid pointer
Aborted (core dumped)
jam@jam-S1-Pro-Series:~/Desktop/test$ ls
core coredump.cpp core-test-1685784149 test
jam@jam-S1-Pro-Series:~/Desktop/test$

如上,一个包含了时间、发生core dump的命令执行这些信息的core文件就生成了,除此以外,还可以在core前面添加路径来指定core文件的生成位置,另外就是上面%e之类的参数解释如下:

  • %e,添加命令名
  • %g,添加当前gid
  • %h,添加主机名
  • %p,添加pid
  • %s,添加导致core产生的信号
  • %t,添加core生成的unix时间
  • %u,添加当前uid

大致上就这样,简单使用是够了的。

比core dump简单的检测工具

ASan能够很好的检测内存问题,而且它不需要多余的配置,只要gcc版本是4.8以后就行,使用起来就是添加一下编译参数即可:

jam@jam-S1-Pro-Series:~/Desktop/test$ g++ -o test -fsanitize=address -g coredump.cpp && ./test
coredump.cpp: In function ‘void test()’:
coredump.cpp:4:15: warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
4 | char *p = "abaaba";
| ^~~~~~~~
coredump.cpp:5:13: warning: ‘void operator delete(void*, std::size_t)’ called on a pointer to an unallocated object ‘"abaaba"’ [-Wfree-nonheap-object]
5 | delete(p);
| ^
coredump.cpp:5:12: note: assigned here
5 | delete(p);
| ~^~
AddressSanitizer:DEADLYSIGNAL
=================================================================
==9999==ERROR: AddressSanitizer: SEGV on unknown address 0x564037c48010 (pc 0x7f7a4b82df77 bp 0x000000000002 sp 0x7ffe216af7d0 T0)
==9999==The signal is caused by a WRITE memory access.
#0 0x7f7a4b82df77 in bool __sanitizer::atomic_compare_exchange_strong<__sanitizer::atomic_uint8_t>(__sanitizer::atomic_uint8_t volatile*, __sanitizer::atomic_uint8_t::Type*, __sanitizer::atomic_uint8_t::Type, __sanitizer::memory_order) ../../../../src/libsanitizer/sanitizer_common/sanitizer_atomic_clang.h:80
#1 0x7f7a4b82df77 in __asan::Allocator::AtomicallySetQuarantineFlagIfAllocated(__asan::AsanChunk*, void*, __sanitizer::BufferedStackTrace*) ../../../../src/libsanitizer/asan/asan_allocator.cpp:620
#2 0x7f7a4b82df77 in __asan::Allocator::Deallocate(void*, unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType) ../../../../src/libsanitizer/asan/asan_allocator.cpp:696
#3 0x7f7a4b8c14ec in operator delete(void*, unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:164
#4 0x564037c47275 in test() /home/jam/Desktop/test/coredump.cpp:5
#5 0x564037c47285 in main /home/jam/Desktop/test/coredump.cpp:9
#6 0x7f7a4b02350f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#7 0x7f7a4b0235c8 in __libc_start_main_impl ../csu/libc-start.c:381
#8 0x564037c47184 in _start (/home/jam/Desktop/test/test+0x1184)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ../../../../src/libsanitizer/sanitizer_common/sanitizer_atomic_clang.h:80 in bool __sanitizer::atomic_compare_exchange_strong<__sanitizer::atomic_uint8_t>(__sanitizer::atomic_uint8_t volatile*, __sanitizer::atomic_uint8_t::Type*, __sanitizer::atomic_uint8_t::Type, __sanitizer::memory_order)
==9999==ABORTING
jam@jam-S1-Pro-Series:~/Desktop/test$

如上添加-fsanitize=address -g,然后运行编译生成的可执行文件就可以直接查看问题堆栈了。如果链接的时候出现找不到/usr/lib64/libasan.so的问题,就需要下载库文件了:sudo apt install libasan。其实windows下也有着windbg这样的工具提供core dump之类的使用,不过暂时用不上,留待以后吧。

网络编程的简单测试命令

在网络编程中,常常需要检查的就是服务器进程的状态了,对应的就是netstat和ps这两个常用命令了,比如有一个服务器程序helloserver.exe,然后把它运行起来,然后查看一下它的状态:

PS C:\Users\penta> netstat -anop tcp | findstr 8090
TCP 0.0.0.0:8090 0.0.0.0:0 LISTENING 30428
PS C:\Users\penta> tasklist | findstr 30428
helloserver.exe 30428 Console 1 3,520 K
PS C:\Users\penta>

在windows中使用,效果如上,linux里面呢,字符查找就该换一下了:

jam@jam-S1-Pro-Series:~/Desktop/test$ netstat -lnpt | grep helloserver
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:8090 0.0.0.0:* LISTEN 10362/./helloserver
jam@jam-S1-Pro-Series:~/Desktop/test$ netstat -lnpt | grep 8090
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 0.0.0.0:8090 0.0.0.0:* LISTEN 10362/./helloserver
jam@jam-S1-Pro-Series:~/Desktop/test$ ps -ef | grep helloserver
jam 10362 5746 0 18:11 pts/0 00:00:00 ./helloserver 8090
jam 10580 10363 0 18:15 pts/1 00:00:00 grep --color=auto helloserver
jam@jam-S1-Pro-Series:~/Desktop/test$

没多大问题,想要更复杂点,可以使用wireshark或者其他的抓包工具了。

多线程多进程调试

关于多线程和多进程,在linux中可以使用ps命令查看进程,pstree查看含有父子关系的进程树,如下是一个多进程例子,跑起来,然后阻塞它看看效果:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid = fork();
if(pid == 0) { // 子进程
printf("I am child, my pid = %d, my parent pid = %d\n", getpid(), getppid());
sleep(20);
} else if(pid > 0) { // 父进程
printf("I am parent, my pid = %d, my child pid = %d\n", getpid(), pid);
wait(NULL); // 等待子进程退出
} else {
perror("fork error!\n");
return -1;
}
return 0;
}
jam@jam-S1-Pro-Series:~/Desktop/test$ ps -ef | grep multi_process
jam 11211 5746 0 19:11 pts/0 00:00:00 ./multi_process
jam 11212 11211 0 19:11 pts/0 00:00:00 ./multi_process
jam 11214 10905 0 19:11 pts/1 00:00:00 grep --color=auto multi_process
jam@jam-S1-Pro-Series:~/Desktop/test$ pstree -p | grep multi_process
| |-gnome-terminal-(5728)-+-bash(5746)---multi_process(11211)---multi_process(11212)
jam@jam-S1-Pro-Series:~/Desktop/test$

如上,使用ps可以查看进程,但使用pstree展现起来,效果更加好。那么,多线程呢?设计了一个多线程的例子,在进程中新开了两个子线程,线程sleep个10秒,然后再用ps和pstree来查看一下:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//volatile关键字用于通知计算机代理可以改变该值,这里表示共享数据,不加static volatile也行
static volatile long long sum = 0;
void *incr(void *arg) {
printf("[%s] begin\n", (char *)arg);
for (int i = 0;i < 1e7; i++)
sum += 1;
printf("[%s] done\n", (char *)arg);
sleep(10);
//线程结束
return NULL;
}
void *des(void *arg) {
printf("[%s] begin\n", (char *)arg);
for (int i = 0; i < 1e7; i++)
sum -= 1;
printf("[%s] done\n", (char *)arg);
sleep(10);
//线程结束
return NULL;
}
int main() {
//创建两个线程,传入不同字符作为another_main的参数
pthread_t p1, p2;
printf("main begin, sum = %lld\n", sum);
pthread_create(&p1, NULL, incr, "A");
pthread_create(&p2, NULL, des, "B");
//等待p1、p2对应的线程结束才结束main运行
pthread_join(p1, NULL);
pthread_join(p2, NULL);
printf("[main] sum: %lld\n", sum);
return 0;
}
jam@jam-S1-Pro-Series:~/Desktop/test$ ps -ef | grep multi_pthread
jam 11259 5746 7 19:15 pts/0 00:00:00 ./multi_pthread
jam 11263 10905 0 19:15 pts/1 00:00:00 grep --color=auto multi_pthread
jam@jam-S1-Pro-Series:~/Desktop/test$ pstree -p | grep multi_pthread
| |-gnome-terminal-(5728)-+-bash(5746)---multi_pthread(11259)-+-{multi_pthread}(11260)
| | | `-{multi_pthread}(11261)
jam@jam-S1-Pro-Series:~/Desktop/test$

gdb在多线程多进程的调试

如上,查看效果还是pstree更好用,当然还有更多的进阶版的,但简单使用这里就够了,问题是这些多进程多线程程序的调试。编译gdb调试文件

# 多进程
jam@jam-S1-Pro-Series:~/Desktop/test$ gcc multi_process.c -o multi_process -g
jam@jam-S1-Pro-Series:~/Desktop/test$ ./multi_process
I am parent, my pid = 11211, my child pid = 11212
I am child, my pid = 11212, my parent pid = 11211
# 多线程
jam@jam-S1-Pro-Series:~/Desktop/test$ gcc multi_pthread.c -o multi_pthread -lpthread -g
jam@jam-S1-Pro-Series:~/Desktop/test$ ./multi_pthread
main begin, sum = 0
[A] begin
[B] begin
[A] done
[B] done
[main] sum: -7796662
jam@jam-S1-Pro-Series:~/Desktop/test$

在gdb中,可以使用info来查看信息,查看进程的命令是info inferiors,查看线程的是info threads,汇总一下这些个命令:

命令 解释
info inferiors 查看所有进程
inferiors n 切换到编号为n的进程
info threads 查看所有线程
thread n 切换到编号为n的线程
detach inferiors n detach编号为n的进程
set schedule-multiple off 只有当前进程执行,其他进程挂起
set schedule-multiple on 所有进程正常执行
show schedule-multiple 查看schedule-multiple的值

关于多线程调试,其实经常遇到的就是检查死锁问题,如果出现死锁,可以检查进程号,在使用pstack来查看其堆栈,显示效果会如同上面的core dump的展示。简单总结就这样吧。

更多文章记录可以看这个

posted @   夏目&贵志  阅读(258)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
点击右上角即可分享
微信分享提示