diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index fb73988cbd224bf4accbf2efadbbe91fa414008f..319a85a0944d5fd35408c03b3a33e15427ff748d 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -43,6 +43,9 @@ type BinaryInfo struct { loadModuleDataOnce sync.Once moduleData []moduleData nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry + + loadErrMu sync.Mutex + loadErr error } var UnsupportedLinuxArchErr = errors.New("unsupported architecture - only linux/amd64 is supported") @@ -131,6 +134,16 @@ func (bi *BinaryInfo) Close() error { return bi.closer.Close() } +func (bi *BinaryInfo) setLoadError(fmtstr string, args ...interface{}) { + bi.loadErrMu.Lock() + bi.loadErr = fmt.Errorf(fmtstr, args...) + bi.loadErrMu.Unlock() +} + +func (bi *BinaryInfo) LoadError() error { + return bi.loadErr +} + // ELF /////////////////////////////////////////////////////////////// func (bi *BinaryInfo) LoadBinaryInfoElf(path string, wg *sync.WaitGroup) error { @@ -169,18 +182,18 @@ func (bi *BinaryInfo) parseDebugFrameElf(exe *elf.File, wg *sync.WaitGroup) { if debugFrameSec != nil && debugInfoSec != nil { debugFrame, err := exe.Section(".debug_frame").Data() if err != nil { - fmt.Println("could not get .debug_frame section", err) - os.Exit(1) + bi.setLoadError("could not get .debug_frame section: %v", err) + return } dat, err := debugInfoSec.Data() if err != nil { - fmt.Println("could not get .debug_info section", err) - os.Exit(1) + bi.setLoadError("could not get .debug_frame section: %v", err) + return } bi.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat)) } else { - fmt.Println("could not find .debug_frame section in binary") - os.Exit(1) + bi.setLoadError("could not find .debug_frame section in binary") + return } } @@ -196,24 +209,24 @@ func (bi *BinaryInfo) obtainGoSymbolsElf(exe *elf.File, wg *sync.WaitGroup) { if sec := exe.Section(".gosymtab"); sec != nil { symdat, err = sec.Data() if err != nil { - fmt.Println("could not get .gosymtab section", err) - os.Exit(1) + bi.setLoadError("could not get .gosymtab section: %v", err) + return } } if sec := exe.Section(".gopclntab"); sec != nil { pclndat, err = sec.Data() if err != nil { - fmt.Println("could not get .gopclntab section", err) - os.Exit(1) + bi.setLoadError("could not get .gopclntab section: %v", err) + return } } pcln := gosym.NewLineTable(pclndat, exe.Section(".text").Addr) tab, err := gosym.NewTable(symdat, pcln) if err != nil { - fmt.Println("could not get initialize line table", err) - os.Exit(1) + bi.setLoadError("could not get initialize line table: %v", err) + return } bi.goSymTable = tab @@ -225,13 +238,13 @@ func (bi *BinaryInfo) parseDebugLineInfoElf(exe *elf.File, wg *sync.WaitGroup) { if sec := exe.Section(".debug_line"); sec != nil { debugLine, err := exe.Section(".debug_line").Data() if err != nil { - fmt.Println("could not get .debug_line section", err) - os.Exit(1) + bi.setLoadError("could not get .debug_line section: %v", err) + return } bi.lineInfo = line.Parse(debugLine) } else { - fmt.Println("could not find .debug_line section in binary") - os.Exit(1) + bi.setLoadError("could not find .debug_line section in binary") + return } } @@ -246,8 +259,8 @@ func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) { // offset in libc's TLS block. symbols, err := exe.Symbols() if err != nil { - fmt.Println("could not parse ELF symbols", err) - os.Exit(1) + bi.setLoadError("could not parse ELF symbols: %v", err) + return } var tlsg *elf.Symbol for _, symbol := range symbols { @@ -325,41 +338,41 @@ func (bi *BinaryInfo) parseDebugFramePE(exe *pe.File, wg *sync.WaitGroup) { if debugFrameSec != nil && debugInfoSec != nil { debugFrame, err := debugFrameSec.Data() if err != nil && uint32(len(debugFrame)) < debugFrameSec.Size { - fmt.Println("could not get .debug_frame section", err) - os.Exit(1) + bi.setLoadError("could not get .debug_frame section: %v", err) + return } if 0 < debugFrameSec.VirtualSize && debugFrameSec.VirtualSize < debugFrameSec.Size { debugFrame = debugFrame[:debugFrameSec.VirtualSize] } dat, err := debugInfoSec.Data() if err != nil { - fmt.Println("could not get .debug_info section", err) - os.Exit(1) + bi.setLoadError("could not get .debug_info section: %v", err) + return } bi.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat)) } else { - fmt.Println("could not find .debug_frame section in binary") - os.Exit(1) + bi.setLoadError("could not find .debug_frame section in binary") + return } } -func (dbp *BinaryInfo) obtainGoSymbolsPE(exe *pe.File, wg *sync.WaitGroup) { +func (bi *BinaryInfo) obtainGoSymbolsPE(exe *pe.File, wg *sync.WaitGroup) { defer wg.Done() _, symdat, pclndat, err := pclnPE(exe) if err != nil { - fmt.Println("could not get Go symbols", err) - os.Exit(1) + bi.setLoadError("could not get Go symbols: %v", err) + return } pcln := gosym.NewLineTable(pclndat, uint64(exe.Section(".text").Offset)) tab, err := gosym.NewTable(symdat, pcln) if err != nil { - fmt.Println("could not get initialize line table", err) - os.Exit(1) + bi.setLoadError("could not get initialize line table: %v", err) + return } - dbp.goSymTable = tab + bi.goSymTable = tab } // Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go @@ -438,16 +451,16 @@ func (bi *BinaryInfo) parseDebugLineInfoPE(exe *pe.File, wg *sync.WaitGroup) { if sec := exe.Section(".debug_line"); sec != nil { debugLine, err := sec.Data() if err != nil && uint32(len(debugLine)) < sec.Size { - fmt.Println("could not get .debug_line section", err) - os.Exit(1) + bi.setLoadError("could not get .debug_line section: %v", err) + return } if 0 < sec.VirtualSize && sec.VirtualSize < sec.Size { debugLine = debugLine[:sec.VirtualSize] } bi.lineInfo = line.Parse(debugLine) } else { - fmt.Println("could not find .debug_line section in binary") - os.Exit(1) + bi.setLoadError("could not find .debug_line section in binary") + return } } @@ -485,18 +498,18 @@ func (bi *BinaryInfo) parseDebugFrameMacho(exe *macho.File, wg *sync.WaitGroup) if debugFrameSec != nil && debugInfoSec != nil { debugFrame, err := exe.Section("__debug_frame").Data() if err != nil { - fmt.Println("could not get __debug_frame section", err) - os.Exit(1) + bi.setLoadError("could not get __debug_frame section: %v", err) + return } dat, err := debugInfoSec.Data() if err != nil { - fmt.Println("could not get .debug_info section", err) - os.Exit(1) + bi.setLoadError("could not get .debug_info section: %v", err) + return } bi.frameEntries = frame.Parse(debugFrame, frame.DwarfEndian(dat)) } else { - fmt.Println("could not find __debug_frame section in binary") - os.Exit(1) + bi.setLoadError("could not find __debug_frame section in binary") + return } } @@ -512,24 +525,24 @@ func (bi *BinaryInfo) obtainGoSymbolsMacho(exe *macho.File, wg *sync.WaitGroup) if sec := exe.Section("__gosymtab"); sec != nil { symdat, err = sec.Data() if err != nil { - fmt.Println("could not get .gosymtab section", err) - os.Exit(1) + bi.setLoadError("could not get .gosymtab section: %v", err) + return } } if sec := exe.Section("__gopclntab"); sec != nil { pclndat, err = sec.Data() if err != nil { - fmt.Println("could not get .gopclntab section", err) - os.Exit(1) + bi.setLoadError("could not get .gopclntab section: %v", err) + return } } pcln := gosym.NewLineTable(pclndat, exe.Section("__text").Addr) tab, err := gosym.NewTable(symdat, pcln) if err != nil { - fmt.Println("could not get initialize line table", err) - os.Exit(1) + bi.setLoadError("could not get initialize line table: %v", err) + return } bi.goSymTable = tab @@ -541,12 +554,12 @@ func (bi *BinaryInfo) parseDebugLineInfoMacho(exe *macho.File, wg *sync.WaitGrou if sec := exe.Section("__debug_line"); sec != nil { debugLine, err := exe.Section("__debug_line").Data() if err != nil { - fmt.Println("could not get __debug_line section", err) - os.Exit(1) + bi.setLoadError("could not get __debug_line section: %v", err) + return } bi.lineInfo = line.Parse(debugLine) } else { - fmt.Println("could not find __debug_line section in binary") - os.Exit(1) + bi.setLoadError("could not find __debug_line section in binary") + return } } diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index ce34a9d0d6314d8ec6848603cabf8f448c103c92..e1b496df05d3bd5a59fcf597807d38943c719ef3 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -175,8 +175,14 @@ func OpenCore(corePath, exePath string) (*Process, error) { } var wg sync.WaitGroup - p.bi.LoadBinaryInfo(exePath, &wg) + err = p.bi.LoadBinaryInfo(exePath, &wg) wg.Wait() + if err == nil { + err = p.bi.LoadError() + } + if err != nil { + return nil, err + } for _, th := range p.core.Threads { p.currentThread = th diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go index 5682dc6436f53f6c41fdcc2cc3684a347648d451..58c7a362d6217c617524537fa3540ee47b121247 100644 --- a/pkg/proc/core/core_test.go +++ b/pkg/proc/core/core_test.go @@ -132,7 +132,7 @@ func withCoreFile(t *testing.T, name, args string) *Process { if err != nil { t.Fatal(err) } - fix := test.BuildFixture(name) + fix := test.BuildFixture(name, 0) bashCmd := fmt.Sprintf("cd %v && ulimit -c unlimited && GOTRACEBACK=crash %v %s", tempDir, fix.Path, args) exec.Command("bash", "-c", bashCmd).Run() cores, err := filepath.Glob(path.Join(tempDir, "core*")) diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index d376e23359a8395e867163813da082b50c130237..17a6d2fcef6fdc9340b27ebbb9e30a945857933b 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -263,11 +263,14 @@ func (p *Process) Connect(conn net.Conn, path string, pid int) error { var wg sync.WaitGroup err = p.bi.LoadBinaryInfo(path, &wg) + wg.Wait() + if err == nil { + err = p.bi.LoadError() + } if err != nil { conn.Close() return err } - wg.Wait() // None of the stubs we support returns the value of fs_base or gs_base // along with the registers, therefore we have to resort to executing a MOV diff --git a/pkg/proc/gdbserial/rr_test.go b/pkg/proc/gdbserial/rr_test.go index feb3bd4469e9efac28bd8d8ec0334d5358108623..f19b043be397b8d27cdf4cbfd915217facb38f62 100644 --- a/pkg/proc/gdbserial/rr_test.go +++ b/pkg/proc/gdbserial/rr_test.go @@ -13,7 +13,7 @@ import ( ) func withTestRecording(name string, t testing.TB, fn func(p *gdbserial.Process, fixture protest.Fixture)) { - fixture := protest.BuildFixture(name) + fixture := protest.BuildFixture(name, 0) protest.MustHaveRecordingAllowed(t) if path, _ := exec.LookPath("rr"); path == "" { t.Skip("test skipped, rr not found") diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index 3f3854ec18ac0ab002382c3ad4648ad65c0a407d..482abbb0a6251a6ffab55c43b981838e88eb6139 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -163,10 +163,13 @@ func (dbp *Process) LoadInformation(path string) error { wg.Add(1) go dbp.loadProcessInformation(&wg) - dbp.bi.LoadBinaryInfo(path, &wg) + err := dbp.bi.LoadBinaryInfo(path, &wg) wg.Wait() + if err == nil { + err = dbp.bi.LoadError() + } - return nil + return err } // RequestManualStop sets the `halt` flag and @@ -388,11 +391,11 @@ func (dbp *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) { func initializeDebugProcess(dbp *Process, path string) (*Process, error) { err := dbp.LoadInformation(path) if err != nil { - return nil, err + return dbp, err } if err := dbp.updateThreadList(); err != nil { - return nil, err + return dbp, err } // selectedGoroutine can not be set correctly by the call to updateThreadList diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go index 38e400511e4f0d29da94c7310fa50f9b9a659e76..190f62e2d99ecc8866b38705709edacebb9ce2a4 100644 --- a/pkg/proc/native/proc_darwin.go +++ b/pkg/proc/native/proc_darwin.go @@ -152,7 +152,12 @@ func Attach(pid int) (*Process, error) { return nil, err } - return initializeDebugProcess(dbp, "") + dbp, err = initializeDebugProcess(dbp, "") + if err != nil { + dbp.Detach(false) + return nil, err + } + return dbp, nil } // Kill kills the process. diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index a62579599536c1ff25c0f6c71dc22ecad1fc1a1b..7877386b6196dfa48fc86fe6c64a0503b9704935 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -90,7 +90,12 @@ func Attach(pid int) (*Process, error) { return nil, err } - return initializeDebugProcess(dbp, "") + dbp, err = initializeDebugProcess(dbp, "") + if err != nil { + dbp.Detach(false) + return nil, err + } + return dbp, nil } // Kill kills the target process. diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index 9440e8e26f7e66d5ac3bb057eb2f11019f703445..d91776e6d319698ab4b7f0a95a13b59cbe45e03b 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -162,7 +162,12 @@ func Attach(pid int) (*Process, error) { if err != nil { return nil, err } - return newDebugProcess(New(pid), exepath) + dbp, err := newDebugProcess(New(pid), exepath) + if err != nil { + dbp.Detach(false) + return nil, err + } + return dbp, nil } // Kill kills the process. diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index e69c102befa1d8b6cd1de9bc2fa983991afdfa8f..10977da5e1f5908f997c53562867bef05d682d1c 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -48,7 +48,7 @@ func TestMain(m *testing.M) { } func withTestProcess(name string, t testing.TB, fn func(p proc.Process, fixture protest.Fixture)) { - fixture := protest.BuildFixture(name) + fixture := protest.BuildFixture(name, 0) var p proc.Process var err error var tracedir string @@ -81,7 +81,7 @@ func withTestProcess(name string, t testing.TB, fn func(p proc.Process, fixture } func withTestProcessArgs(name string, t testing.TB, wd string, fn func(p proc.Process, fixture protest.Fixture), args []string) { - fixture := protest.BuildFixture(name) + fixture := protest.BuildFixture(name, 0) var p proc.Process var err error var tracedir string @@ -2794,7 +2794,7 @@ func TestAttachDetach(t *testing.T) { if testBackend == "rr" { return } - fixture := protest.BuildFixture("testnextnethttp") + fixture := protest.BuildFixture("testnextnethttp", 0) cmd := exec.Command(fixture.Path) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -3085,3 +3085,65 @@ func TestShadowedFlag(t *testing.T) { } }) } + +func TestAttachStripped(t *testing.T) { + if testBackend == "lldb" && runtime.GOOS == "linux" { + bs, _ := ioutil.ReadFile("/proc/sys/kernel/yama/ptrace_scope") + if bs == nil || strings.TrimSpace(string(bs)) != "0" { + t.Logf("can not run TestAttachStripped: %v\n", bs) + return + } + } + if testBackend == "rr" { + return + } + if runtime.GOOS == "darwin" { + t.Log("-s does not produce stripped executables on macOS") + return + } + fixture := protest.BuildFixture("testnextnethttp", protest.LinkStrip) + cmd := exec.Command(fixture.Path) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + assertNoError(cmd.Start(), t, "starting fixture") + + // wait for testnextnethttp to start listening + t0 := time.Now() + for { + conn, err := net.Dial("tcp", "localhost:9191") + if err == nil { + conn.Close() + break + } + time.Sleep(50 * time.Millisecond) + if time.Since(t0) > 10*time.Second { + t.Fatal("fixture did not start") + } + } + + var p proc.Process + var err error + + switch testBackend { + case "native": + p, err = native.Attach(cmd.Process.Pid) + case "lldb": + path := "" + if runtime.GOOS == "darwin" { + path = fixture.Path + } + p, err = gdbserial.LLDBAttach(cmd.Process.Pid, path) + default: + t.Fatalf("unknown backend %q", testBackend) + } + + t.Logf("error is %v", err) + + if err == nil { + p.Detach(true) + t.Fatalf("expected error after attach, got nothing") + } else { + cmd.Process.Kill() + } + os.Remove(fixture.Path) +} diff --git a/pkg/proc/test/support.go b/pkg/proc/test/support.go index 78716e7d863cb6546e76bf8c389c971f3c1f28de..ef3f8033a664cd785745417e3c63c2ca62185274 100644 --- a/pkg/proc/test/support.go +++ b/pkg/proc/test/support.go @@ -38,8 +38,14 @@ func FindFixturesDir() string { return fixturesDir } -func BuildFixture(name string) Fixture { - if f, ok := Fixtures[name]; ok { +type BuildFlags uint32 + +const ( + LinkStrip = 1 << iota +) + +func BuildFixture(name string, flags BuildFlags) Fixture { + if f, ok := Fixtures[name]; ok && flags == 0 { return f } @@ -62,6 +68,9 @@ func BuildFixture(name string) Fixture { // Work-around for https://github.com/golang/go/issues/13154 buildFlags = append(buildFlags, "-ldflags=-linkmode internal") } + if flags&LinkStrip != 0 { + buildFlags = append(buildFlags, "-ldflags=-s") + } buildFlags = append(buildFlags, "-gcflags=-N -l", "-o", tmpfile) if path != "" { buildFlags = append(buildFlags, name+".go") @@ -80,7 +89,13 @@ func BuildFixture(name string) Fixture { source, _ := filepath.Abs(path) source = filepath.ToSlash(source) - Fixtures[name] = Fixture{Name: name, Path: tmpfile, Source: source} + fixture := Fixture{Name: name, Path: tmpfile, Source: source} + + if flags != 0 { + return fixture + } + + Fixtures[name] = fixture return Fixtures[name] } diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index ccdf7ba10a17eabcb57ef0dfdd0f2f3fef6ea048..cbf65a4d7bf1bf12bd1b60de7ca9fa9083315012 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -104,7 +104,7 @@ func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) { defer listener.Close() server := rpccommon.NewServer(&service.Config{ Listener: listener, - ProcessArgs: []string{test.BuildFixture(name).Path}, + ProcessArgs: []string{test.BuildFixture(name, 0).Path}, Backend: testBackend, }, false) if err := server.Run(); err != nil { diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go index 601f2812e6255795a53370e568970a22092f4de7..df9f7485d3c10abb2f27c088fd4d35b9df26a6ce 100644 --- a/service/test/integration1_test.go +++ b/service/test/integration1_test.go @@ -31,7 +31,7 @@ func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) { defer listener.Close() server := rpccommon.NewServer(&service.Config{ Listener: listener, - ProcessArgs: []string{protest.BuildFixture(name).Path}, + ProcessArgs: []string{protest.BuildFixture(name, 0).Path}, Backend: testBackend, }, false) if err := server.Run(); err != nil { @@ -740,7 +740,7 @@ func Test1ClientServer_FullStacktrace(t *testing.T) { if arg.Name != "i" { continue } - t.Logf("frame %d, variable i is %v\n", i, arg) + t.Logf("frame %d, variable i is %v\n", i, arg) argn, err := strconv.Atoi(arg.Value) if err == nil { found[argn] = true diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index f93ed76a0c49360f18fe335466bcc595349c6aea..1d81c8ec25f5c6e6d19c0ed1fb018fc0a3ec0240 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -48,7 +48,7 @@ func withTestClient2(name string, t *testing.T, fn func(c service.Client)) { defer listener.Close() server := rpccommon.NewServer(&service.Config{ Listener: listener, - ProcessArgs: []string{protest.BuildFixture(name).Path}, + ProcessArgs: []string{protest.BuildFixture(name, 0).Path}, Backend: testBackend, }, false) if err := server.Run(); err != nil { diff --git a/service/test/variables_test.go b/service/test/variables_test.go index f6196a0ed5b6089d37bad7feb134191d4ec4ae8b..790f9b9fbea24d2c250432636e4725b2493a811f 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -106,7 +106,7 @@ func setVariable(p proc.Process, symbol, value string) error { } func withTestProcess(name string, t *testing.T, fn func(p proc.Process, fixture protest.Fixture)) { - fixture := protest.BuildFixture(name) + fixture := protest.BuildFixture(name, 0) var p proc.Process var err error var tracedir string