Chinaunix首页 | 论坛 | 博客
  • 博客访问: 365770
  • 博文数量: 83
  • 博客积分: 5322
  • 博客等级: 中校
  • 技术积分: 1057
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-11 11:27
个人简介

爱生活,爱阅读

文章分类

全部博文(83)

文章存档

2015年(1)

2013年(1)

2012年(80)

2011年(1)

分类: LINUX

2012-09-09 22:38:54

Using GNU's GDB Debugger

By Peter Jay Salzman

Previous: Introduction

Next: Debugging With Your Brain

我们将要去哪里?

To effectively learn how to use GDB, you must understand frames, which are also called stack frames because they're the frames that comprise the stack. To learn about the stack, we need to learn about the memory layout of an executing program. The discussion will mainly be theoretical, but to keep things interesting we'll conclude the chapter with an example of the stack and stack frames using GDB.

为了有效地学习怎样使用GDB,你必须理解帧(frame)。“帧(frame)”也称作“栈帧(stack frame)”,因为栈(stack)是由帧(frame)组成的。为了学习栈(stack),我们需要学习执行中程序的内存布局。这些讨论主要是理论性的,但为了趣味性,我们将用GDB来研究一个关于栈(stack)和栈帧(stack frame)的例子来结束本章。

The material learned in this chapter may seem rather theoretical, but it does serve a few very useful purposes:

本章的学习材料可能看起来相当的理论,但它的确服务于一些非常有用的目的:

1.   Understanding the stack is absolutely necessary for using a symbolic debugger like GDB.

理解栈(stack)对于使用像GDB一样的符号调试器绝对必要的。

2.   Knowing the memory layout of a process will help us understand what exactly a segmentation fault (or segfault) is, and why they happen (or sometimes, more importantly) don't happen when they should. In brief, segfaults are the most common immediate cause for a program to bomb.

知道进程的内存布局将帮助我们理解段错误(segmentation fault)的确切意思是什么,以及它为什么发生,(或者有时候,更重要的)应该出现段错误而没有发生。简要的说,段错误(segfault)是“引爆(bomb)”程序的最常见的直接原因。【这里的引爆的意思是:使程序崩溃】

3.   A knowledge of a program's memory space can often allow us to figure out the location of well-hidden bugs without the use of print() statements, a compiler or even GDB! In the next section, which is a guest written piece by one my friends, Mark Kim, we'll see some real Sherlock Holmes style sleuthing. Mark homes in on a well hidden bug in somewhat lengthy code. It only took him about 5 or 10 minutes, and all he did was look at the program and use his knowledge of how a program's memory space works. It's really impressive!

理解程序的内存空间,使得我们能够经常指出被隐藏很深的缺陷的位置,而不需要使用print()语句、编译器,甚至GDB! 接下来内容中的一部分是由我的一个朋友Mark Kim写作的。我们将看到一些真实的福尔摩斯(Sherlock Holmes)风格的侦探(sleuthing)。在一段稍微冗长的代码中,Mark以追踪到隐藏很深的缺陷为目标。他仅仅花费了510分钟的时间。并且他所做的就是看看问题,然后运用程序的内存空间如何工作的知识。真让人印象深刻。

So without futher ado, let's take a look at how programs are laid out in memory.

所以,不用再耽搁了,现在就让我们看一看程序在内存中是如何分布的。

Virtual Memory (VM)

虚拟内存

Whenever a process is created, the kernel provides a chunk of physical memory which can be located anywhere at all. However, through the magic of virtual memory (VM), the process believes it has all the memory on the computer. You might have heard "virtual memory" in the context of using hard drive space as memory when RAM runs out. That's called virtual memory too, but is largely unrelated to what we're talking about. The VM we're concerned with consists of the following principles:

无论何时创建一个进程,内核都将提供一块物理内存,该内存可以位于任何位置。然而,通过虚拟内存(VM)的魔法,进程相信它自身拥有计算机上所有的内存。,你可能听说过这样一种 “虚拟内存”,即:当RAM用尽时,计算机使用磁盘空间作为内存(虚拟内存)。这也被称作虚拟内存,但是却与我们正在讲述的毫不相关。我们关注的虚拟内存由以下几点原则构成:

1.   Each process is given physical memory called the process's virtual memory space.

每个进程分配的物理内存(physical memory)被称作进程的虚拟内存空间

2.   A process is unaware of the details of its physical memory (i.e. where it physically resides). All the process knows is how big the chunk is and that its chunk begins at address 0.

进程不关注其物理内存的细节(例如,内存的物理驻留地址在哪里)。进程所知道的只是内存有多大以及内存起始地址为0

3.   Each process is unaware of any other chunks of VM belonging to other processes.

每个进程不关注属于其它进程的虚拟内存的数量。

4.   Even if the process did know about other chunks of VM, it's physically prevented from accessing that memory.

尽管进程确实知道其它虚拟内存的数量,但是它已经从物理上被阻止了对该内存的访问。

Each time a process wants to read or write to memory, its request must be translated from a VM address to a physical memory address. Conversely, when the kernel needs to access the VM of a process, it must translate a physical memory address into a VM address. There are two major issues with this:

每当进程项读取或者写入内存,它的请求必须从虚拟内存地址转换为物理内存地址。相反地,当内核需要访问进程的虚拟内存,它必须将物理内存地址转换为虚拟内存地址。这里有两个主要问题:

1.   Computers constantly access memory, so translations are very common; they must be lighting fast.

计算机经常访问内存,所以地址转换过程必须迅速。

2.   How can the OS ensure that a process doesn't trample on another process's VM?

操作系统(OS)如何保证进程不会破坏(trample on)另一个进程的虚拟内存?

The answer to both questions lies in the fact that the OS doesn't manage VM by itself; it gets help from the CPU. Many CPUs contain a device called an MMU: a memory management unit. The MMU and the OS are jointly responsible for managing VM, translating between virtual and physical addresses, enforcing permissions on which processes are allowed to access which memory locations, and enforcing read/write permissions on sections of a VM space, even for the process that owns that space.

这两个问题的答案基于这样一个事实:操作系统本身不亲自管理虚拟内存;它从中央处理器(CPU)获得帮助。很多中央处理器(CPU)包含一个叫做内存管理单元(MMU)的设备:它负责内存的管理。MMU和操作系统共同负责管理虚拟内存:将虚拟内存地址与物理内存地址之间转换,保证哪个进程允许访问哪块儿内存,以及确保进程对虚拟内存空间块儿上的读/写权限,甚至包括拥有该内存空间的进程。

