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