Emacs除了具有强大的编辑功能,还可以作为调试工具gdb的前端,对程序进行调试。使用Emacs进行调试,可以将程序的编写与调试统一到Emacs中,并利用Emacs强大的功能辅助调试,是将Emacs作为IDE使用的一项必备功能。
本文假定读者具有基本的程序调试知识,希望知道在Emacs下进行基本调试的对应操作。水平有限,欢迎拍砖。
要使程序能被gdb调试,需要在编译时加入调试所需的信息。如果使用gcc/g++进行编译,需要使用参数-g,如:
gcc prog.c -g -o prog
如果使用 ./confiugre && make 的编译流程,可以将使用如下方式引入-g参数:
CFLAGS="-g" ./configure make
注意:不要加入任何优化参数(例如-O、-O2),不然调试时会有很灵异的现象发生……
在编译好程序后,就可以开始调试了。直接运行gdb命令 M-x gdb RET 在minibuffer中会出现需要执行的gdb命令。例如: gdb –annotate=3 prog 如果当前目录下有可执行文件(通常恰好是需要调试的文件),gdb会在其后自动补上可执行文件,否则需要在minibuffer中补上要调试的程序文件名。
继续回车,Emacs的GUD(Grand Unified Debugger)就会关联到gdb并加载要调试的程序了。
启动gdb后,Emacs的界面会变成下面两种之一:
GDB单窗格模式
GDB多窗格模式
可以通过gdb-many-windows来切换这两种界面布局。
如果界面被打乱了(例如,在minibuffer中使用补全,查看帮助,重新编译程序),可以使用gdb-restore-windows来恢复界面布局。
命令 | 功能 |
---|---|
gdb | 启动gdb进行调试 |
gdb-many-windows | 切换单窗格/多窗格模式 |
gdb-restore-windows | 恢复窗格布局 |
接下来就要开始调试程序了。
首先将断点设置在要调试的地方。有两种方法:
第一种,在要设置断点的行左边的fringe上单击一下(就是文本左边与滚动条之间空出的那一块)。隐藏了fringe的朋友可以M-x fringe-mode显示它。
第二种,使用默认快捷键C-x C-a C-b, 或者 C-x <SPC>。它们都关联到命令gud-break。
无论使用哪种方法,fringe上都会在设置了断点的行上显示一个红点,表示这行设了断点:
fringe上的断点标记
同时,在断点buffer中也会显示已有的断点信息:
断点buffer
要删除断点,同样有两种对应的方法:在fringe的断点上单击一下,或者使用快捷键C-x C-a C-d(对应命令gud-remove)。
可以在断点buffer上单击某个断点切换到断点所在位置。将光标移动到断点处回车也有同样的效果。
在断点buffer上按空格键可以切换断点的激活和禁用状态。
设置好断点后就可以运行程序了。单击工具栏上的 就开始运行了。也可以使用gud-go命令来运行。奇怪的是没有任何默认快捷键绑定。
当程序运行到断点时,程序会在断点处停下来,并自动打开停下的语句所在的代码文件。同时在fringe上在停下的语句处有三角形的指示器。
当前语句指示器
现在,我们来一步步运行程序。
在调试中最常用的功能就是单步执行了。单步执行有两种:将函数调用作为一条语句执行(Next)和遇到函数时进入函数中进行调试(Step)。
要使用第一种方式,默认快捷键是C-x C-a C-n,对应命令为gud-next。也可以单击工具栏上的 。
第二种方式的默认快捷键是C-x C-a C-s,对应命令为gud-step。也可以单击工具栏上的 。
如果想跳出当前函数,可以使用命令gud-finish,默认快捷键为C-x C-a C-f,工具栏上有 可用。
在Emacs中还可以运行到光标所在的行。使用命令gud-until即可,默认快捷键为C-x C-a C-u。(注:我在使用时只有光标所在的行在当前行之后并且位于同一函数内才行,否则会跳到很奇怪的地方,还请高手指教)。
也可以直接把当前语句指示器拖到任意一行,程序会运行到那一行再停下来。
在程序中断后要继续运行程序,依然是使用gud-go命令或 ,也可以使用命令gud-cont,对应快捷键为C-x C-a C-r。
功能 | 命令 | 默认快捷键 |
---|---|---|
添加断点 | 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 |
调试的过程中免不了要查看变更的值。Emacs提供了方便地功能让我们查看变量的值。
如果打开了gdb-many-windows,在右上角会显示Locals buffer,其中显示了当前局部变量的值。如果显示的是寄存器(Register)buffer,单击左边的Locals就可以切换到Locals buffer了。在其中可以方便地观察到各变量的值。
如果没有打开gdb-many-windows,也可以使用gdb-display-locals-buffer来显示该buffer。
遇到一些Locals里没有显示的变量,或者比较复杂的结构,就需要用到观察变量的功能了。
将光标停留在要观察的变量上,执行命令gud-watch,可以将变量加入观察列表中。默认的快捷键是C-x C-a C-w。也可以使用工具栏上的 。
被观察的变量将在Speedbar中显示。对于复杂结构,可以单击Speedbar上的+号将其展开或收缩。在+号上按空格键也有相同的效果。(注:我在使用过程中经常出现展开没反应,或者加入新元素后才展开,运行几步才展开的情况,求高人讲解)
有时候Emacs观察的变量不是你所想要的,一般是a->b这类的情况。这时可以将要观察的部分选中,再使用上述方法即可。
在Speedbar中观察变量
可以用gud-tooltip-mode开启或关闭工具提示。开启后将鼠标指针停留在变量名上时将在工具提示中显示变量的值。
在工具提示中显示变量的值
功能 | 命令 | 默认快捷键 |
---|---|---|
观察变量 | gud-watch | C-x C-a C-w |
展开/收缩变量 | <SPC> | |
开启/关闭工具提示 | gud-tooltip-mode |
如果程序需要与标准输入/输出交互,那么你很可能需要用到下面要介绍的功能。
默认来说,程序的输入输出是在gdb buffer里显示的。这样输出信息和gdb信息混合在一起,阅读起来非常不便。这时候,你需要把输入输出单独显示在一个buffer里,方便查看。
使用gdb-use-separate-io-buffer,可以在程序代码buffer右侧新建一个IO buffer,程序对标准输入输出的操作都会重定向到这里。再执行一次该命令则会隐藏。
需要输入数据的时候,只需要在IO buffer中输入数据回车即可。已经输入的数据会被加粗,以和输出信息区分开来。
有时候我们已经准备好了用于输入的数据在文件中,以避免调试时烦琐的输入。这时候就需要在调试时进行输入输出重定向了。
要进行重定向,只能使用gdb自带的功能。在gdb buffer中输入 run < data.in > data.out 就可以将标准输入重定向到data.in,将标准输出重定向到data.out了。
说实话,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)
有了调试功能,Emacs作为一个IDE才算是完整了。本文介绍了在Emacs下使用gdb调试的基本方法。由于我也是边学边写,一定有许多不足或者错误,还请各位多多指教。
关于Emacs有一个很著名的笑话,就是Emacs = Emacs Makes A Computer Slow。Emacs启用慢是一个人尽皆知的事实。由于启动时要加载大量的脚本和插件,使得Emacs在启动时往往需要数秒之久。因此我在进行一些快速简单的任务,如svn commit时,都是使用nano或者vim来进行的。
Emacs daemon就是为了这个而诞生的。它将Emacs变成了一个C/S模型——只需要启动一个服务器在后台作为守护进程(daemon)跑着,之后启动的每个emacs都是一个客户端,它连接上服务器进行工作。这样一来,只要在启动服务器时运行初始化脚本,客户端启动无需运行脚本,实现启动时间从Firefox到Chrome的转变。
要使用Emacs daemon,至少需要Emacs 23。它提供了一个emacsclient程序,用于启动客户端。
有客户端就必然要有服务端。要启动一个daemon,需要在运行emacs时加入一个--daemon参数。幸运的是,我们不用手动启动服务端,而是可以利用emacsclient的-a参数。emacsclient的-a参数用于指定连接不上服务器时使用的别的编辑器(alternate editor),当把这项留空时,它会自动启动服务端。如果不想指定-a,也可以将ALTERNATE_EDITOR环境变量设为""。
除了-a参数,emacsclient还要手工指定使用终端还是X来启动。要从终端启动,需要使用-t参数:
emacsclient -t -a ""
从X启动则是-c:
emacsclient -c -a ""
每次都要输入上面那些命令太烦了,不符合*nix的终极目标——偷懒
首先把终端版本设一个alias。编辑~/.bashrc,在最后加入
#用ec来快速启动emacs client alias ec='emacsclient -t -a ""' #现在可以将emacs设为默认编辑器啦:P export EDITOR="ec"
以后在终端下输入ec就可以启动emacs的终端客户端了:)
在X下,我习惯用gnome-do来启动程序。因此最方便的方法就是建立一个菜单项,名叫Fastmacs,内容自然就是X模式启动emacsclient的命令啦
俗说说得好,.emacs说有多少就有多少+1。如果在.emacs里对X相关的选项(字体什么的)直接进行设置,那么会发现用emacsclient启动时,这些设置都失效了。这是因为这些设置是在X下的frame创建时才有效的,而启动服务器的时候是没有创建frame的。
解决方法有两种,一种是使用after-make-frame-functions这个hook,在创建一个frame之后才进行设置。代码如下
(defun frame-setting () (set-frame-font "文泉驿等宽微米黑 8") (set-fontset-font "fontset-default" 'gb18030 '("文泉驿等宽微米黑" . "unicode-bmp"))) (if (and (fboundp 'daemonp) (daemonp)) (add-hook 'after-make-frame-functions (lambda (frame) (with-selected-frame frame (frame-setting)))) (frame-setting))
需要判断是否以daemon模式启动,分别进行处理
另一种方法是设置window-system-default-frame-alist,直接设置参数的默认值
(setq window-system-default-frame-alist '( ;; if frame created on x display (x (menu-bar-lines . 1) (tool-bar-lines . nil) ;; mouse (mouse-wheel-mode . 1) (mouse-wheel-follow-mouse . t) (mouse-avoidance-mode . 'exile) ;; face (font . "文泉驿等宽微米黑 8") ) ;; if on term (nil (menu-bar-lines . 0) (tool-bar-lines . 0) ;; (background-color . "black") ;; (foreground-color . "white") ) ) )
可以设置的参数见Emacs Lisp Reference > Frames > Frame Parameters
推荐使用第二种方法,启动客户端时直接就使用所设置的字体了
http://www.wanglianghome.org/blog/2009/01/customization-tips-for-emacs-daemon.html
http://jackycxh.blog.35.cn/2009/07/22/emacs-daemon-and-font/
看到simplyzhao配置好了他的vim,我也忍不住要配置一下我的emacs了,目前的目标是让我的emacs在程序开发上更顺手
暂时先学学这些吧:
C-x RET f
输入目标编码就行了,我是用utf-8-unix
对应命令是set-buffer-file-coding-system
GUI是现代调试器的一个必备部分。这个模式为GDB提供了一个GUI,并保持了Emacs的强大功能。
通过Doxymacs令Emacs可以方便生成C++文档注释