> For the complete documentation index, see [llms.txt](https://wskdsgcf.gitbook.io/mastering-go-zh-cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wskdsgcf.gitbook.io/mastering-go-zh-cn/08.0/08.19.md).

# 08.19 跟踪系统调用

本节将介绍一种非常先进的技术，它使用`syscall`包，并允许你监视在`Go`程序中执行的系统调用。

> *本节不包含在我的《`Go`系统编程》书籍中，`Packt`出版社，2017年*

`Go`程序名为`traceSyscall.go`，并分为五部分。`traceSyscall.go`第一部分代码如下：

```go
package main

import (
    "bufio"
    "fmt"
    "os"
    "os/exec"
    "strings"
    "syscall"
)

var maxSyscalls = 0

const SYSCALLFILE = "SYSCALLS"
```

你将很快学习`SYSCALLFILE`变量的作用。

`traceSyscall.go`第二部分代码如下：

```go
func main() {
    var SYSTEMCALLS []string
    f, err := os.Open(SYSCALLFILE)
    defer f.Close()
    if err != nil {
        fmt.Println(err)
        return
    }

    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        line := scanner.Text()
        line = strings.Replace(line, " ", "", -1)
        line = strings.Replace(line, "SYS_", "", -1)
        temp := strings.ToLower(strings.Split(line, "=")[0])
        SYSTEMCALLS = append(SYSTEMCALLS, temp)
        maxSyscalls++
    }
```

请注意，`SYSCALLS`文件的信息取自`syscall`包的文档，它将每个系统调用与一个数字相关联，该数字是系统调用的内部`Go`表示形式。该文件主要用于打印被跟踪程序所使用的系统调用的名称。

`SYSCALLS`文件的格式如下：

```
SYS_READ = 0
SYS_WRITE = 1
SYS_OPEN = 2
SYS_CLOSE = 3
SYS_STAT = 4
```

在读取文本文件后，程序创建名为`SYSTEMCALLS`的切片来存储信息。

`traceSyscall.go`第三部分代码如下：

```go
    COUNTER := make([]int, maxSyscalls)
    var regs syscall.PtraceRegs
    cmd := exec.Command(os.Args[1], os.Args[2:]...)
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.SysProcAttr = &syscall.SysProcAttr{Ptrace: true}
    err = cmd.Start()
    err = cmd.Wait()
    if err != nil {
        fmt.Println("Wait:", err)
    }
    pid := cmd.Process.Pid
    fmt.Println("Process ID:", pid)
```

`COUNTER`切片存储在被跟踪的程序中每个系统调用的次数。

`traceSyscall.go`的第四部分代码如下：

```go
    before := true
    forCount := 0
    for {
        if before {
            err := syscall.PtraceGetRegs(pid, &regs)
            if err != nil {
                break
            }
            if regs.Orig_rax > uint64(maxSyscalls) {
                fmt.Println("Unknown:", regs.Orig_rax)
                return
            }
            COUNTER[regs.Orig_rax]++
            forCount++
        }
        err = syscall.PtraceSyscall(pid, 0)
        if err != nil {
            fmt.Println("PtraceSyscall:", err)
            return
        }
        _, err = syscall.Wait4(pid, nil, 0, nil)
        if err != nil {
            fmt.Println("Wait4:", err)
            return
        }
        before = !before
    }
```

`syscall.PtraceSyscall()`函数的作用是：告诉`Go`继续执行正在被跟踪的程序，但是当程序进入或退出系统调用时停止执行，这正是我们想要的！由于每个系统调用在被调用之前和完成其工作之后都会被跟踪，因此我们使用`before`变量来计算每个系统调用仅一次。

`traceSyscall.go`的最后一部分代码如下：

```go
    for i, x := range COUNTER {
        if x != 0 {
            fmt.Println(SYSTEMCALLS[i], "->", x)
        }
    }
    fmt.Println("Total System Calls:", forCount)
}
```

在这一部分中，我们打印切片`COUNTER`的内容。切片`SYSTEMCALLS`用于在知道系统调用的`Go`数字表示时，来查找系统调用的名称。

在`macOS High Sierra`机器上执行`traceSyscall.go`会创建如下的输出：

```
$ go run traceSyscall.go
# command-line-arguments
./traceSyscall.go:36:11: undefined: syscall.PtraceRegs
./traceSyscall.go:57:11: undefined: syscall.PtraceGetRegs
./traceSyscall.go:70:9: undefined: syscall.PtraceSyscall
```

同样，`traceSyscall.go`程序不能在`macOS`和`Mac OS X`上运行。

在`Debian Linux`机器上执行程序会创建如下的输出：

```
$ go run traceSyscall.go ls /tmp/
Wait: stop signal: trace/breakpoint trap
Process ID: 5657
go-build084836422 test.go upload_progress_cache
read -> 11
write -> 1
open -> 37
close -> 27
stat -> 1
fstat -> 25
mmap -> 39
mprotect -> 16
munmap -> 4
brk -> 3
rt_sigaction -> 2
rt_sigprocmask -> 1
ioctl -> 2
access -> 9
execve -> 1
getdents -> 2
getrlimit -> 1
statfs -> 2
arch_prctl -> 1
futex -> 1
set_tid_address -> 1
openat -> 1
set_robust_list -> 1
Total System Calls: 189
```

在程序结束时，`traceSyscall.go`打印程序中调用每个系统调用的次数！`traceSyscall.go`的正确性通过`strace -c`程序的输出进行验证。

```
$ strace -c ls /tmp
test.go upload_progress_cache
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
0.00  0.000000          0    11         read
0.00  0.000000          0     1         write
0.00  0.000000          0    37      13 open
0.00  0.000000          0    27         close
0.00  0.000000          0     1         stat
0.00  0.000000          0    25         fstat
0.00  0.000000          0    39         mmap
0.00  0.000000          0    16         mprotect
0.00  0.000000          0     4         munmap
0.00  0.000000          0     3         brk
0.00  0.000000          0     2         rt_sigaction
0.00  0.000000          0     1         rt_sigprocmask
0.00  0.000000          0     2         ioctl
0.00  0.000000          0     9       9 access
0.00  0.000000          0     1         execve
0.00  0.000000          0     2         getdents
0.00  0.000000          0     1         getrlimit
0.00  0.000000          0     2       2 statfs
0.00  0.000000          0     1         arch_prctl
0.00  0.000000          0     1         futex
0.00  0.000000          0     1         set_tid_address
0.00  0.000000          0     1         openat
0.00  0.000000          0     1         set_robust_list
------ ----------- ----------- --------- --------- -------------
100.00 0.000000              189        24 total
```
