Home | History | Annotate | Download | only in runtime
      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 
      5 package runtime_test
      6 
      7 import (
      8 	"debug/elf"
      9 	"debug/macho"
     10 	"encoding/binary"
     11 	"internal/testenv"
     12 	"io"
     13 	"io/ioutil"
     14 	"os"
     15 	"os/exec"
     16 	"path/filepath"
     17 	"runtime"
     18 	"strings"
     19 	"testing"
     20 )
     21 
     22 var lldbPath string
     23 
     24 func checkLldbPython(t *testing.T) {
     25 	cmd := exec.Command("lldb", "-P")
     26 	out, err := cmd.CombinedOutput()
     27 	if err != nil {
     28 		t.Skipf("skipping due to issue running lldb: %v\n%s", err, out)
     29 	}
     30 	lldbPath = strings.TrimSpace(string(out))
     31 
     32 	cmd = exec.Command("/usr/bin/python2.7", "-c", "import sys;sys.path.append(sys.argv[1]);import lldb; print('go lldb python support')", lldbPath)
     33 	out, err = cmd.CombinedOutput()
     34 
     35 	if err != nil {
     36 		t.Skipf("skipping due to issue running python: %v\n%s", err, out)
     37 	}
     38 	if string(out) != "go lldb python support\n" {
     39 		t.Skipf("skipping due to lack of python lldb support: %s", out)
     40 	}
     41 
     42 	if runtime.GOOS == "darwin" {
     43 		// Try to see if we have debugging permissions.
     44 		cmd = exec.Command("/usr/sbin/DevToolsSecurity", "-status")
     45 		out, err = cmd.CombinedOutput()
     46 		if err != nil {
     47 			t.Skipf("DevToolsSecurity failed: %v", err)
     48 		} else if !strings.Contains(string(out), "enabled") {
     49 			t.Skip(string(out))
     50 		}
     51 		cmd = exec.Command("/usr/bin/groups")
     52 		out, err = cmd.CombinedOutput()
     53 		if err != nil {
     54 			t.Skipf("groups failed: %v", err)
     55 		} else if !strings.Contains(string(out), "_developer") {
     56 			t.Skip("Not in _developer group")
     57 		}
     58 	}
     59 }
     60 
     61 const lldbHelloSource = `
     62 package main
     63 import "fmt"
     64 func main() {
     65 	mapvar := make(map[string]string,5)
     66 	mapvar["abc"] = "def"
     67 	mapvar["ghi"] = "jkl"
     68 	intvar := 42
     69 	ptrvar := &intvar
     70 	fmt.Println("hi") // line 10
     71 	_ = ptrvar
     72 }
     73 `
     74 
     75 const lldbScriptSource = `
     76 import sys
     77 sys.path.append(sys.argv[1])
     78 import lldb
     79 import os
     80 
     81 TIMEOUT_SECS = 5
     82 
     83 debugger = lldb.SBDebugger.Create()
     84 debugger.SetAsync(True)
     85 target = debugger.CreateTargetWithFileAndArch("a.exe", None)
     86 if target:
     87   print "Created target"
     88   main_bp = target.BreakpointCreateByLocation("main.go", 10)
     89   if main_bp:
     90     print "Created breakpoint"
     91   process = target.LaunchSimple(None, None, os.getcwd())
     92   if process:
     93     print "Process launched"
     94     listener = debugger.GetListener()
     95     process.broadcaster.AddListener(listener, lldb.SBProcess.eBroadcastBitStateChanged)
     96     while True:
     97       event = lldb.SBEvent()
     98       if listener.WaitForEvent(TIMEOUT_SECS, event):
     99         if lldb.SBProcess.GetRestartedFromEvent(event):
    100           continue
    101         state = process.GetState()
    102         if state in [lldb.eStateUnloaded, lldb.eStateLaunching, lldb.eStateRunning]:
    103           continue
    104       else:
    105         print "Timeout launching"
    106       break
    107     if state == lldb.eStateStopped:
    108       for t in process.threads:
    109         if t.GetStopReason() == lldb.eStopReasonBreakpoint:
    110           print "Hit breakpoint"
    111           frame = t.GetFrameAtIndex(0)
    112           if frame:
    113             if frame.line_entry:
    114               print "Stopped at %s:%d" % (frame.line_entry.file.basename, frame.line_entry.line)
    115             if frame.function:
    116               print "Stopped in %s" % (frame.function.name,)
    117             var = frame.FindVariable('intvar')
    118             if var:
    119               print "intvar = %s" % (var.GetValue(),)
    120             else:
    121               print "no intvar"
    122     else:
    123       print "Process state", state
    124     process.Destroy()
    125 else:
    126   print "Failed to create target a.exe"
    127 
    128 lldb.SBDebugger.Destroy(debugger)
    129 sys.exit()
    130 `
    131 
    132 const expectedLldbOutput = `Created target
    133 Created breakpoint
    134 Process launched
    135 Hit breakpoint
    136 Stopped at main.go:10
    137 Stopped in main.main
    138 intvar = 42
    139 `
    140 
    141 func TestLldbPython(t *testing.T) {
    142 	testenv.MustHaveGoBuild(t)
    143 	if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final {
    144 		t.Skip("gdb test can fail with GOROOT_FINAL pending")
    145 	}
    146 
    147 	checkLldbPython(t)
    148 
    149 	dir, err := ioutil.TempDir("", "go-build")
    150 	if err != nil {
    151 		t.Fatalf("failed to create temp directory: %v", err)
    152 	}
    153 	defer os.RemoveAll(dir)
    154 
    155 	src := filepath.Join(dir, "main.go")
    156 	err = ioutil.WriteFile(src, []byte(lldbHelloSource), 0644)
    157 	if err != nil {
    158 		t.Fatalf("failed to create file: %v", err)
    159 	}
    160 
    161 	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags", "-N -l", "-o", "a.exe")
    162 	cmd.Dir = dir
    163 	out, err := cmd.CombinedOutput()
    164 	if err != nil {
    165 		t.Fatalf("building source %v\n%s", err, out)
    166 	}
    167 
    168 	src = filepath.Join(dir, "script.py")
    169 	err = ioutil.WriteFile(src, []byte(lldbScriptSource), 0755)
    170 	if err != nil {
    171 		t.Fatalf("failed to create script: %v", err)
    172 	}
    173 
    174 	cmd = exec.Command("/usr/bin/python2.7", "script.py", lldbPath)
    175 	cmd.Dir = dir
    176 	got, _ := cmd.CombinedOutput()
    177 
    178 	if string(got) != expectedLldbOutput {
    179 		if strings.Contains(string(got), "Timeout launching") {
    180 			t.Skip("Timeout launching")
    181 		}
    182 		t.Fatalf("Unexpected lldb output:\n%s", got)
    183 	}
    184 }
    185 
    186 // Check that aranges are valid even when lldb isn't installed.
    187 func TestDwarfAranges(t *testing.T) {
    188 	testenv.MustHaveGoBuild(t)
    189 	dir, err := ioutil.TempDir("", "go-build")
    190 	if err != nil {
    191 		t.Fatalf("failed to create temp directory: %v", err)
    192 	}
    193 	defer os.RemoveAll(dir)
    194 
    195 	src := filepath.Join(dir, "main.go")
    196 	err = ioutil.WriteFile(src, []byte(lldbHelloSource), 0644)
    197 	if err != nil {
    198 		t.Fatalf("failed to create file: %v", err)
    199 	}
    200 
    201 	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
    202 	cmd.Dir = dir
    203 	out, err := cmd.CombinedOutput()
    204 	if err != nil {
    205 		t.Fatalf("building source %v\n%s", err, out)
    206 	}
    207 
    208 	filename := filepath.Join(dir, "a.exe")
    209 	if f, err := elf.Open(filename); err == nil {
    210 		sect := f.Section(".debug_aranges")
    211 		if sect == nil {
    212 			t.Fatal("Missing aranges section")
    213 		}
    214 		verifyAranges(t, f.ByteOrder, sect.Open())
    215 	} else if f, err := macho.Open(filename); err == nil {
    216 		sect := f.Section("__debug_aranges")
    217 		if sect == nil {
    218 			t.Fatal("Missing aranges section")
    219 		}
    220 		verifyAranges(t, f.ByteOrder, sect.Open())
    221 	} else {
    222 		t.Skip("Not an elf or macho binary.")
    223 	}
    224 }
    225 
    226 func verifyAranges(t *testing.T, byteorder binary.ByteOrder, data io.ReadSeeker) {
    227 	var header struct {
    228 		UnitLength  uint32 // does not include the UnitLength field
    229 		Version     uint16
    230 		Offset      uint32
    231 		AddressSize uint8
    232 		SegmentSize uint8
    233 	}
    234 	for {
    235 		offset, err := data.Seek(0, io.SeekCurrent)
    236 		if err != nil {
    237 			t.Fatalf("Seek error: %v", err)
    238 		}
    239 		if err = binary.Read(data, byteorder, &header); err == io.EOF {
    240 			return
    241 		} else if err != nil {
    242 			t.Fatalf("Error reading arange header: %v", err)
    243 		}
    244 		tupleSize := int64(header.SegmentSize) + 2*int64(header.AddressSize)
    245 		lastTupleOffset := offset + int64(header.UnitLength) + 4 - tupleSize
    246 		if lastTupleOffset%tupleSize != 0 {
    247 			t.Fatalf("Invalid arange length %d, (addr %d, seg %d)", header.UnitLength, header.AddressSize, header.SegmentSize)
    248 		}
    249 		if _, err = data.Seek(lastTupleOffset, io.SeekStart); err != nil {
    250 			t.Fatalf("Seek error: %v", err)
    251 		}
    252 		buf := make([]byte, tupleSize)
    253 		if n, err := data.Read(buf); err != nil || int64(n) < tupleSize {
    254 			t.Fatalf("Read error: %v", err)
    255 		}
    256 		for _, val := range buf {
    257 			if val != 0 {
    258 				t.Fatalf("Invalid terminator")
    259 			}
    260 		}
    261 	}
    262 }
    263