release版本core推导与相应工具学习


一般进行开发工作时,会有debug版本和release版本之分,debug版本方便调试,但与release版本相比,体积臃肿,运行速度慢。一般debug版本会带有符号表与调试信息,而release版本会把符号表和调试信息等strip掉。这时候,如果release版本出现core,而又不具备在debug版本复现的条件,就需要直接在release版本定位,由于没有符号表与调试信息,定位起来会比较麻烦。
先补充一下基础知识,涉及以下几个方面:
1.常用命令
2.obj文件与可执行文件段结构
3.进程内存映像
4.符号表与调试信息
5.gdb命令

一、常用命令

以下介绍的命令都是gnu binutils套件的一部分,手册地址链接。包含工具的概览如下:

命令用处
ar创建、修改和提取存档
nm列出对象文件中的符号
objcopy复制和翻译对象文件
objdump显示对象文件中的信息
ranlib生成索引以存档内容
size列出节大小和总大小
strings列出文件中的可打印字符串
strip丢弃符号
c++filt用于对编码C++符号进行反处理的筛选器,其实就是反编译
cxxfiltMS-DOS name for c++filt
addr2line将地址转换为文件和行
windmcWindows 消息资源的生成器
windres操作Windows 资源
dlltool创建生成和使用 DLL 所需的文件
readelf显示 ELF 格式文件的内容
elfedit更新 ELF 文件的 ELF 标头和属性

1.ar

ar - create, modify, and extract from archives

ar命令最常见的用法是将目标文件打包为静态链接库。

指令参数:

-d 删除备存文件中的成员文件。
-r 将文件插入备存文件中。
-t 显示备存文件中所包含的文件。
-x 自备存文件中取出成员文件。

选项参数:

c 建立备存文件。
v 程序执行时显示详细的信息。

使用实例:
1.创建静态库

$ ar -crv libmakefile_test.a cli.o lib.o resource.o
r - cli.o
r - lib.o
r - resource.o

2.使用静态库编译

$ gcc -o app_test_static  ./app/main.c -I ./include -L ./ -lmakefile_test -static
$ gcc -o app_test_dynamic ./app/main.c -I ./include -L ./ -lmakefile_test

-I选项指定头文件搜索路径
-L选项指定库文件搜索路径
-l选项指明需要链接库文件路径下的哪一个库
-static 在支持动态链接的系统,这个参数将覆盖-pie选项并且防止链接到共享库。在其他系统,这个参数无用。
查看编译的文件:

$ file app_test_static
app_test_static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=2f7ba0b5feb0bee3acd3113608215c8b3fa25c43, for GNU/Linux 3.2.0, not stripped
$ file app_test_dynamic
app_test_dynamic: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b8f4aae218ee51b8559f737609d156d4dccd111c, for GNU/Linux 3.2.0, not stripped

关于静态库和动态库编译及使用,可查看这篇文章

2.nm

nm - list symbols from object files

常用参数:
-A: 在输出时每行前加上文件名;
-a: 输出所有符号,包含debugger-only symbols;
-B: BSD码显示,兼容MIPS nm;
-C: 将低级符号名解析为用户级名字,可以使得C++函数名更具可读性;
-D: 显示动态符号。该选项只对动态目标(如特定类型的共享库)有意义;
-f format/–format=format 使用format格式输出。format可以选取bsd、sysv或posix,该选项在GNU的nm中有用。默认为bsd
-g: 只显示外部符号;
-l: 对于每个符号,使用debug信息找到文件名和行号;
-n: 按符号对应地址的顺序排序,而非按符号名字字符顺序排序;
-P: 按照POSIX2.0标准格式输出,等同于使用 -f posix;
-p: 按照目标文件中遇到的符号顺序显示,不排序;
-r: 反转排序;
-s: 当列出库成员符号时,包含索引。索引的内容:模块和其包含名字的映射;
-u: 只显示未定义符号;
–defined-only: 只显示定义了的符号。

$ cat app/main.c
#include<stdio.h>
#include"../include/cli.h"
#include"../include/lib.h"
#include"../include/resource.h"

int main()
{
    installcmd();
    openlib();
    initresource();
    return 0;
}
$ nm -n main.o
                 U _GLOBAL_OFFSET_TABLE_
                 U initresource
                 U installcmd
                 U openlib
0000000000000000 T main

第二列的符号含义

