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

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

13
	"github.com/derekparker/delve/pkg/dwarf/godwarf"
A
aarzilli 已提交
14
	"github.com/derekparker/delve/pkg/dwarf/reader"
D
Derek Parker 已提交
15 16
)

17 18
// Thread represents a thread.
type Thread interface {
19
	MemoryReadWriter
20 21 22
	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.
23
	Breakpoint() BreakpointState
24 25 26 27
	ThreadID() int
	Registers(floatingPoint bool) (Registers, error)
	Arch() Arch
	BinInfo() *BinaryInfo
28
	StepInstruction() error
29 30
	// Blocked returns true if the thread is blocked
	Blocked() bool
31 32
	// SetCurrentBreakpoint updates the current breakpoint of this thread
	SetCurrentBreakpoint() error
33 34
}

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

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

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

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

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

79 80 81 82 83 84 85 86
type NoSourceForPCError struct {
	pc uint64
}

func (err *NoSourceForPCError) Error() string {
	return fmt.Sprintf("no source for pc %#x", err.pc)
}

A
aarzilli 已提交
87 88 89 90 91 92 93
// 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.
94 95 96 97 98 99 100 101 102 103
//
// Regardless of stepInto the following breakpoints will be set:
// - a breakpoint on the first deferred function with NextDeferBreakpoint
//   kind, the list of all the addresses to deferreturn calls in this function
//   and condition checking that we remain on the same goroutine
// - a breakpoint on each line of the function, with a condition checking
//   that we stay on the same stack frame and goroutine.
// - a breakpoint on the return address of the function, with a condition
//   checking that we move to the previous stack frame and stay on the same
//   goroutine.
A
aarzilli 已提交
104 105 106 107 108 109 110 111 112 113 114
//
// The breakpoint on the return address is *not* set if the current frame is
// an inlined call. For inlined calls topframe.Current.Fn is the function
// where the inlining happened and the second set of breakpoints will also
// cover the "return address".
//
// If inlinedStepOut is true this function implements the StepOut operation
// for an inlined function call. Everything works the same as normal except
// when removing instructions belonging to inlined calls we also remove all
// instructions belonging to the current inlined call.
func next(dbp Process, stepInto, inlinedStepOut bool) error {
115 116
	selg := dbp.SelectedGoroutine()
	curthread := dbp.CurrentThread()
117
	topframe, retframe, err := topframe(selg, curthread)
118 119
	if err != nil {
		return err
120
	}
D
Derek Parker 已提交
121

122
	if topframe.Current.Fn == nil {
123
		return &NoSourceForPCError{topframe.Current.PC}
124 125
	}

A
aarzilli 已提交
126 127 128 129 130
	// sanity check
	if inlinedStepOut && !topframe.Inlined {
		panic("next called with inlinedStepOut but topframe was not inlined")
	}

131 132 133 134 135 136 137
	success := false
	defer func() {
		if !success {
			dbp.ClearInternalBreakpoints()
		}
	}()

138 139
	ext := filepath.Ext(topframe.Current.File)
	csource := ext != ".go" && ext != ".s"
140
	var thread MemoryReadWriter = curthread
141
	var regs Registers
142 143 144
	if selg != nil && selg.Thread != nil {
		thread = selg.Thread
		regs, err = selg.Thread.Registers(false)
145 146 147
		if err != nil {
			return err
		}
D
Derek Parker 已提交
148
	}
149

A
aarzilli 已提交
150
	text, err := disassemble(thread, regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.Current.Fn.Entry, topframe.Current.Fn.End, false)
A
aarzilli 已提交
151 152 153
	if err != nil && stepInto {
		return err
	}
154

155
	sameGCond := SameGoroutineCondition(selg)
A
aarzilli 已提交
156 157
	retFrameCond := andFrameoffCondition(sameGCond, retframe.FrameOffset())
	sameFrameCond := andFrameoffCondition(sameGCond, topframe.FrameOffset())
158 159
	var sameOrRetFrameCond ast.Expr
	if sameGCond != nil {
A
aarzilli 已提交
160 161 162 163 164 165 166 167 168 169 170 171
		if topframe.Inlined {
			sameOrRetFrameCond = sameFrameCond
		} else {
			sameOrRetFrameCond = &ast.BinaryExpr{
				Op: token.LAND,
				X:  sameGCond,
				Y: &ast.BinaryExpr{
					Op: token.LOR,
					X:  frameoffCondition(topframe.FrameOffset()),
					Y:  frameoffCondition(retframe.FrameOffset()),
				},
			}
172 173
		}
	}
174

A
aarzilli 已提交
175
	if stepInto {
176
		for _, instr := range text {
A
aarzilli 已提交
177 178 179 180 181
			if instr.Loc.File != topframe.Current.File || instr.Loc.Line != topframe.Current.Line || !instr.IsCall() {
				continue
			}

			if instr.DestLoc != nil && instr.DestLoc.Fn != nil {
182
				if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, sameGCond); err != nil {
A
aarzilli 已提交
183 184 185 186
					return err
				}
			} else {
				// Non-absolute call instruction, set a StepBreakpoint here
187
				if _, err := dbp.SetBreakpoint(instr.Loc.PC, StepBreakpoint, sameGCond); err != nil {
A
aarzilli 已提交
188 189 190 191
					if _, ok := err.(BreakpointExistsError); !ok {
						return err
					}
				}
192 193 194
			}
		}
	}
