// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package runtime_test import ( "debug/elf" "debug/macho" "encoding/binary" "internal/testenv" "io" "io/ioutil" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" ) var lldbPath string func checkLldbPython(t *testing.T) { cmd := exec.Command("lldb", "-P") out, err := cmd.CombinedOutput() if err != nil { t.Skipf("skipping due to issue running lldb: %v\n%s", err, out) } lldbPath = strings.TrimSpace(string(out)) cmd = exec.Command("/usr/bin/python2.7", "-c", "import sys;sys.path.append(sys.argv[1]);import lldb; print('go lldb python support')", lldbPath) out, err = cmd.CombinedOutput() if err != nil { t.Skipf("skipping due to issue running python: %v\n%s", err, out) } if string(out) != "go lldb python support\n" { t.Skipf("skipping due to lack of python lldb support: %s", out) } if runtime.GOOS == "darwin" { // Try to see if we have debugging permissions. cmd = exec.Command("/usr/sbin/DevToolsSecurity", "-status") out, err = cmd.CombinedOutput() if err != nil { t.Skipf("DevToolsSecurity failed: %v", err) } else if !strings.Contains(string(out), "enabled") { t.Skip(string(out)) } cmd = exec.Command("/usr/bin/groups") out, err = cmd.CombinedOutput() if err != nil { t.Skipf("groups failed: %v", err) } else if !strings.Contains(string(out), "_developer") { t.Skip("Not in _developer group") } } } const lldbHelloSource = ` package main import "fmt" func main() { mapvar := make(map[string]string,5) mapvar["abc"] = "def" mapvar["ghi"] = "jkl" intvar := 42 ptrvar := &intvar fmt.Println("hi") // line 10 _ = ptrvar } ` const lldbScriptSource = ` import sys sys.path.append(sys.argv[1]) import lldb import os TIMEOUT_SECS = 5 debugger = lldb.SBDebugger.Create() debugger.SetAsync(True) target = debugger.CreateTargetWithFileAndArch("a.exe", None) if target: print "Created target" main_bp = target.BreakpointCreateByLocation("main.go", 10) if main_bp: print "Created breakpoint" process = target.LaunchSimple(None, None, os.getcwd()) if process: print "Process launched" listener = debugger.GetListener() process.broadcaster.AddListener(listener, lldb.SBProcess.eBroadcastBitStateChanged) while True: event = lldb.SBEvent() if listener.WaitForEvent(TIMEOUT_SECS, event): if lldb.SBProcess.GetRestartedFromEvent(event): continue state = process.GetState() if state in [lldb.eStateUnloaded, lldb.eStateLaunching, lldb.eStateRunning]: continue else: print "Timeout launching" break if state == lldb.eStateStopped: for t in process.threads: if t.GetStopReason() == lldb.eStopReasonBreakpoint: print "Hit breakpoint" frame = t.GetFrameAtIndex(0) if frame: if frame.line_entry: print "Stopped at %s:%d" % (frame.line_entry.file.basename, frame.line_entry.line) if frame.function: print "Stopped in %s" % (frame.function.name,) var = frame.FindVariable('intvar') if var: print "intvar = %s" % (var.GetValue(),) else: print "no intvar" else: print "Process state", state process.Destroy() else: print "Failed to create target a.exe" lldb.SBDebugger.Destroy(debugger) sys.exit() ` const expectedLldbOutput = `Created target Created breakpoint Process launched Hit breakpoint Stopped at main.go:10 Stopped in main.main intvar = 42 ` func TestLldbPython(t *testing.T) { testenv.MustHaveGoBuild(t) if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final { t.Skip("gdb test can fail with GOROOT_FINAL pending") } checkLldbPython(t) dir, err := ioutil.TempDir("", "go-build") if err != nil { t.Fatalf("failed to create temp directory: %v", err) } defer os.RemoveAll(dir) src := filepath.Join(dir, "main.go") err = ioutil.WriteFile(src, []byte(lldbHelloSource), 0644) if err != nil { t.Fatalf("failed to create file: %v", err) } cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags", "-N -l", "-o", "a.exe") cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("building source %v\n%s", err, out) } src = filepath.Join(dir, "script.py") err = ioutil.WriteFile(src, []byte(lldbScriptSource), 0755) if err != nil { t.Fatalf("failed to create script: %v", err) } cmd = exec.Command("/usr/bin/python2.7", "script.py", lldbPath) cmd.Dir = dir got, _ := cmd.CombinedOutput() if string(got) != expectedLldbOutput { if strings.Contains(string(got), "Timeout launching") { t.Skip("Timeout launching") } t.Fatalf("Unexpected lldb output:\n%s", got) } } // Check that aranges are valid even when lldb isn't installed. func TestDwarfAranges(t *testing.T) { testenv.MustHaveGoBuild(t) dir, err := ioutil.TempDir("", "go-build") if err != nil { t.Fatalf("failed to create temp directory: %v", err) } defer os.RemoveAll(dir) src := filepath.Join(dir, "main.go") err = ioutil.WriteFile(src, []byte(lldbHelloSource), 0644) if err != nil { t.Fatalf("failed to create file: %v", err) } cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe") cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("building source %v\n%s", err, out) } filename := filepath.Join(dir, "a.exe") if f, err := elf.Open(filename); err == nil { sect := f.Section(".debug_aranges") if sect == nil { t.Fatal("Missing aranges section") } verifyAranges(t, f.ByteOrder, sect.Open()) } else if f, err := macho.Open(filename); err == nil { sect := f.Section("__debug_aranges") if sect == nil { t.Fatal("Missing aranges section") } verifyAranges(t, f.ByteOrder, sect.Open()) } else { t.Skip("Not an elf or macho binary.") } } func verifyAranges(t *testing.T, byteorder binary.ByteOrder, data io.ReadSeeker) { var header struct { UnitLength uint32 // does not include the UnitLength field Version uint16 Offset uint32 AddressSize uint8 SegmentSize uint8 } for { offset, err := data.Seek(0, io.SeekCurrent) if err != nil { t.Fatalf("Seek error: %v", err) } if err = binary.Read(data, byteorder, &header); err == io.EOF { return } else if err != nil { t.Fatalf("Error reading arange header: %v", err) } tupleSize := int64(header.SegmentSize) + 2*int64(header.AddressSize) lastTupleOffset := offset + int64(header.UnitLength) + 4 - tupleSize if lastTupleOffset%tupleSize != 0 { t.Fatalf("Invalid arange length %d, (addr %d, seg %d)", header.UnitLength, header.AddressSize, header.SegmentSize) } if _, err = data.Seek(lastTupleOffset, io.SeekStart); err != nil { t.Fatalf("Seek error: %v", err) } buf := make([]byte, tupleSize) if n, err := data.Read(buf); err != nil || int64(n) < tupleSize { t.Fatalf("Read error: %v", err) } for _, val := range buf { if val != 0 { t.Fatalf("Invalid terminator") } } } }