1 背景介绍

gdb 调试多任务程序时会有些麻烦: fork 之后没法同时跟踪父进程和子进程,如果在子进程里设置了一个 breakpoint,那么子进程将会收到一个 SIGTRAP 信号并退出。gdb 手册里提到了一种小技巧,那就是在想要插入 breakpoint 的位置添加 sleep() 调用。但经过笔者试验,添加以下代码更加适合:

  1. static volatile int hold = 1;
  2. while (hold) ;

然后,重新编译程序并执行。这样,当程序运行到 while 位置的时候就会循环在那里,接下来就可以调试了。

2 调试步骤

多任务基本调试步骤如下:

  1. ps -ef | grep xxx,找到你关心的进程
  2. gdb attach pid,关联到当前 pid 对应的 program
  3. info threads,查看哪个线程正在执行你关心的 path
  4. thread xx,切换到那个线程
  5. set hold=0,设置 hold 为 0,使程序继续运行。

如果发生了进程间通信,数据流从 A 发送往 B 了,此时便可以采取上述方法,同时跟踪 A 和 B。每次发生了 pipe/socket 通信,就重复上述步骤。通过这种方式,一些很复杂的多任务程序也可以清晰调试。

3 调试案例

Libvirt 是用于管理虚拟化平台的开源的 API,后台程序和管理工具。它可以用于管理 KVM、Xen、VMware ESX,QEMU 和其他虚拟化技术。

接下来以 libvirt 的分析过程为例来介绍 gdb 的多任务调试。

libvirt 的基本操作和大概结构是这样的:

  • libvirt 组件有一个 shell,被称为 virsh,提供类似 shell 的界面,可以输入 start、shutdown 等命令操作虚拟机

  • libvirt 有一个守护进程,libvirtd,其对 virsh 的命令做出响应
    • 以 non-root 执行 virsh start 时,将以 qemu://session 的方式运行。libvirtd 将启动一个 non-root 的子进程来与 virsh 进行 socket 通信
    • 以 root 执行 virsh start 时,将以 qemu://system 方式运行,libvirtd 直接与 virsh 进行 socket 通信
  • 无论是上述哪种方式,都会创建多个(一般16个)线程,该线程的的作用是将 socket 传递过来的各个命令和配置进行解析,最终形成一个 cmd。

  • 子线程会将 cmd 通过 pipe 传递给 libvirtd,libvirtd 会 fork 出一个子进程,并 exec cmd

但如果我们想弄清楚 virsh 启动 qemu 的全过程的细节,即在 virsh 里敲入 start xxx_domain,到 exec qemu bin,这中间究竟发生了什么细节呢?这就必须要 gdb 调试了。可以想象,这过程中必定有大量的进程间通信(socket、pipe),这时就出现了文章开头说明的问题:当前 thread 将数据流发给了另外的 thread,而另外的 thread 却没法跟踪并停止。

  1. 我们通过 log 大概知道了 qemuProcessStart 是启动的必经之路,因此在这个函数里添加代码:

    1. int
    2. qemuProcessStart(virConnectPtr conn, unsigned int flags)
    3. {
    4. ...
    5. static volatile int hold = 1;
    6. while (hold) ;
    7. }
  2. 重新编译、安装、重启 libvirtd

    1. # make && make install
    2. # service libvirtd restart
  3. 跟踪 libvirtd

    1. # ps -ef | grep libvirtd
    2. root 16529 1 0 16:34 ? 00:00:00 /usr/local/sbin/libvirtd --listen
    3. # gdb /usr/local/sbin/libvirtd 16529
  4. 在其他的 console 里启动虚拟机

    1. # virsh start xxx
  5. 按下 Ctrl+c 停止 gdb,查看所有 threads:

    1. (gdb) info threads
    2. Id Target Id Frame
    3. 15 Thread 0x7f915bccd700 (LWP 16531) "libvirtd" 0x00007f9156ba3296 in qemuProcessStart (conn=conn@entry=0x7f914c1056e0, flags=flags@entry=1)
    4. ...
    5. * 1 Thread 0x7f916325d840 (LWP 16529) "libvirtd" 0x00007f9160c0ca4d in poll () from /lib64/libc.so.6
    6. (gdb)
  6. 切换到 thread 15,set hold=0

    1. (gdb) thread 15
    2. [Switching to thread 15 (Thread 0x7fb9ac921700 (LWP 27260))]
    3. #0 qemuProcessStart (conn=conn@entry=0x7fb97c000aa0, driver=driver@entry=0x7fb99c00da90, vm=vm@entry=0x7fb99c00b910, updatedCPU=updatedCPU@entry=0x0,
    4. asyncJob=asyncJob@entry=QEMU_ASYNC_JOB_START, migrateFrom=migrateFrom@entry=0x0, migrateFd=migrateFd@entry=-1, migratePath=migratePath@entry=0x0,
    5. snapshot=snapshot@entry=0x0, vmop=vmop@entry=VIR_NETDEV_VPORT_PROFILE_OP_CREATE, flags=flags@entry=1) at qemu/qemu_process.c:5878
    6. 5878 while (hold)
    7. (gdb) set hold=0

