简体   繁体   English

Windows中的gdb:调试已编译的C和C ++代码时的不同行为

[英]gdb in Windows: different behaviour when debugging compiled C and C++ code

I've noticed a strange behaviour of GDB 7.5 on Windows. 我注意到Windows上GDB 7.5的奇怪行为。 Consider the following C program: 考虑下面的C程序:

int foo(void){
    int i = 5;
    return i;
}

int main(int argc, char** argv){
    foo();
    return 0;
}

When compiled as either Classic C or C++, the GDB disass foo command gives the same assembly code, as follows: 当编译为Classic C或C ++时,GDB disass foo命令将提供相同的汇编代码,如下所示:

Dump of assembler code for function foo:
0x00401954 <+0>:     push   %ebp
0x00401955 <+1>:     mov    %esp,%ebp
0x00401957 <+3>:     sub    $0x10,%esp
0x0040195a <+6>:     movl   $0x5,-0x4(%ebp)
0x00401961 <+13>:    mov    -0x4(%ebp),%eax
0x00401964 <+16>:    leave
0x00401965 <+17>:    ret
End of assembler dump.

However, after inserting a breakpoint at the "leave" command, like so: br *0x00401964 , running the code up to that line, and attempting to print out the variable i, the executables produced by compiling it as C and C++ behaves differently. 但是,在“ leave”命令上插入断点后,像这样: br *0x00401964 ,将代码运行到该行,并尝试打印出变量i,将其编译为C和C ++生成的可执行文件的行为会有所不同。 The C executable works as expected and prints out $i = 5 , while with the C++ executable GDB chokes up and says "no symbol i in current context". C可执行文件按预期工作,并输出$i = 5 ,而C ++可执行文件GDB阻塞并说“在当前上下文中没有符号i”。

So just out of curiosity I'd like to know if this is a GDB bug or feature? 因此,出于好奇,我想知道这是GDB错误还是功能? Or is the compiler (GCC) doing something subtly different so that there's something happening between the lines? 还是编译器(GCC)做了一些细微的不同,使行之间发生了什么? Thanks. 谢谢。

EDIT: Well, I don't think it's true the compiler removed the function completely, because breaking at the line before "leave" and printing the value of i does work. 编辑:好吧,我认为编译器完全删除了该函数是不正确的,因为在“ leave”之前的行处中断并打印i的值确实有效。

This is neither bug/feature nor a side effect of compiler optimization. 这既不是错误/功能,也不是编译器优化的副作用。 The disassembly clearly is the output of a non-optmized build ( i is written to the stack in foo+6 and reread from stack one step later in foo+13 ). 显然,反汇编是未优化构建的输出( ifoo+6写入堆栈,并在foo+13稍后从堆栈中重新读取)。

While the assembly output of C and C++ is the same in this case, the debug symbol output however is slightly different. 尽管在这种情况下,C和C ++的汇编输出相同,但是调试符号输出却略有不同。 The scope of i is more limited in C++. i的范围在C ++中受到更多限制。 I can only speculate for the reasons. 我只能推测原因。 I would guess that this is related to the fact that scoping is more complex in C++ (think of constructors, destructors, exception) and so the C++ part of gcc is stricter on scopes than the C part of gcc. 我想这与以下事实有关:C ++的作用域更加复杂(考虑构造函数,析构函数,异常),因此gcc的C ++部分在范围上比gcc的C部分更为严格。

Details 细节

(I checked everything on a 32-bit build but on a 64-bit Linux with gcc 4.8 and gdb 7.6. While some details will differ on Windows I expect the general mechanics to be the same) (我在32位版本上检查了所有内容,但在带有gcc 4.8和gdb 7.6的64位Linux上检查了所有内容。尽管某些细节在Windows上会有所不同,但我希望一般机制是相同的)

Note that addresses differ in my case. 请注意,在我的情况下,地址有所不同。

(gdb) disas foo
Dump of assembler code for function foo:
   0x080483ed <+0>:     push   %ebp
   0x080483ee <+1>:     mov    %esp,%ebp
   0x080483f0 <+3>:     sub    $0x10,%esp
   0x080483f3 <+6>:     movl   $0x5,-0x4(%ebp)
   0x080483fa <+13>:    mov    -0x4(%ebp),%eax
   0x080483fd <+16>:    leave  
   0x080483fe <+17>:    ret    