符号含义
A该符号的值是绝对的,在以后的链接过程中,不允许进行改变。这样的符号值,常常出现在中断向量表中,例如用符号来表示各个中断向量函数在中断向量表中的位置。
B该符号的值出现在非初始化数据段(bss)中。例如,在一个文件中定义全局static int test。则该符号test的类型为b,位于bss section中。其值表示该符号在bss段中的偏移。一般而言,bss段分配于RAM中。
C该符号为common。common symbol是未初始话数据段。该符号没有包含于一个普通section中。只有在链接过程中才进行分配。符号的值表示该符号需要的字节数。例如在一个c文件中,定义int test,并且该符号在别的地方会被引用,则该符号类型即为C。否则其类型为B。
D该符号位于初始化数据段中。一般来说,分配到data section中。例如定义全局int baud_table[5] = {9600, 19200, 38400, 57600, 115200},则会分配于初始化数据段中。
G该符号也位于初始化数据段中。主要用于small object提高访问small data object的一种方式。
I该符号是对另一个符号的间接引用。
N该符号是一个debugging符号。
R该符号位于只读数据区。例如定义全局const int test[] = {123, 123};则test就是一个只读数据区的符号。注意在cygwin下如果使用gcc直接编译成MZ格式时,源文件中的test对应_test,并且其符号类型为D,即初始化数据段中。但是如果使用m6812-elf-gcc这样的交叉编译工具,源文件中的test对应目标文件的test,即没有添加下划线,并且其符号类型为R。一般而言,位于rodata section。值得注意的是,如果在一个函数中定义const char *test = “abc”, const char test_int = 3。使用nm都不会得到符号信息,但是字符串“abc”分配于只读存储器中,test在rodata section中,大小为4。
S符号位于非初始化数据区,用于small object。
T该符号位于代码区text section。
U该符号在当前文件中是未定义的,即该符号的定义在别的文件中。例如,当前文件调用另一个文件中定义的函数,在这个被调用的函数在当前就是未定义的;但是在定义它的文件中类型是T。但是对于全局变量来说,在定义它的文件中,其符号类型为C,在使用它的文件中,其类型为U。
V该符号是一个weak object。
W该符号是没有被明确标记为weak object的弱符号类型。
-该符号是a.out格式文件中的stabs symbol。
?该符号类型没有定义。

3.objcopy

objcopy - copy and translate object files
常用参数:
–set-start val
设定新文件的起始地址为val,并不是所有格式的目标文件都支持设置起始地址。

–change-start incr
–adjust-start incr
通过增加incr量来调整起始地址,并不是所有格式的目标文件都支持设置起始地址。

–change-address incr
–adjust-vma incr
通过增加incr量调整所有sections的VMA(virtual memory address)和LMA(linear memory address),以及起始地址。

–only-keep-debug
对文件进行strip,移走所有不会被–strip-debug移走的section,并且保持调试相关的section原封不动。
–add-gnu-debuglink= 增加 .gnu_debuglink到文件中
-S 移除所有符号表
-g 移除调试符号和节

$objcopy --only-keep-debug algorithm algorithm.dbg
$ls
algorithm  algorithm.dbg
$strip -s algorithm
$objcopy --add-gnu-debuglink=$(pwd)/algorithm.dbg algorithm
$readelf -S algorithm
...
[28] .gnu_debuglink    PROGBITS         0000000000000000  0000303c
       0000000000000014  0000000000000000           0     0     4
...

4.objdump

objdump - display information from object files
常用参数:
-a :显示档案库的成员信息,类似ls -l将lib*.a的信息列出。
-C :将底层的符号名解码成用户级名字,除了去掉所开头的下划线之外,还使得C++函数名以可理解的方式显示出来。
-g :显示调试信息。企图解析保存在文件中的调试信息并以C语言的语法显示出来。仅仅支持某些类型的调试信息。有些其他的格式被readelf -w支持。
-e :类似-g选项,但是生成的信息是和ctags工具相兼容的格式。
-d :从objfile中反汇编那些特定指令机器码的section。可用于查看对应汇编的行号。
-D :与 -d 类似,但反汇编所有section.
–endian={big|little} :指定目标文件的大小端。这个项将影响反汇编出来的指令。在反汇编的文件没描述小端信息的时候用。例如S-records.
-f :显示objfile中每个文件的整体头部摘要信息。
-h :显示目标文件各个section的头部摘要信息。
-i :显示对于 -b 或者 -m 选项可用的架构和目标格式列表。
-I, --include=DIR 将指定文件夹添加到源文件搜索列表
-j name:仅仅显示指定名称为name的section的信息
-l:用文件名和行号标注相应的目标代码,仅仅和-d、-D或者-r一起使用使用-ld和使用-d的区别不是很大,在源码级调试的时候有用,要求编译时使用了-g之类的调试编译选项。
-m machine :指定反汇编目标文件时使用的架构,当待反汇编文件本身没描述架构信息的时候(比如S-records),这个选项很有用。可以用-i选项列出这里能够指定的架构.
-t :显示文件的符号表。类似于nm -s提供的信息
-T :显示文件的动态符号表,仅仅对动态目标文件意义,比如某些共享库。它显示的信息类似于 nm -D|–dynamic 显示的信息。
-r :显示文件的重定位入口。如果和-d或者-D一起使用,重定位部分以反汇编后的格式显示出来。
-R :显示文件的动态重定位入口,仅仅对于动态目标文件意义,比如某些共享库。
-s :显示指定section的完整内容。默认所有的非空section都会被显示。
-S :尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,效果比较明显。隐含了-d参数。
–show-raw-insn :反汇编的时候,显示每条汇编指令对应的机器码,如不指定–prefix-addresses,这将是缺省选项。
–no-show-raw-insn :反汇编时,不显示汇编指令的机器码,如指定–prefix-addresses,这将是缺省选项。
–start-address=address :从指定地址开始显示数据,该选项影响-d、-r和-s选项的输出。
–stop-address=address :显示数据直到指定地址为止,该项影响-d、-r和-s选项的输出。
-x :显示所可用的头信息,包括符号表、重定位入口。-x 等价于-a -f -h -r -t 同时指定。
-z :一般反汇编输出将省略大块的零,该选项使得这些零块也被反汇编。
@file 可以将选项集中到一个文件中,然后使用这个@file将选项载入。

