threads.go 10.5 KB
Newer Older
D
Derek Parker 已提交
1
package proc
D
Derek Parker 已提交
2 3

import (
4
	"debug/gosym"
5
	"encoding/binary"
6
	"errors"
D
Derek Parker 已提交
7
	"fmt"
8
	"go/ast"
9
	"path/filepath"
10
	"reflect"
A
aarzilli 已提交
11
	"strings"
D
Derek Parker 已提交
12

13
	"golang.org/x/debug/dwarf"
D
Derek Parker 已提交
14 15
)

16 17
// Thread represents a thread.
type Thread interface {
18
	MemoryReadWriter
19 20 21 22 23 24 25 26 27 28 29 30
	Location() (*Location, error)
	// Breakpoint will return the breakpoint that this thread is stopped at or
	// nil if the thread is not stopped at any breakpoint.
	// Active will be true if the thread is stopped at a breakpoint and the
	// breakpoint's condition is met.
	// If there was an error evaluating the breakpoint's condition it will be
	// returned as condErr
	Breakpoint() (breakpoint *Breakpoint, active bool, condErr error)
	ThreadID() int
	Registers(floatingPoint bool) (Registers, error)
	Arch() Arch
	BinInfo() *BinaryInfo
31
	StepInstruction() error
32 33
	// Blocked returns true if the thread is blocked
	Blocked() bool
34 35
}

D
Derek Parker 已提交
36
// Location represents the location of a thread.
D
Derek Parker 已提交
37 38
// Holds information on the current instruction
// address, the source file:line, and the function.
39 40 41 42 43 44 45
type Location struct {
	PC   uint64
	File string
	Line int
	Fn   *gosym.Func
}

D
Derek Parker 已提交
46 47
// ThreadBlockedError is returned when the thread
// is blocked in the scheduler.
48 49 50 51 52 53
type ThreadBlockedError struct{}

func (tbe ThreadBlockedError) Error() string {
	return ""
}

54
// returns topmost frame of g or thread if g is nil
55
func topframe(g *G, thread Thread) (Stackframe, error) {
56
	var frames []Stackframe
57
	var err error
58

59
	if g == nil {
60
		if thread.Blocked() {
61
			return Stackframe{}, ThreadBlockedError{}
62
		}
63
		frames, err = ThreadStacktrace(thread, 0)
64
	} else {
65
		frames, err = g.Stacktrace(0)
66
	}
D
Derek Parker 已提交
67
	if err != nil {
68
		return Stackframe{}, err
D
Derek Parker 已提交
69
	}
70
	if len(frames) < 1 {
71 72 73 74 75
		return Stackframe{}, errors.New("empty stack trace")
	}
	return frames[0], nil
}

A
aarzilli 已提交
76 77 78 79 80 81 82
// Set breakpoints at every line, and the return address. Also look for
// a deferred function and set a breakpoint there too.
// If stepInto is true it will also set breakpoints inside all
// functions called on the current source line, for non-absolute CALLs
// a breakpoint of kind StepBreakpoint is set on the CALL instruction,
// Continue will take care of setting a breakpoint to the destination
// once the CALL is reached.
83
func next(dbp Process, stepInto bool) error {
84 85 86
	selg := dbp.SelectedGoroutine()
	curthread := dbp.CurrentThread()
	topframe, err := topframe(selg, curthread)
87 88
	if err != nil {
		return err
89
	}
D
Derek Parker 已提交
90

91 92 93 94 95 96 97
	success := false
	defer func() {
		if !success {
			dbp.ClearInternalBreakpoints()
		}
	}()

A
aarzilli 已提交
98
	csource := filepath.Ext(topframe.Current.File) != ".go"
99
	var thread MemoryReadWriter = curthread
100
	var regs Registers
101 102 103
	if selg != nil && selg.Thread != nil {
		thread = selg.Thread
		regs, err = selg.Thread.Registers(false)
104 105 106
		if err != nil {
			return err
		}
D
Derek Parker 已提交
107
	}
108

109
	text, err := disassemble(thread, regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.FDE.Begin(), topframe.FDE.End())
A
aarzilli 已提交
110 111 112
	if err != nil && stepInto {
		return err
	}
113

114 115 116 117 118 119
	for i := range text {
		if text[i].Inst == nil {
			fmt.Printf("error at instruction %d\n", i)
		}
	}

120
	cond := SameGoroutineCondition(selg)
121

A
aarzilli 已提交
122
	if stepInto {
123
		for _, instr := range text {
A
aarzilli 已提交
124 125 126 127 128
			if instr.Loc.File != topframe.Current.File || instr.Loc.Line != topframe.Current.Line || !instr.IsCall() {
				continue
			}

			if instr.DestLoc != nil && instr.DestLoc.Fn != nil {
129
				if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, cond); err != nil {
A
aarzilli 已提交
130 131 132 133
					return err
				}
			} else {
				// Non-absolute call instruction, set a StepBreakpoint here
134
				if _, err := dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, cond); err != nil {
A
aarzilli 已提交
135 136 137 138
					if _, ok := err.(BreakpointExistsError); !ok {
						return err
					}
				}
139 140 141
			}
		}
	}
