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

爱生活,爱阅读

文章分类

全部博文(83)

文章存档

2015年(1)

2013年(1)

2012年(80)

2011年(1)

分类:

2012-09-17 20:21:12

Using GNU's GDB Debugger

Initialization, Listing, And Running

By Peter Jay Salzman

我们现在在哪里?

在上一章中,我们学习了进程的内存布局,即:被划分为段。其中一个重要的段称作调用栈(call stack)(或者栈(stack))。栈是由栈帧(stack frame)构成的。每个函数调用对应一个帧(栈帧),并且该帧保存了三类重要信息:

1.   The local variables for the function.

函数的局部变量。

2.   The current address pointer within the function.

函数内的当前地址指针。【注:应该是被调用函数在调用函数内的地址指针。】

3.   The arguments passed to the function.

传递给函数的参数。

当调用函数时,新的栈帧被分配并添加到栈中。当函数返回时,它的栈帧被返还给未用的栈内存,代码重新返回到前一个函数的当前地址指针指向的地址。我们可以通过backtrace命令让GDB告诉我们栈的形式。我们也能够通过frame命令找出GDB的上下文环境所在的帧。最后,我们可以通过frame n命令将GDB的上下文环境切换到第n帧。

可执行程序没有包含对目标(函数与变量)名或者源代码行号的引用。调试一个没有这些信息的程序是非常痛苦的,所以,我们通过gcc-g选项产生了符号符号表。

最后,我们简要的学习如何利用break命令来使得GDB暂停程序的执行,以及通过step命令执行源代码中的一行。关于这些命令,我们现在还有很多要说的。

我们将要去向何方?

在本章中,我们将研究list命令,该命令(令人惊讶地)列出源代码行。我们将深入的理解GDB的初始化文件.gdbinit。最后,我们将查看run命令,该命令将从GDB内执行一个程序。

源代码的基本列表

下载导数(derivative),一个计算数字导数的程序,并一起讨论:。花点时间来熟悉代码。注意那些绝妙的函数指针的应用。

你可以通过GDBlist命令列出源代码,也可以使用其缩写形式:l。通过GDB执行该可执行程序,并使用list命令:

$ gdb driver

(gdb) list

12    }

13

14

15

16    int main(int argc, char *argv[])

17    {

18        double x, dx, ans;

19        double Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta;

20

21        if (argc != 1) {

默认情况下,GDB总是列出10行源代码。当你首次使用list时,GDB将列出以main()函数为中心的10行源代码。接下来的list命令将列出之后的10行源代码。尝试一下:

(gdb) list

22        printf("You must supply a value for the derivative location!\n");

23        return EXIT_FAILURE;

24    }

25

26    x   = atol(argv[1]);

27    ans = sin(log(x)) / x;

28

29    printf("%23s%10s%10s%11s%10s%11s\n", "Forward", "error", "Central",

30        "error", "Extrap", "error");

31

(gdb)

使用3次或更多list,你将看到:

     ... output suppressed

 

45            printf("dx=%e: %.5e %.4f  %.5e %.4f  %.5e %.4f\n",

46                dx, Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta);

47        }

48 

49        return 0;

50    }

(gdb) list

Line number 51 out of range; driver.c has 50 lines.

(gdb)

当我们第二次使用list时,GDB仅输出了9行源代码,这是因为已经到达了文件的末尾。最后一个list不会列出任何代码行。这是因为list总是输出之前已列出来的代码之后的10行。不过已经没有更多的代码可以列出了。

list -”像list一样工作,不过却是以相反地方式。它列出了上次列出的代码行之前的10行代码。由于第50行已经是最后的代码行了,List –应该列出第41行到第50行:

(gdb) list -

41

42                Extr      = ExtrapolatedDiff(x, dx, &f);

43                ExtrDelta = fabs(Extr - ans);

44

45                printf("dx=%e: %.5e %.4f  %.5e %.4f  %.5e %.4f\n",

46                    dx, Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta);

47            }

48

49        return 0;

50    }

(gdb)

如果你为list命令指定了一个行号,那么GDB将列出以该行号为中心的10行代码:

(gdb) list 13

8

9      double f(double x)

10     {

11          return cos(log(x));

12     }

13

14

15

16     int main(int argc, char *argv[])

17     {

(gdb)

为了节省空间,我将抑制list的输出。然而我强烈的鼓励你自己,按照我的例子来执行GDB。在你实际执行这些操作之前,尝试想象一下这些操作的输出结果。

你将发现其他的列表操作(listing operation)很有用处:

starting with some line number

从某一行开始

(gdb) list 5,

ending with some line number

到某一行结束

(gdb) list ,28

between two numbers:

在两行之间:

(gdb) list 21,25

by function name:

通过函数名:

(gdb) list f

functions in the other file:

在其他文件中的函数

(gdb) list CentralDiff

by filename and line number:

通过文件名于行号:

(gdb) list derivative.c:12

filename and function name:

通过文件名于函数名:

(gdb) list derivative.c:ForwardDiff

list命令将“记忆”被列出过的文件,用以列出源代码。开始时,我们列出了文件driver.c中的代码。然后通过告诉GDB列出函数CentraDiff(),转换到了文件derivative.c中。这样,现在list位于文件derivative.c的“上下文环境(context)”中。因此,如果我们再次执行list,那么它将列出derivative.c中的代码。

(gdb) list

11    }