接下来就可以继续调试下去了。

通过这种办法就可以得知整个过程。以下是通过重复上述步骤获取到的知识:

  • 子线程将 cmd 通过 pipe 传递给 libvirtd

    1. (gdb)
    2. #0 virCommandHandshakeNotify (cmd=cmd@entry=0x7f6e4400fa40) at util/vircommand.c:2757
    3. #1 0x00007f6e5e7666cd in qemuProcessLaunch (conn=conn@entry=0x7f6e500009a0, driver=driver@entry=0x7f6e54000e80, vm=vm@entry=0x7f6e54012ec0,
    4. asyncJob=asyncJob@entry=QEMU_ASYNC_JOB_START, incoming=incoming@entry=0x0, snapshot=snapshot@entry=0x0, vmop=vmop@entry=VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
    5. flags=flags@entry=17) at qemu/qemu_process.c:5685
    6. 2729 int virCommandHandshakeNotify(virCommandPtr cmd)
    7. ...
    8. 2749 if (safewrite(cmd->handshakeNotify[1], &c, sizeof(c)) != sizeof(c)) {
    9. 2750 virReportSystemError(errno, "%s", _("Unable to notify child process"));
    10. 2751 VIR_FORCE_CLOSE(cmd->handshakeNotify[1]);
    11. 2752 return -1;
    12. 2753 }
    13. ...
    14. 2756 }
  • libvirtd 收到 cmd 后,会执行 virExec,

    1. #0 virExec (cmd=cmd@entry=0x7f72bc0026d0) at util/vircommand.c:491
    2. #1 0x00007f72d2219b07 in virCommandRunAsync (cmd=cmd@entry=0x7f72bc0026d0, pid=pid@entry=0x0) at util/vircommand.c:2452
    3. #2 0x00007f72d221a0c4 in virCommandRun (cmd=cmd@entry=0x7f72bc0026d0, exitstatus=exitstatus@entry=0x7f72c29fab64) at util/vircommand.c:2284
    4. #3 0x00007f72d222c166 in virFirewallCheckUpdateLock (lockflag=lockflag@entry=0x7f72d26768a3 <iptablesUseLock>, args=args@entry=0x7f72c29fabc0) at util/virfirewall.c:124
    5. ...
    6. #20 0x00007f72d072035d in clone () from /lib64/libc.so.6
  • virExec() 将会 fork 出子进程,子进程将会执行 exec(qmeu-system-x86_64)

    1. 475 virExec(virCommandPtr cmd)
    2. ...
    3. 749 if (cmd->uid != (uid_t)-1 || cmd->gid != (gid_t)-1 || cmd->capabilities || (cmd->flags & VIR_EXEC_CLEAR_CAPS)) {
    4. 753 if (virSetUIDGIDWithCaps(cmd->uid, cmd->gid, groups, ngroups,
    5. ...
    6. 790 if (cmd->env)
    7. 791 execve(binary, cmd->args, cmd->env); <--- 启动了qemu
    8. ...

在执行了 execve 之后,当前父进程就会结束退出了。如果想要继续跟踪子进程 qemu,那么仍然需要重复上述过程。

4 libvirt 分析小结

我们以一张流程图来展示整个 libvirt 启动 qemu 的过程

5 抛砖引玉

在 Windows 调试工具 windbg 上,用户可以选择子进程在创建后,wait 并等待用户调试,从而省去了上述麻烦。gdb 是不是可以引入这个特性呢?

6 附录

GDB online doc – Fork.html:

On most systems, GDB has no special support for debugging programs which create additional processes using the fork function. When a program forks, GDB will continue to debug the parent process and the child process will run unimpeded. If you have set a breakpoint in any code which the child then executes, the child will get a SIGTRAP signal which (unless it catches the signal) will cause it to terminate.

However, if you want to debug the child process there is a workaround which isn’t too painful. Put a call to sleep in the code which the child process executes after the fork. It may be useful to sleep only if a certain environment variable is set, or a certain file exists, so that the delay need not occur when you don’t want to run GDB on the child. While the child is sleeping, use the ps program to get its process ID. Then tell GDB (a new invocation of GDB if you are also debugging the parent process) to attach to the child process (see Attach). From that point on you can debug the child process just like any other process which you attached to.

libvirt 启动 qemu 的过程相关推荐

  1. KVM 介绍(7):使用 libvirt 做 QEMU/KVM 快照和 Nova 实例的快照 (Nova Instances Snapshot Libvirt)...

    学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...

  2. 【KVM系列08】使用 libvirt 迁移 QEMU/KVM 虚机和 Nova 虚机

    第八章 使用 libvirt 迁移 QEMU/KVM 虚机和 Nova 虚机 1. QEMU/KVM 迁移的概念 1.1 迁移效率的衡量 1.2 KVM 迁移的原理 1.3 使用命令行的方式做动态迁移 ...

  3. 【KVM系列07】使用 libvirt 做 QEMU/KVM 快照和 Nova 实例的快照

    第七章 使用 libvirt 做 QEMU/KVM 快照和 Nova 实例的快照 1. QEMU/KVM 快照 1.1 概念 1.2 使用 virsh 实验 1.3 外部快照的删除 2. OpenSt ...

  4. 【KVM系列06】Nova 通过 libvirt 管理 QEMU/KVM 虚机

    第六章 Nova 通过 libvirt 管理 QEMU/KVM 虚机 1. Libvirt 在 OpenStack 架构中的位置 2. Nova 中 libvirt 的使用 2.1 创建 QEMU/K ...

  5. windows上dmg转换cdr_云主机装黑果实践(6):处理云主机上变色龙启动后置过程:驱动和黑屏...

    本文关键字:无显驱vesa方式驱动osx10.14,mojave vga黑屏,云主机的显示器,非n非a卡黑果,waitting for root device,apfs modules stop 14 ...

  6. KVM 介绍(6):Nova 通过 libvirt 管理 QEMU/KVM 虚机 [Nova Libvirt QEMU/KVM Domain]

    KVM 介绍(6):Nova 通过 libvirt 管理 QEMU/KVM 虚机 [Nova Libvirt QEMU/KVM Domain] 学习 KVM 的系列文章: (1)介绍和安装 (2)CP ...

  7. 云主机装黑果实践(6):处理云主机上变色龙启动后置过程:驱动和黑屏

    本文关键字:无显驱vesa方式驱动osx10.14,mojave vga黑屏,云主机的显示器,非n非a卡黑果,waitting for root device,apfs modules stop 14 ...

  8. KVM之使用libvirt迁移QEMU/KVM、Nova虚机

    学习 KVM 的系列文章:  (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接 ...

  9. KVM之Nova通过libvirt管理QEMU/KVM虚机

    学习 KVM 的系列文章:  (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接 ...

最新文章

  1. 通过组策略实现IE自动以当前域账号登录某站点
  2. PAT_B_1006 换个格式输出整数
  3. 利用IDA6.6进行apk dex代码动态调试
  4. VTK:绘图之BarChart
  5. 前台分页,感觉一般还能优化
  6. 机器学习 综合评价_PyCaret:机器学习综合
  7. 如何减小与“大牛”的差距
  8. myeclipse去掉js报错
  9. html按钮绑定点击事件无效,jquery添加的html元素按钮为何不执行类样式绑定的click事件...
  10. centos下配置LNMP环境(源码安装)
  11. C#中IntPtr类型
  12. 开源软件、自由软件和免费软件的区别
  13. VXLAN技术——数据中心底层技术
  14. Mstar的Monitor方案笔记(七)——EDID基本数据结构
  15. 个人博客_温州个人博客_Duing-冬忆个人博客
  16. _GLIBCXX_USE_CXX11_ABI 定义不一致带来的宕机问题
  17. 父母生命末期,是在医院昂贵痛苦地治疗,还是接老人回家亲情陪伴
  18. 《Chrysanthemums Terrace》《菊花台》
  19. 零基础是学习Java还是大数据?
  20. java 建立临时文件夹

热门文章

  1. [git/svn]Git和SVN差异
  2. c++11 字符串与int类型的转换
  3. Delphi 中的 XMLDocument 类详解(5) - 获取元素内容
  4. sqlserver之定位死锁(经验分享)
  5. shell脚本的规范
  6. Android webView 支持缩放及自适应屏幕
  7. MySQL数据库的高可用方案总结
  8. Android L 仍需改善的三个问题
  9. [Android] Bitmap OOM解决办法二
  10. 实现 ASP.NET 网站地图提供者