gdb 多线程调试 暂停其他线程_OpenStack断点调试方法总结

784907a984f0f419327cb37932e5af97.png

1 关于断点

调试断点是调试应用程序最主要的方式之一,通过设置断点,可以实现单步执行代码,检查变量的值以及跟踪调用栈,甚至修改进程的内存变量值或者运行代码,从而观察修改后的程序行为。

大多数的调试器都是通过ptrace系统调用控制和监视进程状态,通过INT 3软件中断实现断点。当我们在代码中插入一个断点时,其实就是调试器找到指令位置(编译成机器码后的位置)嵌入一个INT 3指令,进程运行时遇到INT 3指令时,操作系统就会将该进程暂停,并发送一个SIGTRAP信号,此时调试器接收到进程的停止信号,通过ptrace查看进程状态,并通过标准输入输出与用户交互,更多关于断点和调试信息实现原理可以参考国外的一篇文章How debuggers work【1】,这里只需要注意调试器是通过标准输入输出(stdin、stdout)与用户交互的。

目前主流的调试工具如gdb、jdb以及针对Python语言的pdb等。本文接下来主要介绍的是针对OpenStack的一些调试方法,这些方法不仅适用于OpenStack,其他Python程序同样适用。

2 Python调试工具介绍


Python主要使用pdb工具进行调试,用法也很简单,只要在需要打断点的位置嵌入pdb.set_trace()代码即可。比如如下Python代码:

3f962b0433d78d07b583c3a64b040ebc.png

该代码相当于在say()函数第一行嵌入了一个断点,当代码执行到该函数时,会立即停止,此时可以通过pdb执行各种指令,比如查看代码、查看变量值以及调用栈等,如下:

3e66c7b19606c6e640c53eecfb1ca994.png

当然你也可以使用ipdb替换pdb,界面更友好,支持语法高亮以及命令自动补全,使用体验类似于ipython,如图2-1:

edf43765f0e8625d19b986e64a0e3512.png

图2-1 ipdb界面或者也可以使用功能更强大的ptpdb工具,支持多屏以及更强大的命令补全,如图2-2:

566c465c116aa7663087e065bbf88889.png

图2-2 ptpdb界面

最上面为pdb指令输入框,左下为代码执行位置,右下为当前调用栈。

以上三个工具的pdb指令都是一样的,基本都是pdb工具的包装,详细的使用方法可以查看官方文档【2】或者Google相关资料,这里不对pdb命令进行过多介绍。

3 OpenStack常规调试方法

OpenStack断点调试是学习OpenStack工作流程的最佳方式之一,关于OpenStack源码结构可以参考我之前的一篇文章《如何阅读OpenStack源码》【3】。

我们知道OpenStack是基于Python语言开发的,因此自然可以使用如上介绍的pdb工具进行断点调试。比如,我想了解OpenStack Nova是如何调用Libvirt Driver创建虚拟机的,只需要在nova/virt/libvirt/driver.py模块的spawn()方法打上断点:

decb885733e73b32a66826238f0ce53c.png

然后停止nova-compute服务,使用命令行手动运行nova-compute:

3efff567e221cd65b6c0e12ffcf57514.png

在另外一个终端使用nova boot命令启动虚拟机,如果有多个计算节点,为了保证能够调度到打了断点的节点,建议把其他计算节点disable掉。

此时nova-compute会在spawn()方法处停止运行,此时可以通过pdb工具查看变量、单步执行等。

对于一些支持多线程多进程的OpenStack服务,为了方便调试,我一般会把verbose选项以及debug设置为False,避免打印太多的干扰信息,并把服务的workers数调成1,防止多个线程断点同时进入导致调试错乱。

比如调试nova-api服务,我会把osapi_compute_workers配置项临时设置为1。

通过如上调试方法,基本可以完成大多数的OpenStack服务调试,但并不能覆盖全部服务,某些OpenStack服务不能直接使用pdb进行调试,比如Keystone、Swift等某些组件,此部分内容将在下一节中进行详细介绍。

4 OpenStack不能直接使用pdb调试的情况

我们前面提到能够调试的前提是终端能够与进程的stdin、stdout直接交互,对于某些不能交互的情况,则必然不能直接通过pdb进行调试。主要包括如下几种情况:

4.1 进程关闭了stdin/stdout

cloud-init就是最经典的案例,在cloudinit/cmd/main.py的入口函数main_init()调用了close_stdin()方法关闭stdin,如下:

08d7c1c0ca3848f6507780d86679c5ef.png

close_stdin()方法实现如下:

6ffecbcfd7054d253e0e78c794b89443.png

相当于把stdin重定向到/dev/null了。因此当我们在cloud-init打上断点时,并不会弹出pdb调试页面,而是直接抛出异常。比如制作镜像时经常出现cloud-init修改密码失败,于是需要断点调试,我们在cloudinit/config/cc_set_passwords.py模块的handle()方法打上断点,结果pdb直接异常退出,从/var/log/cloud-init.log中可以看到如下错误信息:

64e4f1967bd857570d5a5494ab0c5624.png

我们从close_stdin()以及redirect_output方法可以发现,我们可以通过设置_CLOUD_INIT_SAVE_STDIN以及_CLOUD_INIT_SAVE_STDOUT环境变量开放stdin/stdout,从而允许我们进入调试:

0b76592597de7f7018423660f51bad04.png

除了cloud-init,OpenStack Swift也类似,可以查看swift/common/utils.py模块的capture_stdio()方法,

66be1a0b1cdb33607777ecb6157710b1.png

因此account-server、container-server以及object-server均无法直接使用pdb调试。

4.2 Fork多进程

如果一个进程Fork了子进程,则子进程的stdin、stdout不能直接与终端交互。最经典的场景就是OpenStack组件使用了cotyledon库而不是oslo_service库实现daemon。

我们知道oslo_service使用eventlet库通过多线程实现并发,而cotyledon则使用了multiprocess库通过多进程实现并发,更多关于cotyledon的介绍可以参考官方文档【4】。

因此使用cotyledon实现的daemon服务不能通过pdb直接进行调试,比如Ceilometer的polling-agent以及Kuryr的kuryr-daemon服务等。文章《使用pdb调试ceilometer代码》【5】提出通过实现一个新的类ForkedPdb重定向stdin的方法实现子进程调试,这种方法我本人没有尝试过,不知道是否可行。

4.3 运行在Web服务器

最经典的如Keystone服务以及Horizon服务,我们通常会把该服务运行在Apache服务器上,显然这种情况终端没法直接和Keystone的stdin、stdout进行交互,因此不能通过pdb直接调试。

5 如何解决不能使用pdb直接调试的问题

我们前面总结了几种不能使用pdb直接调试的情况,其根本原因就是终端无法和进程的stdin/stdout交互,因此我们解决的思路就是让终端与进程的stdin/stdout打通。

我们知道stdin以及stdout都是文件流,有没有其他的流呢?显然socket也是一种流。因此我们可以通过把stdin、stdout重定向到一个socket流中,从而实现远程调试。

定义如下方法,把stdin、stdout重定向到本地的一个TCP socket中,监听地址端口为1234:

4c97ba81e80685a33b8692621922f329.png

当然我们也需要把pdb的stdin、stdout也重定向到该socket中,这样才能与pdb交互,用法如下:

e3a9bdeda71d6448702d2959e8ad3147.png

运行该程序后,使用另一个终端通过nc或者telnet连接1234端口即可进行调试,如图5-1:

0e5c202d3fd74f974e8ad9b0740cdf36.png

可见,通过这种方式可以实现远程调试,不过我们不用每次都写那么长一段代码,社区已经有实现了,只需要使用rpdb替换pdb即可进行远程调试,原理与之类似,默认监听的端口为4444。比如调试Keystone的list_projects()方法:

d783d1f6c8ff33665d61927a1328f0cc.png

然后重启httpd服务,重启完毕调用project list API:

73cdb5be85495ff11dcf955d8c3e9a09.png

如上openstack project list命令会hang住,此时通过nc或者telnet连接本地4444端口进行调试:

15c852c7108ea1e5407accb6f74d7c50.png

可见成功attach了pdb,此时可以像普通pdb一样进行单步调试了。

参考资料

【1】How debuggers work:https://eli.thegreenplace.net/tag/debuggers
【2】pdb官方文档:https://docs.python.org/2.7/library/pdb.html
【3】如何阅读OpenStack源码:https://zhuanlan.zhihu.com/p/28959724
【4】cotyledon官方文档:https://cotyledon.readthedocs.io/en/latest/index.html
【5】使用pdb调试ceilometer代码:https://blog.csdn.net/mengalong/article/details/81125585

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值