Как отследить многопроцессорное или многопоточное приложение из Go - PullRequest
2 голосов
/ 06 августа 2020

Я пытаюсь написать программу, которая распечатывала бы все системные вызовы, выполняемые программой. У меня возникли проблемы с расширением этого кода для работы со сценариями с несколькими обработками. Я начал с кода из https://github.com/lizrice/strace-from-scratch, а теперь я хотел бы также отслеживать дочерние процессы.

Я попытался добавить параметры PTRACE_O_TRACEVFORK | PTRACE_O_TRACEFORK | PTRACE_O_TRACECLONE, но это приводит к зависанию процесса по какой-то причине. Я не знаю, почему. Если я не укажу эти параметры, процесс будет завершен, но, конечно, дочерние элементы не будут отслеживаться.

package main

import (
    "fmt"
    seccomp "github.com/seccomp/libseccomp-golang"
    "golang.org/x/sys/unix"
    "log"
    "os"
    "os/exec"
    "runtime"
)

func init() {
    runtime.LockOSThread()
}

func main() {
    var err error

    if len(os.Args) < 2 {
        log.Fatalf("usage: ./trace-files program [arg]...")
    }
    cmd := exec.Command(os.Args[1], os.Args[2:]...)
    cmd.Stderr = os.Stderr
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.SysProcAttr = &unix.SysProcAttr{
        Ptrace: true,
    }
    if err = cmd.Start(); err != nil {
        log.Fatalf("error starting command: %s\n", err)
    }
    if err = cmd.Wait(); err != nil {
        // We expect "trace/breakpoint trap" here.
        fmt.Printf("Wait returned: %s\n", err)
    }

    pid := cmd.Process.Pid
    exit := true

    var regs unix.PtraceRegs
    var status unix.WaitStatus

    // TODO: Setting these options causes the multiprocessing Python script to hang.
    ptraceOptions := unix.PTRACE_O_TRACEVFORK | unix.PTRACE_O_TRACEFORK | unix.PTRACE_O_TRACECLONE
    if err = unix.PtraceSetOptions(pid, ptraceOptions); err != nil {
        log.Fatalf("error setting ptrace options: %s", err)
    }

    fmt.Println("pid\tsyscall")

    for {
        if exit {
            err = unix.PtraceGetRegs(pid, &regs)
            if err != nil {
                break
            }
            name, err := seccomp.ScmpSyscall(regs.Orig_rax).GetName()
            if err != nil {
                fmt.Printf("error getting syscall name for orig_rax %d\n", regs.Orig_rax)
            }
            fmt.Printf("%d\t%s\n", pid, name)
        }
        if err = unix.PtraceSyscall(pid, 0); err != nil {
            log.Fatalf("error calling ptrace syscall: %s\n", err)
        }
        // TODO: is it OK to overwrite pid here?
        pid, err = unix.Wait4(pid, &status, 0, nil)
        if err != nil {
            log.Fatalf("error calling wait")
        }
        exit = !exit
    }
}

В целях тестирования я написал сценарий Python, который использует многопроцессорность и печатает идентификаторы процессов, которые он породил.

import multiprocessing
import os

def fun(x):
    return os.getpid()

if __name__ == "__main__":
    print("PYTHON: starting multiprocessing pool")
    with multiprocessing.Pool() as pool:
        processes = pool.map(fun, range(1000000))
    print("PYTHON: ended multiprocessing pool")
    processes = map(str, set(processes))
    print("PYTHON: process IDs: ", ", ".join(processes))

Когда я запускаю приведенный выше код Go в одной программе процесса, например ls, все работает нормально.

go run . ls

Но когда я запускаю код Go в сценарии Python, вывод зависает (но только если я предоставлю параметры ptrace, упомянутые выше).

go run . python script.py

Моя конечная цель для этой программы - получить список всех файлов, которые использует программа. Я буду проверять /proc/PID/maps для каждого системного вызова для этой части, но сначала я хотел бы узнать, как отслеживать многопроцессорные программы. Я попытался просмотреть документацию и код для strace, но это еще больше сбило меня с толку ...

...