Debug Python with GDB in off-line CentOS

GDB不仅可以调试C/C++程序,它还可以调试Python等程序,是非常重要和有力的开发工具之一。大部分情况下,都可以直接安装发行版提供的gdb来调试程序,但是,某些情况下需要自己编译,例如,机器无法上网;甚至没有权限修改系统,无法安装软件。下面以CentOS为例,介绍如何在离线并且没有全部权限的情况下使用GDB调试Python程序。

首先,需要在相同系统版本可以上网的机器上编译GDB

wget -c http://ftp.gnu.org/gnu/gdb/gdb-8.0.1.tar.gz

tar xvf gdb-8.0.1.tar.gz

cd gdb-8.0.1.tar.gz

mkdir build

cd build

../configure --enable-static --with-python --disable-interprocess-agent --with-lzma

make

需要拿到离线机器使用,所以这里使用‘–enable-static –disable-interprocess-agent’,期望获得一个静态链接的二进制文件。通过‘–with-python’启用了python调试功能。‘–with-lzma’启用了lzma支持,由于某些发行版分发的二进制文件使用了‘MiniDebugInfo’特性:在二进制文件中存在一个名为‘gnu_debugdata’的特殊区域,该区域保存了经过lzma压缩的额外符号信息。

如果在configure过程中报错,一般都是缺少一些依赖包,根据提示进行安装就可以了。

编译完成后,需要的文件都在build/gdb目录内,将他们复制到另一个目录

mkdir -p ~/gdb/data-directory

cd gdb

cp gdb ~/gdb/gdb

cp -r data-directory/* ~/gdb/data-directory

cd ~/gdb

chmod +x ./gdb

data-directory目录内保存了gdb需要使用的数据文件。现在就可以使用这个gdb在离线系统上简单调试程序了

./gdb --data-directory=./data-directory your-program

但是当进入到一些库函数时,会出现错误,因为系统上没有相应的调试信息文件。需要手动部署这些文件,以调试Python为例,首先获取系统中python包的版本

yum list installed | grep ^python.x86_64

假如得到的信息是‘python.x86_64 2.7.5-58.el7’,去debuginfo.centos.org下载相应的调试信息文件,例如,’python-debuginfo-2.7.5-58.el7.x86_64.rpm‘。

将该文件解包

rpm2cpio python-debuginfo-2.7.5-58.el7.x86_64.rpm | cpio -idmv

正常情况下,该文件是通过rpm安装到系统里的,安装后的结构和直接解压出来的结构可能不同,系统目录:

ls -l /usr/lib/debug/

drwxr-xr-x. 3 root root 64 11月 5 2016 .

dr-xr-xr-x. 44 root root 4096 10月 28 21:38 ..

lrwxrwxrwx. 1 root root 7 10月 28 21:21 bin -> usr/bin

lrwxrwxrwx. 1 root root 7 10月 28 21:21 lib -> usr/lib

lrwxrwxrwx. 1 root root 9 10月 28 21:21 lib64 -> usr/lib64

lrwxrwxrwx. 1 root root 8 10月 28 21:21 sbin -> usr/sbin

drwxr-xr-x. 6 root root 65 10月 28 21:21 usr

解包目录:

ls -al ./usr/lib/debug/

drwxr-xr-x. 5 buildbot buildbot 46 10月 29 23:26 .

drwxrwxr-x. 3 buildbot buildbot 19 10月 29 23:26 ..

drwxr-xr-x. 114 buildbot buildbot 4096 10月 29 23:26 .build-id

drwxr-xr-x. 2 buildbot buildbot 40 10月 29 23:26 .dwz

drwxr-xr-x. 4 buildbot buildbot 30 10月 29 23:26 usr

系统目录中的符号链接,解包目录里是没有的,需要手动加上。解包目录里的‘.build-id’文件夹是非常重要的文件夹。

GDB允许将调试信息和可执行代码分开存放,因为一般情况,调试信息比可执行代码大很多,普通用户不需要调试信息。分开存放很好的解决了此问题。

GDB支持两种指定调试信息文件的方法:

  • 可执行文件包含一个‘debug link’,其指定了调试文件的名字。通常调试信息文件的名字是‘executable.debug’,其中,‘executable’是可执行文件的名字(不包括路径)。并且,‘debug link’还包含了可执行文件的CRC32值,GDB可以利用该值判断可执行文件和调试信息文件是否来自相同的build。
  • 可执行文件包含一个特殊的字符串‘build-id’,该字符串同时也存在于调试信息文件中。调试信息文件的名字可以通过‘build-id’计算出来。

对于以上两种不同的方式,GDB采用两种方法查找调试信息文件:

  • 对于‘debug link’方式,GDB先在可执行文件所在目录搜索指定名字的调试信息文件,然后是可执行文件所在目录中名为‘.debug’的子目录里搜索,最后,在所有全局‘debug directory’里的和可执行文件绝对目录名字一样的子目录里搜索。
  • 对于‘build-id’方式,GDB在每一个全局‘debug directory’里名为‘build-id’的子目录中搜索名为‘nn/nnnnnnnn.debug’的文件(CentOS中该文件是一个符号链接)。

准备好了以上文件后,还需要最后一个很重要的文件,该文件定义了调试需要用到的GDB命令。该文件位于

./usr/lib/debug/usr/lib64/libpython2.7.so.1.0.debug-gdb.py

将该文件重命名为python-gdb.py,并和其他文件放一起。现在gdb目录的结构是这样的:

gdb

data-directory

usr

python-gdb.py

启动gdb后需要import python-gdb.py,修改PYTHONPATH

export PYTHONPATH="${PYTHONPATH}:${HOME}/gdb"

然后启动gdb

./gdb --data-directory=./data-directory

设置debug文件查找目录,告诉GDB去准备好的解包目录找调试信息

set debug-file-directory ./usr/lib/debug/

载入python模块,载入自定义GDB命令

python import python-gdb

然后attach到需要调试的进程就可以了

attach pid

Ref