Test added to compare the Scanner vs GDB

gdb_test.go and testdata implement a go test to compare and verify that
we get correct results from the scanner.

testdata/simple.c is a small program that embeds Python and runs the
same code as in runforever.py.  The Makefile compiles this using
python3.7.  Adjustments to the flags might be needed in your environment

gdb_test.go contains only one test, TestSimpleGDB, that
  1. compiles simple.c
  2. runs it multiple times
  3. calls gdb to extract the address of symbol _PyRuntime for each pid
  4. runs our Scanner
  5. compares the results from gdb and our scanner
This commit is contained in:
Özgür Kesim 2020-01-16 10:30:49 +01:00
parent 6eebe8c9f4
commit 554ae92194
4 changed files with 142 additions and 0 deletions

View File

@ -0,0 +1,106 @@
package symbolyze
import (
"bufio"
"fmt"
"os"
"os/exec"
"regexp"
"strconv"
"testing"
)
func buildSimple() error {
cmd := exec.Command("make")
cmd.Dir = "testdata"
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.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()
go 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)
}
}
}
var searchRX = regexp.MustCompile(`Symbol "_PyRuntime" is static storage at address 0x([0-9a-fA-F]+)`)
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")
}

View File

@ -0,0 +1,2 @@
simple
simple.o

View File

@ -0,0 +1,12 @@
CC=gcc
PC=python3.7dm-config
PIE=-fPIE # this was necessary on my machine
CFLAGS=$(shell $(PC) --cflags)
LDFLAGS=$(shell $(PC) --ldflags)
simple: simple.o
$(CC) -o $@ $< $(LDFLAGS)
simple.o: simple.c
$(CC) -c $(CFLAGS) $(PIE) $<

View File

@ -0,0 +1,22 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static char script[] = "import time\n"
"import os\n"
"\n"
"print(\"Process ID: {}\".format(os.getpid()))\n"
"print(\"Entering infinite loop ...\")\n"
"while 1:\n"
" time.sleep(2)\n"
"\n";
int
main(int argc, char *argv[])
{
Py_Initialize();
PyRun_SimpleString(script);
if (Py_FinalizeEx() < 0) {
exit(120);
}
return 0;
}