gcc -g: 会发生什么?

65
这个问题是在一次面试中问我的。 他们问我如何生成一个核心转储文件,以便我可以进行调试。 然后我说,使用gcc中的-g标志即可实现。
接着他们问我,这个-g标志究竟对编译器做了什么。
我回答说(可能是错误的答案),它将打开核心文件中的所有符号,这些符号可以用于调试。
请问有人能告诉我它到底做了什么吗?

6
值得注意的是,如果你想生成一个进程的核心转储文件,可以将其PID传递给gcore命令。 - atx
7个回答

67
那差不多正确,但不完整。 -g请求编译器和链接器在可执行文件本身中生成并保留源级调试/符号信息。
如果程序稍后崩溃并生成核心文件(这表明实际代码中存在某些问题),或者有一个故意的操作系统命令强制它核心(例如kill -SIGQUIT pid),或者程序调用转储核心的函数(例如abort)--这些情况都不是由使用-g引起的--然后调试器将知道如何从可执行文件中读取该"-g"符号信息,并与核心进行交叉参考。这意味着您可以在可执行文件中的堆栈帧中查看变量和函数的正确名称,获取行号并在可执行文件中步进时查看源代码。
无论您是从内核还是仅从可执行文件开始调试,该调试信息都很有用。甚至可以帮助命令(如pstack)产生更好的输出。
请注意,您的环境可能有其他设置来控制是否生成核心转储文件(它们可能很大,并且没有通用的方法可以知道何时可以删除它们,因此它们并不总是需要的)。例如,在UNIX/LINUX shell中,通常使用 ulimit -c 命令。

您可能还会对了解 DWARF Wikipedia 感兴趣——这是一种常用的调试信息格式,用于编码可执行文件/库对象中嵌入的调试/符号信息(例如在UNIX和Linux上)。

根据 Victor 在评论中的要求更新...

符号信息 列出源代码中的标识符(通常只有在需要任何name mangling之后),它们将加载到进程内存中的(虚拟)内存地址/偏移量以及类型(例如数据 vs. 代码)。例如...

$ cat ok.cc
int g_my_num;
namespace NS { int ns_my_num = 2; }
int f() { return g_my_num + NS::ns_my_num; }
int main() { return f(); }

$ g++ -g ok.cc -o ok    # compile ok executable with symbol info

$ nm ok    # show mangled identifiers
00000000004017c8 d _DYNAMIC
0000000000401960 d _GLOBAL_OFFSET_TABLE_
0000000000400478 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
000000000040037c T _Z1fv                     # this is f()
0000000000401798 D _ZN2NS9ns_my_numE         # this is NS::ns_my_num
00000000004017a8 d __CTOR_END__
00000000004017a0 d __CTOR_LIST__
00000000004017b8 d __DTOR_END__
00000000004017b0 d __DTOR_LIST__
0000000000400540 r __FRAME_END__
00000000004017c0 d __JCR_END__
00000000004017c0 d __JCR_LIST__
00000000004017c8 d __TMC_END__
00000000004017c8 d __TMC_LIST__
0000000000401980 A __bss_start
0000000000401788 D __data_start
0000000000400440 t __do_global_ctors_aux
00000000004002e0 t __do_global_dtors_aux
0000000000401790 d __dso_handle
0000000000000000 a __fini_array_end
0000000000000000 a __fini_array_start
                 w __gmon_start__
0000000000000000 a __init_array_end
0000000000000000 a __init_array_start
00000000004003a0 T __libc_csu_fini
00000000004003b0 T __libc_csu_init
                 U __libc_start_main
0000000000000000 a __preinit_array_end
0000000000000000 a __preinit_array_start
0000000000401980 A _edata
0000000000401994 A _end
0000000000400494 T _fini
000000000040047c T _init
0000000000400220 T _start
000000000040024c t call_gmon_start
0000000000401980 b completed.6118
0000000000401788 W data_start
0000000000400270 t deregister_tm_clones
0000000000401988 b dtor_idx.6120
0000000000401994 A end
0000000000400350 t frame_dummy
0000000000401990 B g_my_num                   # our global g_my_num
0000000000400390 T main                       # the int main() function
00000000004002a0 t register_tm_clones