12

13

14

15     double ExtrapolatedDiff( double x, double dx, double (*f)(double) )

16     {

17         double term1 = 8.0 * ( f(x + dx/4.0) - f(x - dx/4.0) );

18         double term2 = ( f(x + dx/2.0) - f(x - dx/2.0) );

19

20         return (term1 - term2) / (3.0*dx);

但是,如果我们又想从文件driver.c中列出代码的话,应该怎么办呢?我们如何回到那个文件?我们简单地列出了driver.c所有内容,包括函数或行号。所有以下命令将重设list命令的上下文环境从derivative.c重回到driver.c

   list main

   list f

   list driver.c:main

   list driver.c:f

   list driver.c:20

等等。规则并不复杂;在调试了一些多文件的程序后,你将会掌握一些窍门。

通过内存地址进行列表(高级的)

每个函数均是从某个内存地址开始的。你可以通过print function找到该地址。例如,我们将要找到main()函数的地址:

(gdb) print *main

$1 = {int (int, char **)} 0x8048647

(gdb)

因而main()函数在0x8048647地址处。我们也可以通过内存地址来使用list命令;语法与C语言很类似:

(gdb) list *0x8048647

0x8048647 is in main (driver.c:17).

12     }

13

14

15

16     int main(int argc, char *argv[])

