启动进程
实现目标:godbg exec <prog>
调试器执行调试,首先得确定要调试的目标。通常目标可能是一个进程实例,或者是一个core文件。
我们先关注如何调试一个进程,core文件只是进程的一个快照,调试器只能查看当时的栈帧情况。对进程进行调试涉及到的方方面面基本覆盖了对core文件进行调试的内容,所以我们先将重点放在对进程调试上。
调试一个进程,主要有以下几种情况:
- 如果进程还未存在,我们需要启动指定进程,如dlv、gdb等指定程序名启动调试时会启动进程;
- 如果进程已经存在,我们需要通过进程pid来挂住进程,如dlv、gdb等通过-p指定pid对运行进程调试;
为了方便开发、调试,调试器可能也包含了编译构建的任务,如保证构建产物中包含调试信息、避免过优化对调试的不利影响等。通常这些操作需要传递特殊的选项给编译器、连接器,对开发者而言并不是一件很友好的事情。考虑到这点,go调试器dlv在执行dlv debug
命令时,会自动传递-gcflags="all=-N -l"
选项信息来禁止内联、优化等,来保证生成符合调试需求的构建产物。
下面先介绍下第一种情况,指定程序路径,启动程序创建进程。
我们将实现程序godbg,它支持exec子命令,支持接收参数prog,godbg将启动程序prog并获取其执行结果。
prog代表一个可执行程序,它可能是一个指向可执行程序的路径,也可能是一个在PATH路径中可以搜索到的可执行程序的名称。
基础知识
go标准库提供了对应的函数来完成启动程序创建进程的任务,我们先了解下相关的函数。
通过exec.Command(...)
方法我们可以创建一个Cmd实例,之后则可以通过Cmd.Start()
方法来启动程序,如果希望等待程序执行结束并获取执行结果,也可以通过Cmd.Run()
方法启动程序,然后通过Cmd.CombineOutput()
来获取程序在stdout、stderr上的输出结果。当然通过Cmd.Start()
启动进程,之后通过Cmd.Wait()
等待进程结束再获取结果也是可以的。
package exec // import "os/exec"
// Command 该方法接收可执行程序名称或者路径,arg是传递给可执行程序的参数信息,
// 该函数返回一个Cmd对象,通过它来启动程序、获取程序执行结果等,注意参数name
// 可以是一个可执行程序的路径,也可以是一个PATH中可以搜索到的可执行程序名
func Command(name string, arg ...string) *Cmd
// Cmd 通过Cmd来执行程序、获取程序执行结果等等,Cmd一旦调用Start、Run等方法之
// 后就不能再复用了
type Cmd struct {
...
}
// CombinedOutput 返回程序执行时输出到stdout、stderr的信息
func (c *Cmd) CombinedOutput() ([]byte, error)
// Output 返回程序执行时输出到stdout的信息,返回值列表中的error表示执行中遇到错误
func (c *Cmd) Output() ([]byte, error)
// Run 启动程序并且等待程序执行结束,返回值列表中的error表示执行中遇到错误
func (c *Cmd) Run() error
// Start 启动程序,但是不等待程序执行结束,返回值列表中的error表示执行中遇到错误
func (c *Cmd) Start() error
...
// WAait 等待cmd执行结束,该方法必须与Start()方法配合使用,返回值error表示执行中遇到错误
//
// Wait等待程序执行结束并获得程序的退出码(也就是返回值,os.Exit(?)将值返回给操作系统),
// 并释放对应的资源(主要是id资源,联想下PCB)
func (c *Cmd) Wait() error
代码实现
file: main.go
package main
import (
"fmt"
"os"
"os/exec"
)
const (
usage = "Usage: go run main.go exec <path/to/prog>"
cmdExec = "exec"
)
func main() {
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "%s\n\n", usage)
os.Exit(1)
}
cmd := os.Args[1]
switch cmd {
case cmdExec:
prog := os.Args[2]
progCmd := exec.Command(prog)
buf, err := progCmd.CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "%s exec error: %v, \n\n%s\n\n", err, string(buf))
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "%s\n", string(buf))
default:
fmt.Fprintf(os.Stderr, "%s unknown cmd\n\n", cmd)
os.Exit(1)
}
}
这里的程序逻辑比较简单:
- 程序运行时,首先检查命令行参数,
godbg exec <prog>
,至少有3个参数,如果参数数量不对,直接报错退出;- 接下来校验第2个参数,如果不是exec,也直接报错退出;
- 参数正常情况下,第3个参数应该是一个程序路径或者可执行程序文件名,我们创建一个exec.Cmd对象,然后启动并获取运行结果;
代码测试
您可以自己编译构建,完成相关测试。
go build -o godbg main.go
./godbg exec <prog>
ps: 当然也可以考虑将godbg拷贝到PATH路径下进行测试。
现在的程序逻辑单文件就可以完成,相对来说还比较简单,也可以 go run main.go exec <prog>
进行测试,如 go run main.go exec ls
。