实现一个 Golang 调试器

首发于:https://studygolang.com/articles/12553

写这个系列的目的不是为了列出 Golang 编程语言的调试器的所有特性。如果你想看这些内容,可以看下 Delve。在这篇文章里我们试着去探索下调试器通常是怎样工作的,怎么在 Linux 上完成一个基本的调试,Linux 上比较关心 Golang 的功能,比如 goroutine 。

创建调试器没那么简单。就这一个话题我们单独写一篇文章也讲不完。相反,本篇博文是个开始,这个系列的最终目标是找到解决方案来处理最常见的场景。期间我们会讨论类似 ELF, DWARF 的话题,还会接触到一些架构相关的问题。

环境

整个系列文章中,我们都会使用 Docker 来获取基于 Debian Jessie 的可复制的 playground。我使用的是 x86-64,这可以在一定程度上让我们在做一些底层讨论的时候起点作用。项目结构如下:

我们马上要用到的调试器的主要文件就是 debugger.go,hello.go 文件包含我们整个流程中调试的 sample 程序源代码。现在你写最简单的内容就可以:

我们先写一个非常简单的 Dockerfile:

为了编译 Docker 镜像,到(Dockerfile 所在的)最外层目录,运行:

给容器加速,执行:

这里 有安全运算模式(seccomp)的相关描述。现在剩下的是在容器里编译这这两个程序。第一个可以这样做:

标识 --gcflag 用于禁止 内联函数 (-l),编译优化(-N)可以让调试更容易。调试器如下做编译:

在容器的环境变量 PATH 中包含 /go/bin ,这样不用使用完整路径就可以运行任何刚编译好的程序,不论是 hello 还是 debugger。

第一步

我们的第一个任务很简单。在执行任何指令之前停止程序,然后再运行起来,直到程序停止(不管是自动停止还是出现错误停止)。大多数调试器你都可以这样开始使用。设定一些跟踪点(断点),然后执行类似 continue 的指令真正的跑起来,直到停在你要停的地方。我们看看 Delve 是如何工作的:

让我们看看我们自己怎么实现。

第一步是需要给进程(我们的调试器)找一个机制,去控制其他进程(我们要调试的进程)。幸好在 Linux 上我们有这个-- ptrace。这还不算。Golang 的 syscall 包提供了一个类似 PtraceCont 的接口,可以重启被跟踪的进程。因此这里包含了第二部分内容,但是为了有机会在程序开始执行之前设置断点我们还得做点其他的。创建新进程的时候我们可以通过设置属性-- SysProcAttr 指定进程行为。其中一个是 Ptrace 可以跟踪进程,然后进程会停止并在开启之前给父进程发送 SIGSTOP signal。我们把刚才学到的内容整理成一个工作流程…

第一版的调试器实现方式很简单。启动了一个被跟踪的进程,然后进程在执行第一条指令前停止,并向父进程发送了一个 signal。父进程等待这个 signal,打出日志 log.Printf("State: %v\n", err)。之后程序重启,父进程等待其终止。这种方式可以让我们有机会提前设置断点,启动程序,等一会到达指定跟踪点,看看类似堆栈或注册表里的当前值,检查下进程状态。

哪怕知道一点点,我们也可以做一些很赞的事情。这些都会为今后的提高和实践(不久的将来)奠定基础。

给我们点个赞吧,让更多人能看到这篇文章。如果你想获得新博文的更新或者在工作中获得提高,请关注我们吧。

via: https://medium.com/golangspec/making-debugger-for-golang-part-i-53124284b7c8

作者:Michał Łowicki

译者:ArisAries

校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180904G195YC00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券