It used to be the case that Linux could only be ported to architectures that had an MMU (so Linux wouldn't run on, say, an x286). However, in 1998, Linux was ported to the 68000 which had no MMU. This paved the way for embedded Linux and Linux on devices such as the Palm Pilot.

过去存在这样一种情况,Linux仅仅可以在用用MMU的架构上运行(所以Linux不能在x286上运行)。然而,在1998年,Linux已经在没有MMU68000上运行。这位嵌入式Linux以及Linux在例如Palm Pilot设备上的移植铺平了道路。

Exercises

练习

1.   Read a short Wikipedia blurb on the 

阅读一篇关于MMU的维基简介

2.   Optional: If you want to know more about VM, here's a . This is much more than you need to know.

可选题:如果你想知道更多关于虚拟内存(VM,这里有一个链接。这比你需要知道的多得多。

内存布局

That's how VM works. For the most part, each process's VM space is laid out in a similar and predictable manner:

这就是虚拟内存如何工作的。就大多数而言,每个进程的虚拟地址空间的内存以相似的、可预测的方式存在。

High Address

Args and env vars

Command line arguments and environment variables

Stack
|
V


Unused memory

^
|
Heap

Uninitialized Data Segment (bss)

Initialized to zero by exec.

Initialized Data Segment

Read from the program file by exec.

Low Address

Text Segment

Read from the program file by exec.

  • Text Segment: The text segment contains the actual code to be executed. It's usually sharable, so multiple instances of a program can share the text segment to lower memory requirements. This segment is usually marked read-only so a program can't modify its own instructions.

代码段(Text Segment):代码段包含了将要被执行的实际代码。它通常是共享的(sharable),因而一个程序的多个实例(multi instances)可以共享代码段从而降低内存需要。该段(segment)通常标记为只读的,所以程序不能更改该处的指令。

  • Initialized Data Segment: This segment contains global variables which are initialized by the programmer.

初始化数据段:该段包含了被程序员初始过的全局变量。【实际上还包括初始过的静态变量】

  • Uninitialized Data Segment: Also named "bss" (block started by symbol) which was an operator used by an old assembler. This segment contains uninitialized global variables. All variables in this segment are initialized to 0 or NULL pointers before the program begins to execute.

未初始化数据段:也称作“bss(符号起始块),是一种被老的汇编器使用的操作符(operator)。这个段中包含没有初始化的全局变量。该段中所有的变量在程序运行之前,均被初始化为0或者NULL指针。

  • The stack: The stack is a collection of stack frames which will be described in the next section. When a new frame needs to be added (as a result of a newly called function), the stack grows downward.

栈:栈是将在下一部分中描述的栈帧(stack frame)集合(colection)。当需要添加一个新的帧时(作为调用一个新的函数的结果),栈向下增长。

  • The heap: Most dynamic memory, whether requested via C's malloc() and friends or C++'s new is doled out to the program from the heap. The C library also gets dynamic memory for its own personal workspace from the heap as well. As more memory is requested "on the fly", the heap grows upward.

堆:多数的动态内存,无论是通过C语言中的malloc()及同类函数,还是C++中的new操作符,均从其中获取内存。C 库也是从堆中获取用于其工作空间的动态内存。当需要更多的动态内存时,堆向上增长。

Given an object file or an executable, you can determine the size of each section (realize we're not talking about memory layout; we're talking about a disk file that will eventually be resident in memory). Given , :

假定有一个目标文件或者一个可执行的文件,你可以确定每一个段的大小(应该了解,我们不是在谈论内存布局,而是在讨论一个最终驻留在内存中的磁盘文件)。假定是hello_world-1.cMakefile文件为:

1   // hello_world-1.c

2  

3   #include

4  

5   int main(void)

6   {

7      printf("hello world\n");

8  

9      return 0;

10  }

compile it and link it separately with:

分别通过如下方式编译并连接之:

   $ gcc -W -Wall -c hello_world-1.c

   $ gcc -o hello_world-1  hello_world-1.o

You can use the size command to list out the size of the various sections:

你可以使用size命令列出不同部分的大小:

   $ size hello_world-1 hello_world-1.o

   text   data   bss    dec   hex   filename

    916    256     4   1176   498   hello_world-1

     48      0     0     48    30   hello_world-1.o

The data segment is the initialized and uninitialized segments combined. The dec and hex sections are the file size in decimal and hexidecimal format respectively.

数据段(data segment)包括初始化与未初始化的总和。dechex部分分别为文件大小的十进制形式与十六进制形式。

You can also get the size of the sections of the object file using "objdump -h" or "objdump -x".

你也可以通过“objdump -h”或者“objdump -x”获取目标文件各部分的大小。

   $ objdump -h hello_world-1.o

  

   hello_world-1.o:     file format elf32-i386

  

   Sections:

   Idx Name          Size      VMA       LMA       File off  Algn

     0 .text         00000023  00000000  00000000  00000034  2**2

                     CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

     1 .data         00000000  00000000  00000000  00000058  2**2

                     CONTENTS, ALLOC, LOAD, DATA

     2 .bss          00000000  00000000  00000000  00000058  2**2

                     ALLOC

     3 .rodata       0000000d  00000000  00000000  00000058  2**0

                     CONTENTS, ALLOC, LOAD, READONLY, DATA

     4 .note.GNU-stack 00000000  00000000  00000000  00000065  2**0

                     CONTENTS, READONLY

     5 .comment      0000001b  00000000  00000000  00000065  2**0

                     CONTENTS, READONLY

Exercises

练习

1.   The size command didn't list a stack or heap segment for hello_world or hello_world.o. Why do you think that is?

Size命令没有列出hello_wordhello_word.o的栈或者堆的大小,你是如何看待这个问题的?

2.   There are no global variables in hello_world-1.c. Give an explanation for why size reports that the data and bss segments have zero length for the object file but non-zero length for the executable.

helo_word.c中没有全局变量。解释一下为什么size报告中数据与bss段中的长度为零,但是可执行部分(即,代码段)长度不为零。

3.   size and objdump report different sizes for the text segment. Can you guess where the discrepancy comes from? Hint: How big is the discrepancy? See anything of that length in the source code?

Size objdump 报告中的代码段大小不同。你能解释该矛盾是怎样产生的么?提示:矛盾之处有多大?在源代码中看到该长度的任何代码么?

4.   Optional: Read this  about object file formats.

可选题:阅读关于目标文件格式的链接。

Stack Frames And The Stack

栈帧与栈

You just learned about the memory layout for a process. One section of this memory layout is called the stack, which is a collection of stack frames. Each stack frame represents a function call. As functions are called, the number of stack frames increases, and the stack grows. Conversely, as functions return to their caller, the number of stack frames decreases, and the stack shrinks. In this section, we learn what a stack frame is. A very detailed explanation , but we'll go over what's important for our purposes.

你刚刚学习了进程的内存布局。内存布局的一个部分称作栈,而栈是栈帧的集合(collection)。每一个栈帧表示一次函数调用。在函数被调用时,栈帧的数目增加,同时栈也增长。相反地,当函数返回给其调用者时,栈帧的数目减少,同时栈缩小。在这个部分,我们将学习什么是栈帧。这里将会给出一个详细的解释,但我们将搁置对我们目标重要的部分。【最后一句如何解释】

A program is made up of one or more functions which interact by calling each other. Every time a function is called, an area of memory is set aside, called a stack frame, for the new function call. This area of memory holds some crucial information, like:

程序是由一个或多个通过相互调用而相互影响的函数构成。每当调用一个函数时,将会为该新的函数调用留出一块内存区域,称作栈帧,该内存区域保存了一些关键的信息,像:

1.   Storage space for all the automatic variables for the newly called function.

新调用函数的所有自动变量的存储区域。

2.   The line number of the calling function to return to when the called function returns.

当被调用函数返回时,调用函数所在的,待被调用函数返回后执行代码的行号。【此处意译】

3.   The arguments, or parameters, of the called function.

调用函数的参数。

Each function call gets its own stack frame. Collectively, all the stack frames make up the call stack. We'll use hello_world-2.c for the next example.

每个函数调用都拥有其栈帧。所有的栈帧构成了调用栈(call stack)。我们使用hello_world-2.c作为下一个例子。

1   #include

2   void first_function(void);

3   void second_function(int);

4  

5   int main(void)

6   {

7      printf("hello world\n");

8      first_function();

9      printf("goodbye goodbye\n");

10 

11     return 0;

12  }

13 

14 

15  void first_function(void)

16  {

17     int imidate = 3;

18     char broiled = 'c';

19     void *where_prohibited = NULL;

20 

21     second_function(imidate);

22     imidate = 10;

23  }

24 

25 

26  void second_function(int a)

27  {

28     int b = a;

29  }

 

Frame for main()

When the program starts, there's one stack frame, belonging to main(). Since main() has no automatic variables, no parameters, and no function to return to, the stack frame is uninteresting. Here's what the stack looks like just before the call to first_function()is made.

当程序启动后,有一个属于main()函数的栈帧。由于main()函数没有自动变量,没有参数,没有待返回的函数,其栈帧没有趣味。下面是函数first_functon()调用之前栈的形式。

 

 

Frame for main()

Frame for first_function()
Return to 
main(), line 9
Storage space for an int
Storage space for a char
Storage space for a void *

When the call to first_function() is made, unused stack memory is used to create a frame for first_function(). It holds four things: storage space for an int, a char, and a void *, and the line to return to within main(). Here's what the call stack looks like right before the call to second_function()is made.

 

在调用first_function()时,未使用的栈内存被用来创建函数first_function()的帧(译者注:应该为栈帧,即stack frame)。它包括四部分:整形变量(int)、字符型变量(char)、void类型指针(void*)以及返回到函数main()的行号的存储空间。右侧是在调用second_function()函数之前的栈的形式。

 

When the call to second_function() is made, unused stack memory is used to create a stack frame for second_function(). The frame holds 3 things: storage space for an int and the current address of execution within second_function(). Here's what the stack looks like right before second_function() returns.

当调用函数second_function()时,未被使用的栈内存用于创建函数second_function()的栈帧。该栈帧包括三部分:整型变量(int)的存储空间、函数second_function的返回后的地址的存储空间。下面是second_funciton()函数返回前栈的样子。【译者注:三部分为:局部变量的存储空间,参数的存储空间,以及函数返回后在函数first_function中的执行代码的行号的存储地址。】

 

 

 

Frame for main()

Frame for first_function():
Return to 
main(), line 9
Storage space for an int
Storage space for a char
Storage space for a void *

Frame for second_function():
Return to 
first_function(), line 22
Storage space for an int
Storage for the int parameter named 
a

 

Frame for main()

Frame for first_function():
Return to 
main(), line 9
Storage space for an int
Storage space for a char
Storage space for a void *

 When second_function() returns, its frame is used to determine where to return to (line 22 of first_function()), then deallocated and returned to stack. Here's what the call stack looks like after second_function() returns:

当函数second_functon()返回后,其帧用于确定其返回地址(first_function()函数的第22行),然后解除栈帧的分配并返回栈。下面是second_second()函数返回后,调用栈的样子。

 

Frame for main()

 When first_function() returns, its frame is used to determine where to return to (line 9 of main()), then deallocated and returned to the stack. Here's what the call stack looks like after first_function()return:

当函数first_function()返回后,其帧用于确定返回的地址(函数main()的第9行),然后解除其栈帧并返回栈。下面是函数first_function()返回后栈的样子。

And when main() returns, the program ends.

当函数main()返回时,该程序结束。

Exercises

练习

1.   Suppose a program makes 5 function calls. How many frames should be on the stack?

假设一个程序有5个函数调用。栈中应该有多少栈帧?

2.   We saw that the stack grows linearly downward, and that when a function returns, the last frame on the stack is deallocated and returned to unused memory. Is it possible for a frame somewhere in the middle of the stack to be returned to unused memory? If it did, what would that mean about the running program?

我们看到栈是向下线性增长的,当函数返回时,最后的栈帧被解除分配并返还给未被使用的内存区域(unused memory)。有没有一种可能:位于栈的中间位置的栈帧被返还给未被使用的内存区域?如果真的发生了,这对于正在运行的程序而言意味着什么?

3.   Can a goto() statement cause frames in the middle of the stack to be deallocated? The answer is no, but why?

Goto()语句能够使得一个位于栈中间位置的栈帧被解除分配么?答案是否定的,但为什么?

4.   Can longjmp() cause frames in the middle of the stack to be deallocated?

Longjmp()能够使得栈中间位置的栈帧被解除分配么?

The Symbol Table

符号表

A symbol is a variable or a function. A symbol table is exactly what you think: it's a table of variables and functions within an executable. Normally, symbol tables contain only memory addresses of symbols, since computers don't use (or care) what we name variables and functions.

符号是变量或者函数。符号表正是你认为的那样:它是一个可执行程序内的变量与函数的表。通常情况下,符号表仅包含符号的内存地址,因为计算机不用(或不在乎)变量名于函数名。

But in order for GDB to be useful to us, it needs to be able to refer to variable and function names, not their addresses. Humans use names like main() or i. Computers use addresses like . To that end, we can compile code with "debugging information" which tells GDB two things:

但为了使用GDB,它需要能够引用变量名与函数名,而不是他们的地址。人们使用像main()或者‘i’。计算机使用地址,像0x804b64d 0xbffff784。到最后,我们在编译时使用“调试信息“来告诉GDB两件事情:

1.   How to associate the address of a symbol with its name in the source code.

如何将符号的地址与源代码中符号的名字联系起来。

2.   How to associate the address of a machine code with a line of source code.

如何将机器码的地址与一行源代码联系起来。

A symbol table with this extra debugging information is called an augmented or enhanced symbol table. Because gcc and GDB run on so many different platforms, there are many different formats for debugging information:

拥有此类额外的调试信息的符号表被称作增强的符号表。因为gccGDB运行在如此多的不同的平台上,所以调试信息有很多不同的形式:

l  stabs: The format used by DBX on most BSD systems.

stabs该格式被多数BSD系统上的DBX使用。

l  coff: The format used by SDB on most System V systems before System V Release 4.

coff该格式被System V 发型版本4之前的多数System V系统上的SDB使用。

l  xcoff: The format used by DBX on IBM RS/6000 systems.

xcoff该格式被Rs/6000系统上的DBX使用。

l  dwarf: The format used by SDB on most System V Release 4 systems.

dwarf该格式被大多数System V 发行版本4上的SDB使用。

l  dwarf2: The format used by DBX on IRIX 6.

Dwarf2该格式被IRIX 6上的DBX使用。

l  vms: The format used by DEBUG on VMS systems.

Vms该格式被VMS系统上的DEBUG使用。

In addition to debugging formats, GDB understands enhanced variants of these formats that allow it to make use of GNU extensions. Debugging an executable with a GNU enhanced debugging format with something other than GDB could result in anything from it working correctly to the debugger crashing.

除了调试格式之外,GDB支持这些为了使用GNU扩展的格式的增强变种。采用一种利用GNU增强调试格式的工具调试一个可执行程序,而不是采用GDB,将可能导致任何事情,包括调试器正确的执行以及调试器崩溃。

Don't let all these formats scare you: in the next section, I'll show you that GDB automagically picks whatever format is best for you. And for the .1% of you that need a different format, you're already knowledgeable enough to make that decision.

不要让这些格式吓到你:在下一部分,我将向你展示GDB将自动的为你挑选最好的格式。至于你需要的那些.1%的不同格式,你已经有了足够的知识

Preparing An Executable For Debugging

准备调试程序

If you plan on debugging an executable, a corefile resulting from an executable, or a running process, you must compile the executable with an enhanced symbol table. To generate an enhanced symbol table for an executable, we must compile it with gcc's -g option:

如果你计划调试一个可执行程序,或者一个可执行程序、运行中进程产生的核文件(corefile),你必须用增强符号表的方式编译该程序。为了让可执行程序产生增强符号表,你必须使用gcc-g选项进行编译:

   gcc -g -o filename filename.c

As previously discussed, there are many different debugging formats. The actual meaning of -g is to produce debugging information in the native format for your system.

如前面讨论的,有很多种不同的调试格式。-g选项的实际意思是:为你的系统产生原始格式的调试信息。

As an alternative to -g, you can also use gcc's -ggdb option:

作为-g现象的一个替代,你可以也可以使用gcc-ggdb选项:

   gcc -ggdb -o filename filename.c

which produces debugging information in the most expressive format available, including the GNU enhanced variants previously discussed. I believe this is probably the option you want to use in most cases.

这将以最大程度可读性的格式产生调试信息,包括前面讨论过的GNU增强变种格式。相信这是多数情况下你最想使用的选项。

You can also give a numerical argument to -g, -ggdb and all the other debugging format options, with 1 being the least amount of information and 3 being the most. Without a numerical argument, the debug level defaults to 2. By using -g3 you can even access preprocessor macros, which is really nice. I suggest you always use -ggdb3 to produce an enhanced symbol table.

你也可以为-g –ggdb 以及其它所有调试格式选项设定一个数字化的参数,1代表最少数量的信息,而3代表最多信息。没有指定数字化的参数,调试的级别设定为2.通过使用-g3,你甚至可以访问预编译宏,而这非常的有好。我建议你总是使用-ggdb3来生成增强符号表。

Debugging information compiled into an executable will not be read into memory unless GDB loads the executable. This means that executables with debug information will not run any slower than executables without debug information (a common misconception). While it's true that debugging executables take up more disk space, the executable will not have a larger "memory footprint" unless it's from within GDB. Similarly, executable load time will be nearly the same, again, unless you run the debug executable from within GDB.

除非是使用GDB加载可执行程序,否则,编译进可执行程序的调试信息不会读入内存中。这意味着,任何带有调试信息的可执行程序将不会比任何不带调试信息的程序运行的慢(一个常见的误区)。实际上,带有调试信息的可执行程序占用更多的磁盘空间,该可执行程序不回占用更大的“内存校验(memory footprint)“,除非是通过GDB启动。相似的,可执行程序的加载时间几乎相同,除非使用GDB运行之。

One last comment. It's certainly possible to perform compiler optimizations on an executable which has an augmented symbol table, in other words: gcc -g -O9 try1.c. In fact, GDB is one of the few symbolic debuggers which will generally do quite well debugging optimized executables. However, you should generally turn off optimizations when debugging an executable because there are situations that will confuse GDB. Variables may get optimized out of existence, functions may get inlined, and more things may happen that may or may not confuse gdb. To be on the safe side, turn off optimization when you're debugging a program.

最后一点说明,对一个拥有增强符号表的可执行程序进行编译器优化是可能的,换句话说:gcc –g -09 try1.c。实际上,GDB是对可执行程序进行调试优化的符号调试器之一。然而,在调试一个可执行程序时,你通常应该关闭优化,因为这将有使得GDB很容易混淆,变量可能因为优化而消失,函数可能被内置(inlined),还有更多使得或未使得GDB困惑。为了安全起见,在调试一个程序时,请关闭优化。

Exercises

练习

1.   Run . Look at the file size of try1. Now run "strip --strip-debug try1 and look at the file size. Now run strip --strip-all try1 and look at the file size. Can you guess what's happening? If not, your punishment is to read "man strip", which makes for some provocative reading.

运行"strip --only-keep-debug try1"。查看try1的文件大小。现在运行strip --strip-debug try1"然后查看文件大小。现在运行“strip --strip-all try1”并查看文件大小。你能猜出发生了什么么?如果没有,那么对你的惩罚是月底“man strip”,这将生成一些有趣的阅读材料。

2.   You stripped all the unnecessary symbols from try1 in the previous exercise. Re-run the program to make sure it works. Now run "strip --remove-section=.text try1" and look at the file length. Now try to run try1. What do you suppose is going on?

在前一个练习中,你从try1中剥离了所有的不必要符号。从新运行该程序来确认它可以工作。现在运行“strip --remove-section=.text try1” 查看文件的的长度。现在尝试运行try1。你认为将会发生什么事情?

3.   Read this  about symbol tables (it's short).

阅读关于符号表的链接(它很短)。

4.   Optional: Read this  about the COFF object file for

Investigating The Stack With GDB

GDB研究栈

We'll look at the stack again, this time, using GDB. You may not understand all of this since you don't know about breakpoints yet, but it should be intuitive. Compile and run :

我们将再次审视栈,不过,这次我们使用GDB。,你可能由于不知道断点而不理解下面的内容。但是它很直观。编译和运行try1.c:

   1    #include

   2    static void display(int i, int *ptr);

   3   

   4    int main(void) {

   5       int x = 5;

   6       int *xptr = &x;

   7       printf("In main():\n");

   8       printf("   x is %d and is stored at %p.\n", x, &x);

   9       printf("   xptr points to %p which holds %d.\n", xptr, *xptr);

   10      display(x, xptr);

   11      return 0;

   12   }

   13  

   14    void display(int z, int *zptr) {

   15    printf("In display():\n");

   16       printf("   z is %d and is stored at %p.\n", z, &z);

   17       printf("   zptr points to %p which holds %d.\n", zptr, *zptr);

   18   }

Make sure you understand the output before continuing with this tutorial. Here's what I see:

确保在进行本教程之前,你理解上述代码的输出。下面是我看到的:

   $ ./try1

   In main():

      x is 5 and is stored at 0xbffff948.

      xptr points to 0xbffff948 which holds 5.

   In display():

      z is 5 and is stored at 0xbffff924.

      zptr points to 0xbffff948 which holds 5.

You debug an executable by invoking GDB with the name of the executable. Start a debugging session with try1. You'll see a rather verbose copyright notice:

为了调试一个可执行程序,你需要以可执行程序的名字作为参数执行GDB

启动一个管理try1的调试回话。你将看到一些相当详细的授权通告:

   $ gdb try1

   GNU gdb 6.1-debian

   Copyright 2004 Free Software Foundation, Inc.

   GDB is free software, covered by the GNU General Public License, and you are

   welcome to change it and/or distribute copies of it under certain conditions.

   Type "show copying" to see the conditions.

   There is absolutely no warranty for GDB.  Type "show warranty" for details.

  

   (gdb)

The (gdb) is GDB's prompt. It's now waiting for us to input commands. The program is currently not running; to run it, type run. This runs the program from inside GDB:

gdb)是GDB的提示符。现在它在等待我们的输入命令。当前的程序并没有运行;为了运行它,输入run。这将在GDB内部运行该程序。

   (gdb) run

   Starting program: try1

   In main():

      x is 5 and is stored at 0xbffffb34.

      xptr points to 0xbffffb34 which holds 5.

   In display():

      z is 5 and is stored at 0xbffffb10.

      zptr points to 0xbffffb34 which holds 5.

  

   Program exited normally.

   (gdb)

Well, the program ran. It was a good start, but frankly, a little lackluster. We could've done the same thing by running the program ourself. But one thing we can't do on our own is to pause the program in the middle of execution and take a look at the stack. We'll do this next.

好了,这个程序运行过了。这是一个好的开始,但是坦白地讲,有点毫无用处。我们不能同程序自行运行时一样运行该程序。但是有一点凭我们自己无法实现,那就是:在程序运行过程中间暂时停止它并查看栈。我们将在下面内容中实现这一点。

You get GDB to pause execution by using breakpoints. We'll cover breakpoints later, but for now, all you need to know is that when you tell GDB break 5, the program will pause at line 5. You may ask: does the program execute line 5 (pause between 5 and 6) or does the program not execute line 5 (pause between 4 and 5)? The answer is that line 5 is not executed. Remember these principles:

使用GDB,你可以使用断点(breakpoint)来暂时的停止程序的执行。我们将在晚点的时候涉及断点,但是现在,你需要知道的是,当你告诉GDB break 5”,那么程序将在第5行暂停。你或许会问:程序时已经执行了第5行(暂停在第5与第6之间)还是没有执行第5行(暂停在地4与第5之间)?答案是第5行还没有执行。记住这些原则:

1.  break 5 means to pause at line 5.

Break 5意味着在第5行暂停。

2.  This means GDB pauses between lines 4 and 5. Line 4 has executed. Line 5 has not.

这意味着GDB在第4与第5行之间暂停。第4行已经执行。第5行还没有执行。

Set a breakpoint at line 10 and rerun the program:

在第10行设置一个断点并重新运行该程序:

   (gdb) break 10

   Breakpoint 1 at 0x8048445: file try1.c, line 10.

   (gdb) run

   Starting program: try1

   In main():

      x is 5 and is stored at 0xbffffb34.

      xptr holds 0xbffffb34 and points to 5.

  

   Breakpoint 1, main () at try1.c:10

   10         display(x, xptr);

We set a breakpoint at line 10 of file try1.c. GDB told us this line of code corresponds to memory address 0x8048445. We reran the program and got the first 2 lines of output. We're in main(), sitting before line 10. We can look at the stack by using GDB's backtrace command:

我们在文件try1.c的第10行设置了一个断点。GDB告诉我们改行代码等同于内存地址0x8048445。我们重新运行该程序并得到了前两行输出。我们在main()函数中,在第10行之前。我们可以 使用GDBbacktrace命令来查看栈:

   (gdb) backtrace

   #0  main () at try1.c:10

   (gdb)

There's one frame on the stack, numbered 0, and it belongs to main(). If we execute the next line of code, we'll be in display(). From the previous section, you should know exactly what should happen to the stack: another frame will be added to the bottom of the stack. Let's see this in action. You can execute the next line of code using GDB's step command:

栈中有一个帧,标号为0,且属于main()。如果我们执行下一行代码,那么我们将进入display()。根据前面的部分,你应该知道栈应该发生什么:另一个帧将被添入栈底(the bottom of stack)。让我们实际看一下。你可以使用GDBstep命令执行下一行代码:

   (gdb) step

   display (z=5, zptr=0xbffffb34) at try1.c:15

   15              printf("In display():\n");

   (gdb)

Look at the stack again, and make sure you understand everything you see:

再次查看栈,并确保你理解你看到的一切:

   (gdb) backtrace

   #0  display (z=5, zptr=0xbffffb34) at try1.c:15

   #1  0x08048455 in main () at try1.c:10

Some points to note:

一些注意点:

l  We now have two stack frames, frame 1 belonging to main() and frame 0 belong to display().

我们现在有两个栈帧,帧1属于main(),帧0属于display()。

l  Each frame listing gives the arguments to that function. We see that main() took no arguments, but display() did (and we're shown the value of the arguments).

每一个栈列表上给出了传递给该函数的参数。我们看到函数main()没有参数,但是函数display()有参数(并展示给我们该参数的值)。

l  Each frame listing gives the line number that's currently being executed within that frame. Look back at the source code and verify you understand the line numbers shown in the backtrace.

每个帧列表给出了帧内当前执行的代码的行号。回顾源代码并证实你理解backtrace中显示的行号。

l  Personally, I find the numbering system for the frame to be confusing. I'd prefer for main() to remain frame 0, and for additional frames to get higher numbers. But this is consistent with the idea that the stack grows "downward". Just remember that the lowest numbered frame is the one belonging to the most recently called function.

个人而言,我觉得帧的数字系统(numbersing system)很容易让人混淆。我更愿意帧0是函数main()的,并且其他帧有更大的编号。但这与栈“向下(downward)”增长的观点一致。只需要记住,最小帧号码标识的帧代表了最新的函数调用。

Execute the next two lines of code:

执行下面的两行代码:

   (gdb) step

   In display():

   16         printf("   z is %d and is stored at %p.\n", z, &z);

   (gdb) step

      z is 5 and is stored at 0xbffffb10.

   17         printf("   zptr holds %p and points to %d.\n", zptr, *zptr);

Recall that the frame is where automatic variables for the function are stored. Unless you tell it otherwise, GDB is always in the context of the frame corresponding to the currently executing function. Since execution is currently in display(), GDB is in the context of frame 0. We can ask GDB to tell us which frame its context is in by giving the frame command without arguments:

回想一下,帧是函数中的自动变量(automatic variable)的存储空间。除非你告诉它不要这样,否则,GDB总是出于正在执行的函数所对应的帧的上下文环境中。尽管GDB是在函数display()中执行,但是GDB仍然在第0帧的上下文中。通过执行没有参数的frame命令,我们可以让GDB告诉我们其帧所在的上下文环境:

   (gdb) frame

   #0  display (z=5, zptr=0xbffffb34) at try1.c:17

   17         printf("   zptr holds %p and points to %d.\n", zptr, *zptr);

I didn't tell you what the word "context" means; now I'll explain. Since GDB's context is in frame 0, we have access to all the local variables in frame 0. Conversely, we don't have access to automatic variables in any other frame. Let's investigate this. GDB's print command can be used to give us the value of any variable within the current frame. Since z and zptr are variables in display(), and GDB is currently in the frame for display(), we should be able to print their values:

我之前没有告诉你“上下文环境(context)”的意思;现在我来解释一下。因为GDB的上下文环境在帧0中,所以我们可以访问所有帧0中的局部变量(local variable)。相反地,我们无法访问其它帧中的自动变量(automatic variables)。让我们研究一下这些。GDBprint命令能够列出当前帧中任意变量的值。由于zzptr是函数diplay()中的变量,当前的GDB位于函数display()的帧中,我们应该能够列出它们的值:

   (gdb) print z

   $1 = 5

   (gdb) print zptr

   $2 = (int *) 0xbffffb34

But we do not have access to automatic variables stored in other frames. Try to look at the variables in main(), which is frame 1:

但是我们无法访问存储在其他帧中的自动变量。尝试查看函数main()中的变量,其帧号为1

   (gdb) print x

   No symbol "x" in current context.

   (gdb) print xptr

   No symbol "xptr" in current context.

Now for magic. We can tell GDB to switch from frame 0 to frame 1 using the frame command with the frame number as an argument. This gives us access to the variables in frame 1. As you can guess, after switching frames, we won't have access to variables stored in frame 0. Follow along:

现在,我们可以通过使用帧号作为参数调用frame命令,以告诉GDB从帧0中切换到帧1中去。这使得我们可以访问帧1中的变量。正如你可以猜到的,在切换了帧后,我们无法访问存储在帧0中的变量。如下所示:

   (gdb) frame 1                           <--- switch to frame 1

   #1  0x08048455 in main () at try1.c:10

   10         display(x, xptr);

   (gdb) print x

   $5 = 5                                  <--- we have access to variables in frame 1

   (gdb) print xptr

   $6 = (int *) 0xbffffb34                 <--- we have access to variables in frame 1

   (gdb) print z

   No symbol "z" in current context.       <--- we don't have access to variables in frame 0

   (gdb) print zptr

   No symbol "zptr" in current context.    <--- we don't have access to variables in frame 0

By the way, one of the hardest things to get used to with GDB is seeing the program's output:

顺便提一下,习惯GDB最困难的事情之一就是查看程序的输出:

   x is 5 and is stored at 0xbffffb34.

   xptr holds 0xbffffb34 and points to 5.

intermixed with GDB's output:

混合着GDB的输出:

   Starting program: try1

   In main():

   ...

      Breakpoint 1, main () at try1.c:10

   10         display(x, xptr);

intermixed with your input to GDB:

混合着你在GDB中的输入:

   (gdb) run

intermixed with your input to the program (which would've been present had we called some kind of input function). This can get confusing, but the more you use GDB, the more you get used to it. Things get tricky when the program does terminal handling (e.g. ncurses or svga libraries), but there are always ways around it.

混合着你在程序中的输入(我们之前列出的被称之为输入函数)。这变得令人困惑,你用GDB越多,你越习惯它。当程序处理终端时,事情会变得复杂(例如ncurse库或者svga库),但是仍然有办法绕过它。

Exercises

练习

1.   Continuing from the previous example, switch back to display()'s frame. Verify that you have access to automatic variables in display()'s frame, but not main()'s frame.

继续之前的例子,重新切换到函数display()的帧。确保你可以访问函数display()的帧中的变量,但无法访问函数main()帧中的变量。

2.   Figure out how to quit GDB on your own. Control-d works, but I want you to guess the command that quits GDB.

指出如何从你的GDB中退出。Ctrl+d可以,但是我想让你猜测退出GDB的命令。

3.   GDB has a help feature. If you type help foo, GDB will print a description of command foo. Enter GDB (don't give GDB any arguments) and read the help blurb for all GDB commands we've used in this section.

GDB拥有帮助特征。如果你输入help fooGDB将列出命令foo的描述。进入GDB(不要使用任何GDB参数),并阅读本部分中我们使用的所有的GDB命令的帮助简介。

4.   Debug try1 again and set a breakpoint anywhere in display(), then run the program. Figure out how to display the stack along with the values of every local variable for each frame at the same time. Hint: If you did the previous exercise, and read each blurb, this should be easy.

再次调试try1,并在函数display()内任意位置设置一个断点,然后运行程序。找出如何同时列出所有阵中的局部变量。提示:如果你做了之前的练习,并读取了每一个提示,那么这将变得很容易。

 

阅读(1895) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~