$ nm ok | c++filt            # c++filt "unmangles" identifiers...
00000000004017c8 d _DYNAMIC
0000000000401960 d _GLOBAL_OFFSET_TABLE_
0000000000400478 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
000000000040037c T f()
0000000000401798 D NS::ns_my_num
00000000004017a8 d __CTOR_END__
00000000004017a0 d __CTOR_LIST__
00000000004017b8 d __DTOR_END__
00000000004017b0 d __DTOR_LIST__
0000000000400540 r __FRAME_END__
00000000004017c0 d __JCR_END__
00000000004017c0 d __JCR_LIST__
00000000004017c8 d __TMC_END__
00000000004017c8 d __TMC_LIST__
0000000000401980 A __bss_start
0000000000401788 D __data_start
0000000000400440 t __do_global_ctors_aux
00000000004002e0 t __do_global_dtors_aux
0000000000401790 d __dso_handle
0000000000000000 a __fini_array_end
0000000000000000 a __fini_array_start
                 w __gmon_start__
0000000000000000 a __init_array_end
0000000000000000 a __init_array_start
00000000004003a0 T __libc_csu_fini
00000000004003b0 T __libc_csu_init
                 U __libc_start_main
0000000000000000 a __preinit_array_end
0000000000000000 a __preinit_array_start
0000000000401980 A _edata
0000000000401994 A _end
0000000000400494 T _fini
000000000040047c T _init
0000000000400220 T _start
000000000040024c t call_gmon_start
0000000000401980 b completed.6118
0000000000401788 W data_start
0000000000400270 t deregister_tm_clones
0000000000401988 b dtor_idx.6120
0000000000401994 A end
0000000000400350 t frame_dummy
0000000000401990 B g_my_num
0000000000400390 T main
00000000004002a0 t register_tm_clones

注意:

  • 我们的函数f()main()是类型T(代表“TEXT”-用于只读非零内存内容,无论它实际上是文本、其他数据还是可执行代码),
  • g_my_num是全局变量,类型为B,其内存隐式清零,而
  • NS::ns_my_numD类型,因为可执行文件必须显式提供值2来占用该内存。

man/info-page for nm 进一步说明了这些内容....


4
注意:strip 工具执行相反的操作,即将带有调试符号的库文件去除这些符号。 - Matthieu M.
什么是符号信息?您能为我解释得更通俗易懂一些吗? - Victor Lin
@TonyD 很抱歉。ELI5的意思是“像我五岁一样解释”。我还没有学过编译器课程,所以我不知道那些专业术语。 - Victor Lin
@VictorLin:这里涉及的内容太多了,无法在那个层面上进行解释 - 这将迅速演变为地址与偏移量、"虚拟"内存、操作系统加载器等方面的解释。但我已经提供了一些基本细节和示例 - 希望能有所帮助。 - Tony Delroy

12

-g标志告诉编译器生成调试信息。它不影响是否生成核心文件。在大多数类Unix系统中,可以使用ulimit命令进行设置。


但我想知道的是那个调试信息到底是什么。我知道你已经知道了,但我需要的是超越那个的东西。 - Vijay
除此之外,这是很复杂的 - 详细描述需要几十页。你可以从查看DWARF(http://dwarfstd.org/Download.php)标准开始,该标准是嵌入在可执行文件中的调试符号的规范(在大多数*nixes上)。-g指示编译器/链接器生成并嵌入这些结构到可执行文件中。下一步可能是理解调试器如何使用此调试信息。 - nos
简而言之,它添加了有关符号名称(函数、本地和全局变量等)以及类型、源文件名称和行号的信息。它还可以通过自动定义的宏引入不同的头文件(例如STL和assert宏的定义),以便于调试。它可能还会为内联函数生成特殊输出,以便于进入内联函数/方法。 - Axel

7

gcc的-g标志告诉gcc生成并嵌入调试信息。ulimit -c用于启用核心文件生成。您可以单独使用其中任一项。


3

2
如果您不加上-g选项,在gdb中无法调用list命令以查看源代码的样子。它会显示“未加载符号表。请使用“file”命令。”此外,如果您在gdb中键入info func或info frame、info locals等命令,没有-g选项将不会显示返回数据类型及其参数,基本上没有指令到变量的转换(从符号表映射)。

1

核心转储是进程接收到信号时的默认操作之一,例如在标准信号“SIGQUIT”、“SIGILL”、“SIGABRT”、“SIGFPE”和“SIGSEGV”中。然而,大多数shell会抑制核心文件的创建,因为核心文件往往很大,可能需要一些时间或很长时间。

为了启用核心生成,“ulimit”是您可以使用的实用程序,用于设置shell或其子进程的文件限制。

编译器标志“-g”或其他标志只涉及编译器。从逻辑上讲,它与核心转储无关。


1

核心文件是在分段错误或类似异常情况下生成的。gdb source.cc core是查看核心文件的一种方式。回溯和调查每个框架是查看核心文件的开始。-g将调试符号添加到二进制文件中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接