End of assembler dump.

Technically, foo+0 and foo+1 are the function prologue, foo+3 to foo+13 is the function body, and foo+16 and foo+17 is the function epilogue. 从技术上讲, foo+0foo+1是函数序言, foo+3foo+13是函数体, foo+16foo+17是函数尾声。 So only foo+3 to foo+13 represent the code between { and } . 因此,只有foo+3foo+13表示{}之间的代码。 I would say that the C++ version is more correct in saying that i is out of scope before and after the function body. 我会说C ++版本更正确,因为i在函数主体之前和之后均超出范围。

To see that this is really a matter of debug symbols you can dump out gdb's internals of the debug structures with maintenance print symbols output_file_on_disk . 要了解这实际上是调试符号的问题,可以转储带有maintenance print symbols output_file_on_disk gdb内部调试结构。 For C it looks like: 对于C,它看起来像:

block #000, object at 0x1847710, 1 syms/buckets in 0x80483ed..0x804840e
 int foo(); block object 0x18470d0, 0x80483ed..0x80483ff
 int main(int, char **); block object 0x18475d0, 0x80483ff..0x804840e section .text
  block #001, object at 0x18476a0 under 0x1847710, 1 syms/buckets in 0x80483ed..0x804840e
   typedef int int; 
   typedef char char; 
    block #002, object at 0x18470d0 under 0x18476a0, 1 syms/buckets in 0x80483ed..0x80483ff, function foo
     int i; computed at runtime
    block #003, object at 0x18475d0 under 0x18476a0, 2 syms/buckets in 0x80483ff..0x804840e, function main
     int argc; computed at runtime
     char **argv; computed at runtime

While this is C++ 虽然这是C ++

block #000, object at 0x1a3c790, 1 syms/buckets in 0x80483ed..0x804840e
 int foo(); block object 0x1a3c0c0, 0x80483ed..0x80483ff
 int main(int, char**); block object 0x1a3c640, 0x80483ff..0x804840e section .text
  block #001, object at 0x1a3c720 under 0x1a3c790, 1 syms/buckets in 0x80483ed..0x804840e
   typedef int int; 
   typedef char char; 
    block #002, object at 0x1a3c0c0 under 0x1a3c720, 0 syms/buckets in 0x80483ed..0x80483ff, function foo()
      block #003, object at 0x1a3c050 under 0x1a3c0c0, 1 syms/buckets in 0x80483f3..0x80483fd
       int i; computed at runtime
    block #004, object at 0x1a3c640 under 0x1a3c720, 2 syms/buckets in 0x80483ff..0x804840e, function main(int, char**)
     int argc; computed at runtime
     char **argv; computed at runtime

So the debug symbols for the C++ code distinguish between the whole function (block #002) and the scope of the function body (block #003). 因此,C ++代码的调试符号将整个功能(块#002)与功能主体的范围(块#003)区分开。 This results in your observations. 这导致您的观察。

(And to see that this is really not gdb just handling something wrong you can even analyze the binary with objdump on Linux or dumpbin on Windows. I did it on Linux and indeed it's the DWARF debug symbols that are different :-) ) (要知道这确实不是gdb只是在处理错误,您甚至可以使用Linux上的objdump或Windows上的dumpbin分析二进制文件。我在Linux上做到了,实际上DWARF调试符号是不同的:-))

It's not really a bug or a feature. 它不是真正的错误或功能。 The compiler is permitted to substitute functionally-equivalent code and generally does so if it can find a better way to do things. 允许编译器替换功能等效的代码,并且如果可以找到更好的处理方法,通常可以这样做。 The example code is equivalent to doing nothing at all, so the compiler is free to remove it. 该示例代码等效于完全不执行任何操作,因此编译器可以自由删除它。 This leaves the debugger with nothing to debug, which is good since debugging code that does nothing would be a waste of time anyway. 这使调试器没有什么要调试的,这很好,因为调试不执行任何操作的代码无论如何都会浪费时间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM