在Emacs中使用gdb调试程序

1 引言

Emacs除了具有强大的编辑功能,还可以作为调试工具gdb的前端,对程序进行调试。使用Emacs进行调试,可以将程序的编写与调试统一到Emacs中,并利用Emacs强大的功能辅助调试,是将Emacs作为IDE使用的一项必备功能。

本文假定读者具有基本的程序调试知识,希望知道在Emacs下进行基本调试的对应操作。水平有限,欢迎拍砖。

2 准备工作:将调试信息编译在程序中

要使程序能被gdb调试,需要在编译时加入调试所需的信息。如果使用gcc/g++进行编译,需要使用参数-g,如:

gcc prog.c -g -o prog

如果使用 ./confiugre && make 的编译流程,可以将使用如下方式引入-g参数:

CFLAGS="-g" ./configure
make

注意:不要加入任何优化参数(例如-O、-O2),不然调试时会有很灵异的现象发生……

3 开始:开启Emacs的调试模式GUD

 

3.1 运行gdb

在编译好程序后,就可以开始调试了。直接运行gdb命令 M-x gdb RET 在minibuffer中会出现需要执行的gdb命令。例如: gdb –annotate=3 prog 如果当前目录下有可执行文件(通常恰好是需要调试的文件),gdb会在其后自动补上可执行文件,否则需要在minibuffer中补上要调试的程序文件名。

继续回车,Emacs的GUD(Grand Unified Debugger)就会关联到gdb并加载要调试的程序了。

3.2 gdb界面

启动gdb后,Emacs的界面会变成下面两种之一:

GDB单窗格模式

GDB单窗格模式

GDB多空格模式

GDB多窗格模式

可以通过gdb-many-windows来切换这两种界面布局。

如果界面被打乱了(例如,在minibuffer中使用补全,查看帮助,重新编译程序),可以使用gdb-restore-windows来恢复界面布局。

3.3 小结

命令 功能
gdb 启动gdb进行调试
gdb-many-windows 切换单窗格/多窗格模式
gdb-restore-windows 恢复窗格布局

接下来就要开始调试程序了。

4 调试:设置断点,控制程序流程

 

4.1 设置、删除断点

首先将断点设置在要调试的地方。有两种方法:

第一种,在要设置断点的行左边的fringe上单击一下(就是文本左边与滚动条之间空出的那一块)。隐藏了fringe的朋友可以M-x fringe-mode显示它。

第二种,使用默认快捷键C-x C-a C-b, 或者 C-x <SPC>。它们都关联到命令gud-break。

无论使用哪种方法,fringe上都会在设置了断点的行上显示一个红点,表示这行设了断点:

fringe上的断点标记

fringe上的断点标记

同时,在断点buffer中也会显示已有的断点信息:

断点buffer

断点buffer

要删除断点,同样有两种对应的方法:在fringe的断点上单击一下,或者使用快捷键C-x C-a C-d(对应命令gud-remove)。

可以在断点buffer上单击某个断点切换到断点所在位置。将光标移动到断点处回车也有同样的效果。

在断点buffer上按空格键可以切换断点的激活和禁用状态。

4.2 运行程序

设置好断点后就可以运行程序了。单击工具栏上的 emacs-gdb-toolbar-go.png 就开始运行了。也可以使用gud-go命令来运行。奇怪的是没有任何默认快捷键绑定。

当程序运行到断点时,程序会在断点处停下来,并自动打开停下的语句所在的代码文件。同时在fringe上在停下的语句处有三角形的指示器。

当前语句指示器

当前语句指示器

现在,我们来一步步运行程序。

4.3 单步执行、运行到光标处

在调试中最常用的功能就是单步执行了。单步执行有两种:将函数调用作为一条语句执行(Next)和遇到函数时进入函数中进行调试(Step)。

要使用第一种方式,默认快捷键是C-x C-a C-n,对应命令为gud-next。也可以单击工具栏上的 emacs-gdb-toolbar-next.png

第二种方式的默认快捷键是C-x C-a C-s,对应命令为gud-step。也可以单击工具栏上的 emacs-gdb-toolbar-step.png

