package symbolyze import ( "bufio" "fmt" "os" "os/exec" "regexp" "strconv" "testing" ) // buildSimple calls make in the testdata-directory in order to build the // test-binary `simple` that is linked against python3.7. If this fails, make // necessary adjustments to the Makefile and/or your environment and try again. func buildSimple() error { cmd := exec.Command("make") cmd.Dir = "testdata" cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } // TestSimpeGDB runs the program testdata/simple multiple times and uses gdb to // extract the address of the symbol _PyRuntime for each pid. It compares // those with the results from Scanner.Run(). func TestSimpleGDB(t *testing.T) { ourResults := map[int]uint64{} gdbResults := map[int]uint64{} err := buildSimple() if err != nil { t.Fatal(err) } scanner := NewScanner("_PyRuntime", "*python3*") scanner.OnFound(func(pid int, offset uint64) error { t.Logf("scanner setting pid %d to offset %x", pid, offset) ourResults[pid] = offset return nil }) for i := 0; i < 5; i++ { simple := exec.Command("testdata/simple") if err := simple.Start(); err != nil { t.Fatal(err) } defer func() { simple.Process.Kill() simple.Wait() }() t.Logf("Asking Gdb about PID %d", simple.Process.Pid) offset, err := extractOffsetWithGdb(simple.Process.Pid, t) if err != nil { t.Fatalf("extractOffsetWithGdb: %v", err) } t.Logf("Found offset at 0x%x", offset) gdbResults[simple.Process.Pid] = offset } if err = scanner.Run(); err != nil { t.Logf("Scanner failure: %v", err) } for pid, off := range gdbResults { if off2, ok := ourResults[pid]; !ok { t.Fatalf("Scanner has no offset for pid %d", pid) } else if off != off2 { t.Fatalf("Scanner has found offset %d while gdb has found offset %d for pid %d", off2, off, pid) } } } // searchRX should match the output of gdb when it prints the address of the // symbol var searchRX = regexp.MustCompile(`Symbol "_PyRuntime" is static storage at address 0x([0-9a-fA-F]+)`) // extractOffsetWithGdb calls gdb in batch-mode on a pid and looks up the // address of the symbol _PyRuntime by calling `info address _PyRuntime`. If // found, it extracts the address from the gdb-output. func extractOffsetWithGdb(pid int, t *testing.T) (offset uint64, err error) { cmd := exec.Command("gdb", "--batch", "-p", strconv.Itoa(pid), "-ex", "info address _PyRuntime", "-ex", "quit") output, err := cmd.StdoutPipe() if err != nil { return 0, err } if err := cmd.Start(); err != nil { return 0, err } scanner := bufio.NewScanner(output) for scanner.Scan() { line := scanner.Text() // t.Logf("[gdb]: %v", line) match := searchRX.FindStringSubmatch(line) if len(match) > 1 { return strconv.ParseUint(match[1], 16, 64) } } if err := cmd.Wait(); err != nil { return 0, err } return 0, fmt.Errorf("Symbol not found with gdb") }