$objdump -h out/usr/local/bin/algorithm

out/usr/local/bin/algorithm:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000000318  0000000000000318  00000318  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.gnu.property 00000030  0000000000000338  0000000000000338  00000338  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  0000000000000368  0000000000000368  00000368  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .note.ABI-tag 00000020  000000000000038c  000000000000038c  0000038c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .gnu.hash     00000028  00000000000003b0  00000000000003b0  000003b0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynsym       00000108  00000000000003d8  00000000000003d8  000003d8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .dynstr       000000ca  00000000000004e0  00000000000004e0  000004e0  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version  00000016  00000000000005aa  00000000000005aa  000005aa  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .gnu.version_r 00000030  00000000000005c0  00000000000005c0  000005c0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rela.dyn     000000d8  00000000000005f0  00000000000005f0  000005f0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .rela.plt     00000060  00000000000006c8  00000000000006c8  000006c8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 11 .init         0000001b  0000000000001000  0000000000001000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .plt          00000050  0000000000001020  0000000000001020  00001020  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .plt.got      00000010  0000000000001070  0000000000001070  00001070  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .plt.sec      00000040  0000000000001080  0000000000001080  00001080  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 15 .text         00000151  00000000000010c0  00000000000010c0  000010c0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 16 .fini         0000000d  0000000000001214  0000000000001214  00001214  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 17 .rodata       0000002d  0000000000002000  0000000000002000  00002000  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 18 .eh_frame_hdr 0000003c  0000000000002030  0000000000002030  00002030  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 19 .eh_frame     000000bc  0000000000002070  0000000000002070  00002070  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 20 .init_array   00000008  0000000000003d80  0000000000003d80  00002d80  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 21 .fini_array   00000008  0000000000003d88  0000000000003d88  00002d88  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 22 .dynamic      00000210  0000000000003d90  0000000000003d90  00002d90  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 23 .got          00000060  0000000000003fa0  0000000000003fa0  00002fa0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 24 .data         00000010  0000000000004000  0000000000004000  00003000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 25 .bss          00000010  0000000000004010  0000000000004010  00003010  2**4
                  ALLOC
 26 .comment      0000002b  0000000000000000  0000000000000000  00003010  2**0
                  CONTENTS, READONLY
 27 .debug_aranges 00000060  0000000000000000  0000000000000000  0000303b  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 28 .debug_info   000004a6  0000000000000000  0000000000000000  0000309b  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 29 .debug_abbrev 00000244  0000000000000000  0000000000000000  00003541  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 30 .debug_line   0000013a  0000000000000000  0000000000000000  00003785  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 31 .debug_str    000002a8  0000000000000000  0000000000000000  000038bf  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 32 .debug_line_str 0000011c  0000000000000000  0000000000000000  00003b67  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 33 .debug_loclists 00000028  0000000000000000  0000000000000000  00003c83  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 34 .debug_rnglists 00000017  0000000000000000  0000000000000000  00003cab  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
$objdump -a out/usr/local/lib/libparallelmenu.a
In archive out/usr/local/lib/libparallelmenu.a:

parallelmenu.o:     file format elf64-x86-64
rw-r--r-- 0/0   7216 Jan  1 08:00 1970 parallelmenu.o


parallelmenu_api.o:     file format elf64-x86-64
rw-r--r-- 0/0  25872 Jan  1 08:00 1970 parallelmenu_api.o

5.strip

strip - discard symbols and other data from object files
常用参数:
-s:去除所有符号表
-g:只去除debug符号表
-o:将strip的内容存储到另外的指定文件

$ strip a.out -o a_strip.out
$ ls
a.out  a.out.dbg  a_strip.out  test.c

如果使用了-g,那么调试时还是能看到函数名的,只不过可能看不到行号了。
实例:在使用-g前有38个节,debug就有8个

$readelf -S algorithm|grep debug
  [28] .debug_aranges    PROGBITS         0000000000000000  0000303b
  [29] .debug_info       PROGBITS         0000000000000000  0000309b
  [30] .debug_abbrev     PROGBITS         0000000000000000  00003541
  [31] .debug_line       PROGBITS         0000000000000000  00003785
  [32] .debug_str        PROGBITS         0000000000000000  000038bf
  [33] .debug_line_str   PROGBITS         0000000000000000  00003b67
  [34] .debug_loclists   PROGBITS         0000000000000000  00003c83
  [35] .debug_rnglists   PROGBITS         0000000000000000  00003cab
$readelf -p .strtab algorithm