如果想跳出当前函数,可以使用命令gud-finish,默认快捷键为C-x C-a C-f,工具栏上有 emacs-gdb-toolbar-finish.png 可用。

在Emacs中还可以运行到光标所在的行。使用命令gud-until即可,默认快捷键为C-x C-a C-u。(注:我在使用时只有光标所在的行在当前行之后并且位于同一函数内才行,否则会跳到很奇怪的地方,还请高手指教)。

也可以直接把当前语句指示器拖到任意一行,程序会运行到那一行再停下来。

4.4 继续运行程序

在程序中断后要继续运行程序,依然是使用gud-go命令或 emacs-gdb-toolbar-go.png ,也可以使用命令gud-cont,对应快捷键为C-x C-a C-r。

4.5 小结

功能 命令 默认快捷键
添加断点 gud-break C-x C-a C-b 或 C-x <SPC>
删除断点 gud-remove C-x C-a C-d
运行/继续程序 gud-go
单步执行,无视函数 gud-next C-x C-a C-n
单步执行,进入函数 gud-step C-x C-a C-s
跳出当前函数 gud-finish C-x C-a C-f
运行到光标所在语句 gud-until C-x C-a C-u
继续运行程序 gud-cont C-x C-a C-r

5 察看变量的值

调试的过程中免不了要查看变更的值。Emacs提供了方便地功能让我们查看变量的值。

5.1 本地变量buffer

如果打开了gdb-many-windows,在右上角会显示Locals buffer,其中显示了当前局部变量的值。如果显示的是寄存器(Register)buffer,单击左边的Locals就可以切换到Locals buffer了。在其中可以方便地观察到各变量的值。

如果没有打开gdb-many-windows,也可以使用gdb-display-locals-buffer来显示该buffer。

5.2 察看变量值

遇到一些Locals里没有显示的变量,或者比较复杂的结构,就需要用到观察变量的功能了。

将光标停留在要观察的变量上,执行命令gud-watch,可以将变量加入观察列表中。默认的快捷键是C-x C-a C-w。也可以使用工具栏上的 emacs-gdb-toolbar-watch.png

被观察的变量将在Speedbar中显示。对于复杂结构,可以单击Speedbar上的+号将其展开或收缩。在+号上按空格键也有相同的效果。(注:我在使用过程中经常出现展开没反应,或者加入新元素后才展开,运行几步才展开的情况,求高人讲解)

有时候Emacs观察的变量不是你所想要的,一般是a->b这类的情况。这时可以将要观察的部分选中,再使用上述方法即可。

在Speedbar中观察变量

在Speedbar中观察变量

5.3 用工具提示显示变量值

可以用gud-tooltip-mode开启或关闭工具提示。开启后将鼠标指针停留在变量名上时将在工具提示中显示变量的值。

在工具提示中显示变量的值

在工具提示中显示变量的值

5.4 小结

功能 命令 默认快捷键
观察变量 gud-watch C-x C-a C-w
展开/收缩变量   <SPC>
开启/关闭工具提示 gud-tooltip-mode  

6 输入输出

如果程序需要与标准输入/输出交互,那么你很可能需要用到下面要介绍的功能。

6.1 单独的IO buffer

默认来说,程序的输入输出是在gdb buffer里显示的。这样输出信息和gdb信息混合在一起,阅读起来非常不便。这时候,你需要把输入输出单独显示在一个buffer里,方便查看。

使用gdb-use-separate-io-buffer,可以在程序代码buffer右侧新建一个IO buffer,程序对标准输入输出的操作都会重定向到这里。再执行一次该命令则会隐藏。

6.2 输入数据

需要输入数据的时候,只需要在IO buffer中输入数据回车即可。已经输入的数据会被加粗,以和输出信息区分开来。

6.3 重定向到文件

有时候我们已经准备好了用于输入的数据在文件中,以避免调试时烦琐的输入。这时候就需要在调试时进行输入输出重定向了。

要进行重定向,只能使用gdb自带的功能。在gdb buffer中输入 run < data.in > data.out 就可以将标准输入重定向到data.in,将标准输出重定向到data.out了。

7 按键绑定

说实话,gud自带的按键绑定实在是麻烦,使用一个功能要三次组合键才行,小姆指按Ctrl都按酸了。所以一般将常用的按键绑定在方便的位置,这样才能有和另的IDE一样的快感。

