调试GCC

调试GCC,有点烦。

首先,gcc程序本身并非真正的编译器,它调用cc1或者cc1plus完成真正的编译工作,而cc1(或cc1plus)的参数并非与调用gcc时使用的参数一直,所以必须添加-v选项,把实际调用cc1(或cc1plus)的命令行找出来,然后传给gdb。

其次,我想在Emacs里面调试……

终于不胜其烦,写了自动化脚本(取名debug.sh),使得启动gdb的过程简化为如下:

$ gcc -O -c hello.c  # 啊!? 有bug?!
$ debug.sh !!

脚本内容如下:

#!/bin/bash

CC=$1

if [ -z "$CC" ]
then
    echo "Usage:"
    echo "    debug.sh gcc -O -c hello.c"
    exit 0
fi

shift

CC1_CMD=$($CC -v "$@" 2>&1 | grep -E 'cc1(plus)? ')
echo $CC1_CMD

: ${EMACSCLIENT:=/usr/bin/emacsclient}
echo "Using $EMACSCLIENT"

if `$EMACSCLIENT --help | grep '\-e, --eval\b' >/dev/null`
then
    EMACSCLIENT_OPTIONS="-n"

    if [ -n "$DISPLAY" ]
    then
        if `$EMACSCLIENT --help | grep '\-c, --create-frame\b' >/dev/null`
        then
            EMACSCLIENT_OPTIONS="$EMACSCLIENT_OPTIONS -c"
        elif `$EMACSCLIENT --help | grep '\-nw, -t, --tty\b' >/dev/null`
        then
            EMACSCLIENT_OPTIONS="$EMACSCLIENT_OPTIONS -nw"
        fi
    elif `$EMACSCLIENT --help | grep '\-nw, -t, --tty\b' >/dev/null`
    then
        EMACSCLIENT_OPTIONS="$EMACSCLIENT_OPTIONS -nw"
    fi

    $EMACSCLIENT $EMACSCLIENT_OPTIONS \
        --eval "(gdb \"gdb -cd $PWD --annotate=3 --args $CC1_CMD\")"
else
    echo "Emacsclient doesn't support --eval option."
    echo "Use gdb directly"
    gdb --args $CC1_CMD
fi

实际上,我的脚本还包括了寻找和加载编译目录下的.gdbinit文件,但是局限于特定结构,不能普遍使用,所以上面的脚本并没有包括这部分功能。有兴趣的话,可以根据自己的目录结构添加此功能,我是用-x $GDBINIT的方式传给gdb的。

Extending Emacs bookmark

GNU Emacs带有一个简单实用的书签管理工具——bookmark。

简单地说,它有四个主要的元素:

  • filename: 书签所对应的文件名
  • position: 书签在对应文件里的位置
  • bookmark-set: 在当前buffer的当前光标位置设置书签
  • bookmark-jump: 跳到相应的书签位置

bookmark还提供了一定的扩展功能,如果上述四个概念不足以实现某种特定的书签,可以在其基础上扩展。

doc-view是GNU Emacs用来查看PDF文件的工具,它首先把PDF转换成图片,每页一张图,然后把图片显示出来。对于doc-view来说,书签定义的位置是页,而不是某个字符的位置,所以doc-view必须通过扩展才能使用bookmark。

扩展方式很简单,首先为doc-view-mode定义一个单独的书签创建函数,在书签创建时两条额外的信息

  • 页数
  • 以及一个处理该书签的函数,以跳到相应的页数
(defun doc-view-bookmark-make-record ()
  (nconc (bookmark-make-record-default)
         `((page     . ,(doc-view-current-page))
           (handler  . doc-view-bookmark-jump))))

;;;###autoload
(defun doc-view-bookmark-jump (bmk)
  ;; This implements the `handler' function interface for record type
  ;; returned by `doc-view-bookmark-make-record', which see.
  (prog1 (bookmark-default-handler bmk)
    (let ((page (bookmark-prop-get bmk 'page)))
      (when (not (eq major-mode 'doc-view-mode))
        (doc-view-toggle-display))
      (with-selected-window
          (or (get-buffer-window (current-buffer) 0)
              (selected-window))
        (doc-view-goto-page page)))))

然后用该函数替换掉默认的书签创建函数。具体实现参见doc-view.el文件。

(set (make-local-variable 'bookmark-make-record-function)
     'doc-view-bookmark-make-record)

然而,doc-view提供的书签有一点小小的缺陷,就是没有保存解析度(doc-view-resolution)。doc-view本身提高缩放功能,但是如果缩放比例没有保存的话,使用书签后PDF将以默认比例打开,阅读体验不太友好。

修改的方式和前面几乎一样,唯一的区别是在doc-view-mode-hook里面替换掉doc-view-mode原来的书签创建函数,改用自己写的那个。代码部分摘录如下,具体参见我的wl-ebook-viewer.el中关于doc-view的部分(其中还包括了自动设置书签和打开PDF时自动跳到书签位置的代码,用于模拟定位上次阅读位置功能)。

(defun wl-doc-view-bookmark-make-record ()
  (nconc (bookmark-make-record-default)
         `((page . ,(doc-view-current-page))
           (resolution . ,doc-view-resolution)
           (handler . wl-doc-view-bookmark-jump))))

(defun wl-bookmark-get-resolution (bookmark)
  "Get doc view resolution."
  (or (bookmark-prop-get bookmark 'resolution)
      (default-value 'doc-view-resolution)))

(defun wl-doc-view-bookmark-jump (bookmark)
  (prog1 (doc-view-bookmark-jump bookmark)
    (let ((resolution (wl-bookmark-get-resolution bookmark)))
      (assert (> resolution 0))
      (when (/= doc-view-resolution resolution)
        (set (make-local-variable 'doc-view-resolution) resolution)
        (doc-view-reconvert-doc)))))