1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package runtime_test
6
7import (
8	"internal/testenv"
9	"io/ioutil"
10	"os"
11	"os/exec"
12	"path/filepath"
13	"runtime"
14	"strings"
15	"testing"
16)
17
18var lldbPath string
19
20func checkLldbPython(t *testing.T) {
21	cmd := exec.Command("lldb", "-P")
22	out, err := cmd.CombinedOutput()
23	if err != nil {
24		t.Skipf("skipping due to issue running lldb: %v\n%s", err, out)
25	}
26	lldbPath = strings.TrimSpace(string(out))
27
28	cmd = exec.Command("/usr/bin/python2.7", "-c", "import sys;sys.path.append(sys.argv[1]);import lldb; print('go lldb python support')", lldbPath)
29	out, err = cmd.CombinedOutput()
30
31	if err != nil {
32		t.Skipf("skipping due to issue running python: %v\n%s", err, out)
33	}
34	if string(out) != "go lldb python support\n" {
35		t.Skipf("skipping due to lack of python lldb support: %s", out)
36	}
37
38	if runtime.GOOS == "darwin" {
39		// Try to see if we have debugging permissions.
40		cmd = exec.Command("/usr/sbin/DevToolsSecurity", "-status")
41		out, err = cmd.CombinedOutput()
42		if err != nil {
43			t.Skipf("DevToolsSecurity failed: %v", err)
44		} else if !strings.Contains(string(out), "enabled") {
45			t.Skip(string(out))
46		}
47		cmd = exec.Command("/usr/bin/groups")
48		out, err = cmd.CombinedOutput()
49		if err != nil {
50			t.Skipf("groups failed: %v", err)
51		} else if !strings.Contains(string(out), "_developer") {
52			t.Skip("Not in _developer group")
53		}
54	}
55}
56
57const lldbHelloSource = `
58package main
59import "fmt"
60func main() {
61	mapvar := make(map[string]string,5)
62	mapvar["abc"] = "def"
63	mapvar["ghi"] = "jkl"
64	intvar := 42
65	ptrvar := &intvar
66	fmt.Println("hi") // line 10
67	_ = ptrvar
68}
69`
70
71const lldbScriptSource = `
72import sys
73sys.path.append(sys.argv[1])
74import lldb
75import os
76
77TIMEOUT_SECS = 5
78
79debugger = lldb.SBDebugger.Create()
80debugger.SetAsync(True)
81target = debugger.CreateTargetWithFileAndArch("a.exe", None)
82if target:
83  print "Created target"
84  main_bp = target.BreakpointCreateByLocation("main.go", 10)
85  if main_bp:
86    print "Created breakpoint"
87  process = target.LaunchSimple(None, None, os.getcwd())
88  if process:
89    print "Process launched"
90    listener = debugger.GetListener()
91    process.broadcaster.AddListener(listener, lldb.SBProcess.eBroadcastBitStateChanged)
92    while True:
93      event = lldb.SBEvent()
94      if listener.WaitForEvent(TIMEOUT_SECS, event):
95        if lldb.SBProcess.GetRestartedFromEvent(event):
96          continue
97        state = process.GetState()
98        if state in [lldb.eStateUnloaded, lldb.eStateLaunching, lldb.eStateRunning]:
99          continue
100      else:
101        print "Timeout launching"
102      break
103    if state == lldb.eStateStopped:
104      for t in process.threads:
105        if t.GetStopReason() == lldb.eStopReasonBreakpoint:
106          print "Hit breakpoint"
107          frame = t.GetFrameAtIndex(0)
108          if frame:
109            if frame.line_entry:
110              print "Stopped at %s:%d" % (frame.line_entry.file.basename, frame.line_entry.line)
111            if frame.function:
112              print "Stopped in %s" % (frame.function.name,)
113            var = frame.FindVariable('intvar')
114            if var:
115              print "intvar = %s" % (var.GetValue(),)
116            else:
117              print "no intvar"
118    else:
119      print "Process state", state
120    process.Destroy()
121else:
122  print "Failed to create target a.exe"
123
124lldb.SBDebugger.Destroy(debugger)
125sys.exit()
126`
127
128const expectedLldbOutput = `Created target
129Created breakpoint
130Process launched
131Hit breakpoint
132Stopped at main.go:10
133Stopped in main.main
134intvar = 42
135`
136
137func TestLldbPython(t *testing.T) {
138	testenv.MustHaveGoBuild(t)
139	if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final {
140		t.Skip("gdb test can fail with GOROOT_FINAL pending")
141	}
142	testenv.SkipFlaky(t, 31188)
143
144	checkLldbPython(t)
145
146	dir, err := ioutil.TempDir("", "go-build")
147	if err != nil {
148		t.Fatalf("failed to create temp directory: %v", err)
149	}
150	defer os.RemoveAll(dir)
151
152	src := filepath.Join(dir, "main.go")
153	err = ioutil.WriteFile(src, []byte(lldbHelloSource), 0644)
154	if err != nil {
155		t.Fatalf("failed to create src file: %v", err)
156	}
157
158	mod := filepath.Join(dir, "go.mod")
159	err = ioutil.WriteFile(mod, []byte("module lldbtest"), 0644)
160	if err != nil {
161		t.Fatalf("failed to create mod file: %v", err)
162	}
163
164	// As of 2018-07-17, lldb doesn't support compressed DWARF, so
165	// disable it for this test.
166	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-ldflags=-compressdwarf=false", "-o", "a.exe")
167	cmd.Dir = dir
168	cmd.Env = append(os.Environ(), "GOPATH=") // issue 31100
169	out, err := cmd.CombinedOutput()
170	if err != nil {
171		t.Fatalf("building source %v\n%s", err, out)
172	}
173
174	src = filepath.Join(dir, "script.py")
175	err = ioutil.WriteFile(src, []byte(lldbScriptSource), 0755)
176	if err != nil {
177		t.Fatalf("failed to create script: %v", err)
178	}
179
180	cmd = exec.Command("/usr/bin/python2.7", "script.py", lldbPath)
181	cmd.Dir = dir
182	got, _ := cmd.CombinedOutput()
183
184	if string(got) != expectedLldbOutput {
185		if strings.Contains(string(got), "Timeout launching") {
186			t.Skip("Timeout launching")
187		}
188		t.Fatalf("Unexpected lldb output:\n%s", got)
189	}
190}
191