# 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
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wskdsgcf.gitbook.io/mastering-go-zh-cn/08.0/08.19.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