String dump of section '.strtab':
  [     1]  Scrt1.o
  [     9]  __abi_tag
  [    13]  main.c
  [    1a]  crtstuff.c
  [    25]  deregister_tm_clones
  [    3a]  __do_global_dtors_aux
  [    50]  completed.0
  [    5c]  __do_global_dtors_aux_fini_array_entry
  [    83]  frame_dummy
  [    8f]  __frame_dummy_init_array_entry
  [    ae]  interface.c
  [    ba]  __FRAME_END__
  [    c8]  _DYNAMIC
  [    d1]  __GNU_EH_FRAME_HDR
  [    e4]  _GLOBAL_OFFSET_TABLE_
  [    fa]  __libc_start_main@GLIBC_2.34
  [   117]  _ITM_deregisterTMCloneTable
  [   133]  puts@GLIBC_2.2.5
  [   144]  stdin@GLIBC_2.2.5
  [   156]  _edata
  [   15d]  _fini
  [   163]  interface
  [   16d]  __data_start
  [   17a]  __gmon_start__
  [   189]  __dso_handle
  [   196]  _IO_stdin_used
  [   1a5]  _end
  [   1aa]  __bss_start
  [   1b6]  main
  [   1bb]  BST
  [   1bf]  __TMC_END__
  [   1cb]  parallelmenu
  [   1d8]  _ITM_registerTMCloneTable
  [   1f2]  __cxa_finalize@GLIBC_2.2.5
  [   20d]  _init
  [   213]  getc@GLIBC_2.2.5
$readelf -p .dynstr algorithm

String dump of section '.dynstr':
  [     1]  _ITM_deregisterTMCloneTable
  [    1d]  __gmon_start__
  [    2c]  _ITM_registerTMCloneTable
  [    46]  parallelmenu
  [    53]  BST
  [    57]  __cxa_finalize
  [    66]  __libc_start_main
  [    78]  getc
  [    7d]  puts
  [    82]  stdin
  [    88]  libparallelmenu.so.0
  [    9d]  libbst.so.0
  [    a9]  libc.so.6
  [    b3]  GLIBC_2.2.5
  [    bf]  GLIBC_2.34
$ readelf -s algorithm

Symbol table '.dynsym' contains 11 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _[...]@GLIBC_2.34 (2)
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterT[...]
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (3)
     4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND BST
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND parallelmenu
     7: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMC[...]
     8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getc@GLIBC_2.2.5 (3)
     9: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND [...]@GLIBC_2.2.5 (3)
    10: 0000000000004010     8 OBJECT  GLOBAL DEFAULT   26 stdin@GLIBC_2.2.5 (3)

Symbol table '.symtab' contains 42 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS Scrt1.o
     2: 000000000000038c    32 OBJECT  LOCAL  DEFAULT    4 __abi_tag
     3: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     4: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
     5: 0000000000001110     0 FUNC    LOCAL  DEFAULT   16 deregister_tm_clones
     6: 0000000000001140     0 FUNC    LOCAL  DEFAULT   16 register_tm_clones
     7: 0000000000001180     0 FUNC    LOCAL  DEFAULT   16 __do_global_dtors_aux
     8: 0000000000004018     1 OBJECT  LOCAL  DEFAULT   26 completed.0
     9: 0000000000003d88     0 OBJECT  LOCAL  DEFAULT   22 __do_global_dtor[...]
    10: 00000000000011c0     0 FUNC    LOCAL  DEFAULT   16 frame_dummy
    11: 0000000000003d80     0 OBJECT  LOCAL  DEFAULT   21 __frame_dummy_in[...]
    12: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS interface.c
    13: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    14: 0000000000002128     0 OBJECT  LOCAL  DEFAULT   20 __FRAME_END__
    15: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS
    16: 0000000000003d90     0 OBJECT  LOCAL  DEFAULT   23 _DYNAMIC
    17: 0000000000002030     0 NOTYPE  LOCAL  DEFAULT   19 __GNU_EH_FRAME_HDR
    18: 0000000000003fa0     0 OBJECT  LOCAL  DEFAULT   24 _GLOBAL_OFFSET_TABLE_
    19: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_mai[...]
    20: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterT[...]
    21: 0000000000004000     0 NOTYPE  WEAK   DEFAULT   25 data_start
    22: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5
    23: 0000000000004010     8 OBJECT  GLOBAL DEFAULT   26 stdin@GLIBC_2.2.5
    24: 0000000000004010     0 NOTYPE  GLOBAL DEFAULT   25 _edata
    25: 0000000000001214     0 FUNC    GLOBAL HIDDEN    17 _fini
    26: 00000000000011d0    65 FUNC    GLOBAL DEFAULT   16 interface
    27: 0000000000004000     0 NOTYPE  GLOBAL DEFAULT   25 __data_start
    28: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    29: 0000000000004008     0 OBJECT  GLOBAL HIDDEN    25 __dso_handle
    30: 0000000000002000     4 OBJECT  GLOBAL DEFAULT   18 _IO_stdin_used
    31: 0000000000004020     0 NOTYPE  GLOBAL DEFAULT   26 _end
    32: 00000000000010e0    38 FUNC    GLOBAL DEFAULT   16 _start
    33: 0000000000004010     0 NOTYPE  GLOBAL DEFAULT   26 __bss_start
    34: 00000000000010c0    22 FUNC    GLOBAL DEFAULT   16 main
    35: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND BST
    36: 0000000000004010     0 OBJECT  GLOBAL HIDDEN    25 __TMC_END__
    37: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND parallelmenu
    38: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMC[...]
    39: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@G[...]
    40: 0000000000001000     0 FUNC    GLOBAL HIDDEN    12 _init
    41: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getc@GLIBC_2.2.5