142

A
aarzilli 已提交
143 144 145 146 147 148 149 150 151
	if !csource {
		deferreturns := []uint64{}

		// Find all runtime.deferreturn locations in the function
		// See documentation of Breakpoint.DeferCond for why this is necessary
		for _, instr := range text {
			if instr.IsCall() && instr.DestLoc != nil && instr.DestLoc.Fn != nil && instr.DestLoc.Fn.Name == "runtime.deferreturn" {
				deferreturns = append(deferreturns, instr.Loc.PC)
			}
152
		}
A
aarzilli 已提交
153 154 155

		// Set breakpoint on the most recently deferred function (if any)
		var deferpc uint64 = 0
156 157
		if selg != nil {
			deferPCEntry := selg.DeferPC()
A
Alessandro Arzilli 已提交
158
			if deferPCEntry != 0 {
159
				_, _, deferfn := dbp.BinInfo().PCToLine(deferPCEntry)
A
Alessandro Arzilli 已提交
160 161 162 163 164
				var err error
				deferpc, err = dbp.FirstPCAfterPrologue(deferfn, false)
				if err != nil {
					return err
				}
165 166
			}
		}
A
aarzilli 已提交
167
		if deferpc != 0 && deferpc != topframe.Current.PC {
168
			bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, cond)
A
aarzilli 已提交
169 170 171 172 173 174 175 176 177
			if err != nil {
				if _, ok := err.(BreakpointExistsError); !ok {
					return err
				}
			}
			if bp != nil {
				bp.DeferReturns = deferreturns
			}
		}
178 179 180
	}

	// Add breakpoints on all the lines in the current function
181
	pcs, err := dbp.BinInfo().lineInfo.AllPCsBetween(topframe.FDE.Begin(), topframe.FDE.End()-1, topframe.Current.File)
182 183 184
	if err != nil {
		return err
	}
D
Derek Parker 已提交
185

A
aarzilli 已提交
186 187 188 189 190 191 192
	if !csource {
		var covered bool
		for i := range pcs {
			if topframe.FDE.Cover(pcs[i]) {
				covered = true
				break
			}
D
Derek Parker 已提交
193
		}
194

A
aarzilli 已提交
195
		if !covered {
196 197
			fn := dbp.BinInfo().goSymTable.PCToFunc(topframe.Ret)
			if selg != nil && fn != nil && fn.Name == "runtime.goexit" {
A
aarzilli 已提交
198 199
				return nil
			}
D
Derek Parker 已提交
200
		}
201
	}
202 203

	// Add a breakpoint on the return address for the current frame
204
	pcs = append(pcs, topframe.Ret)
205
	success = true
206
	return setInternalBreakpoints(dbp, topframe.Current.PC, pcs, NextBreakpoint, cond)
207 208
}

209
func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) error {
A
aarzilli 已提交
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
	if len(text) <= 0 {
		return nil
	}

	instr := text[0]

	if instr.DestLoc == nil || instr.DestLoc.Fn == nil {
		return nil
	}

	fn := instr.DestLoc.Fn

	// Ensure PC and Entry match, otherwise StepInto is likely to set
	// its breakpoint before DestLoc.PC and hence run too far ahead.
	// Calls to runtime.duffzero and duffcopy have this problem.
	if fn.Entry != instr.DestLoc.PC {
		return nil
	}

	// Skip unexported runtime functions
	if strings.HasPrefix(fn.Name, "runtime.") && !isExportedRuntime(fn.Name) {
		return nil
	}

	//TODO(aarzilli): if we want to let users hide functions
	// or entire packages from being stepped into with 'step'
	// those extra checks should be done here.

	// Set a breakpoint after the function's prologue
	pc, _ := dbp.FirstPCAfterPrologue(fn, false)
240
	if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, cond); err != nil {
A
aarzilli 已提交
241 242 243 244 245 246
		if _, ok := err.(BreakpointExistsError); !ok {
			return err
		}
	}

	return nil
247
}
248

249
// setInternalBreakpoints sets a breakpoint to all addresses specified in pcs
250
// skipping over curpc and curpc-1
251
func setInternalBreakpoints(dbp Process, curpc uint64, pcs []uint64, kind BreakpointKind, cond ast.Expr) error {
252 253
	for i := range pcs {
		if pcs[i] == curpc || pcs[i] == curpc-1 {
254 255
			continue
		}
256
		if _, err := dbp.SetBreakpoint(pcs[i], kind, cond); err != nil {
D
Derek Parker 已提交
257
			if _, ok := err.(BreakpointExistsError); !ok {
258
				dbp.ClearInternalBreakpoints()
259 260 261
				return err
			}
		}
D
Derek Parker 已提交
262 263 264 265
	}
	return nil
}