195

A
aarzilli 已提交
196 197 198 199 200 201 202 203 204
	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)
			}
205
		}
A
aarzilli 已提交
206 207 208

		// Set breakpoint on the most recently deferred function (if any)
		var deferpc uint64 = 0
209 210
		if selg != nil {
			deferPCEntry := selg.DeferPC()
A
Alessandro Arzilli 已提交
211
			if deferPCEntry != 0 {
212
				_, _, deferfn := dbp.BinInfo().PCToLine(deferPCEntry)
A
Alessandro Arzilli 已提交
213
				var err error
214
				deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
A
Alessandro Arzilli 已提交
215 216 217
				if err != nil {
					return err
				}
218 219
			}
		}
A
aarzilli 已提交
220
		if deferpc != 0 && deferpc != topframe.Current.PC {
221
			bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, sameGCond)
A
aarzilli 已提交
222 223 224 225 226
			if err != nil {
				if _, ok := err.(BreakpointExistsError); !ok {
					return err
				}
			}
227
			if bp != nil && stepInto {
A
aarzilli 已提交
228 229 230
				bp.DeferReturns = deferreturns
			}
		}
231 232 233
	}

	// Add breakpoints on all the lines in the current function
234
	pcs, err := topframe.Current.Fn.cu.lineInfo.AllPCsBetween(topframe.Current.Fn.Entry, topframe.Current.Fn.End-1)
235 236 237
	if err != nil {
		return err
	}
D
Derek Parker 已提交
238

A
aarzilli 已提交
239 240 241 242 243 244 245 246 247 248 249 250
	if !stepInto {
		// Removing any PC range belonging to an inlined call
		frame := topframe
		if inlinedStepOut {
			frame = retframe
		}
		pcs, err = removeInlinedCalls(dbp, pcs, frame)
		if err != nil {
			return err
		}
	}

A
aarzilli 已提交
251 252 253
	if !csource {
		var covered bool
		for i := range pcs {
254
			if topframe.Current.Fn.Entry <= pcs[i] && pcs[i] < topframe.Current.Fn.End {
A
aarzilli 已提交
255 256 257
				covered = true
				break
			}
D
Derek Parker 已提交
258
		}
259

A
aarzilli 已提交
260
		if !covered {
261
			fn := dbp.BinInfo().PCToFunc(topframe.Ret)
262
			if selg != nil && fn != nil && fn.Name == "runtime.goexit" {
A
aarzilli 已提交
263 264
				return nil
			}
D
Derek Parker 已提交
265
		}
266
	}
267

268 269 270 271 272 273 274 275 276
	for _, pc := range pcs {
		if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, sameFrameCond); err != nil {
			if _, ok := err.(BreakpointExistsError); !ok {
				dbp.ClearInternalBreakpoints()
				return err
			}
		}

	}