$strip -g algorithm
$file algorithm
algorithm: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9b9abe6929571743b9a7f78b269953a53224a6b5, for GNU/Linux 3.2.0, not stripped

此时仍显示未被strip,但此时就只有30个节了。而且使用gdb已经无法看到行号了。
再次使用-s,则只剩下28个节

$strip -s algorithm
$file algorithm
algorithm: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9b9abe6929571743b9a7f78b269953a53224a6b5, for GNU/Linux 3.2.0, stripped

.symtab和 .strtab已经被去除, .shstrtab还在(专门存储Section名字的Section(Section Header String Table Section,简写为shstrtab)),.dynsym和.dynstr也都还在。因此大多数函数都不能断了,但是外部函数还能断,因为.dynsym还存在。

6.addr2line

addr2line - convert addresses into file names and line numbers
常用参数:
-a:在函数名、文件名和行号信息之前,以十六进制形式显示地址。
-C:将低级别的符号名解码为用户级别的名字。
-e:指定需要转换地址的可执行文件名,默认文件是a.out。
-f:在显示文件名、行号信息的同时显示函数名。
-s:仅显示每个文件名(the base of each file name)去除目录名。
-i:如果需要转换的地址是一个内联函数,则还将打印返回第一个非内联函数的信息。
-j:读取指定section的偏移而不是绝对地址。
-p:使打印更加人性化:每个地址(location)的信息都打印在一行上。
-r:启用或禁用递归量限制。
编译进程如下,执行后段错误:

int main()
{
	int *p = NULL;
	*p=0;
	printf("hello world!\n");
}

dmesg信息如下:

[ 2022.242578] a.out[6050]: segfault at 0 ip 0000558c30b8f161 sp 00007ffd40f0e6f0 error 6 in a.out[558c30b8f000+1000]
[ 2022.242606] Code: 00 c3 0f 1f 80 00 00 00 00 f3 0f 1e fa e9 77 ff ff ff f3 0f 1e fa 55 48 89 e5 48 83 ec 10 48 c7 45 f8 00 00 00 00 48 8b 45 f8 <c7> 00 00 00 00 00 48 8d 3d 96 0e 00 00 e8 dd fe ff ff b8 00 00 00
[ 2022.242619] potentially unexpected fatal signal 11.

0000558c30b8f161 - 558c30b8f000 = 0x161
需要注意有的系统可以直接使用报错地址查到,有的系统需要减去基地址

7.readelf

-a 显示全部信息,等价于 -h -l -S -s -r -d -V -A -I.
-h 显示elf文件开始的文件头信息.
-l - 显示程序头(段头)信息(如果有的话)。
-S 显示节头信息(如果有的话)。
-g 显示节组信息(如果有的话)。
-t 显示节的详细信息(-S的)。
-s 显示符号表段中的项(如果有的话)。
-e 显示全部头信息,等价于: -h -l -S -n --notes 显示note段(内核注释)的信息。
-r 显示可重定位段的信息。
-u 显示unwind段信息。当前只支持IA64 ELF的unwind段信息。
-d 显示动态段的信息。 .dynamic段
-V 显示版本段的信息。
-A 显示CPU构架信息。
-D 使用动态段中的符号表显示符号,而不是使用符号段。
-x 以16进制方式显示指定段内内容。number指定段表中段的索引,或字符串指定文件中的段名。
-p dump出指定节的内容,看指定节的内容就用他
-w[liaprmfFsoR] or --debug-dump[=line,=info,=abbrev,=pubnames,=aranges,=macro,=frames,=frames-interp,=str,=loc,=Ranges] 显示调试段中指定的内容。
-I 显示符号的时候,显示bucket list长度的柱状图。
-W 宽行输出。
@file 可以将选项集中到一个文件中,然后使用这个@file选项载入。

二、obj文件与可执行文件段结构

linux下的obj文件和可执行文件都是elf格式,其结构如下:

ELF头部(ELF Header):ELF头部是ELF文件的第一个部分,包含了文件类型、机器类型、入口地址、程序头表偏移量、节头表偏移量等信息。

程序头表(Program Header Table):程序头表描述了可执行文件或共享库的段信息,包括段类型、段的虚拟地址、文件偏移量、段大小等信息。

节头表(Section Header Table):节头表描述了ELF文件中的所有节,包括代码段、数据段、符号表、重定位表等。每个节头表项包含了节的名称、类型、大小、偏移量等信息。

节区(Section):ELF文件的数据被组织成许多节,每个节都有一个唯一的名称和类型。常见的节区包括.text(代码段)、.data(数据段)、.bss(未初始化的数据段)等。

符号表(Symbol Table):符号表包含了ELF文件中的所有符号信息,包括函数名、变量名、常量等。每个符号表项包含了符号的名称、类型、值、大小等信息。

字符串表(String Table):字符串表包含了ELF文件中使用的所有字符串,包括节名、符号名、库名等。

重定位表(Relocation Table):重定位表包含了ELF文件中的所有重定位信息,用于修正代码和数据的地址。每个重定位表项包含了需要修正的地址、修正方式等信息。

三、进程内存映像

调试core时加载带调试信息so时需要加地址的原因:

参见《深入理解计算机系统笔记–备忘》:代码段与数据段之间有空隙(为了对齐要求),而栈,共享模块,堆由于安全性还会使用地址空间布局随机化,每次程序运行时,他们的地址都会改变。

core中info proc mappings得到地址为当时动态库加载的地址,而add-symbol-file需要指定文件的text段地址,因此我们的目标是调试时加载的地址就是当时动态库.text段的地址,debug信息也要加载在这。

四、符号表与调试信息

符号表包含.dynsym节和.symtab节
.dynsym保存了引用来自外部文件符号的全局符号。如printf库函数。.dynsym保存的符号是.symtab所保存符合的子集,.symtab中还保存了可执行文件的本地符号。如全局变量,代码中定义的本地函数等。
.dynsym对于动态链接可执行文件的执行是必需的,而.symtab只是用来进行调试和链接的。

Symbol table '.dynsym' contains 7 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _[...]@GLIBC_2.34 (2)
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterT[...]
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (3)
     4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMC[...]
     6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND [...]@GLIBC_2.2.5 (3)

Symbol table '.symtab' contains 36 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS Scrt1.o
     2: 000000000000038c    32 OBJECT  LOCAL  DEFAULT    4 __abi_tag
     3: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
     4: 0000000000001090     0 FUNC    LOCAL  DEFAULT   16 deregister_tm_clones
     5: 00000000000010c0     0 FUNC    LOCAL  DEFAULT   16 register_tm_clones
     6: 0000000000001100     0 FUNC    LOCAL  DEFAULT   16 __do_global_dtors_aux
     7: 0000000000004010     1 OBJECT  LOCAL  DEFAULT   26 completed.0
     8: 0000000000003dc0     0 OBJECT  LOCAL  DEFAULT   22 __do_global_dtor[...]
     9: 0000000000001140     0 FUNC    LOCAL  DEFAULT   16 frame_dummy
    10: 0000000000003db8     0 OBJECT  LOCAL  DEFAULT   21 __frame_dummy_in[...]
    11: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c
    12: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    13: 00000000000020f0     0 OBJECT  LOCAL  DEFAULT   20 __FRAME_END__
    14: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS
    15: 0000000000003dc8     0 OBJECT  LOCAL  DEFAULT   23 _DYNAMIC
    16: 0000000000002014     0 NOTYPE  LOCAL  DEFAULT   19 __GNU_EH_FRAME_HDR
    17: 0000000000003fb8     0 OBJECT  LOCAL  DEFAULT   24 _GLOBAL_OFFSET_TABLE_
    18: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_mai[...]
    19: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterT[...]
    20: 0000000000004000     0 NOTYPE  WEAK   DEFAULT   25 data_start
    21: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5
    22: 0000000000004010     0 NOTYPE  GLOBAL DEFAULT   25 _edata
    23: 0000000000001168     0 FUNC    GLOBAL HIDDEN    17 _fini
    24: 0000000000004000     0 NOTYPE  GLOBAL DEFAULT   25 __data_start
    25: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    26: 0000000000004008     0 OBJECT  GLOBAL HIDDEN    25 __dso_handle
    27: 0000000000002000     4 OBJECT  GLOBAL DEFAULT   18 _IO_stdin_used
    28: 0000000000004018     0 NOTYPE  GLOBAL DEFAULT   26 _end
    29: 0000000000001060    38 FUNC    GLOBAL DEFAULT   16 _start
    30: 0000000000004010     0 NOTYPE  GLOBAL DEFAULT   26 __bss_start
    31: 0000000000001149    30 FUNC    GLOBAL DEFAULT   16 main
    32: 0000000000004010     0 OBJECT  GLOBAL HIDDEN    25 __TMC_END__
    33: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMC[...]
    34: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@G[...]
    35: 0000000000001000     0 FUNC    GLOBAL HIDDEN    12 _init

Linux进程的调试信息包含在ELF文件中的多个段中,这些段通常以.debug开头,例如:

.debug_info:包含了程序的调试信息,例如类型定义、变量定义、函数定义等。
.debug_line:包含了程序的源代码行号信息,用于在调试器中显示源代码。
.debug_str:包含了程序中使用的所有字符串,例如变量名、函数名等。
.debug_abbrev:包含了调试信息的缩写表,用于压缩调试信息。
.debug_loc:包含了程序中局部变量的地址信息,用于在调试器中显示局部变量的值。
.debug_ranges:包含了程序中全局变量和静态变量的地址范围信息,用于在调试器中显示变量的值。

五、GLibc版本查看

有时排查问题需要明确c库的版本,方法列举如下:

ldd --version
strings /lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC
getconf GNU_LIBC_VERSION
/lib/x86_64-linux-gnu/libc.so.6 #直接执行

六、gdb相关命令

1.add-symbol-file

(gdb) help add-symbol-file
从FILE中加载符号,并假定FILE已经被动态加载。
用法:add-symbol-file FILE [-readnow | -readnever] [-o OFF] [ADDR] [-s SECT-NAME SECT-ADDR]…
ADDR是文件.text的起始地址。
每个-s,参数指定了一个段名和段地址,如果数据段和bss段与文本不连续,则应指定该参数。
SECT-NAME 是一个被加载在 SECT-ADDR的段的名字.
OFF是一个可选的偏移量,它被添加到默认加载地址中未指定其他地址的所有节的。
(OFF is an optional offset which is added to the default load addresses of all sections for which no other address was specified.)
'-readnow’选项将导致GDB立即读取整个符号文件。这可能使命令执行变慢,但可能使未来的操作更快。
'-readnever’选项将阻止GDB读取符号文件的符号调试信息。

