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 @ 2023-06-03 22:22  夏目&贵志  阅读(473)  评论(0)    收藏  举报