A
aarzilli 已提交
277 278 279 280 281 282 283 284 285 286 287 288 289 290
	if !topframe.Inlined {
		// Add a breakpoint on the return address for the current frame.
		// For inlined functions there is no need to do this, the set of PCs
		// returned by the AllPCsBetween call above already cover all instructions
		// of the containing function.
		if bp, err := dbp.SetBreakpoint(topframe.Ret, NextBreakpoint, retFrameCond); err != nil {
			if _, isexists := err.(BreakpointExistsError); isexists {
				if bp.Kind == NextBreakpoint {
					// If the return address shares the same address with one of the lines
					// of the function (because we are stepping through a recursive
					// function) then the corresponding breakpoint should be active both on
					// this frame and on the return frame.
					bp.Cond = sameOrRetFrameCond
				}
291
			}
A
aarzilli 已提交
292 293
			// Return address could be wrong, if we are unable to set a breakpoint
			// there it's ok.
294 295
		}
	}
296

297
	if bp := curthread.Breakpoint(); bp.Breakpoint == nil {
298 299
		curthread.SetCurrentBreakpoint()
	}
300
	success = true
301
	return nil
302 303
}

A
aarzilli 已提交
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
// Removes instructions belonging to inlined calls of topframe from pcs.
// If includeCurrentFn is true it will also remove all instructions
// belonging to the current function.
func removeInlinedCalls(dbp Process, pcs []uint64, topframe Stackframe) ([]uint64, error) {
	bi := dbp.BinInfo()
	irdr := reader.InlineStack(bi.dwarf, topframe.Call.Fn.offset, 0)
	for irdr.Next() {
		e := irdr.Entry()
		if e.Offset == topframe.Call.Fn.offset {
			continue
		}
		ranges, err := bi.dwarf.Ranges(e)
		if err != nil {
			return pcs, err
		}
		for _, rng := range ranges {
			pcs = removePCsBetween(pcs, rng[0], rng[1])
		}
		irdr.SkipChildren()
	}
	return pcs, irdr.Err()
}

func removePCsBetween(pcs []uint64, start, end uint64) []uint64 {
	out := pcs[:0]
	for _, pc := range pcs {
		if pc < start || pc >= end {
			out = append(out, pc)
		}
	}
	return out
}

337
func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) error {
A
aarzilli 已提交
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
	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
367
	pc, _ := FirstPCAfterPrologue(dbp, fn, false)
368
	if _, err := dbp.SetBreakpoint(pc, NextBreakpoint, cond); err != nil {
A
aarzilli 已提交
369 370 371 372 373 374
		if _, ok := err.(BreakpointExistsError); !ok {
			return err
		}
	}

	return nil
375
}
376

377
func getGVariable(thread Thread) (*Variable, error) {
A
aarzilli 已提交
378
	regs, err := thread.Registers(false)
379 380 381 382
	if err != nil {
		return nil, err
	}

383 384
	gaddr, hasgaddr := regs.GAddr()
	if !hasgaddr {
385 386
		gaddrbs := make([]byte, thread.Arch().PtrSize())
		_, err := thread.ReadMemory(gaddrbs, uintptr(regs.TLS()+thread.BinInfo().GStructOffset()))
387 388 389 390
		if err != nil {
			return nil, err
		}
		gaddr = binary.LittleEndian.Uint64(gaddrbs)
391
	}
A
go fmt  
aarzilli 已提交
392

393
	return newGVariable(thread, uintptr(gaddr), thread.Arch().DerefTLS())
394 395
}

396
func newGVariable(thread Thread, gaddr uintptr, deref bool) (*Variable, error) {
397
	typ, err := thread.BinInfo().findType("runtime.g")
398 399 400 401 402 403 404
	if err != nil {
		return nil, err
	}

	name := ""

	if deref {
405
		typ = &godwarf.PtrType{godwarf.CommonType{int64(thread.Arch().PtrSize()), "", reflect.Ptr, 0}, typ}
406 407 408 409
	} else {
		name = "runtime.curg"
	}

410
	return newVariableFromThread(thread, name, gaddr, typ), nil
411 412
}

