跟踪系统调用

    Go程序名为traceSyscall.go,并分为五部分。traceSyscall.go第一部分代码如下:

    你将很快学习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. scanner := bufio.NewScanner(f)
    10. for scanner.Scan() {
    11. line := scanner.Text()
    12. line = strings.Replace(line, " ", "", -1)
    13. line = strings.Replace(line, "SYS_", "", -1)
    14. temp := strings.ToLower(strings.Split(line, "=")[0])
    15. SYSTEMCALLS = append(SYSTEMCALLS, temp)
    16. maxSyscalls++
    17. }

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

    1. SYS_READ = 0
    2. SYS_WRITE = 1
    3. SYS_OPEN = 2
    4. SYS_CLOSE = 3
    5. SYS_STAT = 4

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

    traceSyscall.go第三部分代码如下:

    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. return
    11. }
    12. COUNTER[regs.Orig_rax]++
    13. forCount++
    14. err = syscall.PtraceSyscall(pid, 0)
    15. if err != nil {
    16. fmt.Println("PtraceSyscall:", err)
    17. return
    18. }
    19. _, err = syscall.Wait4(pid, nil, 0, nil)
    20. if err != nil {
    21. fmt.Println("Wait4:", err)
    22. return
    23. }
    24. before = !before
    25. }

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

    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. }

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

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

    同样,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. munmap -> 4
    13. brk -> 3
    14. rt_sigaction -> 2
    15. rt_sigprocmask -> 1
    16. ioctl -> 2
    17. access -> 9
    18. getdents -> 2
    19. getrlimit -> 1
    20. statfs -> 2
    21. arch_prctl -> 1
    22. futex -> 1
    23. set_tid_address -> 1
    24. openat -> 1
    25. set_robust_list -> 1
    26. Total System Calls: 189

    在程序结束时,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. ------ ----------- ----------- --------- --------- -------------