17     {

18          double x, dx, ans;

19          double Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta;

20

21          if (argc != 1) {

(gdb)

显而易见,0x8048690也是函数main()的内部地址。让我们来看一看:

(gdb) list *0x8048690

0x8048690 is in main (driver.c:26).

21          if (argc != 1) {

22               printf("You must supply a value for the derivative location!\n");

23               return EXIT_FAILURE;

24          }

25

26          x   = atol(argv[1]);

27          ans = sin(log(x)) / x;

28

29          printf("%23s%10s%10s%11s%10s%11s\n", "Forward", "error", "Central",

30               "error", "Extrap", "error");

(gdb)

Exercises

练习

1.   Using list and print *, figure out how many machine instructions are used for this line of code:

使用list print *计算出下面的代码行对应多少条机器指令:

18          double x, dx, ans;

19          double Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta;

Think about this for a second; you'll learn a bit about compilers and machine instructions.

花费一点时间考虑下这个问题;你将学到一些关于编译器与机器指令的知识点。

设置List大小

在列出代码时,GDB是以10行为增量的。也许太多或者太少了。你可以通过命令set与变量listsize改变列出行的大小:

(gdb) set listsize 5

(gdb) list main

15

16     int main(int argc, char *argv[])

17     {

18          double x, dx, ans;

19          double Forw, ForwDelta, Cent, CentDelta, Extr, ExtrDelta;

(gdb)

Exercises

练习

1.   There's actually a lot of things you can set. Issue help set from GDB's prompt. I'm not expecting you to read it all---I just want you to marvel at how big the list is!

实际上,你可以设置很多的事情。在GDB提示符下执行help set。我并不期望你阅读完所有内容---我只想让你对这么大一个列表感到吃惊!

The .gdbinit File

文件.gdbinit

GDB启动的时候,它将读取并执行一个名字为.gdbinit的初始化文件。它能够包含任何的命令(例如,set break),以及”set listsize” 和“set prompt”等。GDB将在两个位置查找该文件(依顺序):

1.   In your home directory

用户主目录

2.   In the current directory

当前目录

You can put commands to be executed for all your programming projects in  and project-specific commands in $PWD/.gdbinit.

你可以将对所有程序工程执行的命令放在$HOME/.gdbinit中,而将特定工程的命令放在$PWD/.gdbinit中。

You can comment your .gdbinit files with bash's "#". And blank lines, of course, are ignored.

你可以使用bash的‘#’来注释.gdbinit文件。当然,空行会被忽略。

Exercises

练习

1.   When you invoke GDB, it prints a copyright notice. Using GDB's man page, figure out how to prevent GDB from printing this notice. Using your shell's alias feature, make an alias for "gdb" that invokes GDB, but surpresses the copyright notice. I use this alias myself.

当你调用GDB时,将会列出版权提示。使用GDB的手册,找出如何避免输出此类提示。使用shell的别名特征(alias feature),为”gdb”指定一个别名来调用GDB,但是抑制了版权提示。我自己就使用这种别名。

2.   Figure out how to reset GDB's prompt from (gdb) to something that tickles your fancy. Google would be a great way of figuring this out. GDB's help utility would also be useful (hint: you want to "set" the prompt to something else). Modify .gdbinit so that GDB uses your chosen prompt on startup.

找出将GDB提示符(gdb)重新设置为其它令人高兴的形式的方法。Google将是找出答案的方式。GDBhelp功能也很用用处(提示:你可以使用“set”命令来更改提示符)。更改.gdbinit,使得GDB在启动的时候使用你选择的提示符。

3.   You can even use terminal escape codes to put color in your GDB prompt! If you don't know about terminal color escape codes, you can read about them here. One caveat: You have to use the octal code \033 for the escape character. So for example, bold blue would be \033[01;34m. And then don't forget to turn the blue off, otherwise everything  will be blue. I'll let you figure out how to do that yourself! Thanks to Jeff Terrell for pointing this out to me!

你甚至可以使用终端转义码使得你的GDB提示符带有颜色!如果不知道终端的转义码,你可以在这里阅读关于它们的材料。附加说明:你必须为转义字符使用8进制码\033。因而,例如,粗体蓝色应该为\033[01;34。不要忘记将蓝色关闭,否则所有的数据均为蓝色。我将让你自己找出如何关闭之!多谢Jeff Terrell为我指出这一点。

Windows下的gdbinit

Thanks Ted Alves

多谢Ted Alves

.gdbinit文件使用的环境变量HOME,通常在Windows下没有定义。你必须自行设置/定义:(你必须作为管理员登陆Windows来设置环境变量)。邮件点击“我的电脑”,左键点击“属性”,左键选择“高级列表页”,左键选择“环境变量按钮”。增加新的环境变量:HOME:“(你的选择路径) c:\documents and settings\username”(没有双引号),并点击“确认”键保存。在重启之后,为了确定该新的环境变量依然存在,进入“设置”查看。你也可以在重启之前,输入“set HOME=(你选择的路径)”,但这种方法不是永久的。

Windows 资源管理器不接受/创建名字为”.gdbinit”的文件。但Windows本身对于此类文件没有任何问题。在你的%HOME%目录下,创建一个名字类似为:gdb.init的文件,之后进入命令行,并输入“move gdb.init .gdbinit”。这将创建该文件,且资源管理器现在就同它开始工作了。在你用命令为HOME文件进行编辑之前,你可能想把这个文件拷贝到目的工作目录。【什么意思】。

GDB中运行程序

让我们完全地介绍run命令。下载并编译

没有参数的run命令将以没有命令行的方式运行你的程序。如果你想为程序设置参数,使用run命令加上任何你想传递给程序的任何参数:

   $ gdb arguments

   (gdb) run 1 2

   Starting program: try2 1 2

   Argument 0: arguments

   Argument 1: 1

   Argument 2: 2

  

   Program exited normally.

   (gdb)

没有什么比这更简单了,从现在开始,无论何时你再使用run命令,它将自动的使用那些你刚刚使用的参数(例如,“1 2”):

   (gdb) run

   Starting program: arguments 1 2

   Argument 0: arguments

   Argument 1: 1

   Argument 2: 2

  

   Program exited normally.

   (gdb)

直到你告诉它使用不同的参数:

   (gdb) run testing one two three

   Starting program: arguments testing one two three

   Argument 0: testing

   Argument 1: one

   Argument 2: two

   Argument 3: three

  

   Program exited normally.

   (gdb)

假设你想运行没有命令行的程序?你应该怎样停止自动地传递参数?有一个“set args”命令。如果你不带任何参数执行该命令,run将不再自动的向程序传递命令行参数:

   (gdb) set args

   (gdb) run

   Starting program: arguments

   Argument 0: try2

  

   Program exited normally.

   (gdb)


如果你为set args 指定了一个参数,该参数将在下次你使用run的时候,传递给该程序,正如你将这些参数直接指定给run命令一样。

set args”命令有另外的用途。每当开启一个调试会话,如果你都想传递相同的参数给程序,你可以把它放在你的.gdbinit文件中。这样,每当你在一个给定的工程中打开GDB,并执行run命令时,参数将自动传递给程序而不用每次进行指定。

GDB中重新运行程序

有时,你希望在GDB中从头重新执行一个程序。你想这样做的一个原因是:如果你发现在程序中设定的断点太晚了,而你想在较早的地方设置断点。在GDB中重新启动一个程序有三种方式:

1.   Quit GDB and start over.

退出GDB并重新开始

2.   Use the kill command to stop the program, and run to restart it.

使用kill命令终止程序,然后运行run命令重新执行之。

3.   Use the GDB command run. GDB will tell you the program is already running and ask if you want to re-run the program from the beginning.

使用GDBrunGDB将告诉你程序已经在运行,并询问你是否愿意从头开始重新运行该程序。

最后两个选项将使得所有的设置原封不动:断点,观察点,命令,变量【】等等。然而,如果你不介意重新执行程序,且不对上次调试会话中的信息进行任何保留,那么,退出GDB毫无疑问是一个选择。

你或许想知道,既然你可以通过quit退出GDB,或者用run重新运行程序,为什么还有一个kill命令呢。Kill命令看起来是一种多余。使用该命令有以下几种原因,你可以在这里阅读之。多谢Suresh Babu 指出:在进行远程调试或者通过附属命令(attach command)进行调试时,kill命令有所用处。我自己从来没有使用kill命令。

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