D
Derek Parker 已提交
413
// GetG returns information on the G (goroutine) that is executing on this thread.
414
//
D
Derek Parker 已提交
415 416
// 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.
417 418 419 420 421 422 423 424
//
// 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.
//
425 426
// 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.
427
func GetG(thread Thread) (*G, error) {
428
	gaddr, err := getGVariable(thread)
429 430 431
	if err != nil {
		return nil, err
	}
432

433
	g, err := gaddr.parseG()
A
aarzilli 已提交
434
	if err != nil {
435
		return nil, err
A
aarzilli 已提交
436 437 438 439 440 441 442 443 444
	}
	if g.ID == 0 {
		// The runtime uses a special goroutine with ID == 0 to mark that the
		// current goroutine is executing on the system stack (sometimes also
		// referred to as the g0 stack or scheduler stack, I'm not sure if there's
		// actually any difference between those).
		// For our purposes it's better if we always return the real goroutine
		// since the rest of the code assumes the goroutine ID is univocal.
		// The real 'current goroutine' is stored in g0.m.curg
445 446 447 448
		curgvar, err := g.variable.fieldVariable("m").structMember("curg")
		if err != nil {
			return nil, err
		}
A
aarzilli 已提交
449 450
		g, err = curgvar.parseG()
		if err != nil {
451
			return nil, err
452
		}
A
aarzilli 已提交
453 454 455 456 457
		g.SystemStack = true
	}
	g.Thread = thread
	if loc, err := thread.Location(); err == nil {
		g.CurrentLoc = *loc
458
	}
459
	return g, nil
D
Derek Parker 已提交
460
}
461

462
// ThreadScope returns an EvalScope for this thread.
463
func ThreadScope(thread Thread) (*EvalScope, error) {
464
	locations, err := ThreadStacktrace(thread, 0)
465 466 467
	if err != nil {
		return nil, err
	}
468 469 470
	if len(locations) < 1 {
		return nil, errors.New("could not decode first frame")
	}
471
	return FrameToScope(thread.BinInfo(), thread, nil, locations[0]), nil
472 473 474
}

// GoroutineScope returns an EvalScope for the goroutine running on this thread.
475
func GoroutineScope(thread Thread) (*EvalScope, error) {
476
	locations, err := ThreadStacktrace(thread, 0)
477 478 479 480 481 482
	if err != nil {
		return nil, err
	}
	if len(locations) < 1 {
		return nil, errors.New("could not decode first frame")
	}
483
	g, err := GetG(thread)
484 485 486
	if err != nil {
		return nil, err
	}
487
	return FrameToScope(thread.BinInfo(), thread, g, locations[0]), nil
488
}
489

490
func onRuntimeBreakpoint(thread Thread) bool {
D
Derek Parker 已提交
491
	loc, err := thread.Location()
A
aarzilli 已提交
492 493 494 495 496
	if err != nil {
		return false
	}
	return loc.Fn != nil && loc.Fn.Name == "runtime.breakpoint"
}
497

J
Josh Soref 已提交
498
// onNextGoroutine returns true if this thread is on the goroutine requested by the current 'next' command
A
aarzilli 已提交
499
func onNextGoroutine(thread Thread, breakpoints *BreakpointMap) (bool, error) {
500
	var bp *Breakpoint
A
aarzilli 已提交
501
	for i := range breakpoints.M {
502
		if breakpoints.M[i].Kind != UserBreakpoint && breakpoints.M[i].internalCond != nil {
A
aarzilli 已提交
503
			bp = breakpoints.M[i]
A
aarzilli 已提交
504
			break
505 506 507
		}
	}
	if bp == nil {
508
		return false, nil
509
	}
510 511 512 513 514 515 516 517 518 519
	// Internal breakpoint conditions can take multiple different forms:
	// Step into breakpoints:
	//   runtime.curg.goid == X
	// Next or StepOut breakpoints:
	//   runtime.curg.goid == X && runtime.frameoff == Y
	// Breakpoints that can be hit either by stepping on a line in the same
	// function or by returning from the function:
	//   runtime.curg.goid == X && (runtime.frameoff == Y || runtime.frameoff == Z)
	// Here we are only interested in testing the runtime.curg.goid clause.
	w := onNextGoroutineWalker{thread: thread}
520
	ast.Walk(&w, bp.internalCond)
521 522 523 524 525 526 527 528 529 530 531 532 533
	return w.ret, w.err
}

type onNextGoroutineWalker struct {
	thread Thread
	ret    bool
	err    error
}

func (w *onNextGoroutineWalker) Visit(n ast.Node) ast.Visitor {
	if binx, isbin := n.(*ast.BinaryExpr); isbin && binx.Op == token.EQL && exprToString(binx.X) == "runtime.curg.goid" {
		w.ret, w.err = evalBreakpointCondition(w.thread, n.(ast.Expr))
		return nil
A
aarzilli 已提交
534
	}
535
	return w
536
}