2020-01-16 10:30:49 +01:00
|
|
|
|
package symbolyze
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"regexp"
|
|
|
|
|
"strconv"
|
2020-01-16 10:50:06 +01:00
|
|
|
|
"sync"
|
2020-01-16 10:30:49 +01:00
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
2020-01-16 10:43:30 +01:00
|
|
|
|
// 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.
|
2020-01-16 10:30:49 +01:00
|
|
|
|
func buildSimple() error {
|
2020-01-16 15:56:12 +01:00
|
|
|
|
cmd := exec.Command("make", "-s")
|
2020-01-16 10:30:49 +01:00
|
|
|
|
cmd.Dir = "testdata"
|
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
|
|
|
|
|
|
return cmd.Run()
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-16 10:43:30 +01:00
|
|
|
|
// 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().
|
2020-01-16 10:30:49 +01:00
|
|
|
|
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*")
|
2020-01-16 10:50:06 +01:00
|
|
|
|
var m sync.Mutex
|
2020-01-16 10:30:49 +01:00
|
|
|
|
scanner.OnFound(func(pid int, offset uint64) error {
|
|
|
|
|
t.Logf("scanner setting pid %d to offset %x", pid, offset)
|
2020-01-16 10:50:06 +01:00
|
|
|
|
m.Lock()
|
2020-01-16 10:30:49 +01:00
|
|
|
|
ourResults[pid] = offset
|
2020-01-16 10:50:06 +01:00
|
|
|
|
m.Unlock()
|
2020-01-16 10:30:49 +01:00
|
|
|
|
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()
|
2020-01-16 10:43:30 +01:00
|
|
|
|
simple.Wait()
|
2020-01-16 10:30:49 +01:00
|
|
|
|
}()
|
|
|
|
|
|
2020-01-16 10:43:30 +01:00
|
|
|
|
t.Logf("Asking Gdb about PID %d", simple.Process.Pid)
|
2020-01-16 10:30:49 +01:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-16 10:43:30 +01:00
|
|
|
|
// searchRX should match the output of gdb when it prints the address of the
|
|
|
|
|
// symbol
|
2020-01-16 10:30:49 +01:00
|
|
|
|
var searchRX = regexp.MustCompile(`Symbol "_PyRuntime" is static storage at address 0x([0-9a-fA-F]+)`)
|
|
|
|
|
|
2020-01-16 10:43:30 +01:00
|
|
|
|
// 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.
|
2020-01-16 10:30:49 +01:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-16 14:33:02 +01:00
|
|
|
|
defer cmd.Wait()
|
|
|
|
|
|
2020-01-16 10:30:49 +01:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-16 16:02:04 +01:00
|
|
|
|
return 0, fmt.Errorf("[31mSymbol not found with gdb.[0m\n" +
|
|
|
|
|
"[35mMaybe /proc/sys/kernel/yama/ptrace_scope must be set to 0? Or try to run the test as root![0m")
|
2020-01-16 10:30:49 +01:00
|
|
|
|
}
|