08.19 跟踪系统调用
本节将介绍一种非常先进的技术,它使用syscall包,并允许你监视在Go程序中执行的系统调用。
本节不包含在我的《Go系统编程》书籍中,Packt出版社,2017年
Go程序名为traceSyscall.go,并分为五部分。traceSyscall.go第一部分代码如下:
1
package main
2
3
import (
4
"bufio"
5
"fmt"
6
"os"
7
"os/exec"
8
"strings"
9
"syscall"
10
)
11
12
var maxSyscalls = 0
13
14
const SYSCALLFILE = "SYSCALLS"
Copied!
你将很快学习SYSCALLFILE变量的作用。
traceSyscall.go第二部分代码如下:
1
func main() {
2
var SYSTEMCALLS []string
3
f, err := os.Open(SYSCALLFILE)
4
defer f.Close()
5
if err != nil {
6
fmt.Println(err)
7
return
8
}
9
10
scanner := bufio.NewScanner(f)
11
for scanner.Scan() {
12
line := scanner.Text()
13
line = strings.Replace(line, " ", "", -1)
14
line = strings.Replace(line, "SYS_", "", -1)
15
temp := strings.ToLower(strings.Split(line, "=")[0])
16
SYSTEMCALLS = append(SYSTEMCALLS, temp)
17
maxSyscalls++
18
}
Copied!
请注意,SYSCALLS文件的信息取自syscall包的文档,它将每个系统调用与一个数字相关联,该数字是系统调用的内部Go表示形式。该文件主要用于打印被跟踪程序所使用的系统调用的名称。
SYSCALLS文件的格式如下:
1
SYS_READ = 0
2
SYS_WRITE = 1
3
SYS_OPEN = 2
4
SYS_CLOSE = 3
5
SYS_STAT = 4
Copied!
在读取文本文件后,程序创建名为SYSTEMCALLS的切片来存储信息。
traceSyscall.go第三部分代码如下:
1
COUNTER := make([]int, maxSyscalls)
2
var regs syscall.PtraceRegs
3
cmd := exec.Command(os.Args[1], os.Args[2:]...)
4
cmd.Stdin = os.Stdin
5
cmd.Stdout = os.Stdout
6
cmd.Stderr = os.Stderr
7
cmd.SysProcAttr = &syscall.SysProcAttr{Ptrace: true}
8
err = cmd.Start()
9
err = cmd.Wait()
10
if err != nil {
11
fmt.Println("Wait:", err)
12
}
13
pid := cmd.Process.Pid
14
fmt.Println("Process ID:", pid)
Copied!
COUNTER切片存储在被跟踪的程序中每个系统调用的次数。
traceSyscall.go的第四部分代码如下:
1
before := true
2
forCount := 0
3
for {
4
if before {
5
err := syscall.PtraceGetRegs(pid, &regs)
6
if err != nil {
7
break
8
}
9
if regs.Orig_rax > uint64(maxSyscalls) {
10
fmt.Println("Unknown:", regs.Orig_rax)
11
return
12
}
13
COUNTER[regs.Orig_rax]++
14
forCount++
15
}
16
err = syscall.PtraceSyscall(pid, 0)
17
if err != nil {
18
fmt.Println("PtraceSyscall:", err)
19
return
20
}
21
_, err = syscall.Wait4(pid, nil, 0, nil)
22
if err != nil {
23
fmt.Println("Wait4:", err)
24
return
25
}
26
before = !before
27
}
Copied!
syscall.PtraceSyscall()函数的作用是:告诉Go继续执行正在被跟踪的程序,但是当程序进入或退出系统调用时停止执行,这正是我们想要的!由于每个系统调用在被调用之前和完成其工作之后都会被跟踪,因此我们使用before变量来计算每个系统调用仅一次。
traceSyscall.go的最后一部分代码如下:
1
for i, x := range COUNTER {
2
if x != 0 {
3
fmt.Println(SYSTEMCALLS[i], "->", x)
4
}
5
}
6
fmt.Println("Total System Calls:", forCount)
7
}
Copied!
在这一部分中,我们打印切片COUNTER的内容。切片SYSTEMCALLS用于在知道系统调用的Go数字表示时,来查找系统调用的名称。
macOS High Sierra机器上执行traceSyscall.go会创建如下的输出:
1
$ go run traceSyscall.go
2
# command-line-arguments
3
./traceSyscall.go:36:11: undefined: syscall.PtraceRegs
4
./traceSyscall.go:57:11: undefined: syscall.PtraceGetRegs
5
./traceSyscall.go:70:9: undefined: syscall.PtraceSyscall
Copied!
同样,traceSyscall.go程序不能在macOSMac OS X上运行。
Debian Linux机器上执行程序会创建如下的输出:
1
$ go run traceSyscall.go ls /tmp/
2
Wait: stop signal: trace/breakpoint trap
3
Process ID: 5657
4
go-build084836422 test.go upload_progress_cache
5
read -> 11
6
write -> 1
7
open -> 37
8
close -> 27
9
stat -> 1
10
fstat -> 25
11
mmap -> 39
12
mprotect -> 16
13
munmap -> 4
14
brk -> 3
15
rt_sigaction -> 2
16
rt_sigprocmask -> 1
17
ioctl -> 2
18
access -> 9
19
execve -> 1
20
getdents -> 2
21
getrlimit -> 1
22
statfs -> 2
23
arch_prctl -> 1
24
futex -> 1
25
set_tid_address -> 1
26
openat -> 1
27
set_robust_list -> 1
28
Total System Calls: 189
Copied!
在程序结束时,traceSyscall.go打印程序中调用每个系统调用的次数!traceSyscall.go的正确性通过strace -c程序的输出进行验证。
1
$ strace -c ls /tmp
2
test.go upload_progress_cache
3
% time seconds usecs/call calls errors syscall
4
------ ----------- ----------- --------- --------- ----------------
5
0.00 0.000000 0 11 read
6
0.00 0.000000 0 1 write
7
0.00 0.000000 0 37 13 open
8
0.00 0.000000 0 27 close
9
0.00 0.000000 0 1 stat
10
0.00 0.000000 0 25 fstat
11
0.00 0.000000 0 39 mmap
12
0.00 0.000000 0 16 mprotect
13
0.00 0.000000 0 4 munmap
14
0.00 0.000000 0 3 brk
15
0.00 0.000000 0 2 rt_sigaction
16
0.00 0.000000 0 1 rt_sigprocmask
17
0.00 0.000000 0 2 ioctl
18
0.00 0.000000 0 9 9 access
19
0.00 0.000000 0 1 execve
20
0.00 0.000000 0 2 getdents
21
0.00 0.000000 0 1 getrlimit
22
0.00 0.000000 0 2 2 statfs
23
0.00 0.000000 0 1 arch_prctl
24
0.00 0.000000 0 1 futex
25
0.00 0.000000 0 1 set_tid_address
26
0.00 0.000000 0 1 openat
27
0.00 0.000000 0 1 set_robust_list
28
------ ----------- ----------- --------- --------- -------------
29
100.00 0.000000 189 24 total
Copied!
Last modified 2yr ago
Copy link