266
func getGVariable(thread Thread) (*Variable, error) {
267
	arch := thread.Arch()
A
aarzilli 已提交
268
	regs, err := thread.Registers(false)
269 270 271 272
	if err != nil {
		return nil, err
	}

273
	if arch.GStructOffset() == 0 {
274
		// GetG was called through SwitchThread / updateThreadList during initialization
D
Derek Parker 已提交
275
		// thread.dbp.arch isn't setup yet (it needs a current thread to read global variables from)
276 277 278
		return nil, fmt.Errorf("g struct offset not initialized")
	}

279 280
	gaddr, hasgaddr := regs.GAddr()
	if !hasgaddr {
281 282
		gaddrbs := make([]byte, arch.PtrSize())
		_, err := thread.ReadMemory(gaddrbs, uintptr(regs.TLS()+arch.GStructOffset()))
283 284 285 286
		if err != nil {
			return nil, err
		}
		gaddr = binary.LittleEndian.Uint64(gaddrbs)
287
	}
A
go fmt  
aarzilli 已提交
288

289
	return newGVariable(thread, uintptr(gaddr), arch.DerefTLS())
290 291
}

292
func newGVariable(thread Thread, gaddr uintptr, deref bool) (*Variable, error) {
293
	typ, err := thread.BinInfo().findType("runtime.g")
294 295 296 297 298 299 300
	if err != nil {
		return nil, err
	}

	name := ""

	if deref {
301
		typ = &dwarf.PtrType{dwarf.CommonType{int64(thread.Arch().PtrSize()), "", reflect.Ptr, 0}, typ}
302 303 304 305
	} else {
		name = "runtime.curg"
	}

306
	return newVariableFromThread(thread, name, gaddr, typ), nil
307 308
}

D
Derek Parker 已提交
309
// GetG returns information on the G (goroutine) that is executing on this thread.
310
//
D
Derek Parker 已提交
311 312
// The G structure for a thread is stored in thread local storage. Here we simply
// calculate the address and read and parse the G struct.
313 314 315 316 317 318 319 320
//
// We cannot simply use the allg linked list in order to find the M that represents
// the given OS thread and follow its G pointer because on Darwin mach ports are not
// universal, so our port for this thread would not map to the `id` attribute of the M
// structure. Also, when linked against libc, Go prefers the libc version of clone as
// opposed to the runtime version. This has the consequence of not setting M.id for
// any thread, regardless of OS.
//
321 322
// In order to get around all this craziness, we read the address of the G structure for
// the current thread from the thread local storage area.
323
func GetG(thread Thread) (g *G, err error) {
324
	gaddr, err := getGVariable(thread)
325 326 327
	if err != nil {
		return nil, err
	}
328 329

	g, err = gaddr.parseG()
330
	if err == nil {
331
		g.Thread = thread
332 333 334
		if loc, err := thread.Location(); err == nil {
			g.CurrentLoc = *loc
		}
335
	}
336
	return
D
Derek Parker 已提交
337
}
338

339
// ThreadScope returns an EvalScope for this thread.
340
func ThreadScope(thread Thread) (*EvalScope, error) {
341
	locations, err := ThreadStacktrace(thread, 0)
342 343 344
	if err != nil {
		return nil, err
	}
345 346 347
	if len(locations) < 1 {
		return nil, errors.New("could not decode first frame")
	}
348
	return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, nil, thread.BinInfo()}, nil
349 350 351
}

// GoroutineScope returns an EvalScope for the goroutine running on this thread.
352
func GoroutineScope(thread Thread) (*EvalScope, error) {
353
	locations, err := ThreadStacktrace(thread, 0)
354 355 356 357 358 359
	if err != nil {
		return nil, err
	}
	if len(locations) < 1 {
		return nil, errors.New("could not decode first frame")
	}
360
	gvar, err := getGVariable(thread)
361 362 363
	if err != nil {
		return nil, err
	}
364
	return &EvalScope{locations[0].Current.PC, locations[0].CFA, thread, gvar, thread.BinInfo()}, nil
365
}
366

367
func onRuntimeBreakpoint(thread Thread) bool {
D
Derek Parker 已提交
368
	loc, err := thread.Location()
A
aarzilli 已提交
369 370 371 372 373
	if err != nil {
		return false
	}
	return loc.Fn != nil && loc.Fn.Name == "runtime.breakpoint"
}
374

H
Hubert Krauze 已提交
375
// onNextGorutine returns true if this thread is on the goroutine requested by the current 'next' command
376
func onNextGoroutine(thread Thread, breakpoints map[uint64]*Breakpoint) (bool, error) {
377
	var bp *Breakpoint
378 379 380
	for i := range breakpoints {
		if breakpoints[i].Internal() {
			bp = breakpoints[i]
A
aarzilli 已提交
381
			break
382 383 384
		}
	}
	if bp == nil {
385
		return false, nil
386
	}
A
aarzilli 已提交
387 388 389 390 391 392 393
	if bp.Kind == NextDeferBreakpoint {
		// we just want to check the condition on the goroutine id here
		bp.Kind = NextBreakpoint
		defer func() {
			bp.Kind = NextDeferBreakpoint
		}()
	}
394
	return bp.CheckCondition(thread)
395
}