| // 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") | |
| } | |
| } | |
| } | |
| } |