2.info proc mappings

List memory regions mapped by the specified process.
列出特定进程的内存区域

3. info sharedlibrary

Status of loaded shared object libraries.
已加载共享库的状态

4.info proc mappings和info sharedlibrary的差异

wsl@My-win:~/project/algorithm$ objdump -h /usr/local/lib/libparallelmenu.so.0.0.0

/usr/local/lib/libparallelmenu.so.0.0.0:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .note.gnu.property 00000020  00000000000002a8  00000000000002a8  000002a8  2**3
...
...
 13 .text         00000928  00000000000012c0  00000000000012c0  000012c0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE

通过上述命令可以看到.text段相对于动态库的偏移是0x12c0.
如果直接运行进程,然后attach,查看命令:

(gdb) info proc mappings
process 9775
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
      0x561bf6f6f000     0x561bf6f70000     0x1000        0x0 /usr/local/bin/algorithm
      ...
      ...
      0x561bf6f73000     0x561bf6f74000     0x1000     0x3000 /usr/local/bin/algorithm
      0x561bf7300000     0x561bf7321000    0x21000        0x0 [heap]
      0x7fe23e052000     0x7fe23e055000     0x3000        0x0
      0x7fe23e055000     0x7fe23e07d000    0x28000        0x0 /usr/lib/x86_64-linux-gnu/libc.so.6
      ...
      ...
      0x7fe23e26e000     0x7fe23e270000     0x2000   0x218000 /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7fe23e270000     0x7fe23e27d000     0xd000        0x0
      0x7fe23e27d000     0x7fe23e27e000     0x1000        0x0 /usr/local/lib/libparallelmenu.so.0.0.0
      0x7fe23e27e000     0x7fe23e27f000     0x1000     0x1000 /usr/local/lib/libparallelmenu.so.0.0.0
      0x7fe23e27f000     0x7fe23e280000     0x1000     0x2000 /usr/local/lib/libparallelmenu.so.0.0.0
      0x7fe23e280000     0x7fe23e281000     0x1000     0x2000 /usr/local/lib/libparallelmenu.so.0.0.0
      0x7fe23e281000     0x7fe23e282000     0x1000     0x3000 /usr/local/lib/libparallelmenu.so.0.0.0
      0x7fe23e287000     0x7fe23e289000     0x2000        0x0
      0x7fe23e289000     0x7fe23e28b000     0x2000        0x0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      ...
      ...
      0x7fe23e2c3000     0x7fe23e2c5000     0x2000    0x39000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      0x7ffd49381000     0x7ffd493a2000    0x21000        0x0 [stack]
      0x7ffd493d9000     0x7ffd493dd000     0x4000        0x0 [vvar]
      0x7ffd493dd000     0x7ffd493df000     0x2000        0x0 [vdso]
(gdb) info sharedlibrary
From                To                  Syms Read   Shared Object Library
0x00007fe23e27e2c0  0x00007fe23e27ebe8  Yes         /usr/local/lib/libparallelmenu.so.0
0x00007fe23e07d700  0x00007fe23e20fabd  Yes (*)     /lib/x86_64-linux-gnu/libc.so.6
0x00007fe23e28b090  0x00007fe23e2b4335  Yes (*)     /lib64/ld-linux-x86-64.so.2
(*): Shared library is missing debugging information.

如果从gdb启动,显示如下:

(gdb) info proc mappings
process 9789
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
      0x555555554000     0x555555555000     0x1000        0x0 /usr/local/bin/algorithm
      ...
      ...
      0x555555558000     0x555555559000     0x1000     0x3000 /usr/local/bin/algorithm
      0x7ffff7d86000     0x7ffff7d89000     0x3000        0x0
      0x7ffff7d89000     0x7ffff7db1000    0x28000        0x0 /usr/lib/x86_64-linux-gnu/libc.so.6
      ...
      ...
      0x7ffff7fa2000     0x7ffff7fa4000     0x2000   0x218000 /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7ffff7fa4000     0x7ffff7fb1000     0xd000        0x0
      0x7ffff7fb1000     0x7ffff7fb2000     0x1000        0x0 /usr/local/lib/libparallelmenu.so.0.0.0
      0x7ffff7fb2000     0x7ffff7fb3000     0x1000     0x1000 /usr/local/lib/libparallelmenu.so.0.0.0
      0x7ffff7fb3000     0x7ffff7fb4000     0x1000     0x2000 /usr/local/lib/libparallelmenu.so.0.0.0
      0x7ffff7fb4000     0x7ffff7fb5000     0x1000     0x2000 /usr/local/lib/libparallelmenu.so.0.0.0
      0x7ffff7fb5000     0x7ffff7fb6000     0x1000     0x3000 /usr/local/lib/libparallelmenu.so.0.0.0
      0x7ffff7fbb000     0x7ffff7fbd000     0x2000        0x0
      0x7ffff7fbd000     0x7ffff7fc1000     0x4000        0x0 [vvar]
      0x7ffff7fc1000     0x7ffff7fc3000     0x2000        0x0 [vdso]
      0x7ffff7fc3000     0x7ffff7fc5000     0x2000        0x0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      ...
      ...
      0x7ffff7ffd000     0x7ffff7fff000     0x2000    0x39000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