以下是将F5、F7、F8分别绑定到gud-go、gud-step和gud-next的代码:

(add-hook 'gdb-mode-hook '(lambda ()
                            (define-key c-mode-base-map [(f5)] 'gud-go)
                            (define-key c-mode-base-map [(f7)] 'gud-step)
                            (define-key c-mode-base-map [(f8)] 'gud-next)))

之所以绑定到c-mode-base-map上,是因为我基本上在代码buffer中调试。如果要在gdb-buffer中使用的话,需要使用gud-mode-map。如果想在所有buffer上使用的话,可以绑定到全局按键中:

(global-set-key [(f5)] 'gud-go)

8 结尾

有了调试功能,Emacs作为一个IDE才算是完整了。本文介绍了在Emacs下使用gdb调试的基本方法。由于我也是边学边写,一定有许多不足或者错误,还请各位多多指教。

emacs Comments(4) 2010年3月26日 06:02

Emacs Mode for GDB

GUI是现代调试器的一个必备部分。这个模式为GDB提供了一个GUI,并保持了Emacs的强大功能。

继续阅读

emacs Comments(0) 2008年8月30日 19:02

[转]在Emacs下用C、C++编程


  转自http://hi.baidu.com/yilinghl/blog/item/5dc7d55cb4271f43faf2c069.html

用emacs写程序也有5个年头了,深切地体会到Emacs的强大。程序员有三种,一种是用vi的,一种是用emacs的,还有一种是其他。或许有些夸张,但也颇能体现出emacs在程序员中的地位。

emacs最大的问题在于入门门槛较高。它看起来和多数人想象中的IDE相差甚远,很多人看到emacs的第一眼就觉得它是个记事本(还是个非常难用的记事本),稍微好些的往往觉得emacs也就是个ultraEditor而已,真是暴殄天物了。

我是个懒人,不喜欢记太多的快捷键,相信很多人和我一样。所以从我后面的叙述可以看出来,除了常用的命令都是快捷键外,其他命令多数都是用M-x执行或者用鼠标点菜单。这仅仅是个人风格问题,先说明一下。

我的基本编程环境是:

  • Debian GNU/Linux sid 操作系统
  • Gnome 2.10.0 桌面环境
  • GUN Emacs 23.0.0.1 for debian
  • 使用 Gnu tool chains(gcc,make,gdb等等)

后面的叙述都基于上述环境。另外,本文主要针对C/C++程序开发,对其他语言有些也适用,从难度上说,本文主要针对入门者。

本文肯定会有很多错误,请指正,谢谢。

基本流程

写C++程序基本上是这么几个步骤:

  1. 编辑代码
  2. 编写Makefile
  3. 编译代码,修改编译错误
  4. 调试代码,修改逻辑错误

当然,往往还需要阅读别人的代码。

根据上述步骤,本文主要针对以下几个方面:

  • 配置Emacs,建立便利的代码编辑环境和Makefile编写环境。
  • 在Emacs中编译代码,并修改编译错误。
  • 在Emacs中配合GDB调试程序。
  • 利用cscope和ecb在emacs中阅读代码。

基本环境设置

编辑环境配置

要写C++程序,当然要用到cc-mode插件。CC-Mode原本是支持C语言的,但现在也能支持很多语言,比如C++,Java, Objective-C,CORBA,AWK,Pike等等。CC-Mode是gnu-emacs的标准插件。如果您要求不高,那么默认的配置或许就能满 足。CC-Mode的各种行为都可以自由地定制,您可以参考这里的文档:CC-Mode参考文档

这里是我的.emacs文件中关于CC-Mode配置的部分,仅供参考:

;;;; CC-mode配置  http://cc-mode.sourceforge.net/
(require 'cc-mode)
(c-set-offset 'inline-open 0)
(c-set-offset 'friend '-)
(c-set-offset 'substatement-open 0)
;;;;我的C/C++语言编辑策略

(defun my-c-mode-common-hook()
  (setq tab-width 4 indent-tabs-mode nil)
  ;;; hungry-delete and auto-newline
  (c-toggle-auto-hungry-state 1)
  ;;按键定义
  (define-key c-mode-base-map [(control \`)] 'hs-toggle-hiding)
  (define-key c-mode-base-map [(return)] 'newline-and-indent)
  (define-key c-mode-base-map [(f7)] 'compile)
  (define-key c-mode-base-map [(meta \`)] 'c-indent-command)
;;  (define-key c-mode-base-map [(tab)] 'hippie-expand)
  (define-key c-mode-base-map [(tab)] 'my-indent-or-complete)
  (define-key c-mode-base-map [(meta ?/)] 'semantic-ia-complete-symbol-menu)
;;;;我的C/C++语言编辑策略

(defun my-c-mode-common-hook()
  (setq tab-width 4 indent-tabs-mode nil)
  ;;; hungry-delete and auto-newline
  (c-toggle-auto-hungry-state 1)
  ;;按键定义
  (define-key c-mode-base-map [(control \`)] 'hs-toggle-hiding)
  (define-key c-mode-base-map [(return)] 'newline-and-indent)
  (define-key c-mode-base-map [(f7)] 'compile)
  (define-key c-mode-base-map [(meta \`)] 'c-indent-command)
;;  (define-key c-mode-base-map [(tab)] 'hippie-expand)
  (define-key c-mode-base-map [(tab)] 'my-indent-or-complete)
  (define-key c-mode-base-map [(meta ?/)] 'semantic-ia-complete-symbol-menu)

注意一下,上面最后两行是代码自动补齐的快捷键。后面我会提到代码自动补齐。

  ;;预处理设置
  (setq c-macro-shrink-window-flag t)
  (setq c-macro-preprocessor "cpp")
  (setq c-macro-cppflags " ")
  (setq c-macro-prompt-flag t)
  (setq hs-minor-mode t)
  (setq abbrev-mode t)
)
(add-hook 'c-mode-common-hook 'my-c-mode-common-hook)

;;;;我的C++语言编辑策略
(defun my-c++-mode-hook()
  (setq tab-width 4 indent-tabs-mode nil)
  (c-set-style "stroustrup")
;;  (define-key c++-mode-map [f3] 'replace-regexp)
)

自动补齐

自动补齐通常用的都是hippie-expand,我也用了很长时间。不过有时候会觉得这个自动补齐“傻”了一点,常会补齐出一些毫不相干的东西,因为hippie-expand是根据你敲过的词和kill-ring等进行判断的,并不对程序语法进行分析。

所以你还需要安装一个代码分析工具,然后把它加进hippie-expand的扩展策略里去。我们可以用semantic。实际上,hippie-expand+semantic是我所发现的最好的选择了,如果您有更好的,请您也告诉我一声:)

Semantic是CEDET中的一个工具,CEDET是Collection of Emacs Development Environment Tools的缩写,它包含了好几个工具,都挺不错的。可惜我只会用其中两个。

您可以在.emacs中对Semantic进行配置,下面是我的.emacs相关的配置,仅供参考:

导入cedet:

(load-file "~/lib/emacs-lisp/cedet-1.0pre3/common/cedet.el")

配置Semantic的检索范围:

(setq semanticdb-project-roots
   (list
        (expand-file-name "/")))
 

自定义自动补齐命令,这部分是抄hhuu的,如果在单词中间就补齐,否则就是tab。

(defun my-indent-or-complete ()
   (interactive)
   (if (looking-at "\>")
    (hippie-expand nil)
    (indent-for-tab-command))
 )

(global-set-key [(control tab)] 'my-indent-or-complete)

hippie的自动补齐策略,优先调用了senator的分析结果:

(autoload 'senator-try-expand-semantic "senator")

(setq hippie-expand-try-functions-list
    '(
  senator-try-expand-semantic
  try-expand-dabbrev
  try-expand-dabbrev-visible
  try-expand-dabbrev-all-buffers
  try-expand-dabbrev-from-kill
  try-expand-list
  try-expand-list-all-buffers
  try-expand-line
        try-expand-line-all-buffers
        try-complete-file-name-partially
        try-complete-file-name
        try-expand-whole-kill
        )
)

注意一下我前面CC-Mode配置中有这么两行:

  (define-key c-mode-base-map [(tab)] 'my-indent-or-complete)
  (define-key c-mode-base-map [(meta ?/)] 'semantic-ia-complete-symbol-menu)

这样,我们在CC-Mode中就可以调用自定义的hippie补全了,快捷键是Tab。

另外,我还把快捷键“Alt + / ”绑定到了semantic-ia-complete-symbol-menu命令上,这是semantic的命令,它会根据分析结果弹出补齐的菜单,效果如图显示:

CEDET中还有一个不错的工具是speedbar,你可以用它在多个文件中快速切换。在我的.emacs配置文件里,我把speedbar关联到了F5上:

(global-set-key [(f5)] 'speedbar)

这样用F5就可以调出speedbar,效果如下:

 

不过说实话,我自己很少用到speedbar,我通常都是用dired配合bookmark使用:)

编译和调试程序

按上面的配置,写完程序和Makefile文件后,在Emacs源代码窗口中按F7就可以进行编译。因为在my-c-mode-common-hook()函数里,有这么一行:

(define-key c-mode-base-map [(f7)] 'compile)

默认情况下,emacs的compile命令是调用make -k,我把它改成了make。你也可以把它改成其他的,比如gcc之类的。改下面的“make”就行了。

'(compile-command "make")

Emacs会划分一个窗格显示编译的消息,在编译结束后,emacs会自动将编译器的输出和程序关联起来,告诉你第几行的程序有问题。直接在出错的行号上按Enter,就可以跳转到相应文件的相应行。其实我通常都是用鼠标中键去点出错行号:)

搞定了编译错误后,接着要和逻辑错误斗争了。其实对简单的程序来说,把中间结果打印到终端是最简单好用的调试办法:)不过稍微复杂点的程序就会晕菜了,这时我们就需要拿gdb跟踪程序流程了。

你用下面的命令就可以启动gdb了。

M-x gdb

通常我喜欢进入gdb-many-windows模式,这样就会把一个Frame划分为5个窗格,同时显示:gdb命令窗口,当前局部变量,程序文本,调用栈和断点。

gdb的命令就不在这里说了,它的文档几乎到处都是。emacs把gdb的命令和快捷键做了绑定,对于常用的命令,还是输入快捷键比较方便。比如,C-c C-n是Next line,C-c C-s是step in,其实用的最多的快捷键也就是这两个。

下面是我的gdb效果图:

阅读代码

在emacs下读代码通常有三种工具,最简单的是etags,最复杂的是ecb(emacs code browser),位于中间的是cscope。

etags和ctags一样,只不过前者是用于emacs的,后者是用于vi的。我个人觉得etags功能稍稍显得不够用一点,当然,也可能是我用的不好:) 欢迎大牛指导。

使用tags之前要先对源代码分析建立tags文件,在代码所在目录中运行:etags -R 即可。

我常用的就这几个命令和快捷键:

M-x visit-tags-table <RET> FILE <RET>   选择tags文件
M-. [TAG] <RET>                         访问标签
M-*                                     返回
C-u M-.                                 寻找标签的下一个定义

ecb据说功能强大,但是太复杂了,我懒得折腾它。谁搞定了教教我吧:) 下面是一张ecb的效果图。

cscope是我感觉比较合适的一个工具。它其实是一个独立的软件,完全可以脱离vi和emacs使用。但是结合emacs的强大功能, cscope就显得更加方便了。GNU Emacs默认自带cscope的支持。在使用之前,cscope也需要对代码进行索引。在emacs中可以这样做:

C-c s a             设定初始化的目录,一般是你代码的根目录
C-s s I             对目录中的相关文件建立列表并进行索引

建完索引之后,你就可以用cscope在代码里游荡了。常用的一些命令如下:

C-c s s             序找符号
C-c s g             寻找全局的定义
C-c s c             看看指定函数被哪些函数所调用
C-c s C             看看指定函数调用了哪些函数
C-c s e             寻找正则表达式
C-c s f             寻找文件
C-c s i             看看指定的文件被哪些文件include

上面这些快捷键其实我自己也常常记不全,没关系,抬头看看上面的菜单栏,有一栏就是Cscope,这些命令里头都有:)

贴一个cscope的效果图吧:

linux Comments(1) 2008年4月04日 03:57