(gdb) info sharedlibrary
From                To                  Syms Read   Shared Object Library
0x00007ffff7fc5090  0x00007ffff7fee335  Yes (*)     /lib64/ld-linux-x86-64.so.2
0x00007ffff7fb22c0  0x00007ffff7fb2be8  Yes         /usr/local/lib/libparallelmenu.so.0
0x00007ffff7db1700  0x00007ffff7f43abd  Yes (*)     /lib/x86_64-linux-gnu/libc.so.6
(*): Shared library is missing debugging information.

总结:
info proc mappings查看的是动态库运行时加载的地址,如果是直接启动的,这个地址是随机的,如果是gdb启动的,这个地址是固定的。
info sharedlibrary 命令查看的是动态库代码段的起始地址。

4.x与disassemble的异同

(gdb) help x
检查内存:x/FMT ADDRESS。
ADDRESS是要检查的内存地址的表达式。
FMT是一个重复计数,后面跟着一个格式字母和一个大小字母。
格式字母有o(八进制),x(十六进制),d(十进制),u(无符号十进制),
T(二进制),f(浮点),a(地址),i(指令),c(char), s(字符串)
z(十六进制,0在左边填充)
大小字母是b(字节),h(半字),w(字),g(巨型,8字节)。
打印指定大小的指定数量的对象
根据格式。如果指定的是负数,则内存为从地址往后查。
字母的格式和大小的默认值是以前使用的。
默认计数为1。默认地址在最后打印的内容后面用这个命令或者"print"。
(gdb) help disassemble
反汇编内存的指定部分。
默认是所选帧的pc周围的函数。

如果使用/m修饰符,那么将包括源代码行(如果可用)。
这种视图是“以源代码为中心”的:输出按源行顺序排列,而不是优化的代码。只有主源文件
被显示,而不是别的,例如,任何内联函数。这个修饰符在实践中没有被证明是有用的,因此不建议使用

使用/s修饰符,那么将包括源代码行(如果可用)。这与/m在两个重要方面不同:
-输出仍然是PC地址顺序
-显示所有相关源文件的文件名和内容。

使用/r修饰符,将包括十六进制的原始指令。

如果只有一个参数,围绕该地址的函数将被转储。两个参数(以逗号分隔)作为要转储的内存范围,以“start,end”或“start,+length”的形式。

注意,地址被解释为一个表达式,而不是一个位置,就像"break"命令一样。
因此,例如,如果你想在文件foo.c中反汇编函数bar,你必须输入“disassemble ‘foo.c’::bar”而不是“disassemble 'foo.c:bar”。

七、coredump

coredump是一个内存映像,可以是主动生成,也可以是崩溃自动生成。

查看coredump开启情况

查看coredump开启情况:

$ ulimit -c 
0

coredump功能在linux是默认关闭的。

打开 coredump 功能

(1)临时打开 coredump 功能
ulimit -c unlimited 命令:生成 coredump 文件,文件大小不受限制。 或ulimit -c 1024 命令: 生成固定大小的 coredump 文件,单位为 blocks(KB)。
注意:这种修改为临时限制,只存活于当前 shell 会话,仅影响当前 shell。

wsl@My-win:~$ ulimit -c unlimited
wsl@My-win:~$ ulimit -c
unlimited

(2)永久打开 coredump 功能
更改 /etc/security/limits.conf 文件中的内容。
去掉 soft core 0 一行前面的注释 ,同时,将 0 改为 unlimited

#/etc/security/limits.conf
...
#

#*               soft    core            0
#root            hard    core            100000
#*               hard    rss             10000

设置coredump路径

默认生成路径:输入可执行文件运行命令的同一路径下
默认生成名字:默认命名为core。新的core文件会覆盖旧的core文件
以下命令分别添加了进程号和日期作为core文件名字

echo 1 > /proc/sys/kernel/core_uses_pid
echo "/corefile/core-%e-%p-%t" > core_pattern

测试core文件生成情况

kill -s SIGSEGV $$

离线查看

离线查看core是很常见的,因为出问题的设备很可能资源有限。把源文件直接放到core文件同路径就可以看函数以及行号了。离线调试core文件需要对应的交付件与符号表,交付件可以是出问题的设备拷贝出来的,也可以是重编的带符号表的同参数编译的交付件,而符号表可以使用objcopy单独剥离的。

参考文章:
Linux环境Release版本的符号表剥离及调试方法
Why does the same shared library show up multiple times when using info proc mappings in gdb?
为什么 ‘ldd’ 和 ‘(gdb) info sharedlibrary’ 显示不同的动态库基地址?
Thread: addr2line doesn’t work - returns ??:0
如何在没有core文件的情况下用dmesg+addr2line定位段错误
Linux下离线调试之coredump文件介绍
计算机那些事(4)——ELF文件结构
strip,eu-strip 及其符号表,gdb调试strip过的程序
Linux调试工具 | Addr2line
addr2line 输出为?:0可能原因

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值