threads.go 11.6 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"
L
Luke Hoban 已提交
11
	"runtime"
D
Derek Parker 已提交
12

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

D
Derek Parker 已提交
16
// Thread represents a single thread in the traced process
D
Derek Parker 已提交
17
// ID represents the thread id or port, Process holds a reference to the
D
Derek Parker 已提交
18
// Process struct that contains info on the process as
D
Derek Parker 已提交
19 20
// a whole, and Status represents the last result of a `wait` call
// on this thread.
D
Derek Parker 已提交
21
type Thread struct {
A
go fmt  
aarzilli 已提交
22
	ID                       int         // Thread ID or mach port
23
	Status                   *WaitStatus // Status returned from last wait call
A
go fmt  
aarzilli 已提交
24 25 26
	CurrentBreakpoint        *Breakpoint // Breakpoint thread is currently stopped at
	BreakpointConditionMet   bool        // Output of evaluating the breakpoint's condition
	BreakpointConditionError error       // Error evaluating the breakpoint's condition
27

D
Derek Parker 已提交
28
	dbp            *Process
29 30 31
	singleStepping bool
	running        bool
	os             *OSSpecificDetails
32 33
}

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

44 45 46 47 48
// Continue the execution of this thread.
//
// If we are currently at a breakpoint, we'll clear it
// first and then resume execution. Thread will continue until
// it hits a breakpoint or is signaled.
D
Derek Parker 已提交
49
func (thread *Thread) Continue() error {
50 51 52 53
	pc, err := thread.PC()
	if err != nil {
		return err
	}
54 55
	// Check whether we are stopped at a breakpoint, and
	// if so, single step over it before continuing.
56
	if _, ok := thread.dbp.FindBreakpoint(pc); ok {
57
		if err := thread.StepInstruction(); err != nil {
58
			return err
D
Derek Parker 已提交
59 60
		}
	}
D
Cleanup  
Derek Parker 已提交
61
	return thread.resume()
D
Derek Parker 已提交
62 63
}

64
// StepInstruction steps a single instruction.
D
Derek Parker 已提交
65 66 67 68 69
//
// Executes exactly one instruction and then returns.
// If the thread is at a breakpoint, we first clear it,
// execute the instruction, and then replace the breakpoint.
// Otherwise we simply execute the next instruction.
70
func (thread *Thread) StepInstruction() (err error) {
71
	thread.running = true
72
	thread.singleStepping = true
73 74 75 76
	defer func() {
		thread.singleStepping = false
		thread.running = false
	}()
D
Derek Parker 已提交
77
	pc, err := thread.PC()
D
Derek Parker 已提交
78 79 80 81
	if err != nil {
		return err
	}

82
	bp, ok := thread.dbp.FindBreakpoint(pc)
D
Derek Parker 已提交
83 84
	if ok {
		// Clear the breakpoint so that we can continue execution.
85
		_, err = bp.Clear(thread)
D
Derek Parker 已提交
86 87 88 89 90 91
		if err != nil {
			return err
		}

		// Restore breakpoint now that we have passed it.
		defer func() {
92
			err = thread.dbp.writeSoftwareBreakpoint(thread, bp.Addr)
D
Derek Parker 已提交
93 94 95
		}()
	}

D
Derek Parker 已提交
96
	err = thread.singleStep()
D
Derek Parker 已提交
97
	if err != nil {
98 99 100
		if _, exited := err.(ProcessExitedError); exited {
			return err
		}
D
Derek Parker 已提交
101 102
		return fmt.Errorf("step failed: %s", err.Error())
	}
D
Derek Parker 已提交
103
	return nil
D
Derek Parker 已提交
104 105
}

D
Derek Parker 已提交
106
// Location returns the threads location, including the file:line
D
Derek Parker 已提交
107 108
// of the corresponding source code, the function we're in
// and the current instruction address.
D
Derek Parker 已提交
109
func (thread *Thread) Location() (*Location, error) {
110 111 112 113 114 115
	pc, err := thread.PC()
	if err != nil {
		return nil, err
	}
	f, l, fn := thread.dbp.PCToLine(pc)
	return &Location{PC: pc, File: f, Line: l, Fn: fn}, nil
D
Derek Parker 已提交
116
}
D
Derek Parker 已提交
117

D
Derek Parker 已提交
118 119
// ThreadBlockedError is returned when the thread
// is blocked in the scheduler.
120 121 122 123 124 125
type ThreadBlockedError struct{}

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

126 127
// returns topmost frame of g or thread if g is nil
func topframe(g *G, thread *Thread) (Stackframe, error) {
128
	var frames []Stackframe
129
	var err error
130

131
	if g == nil {
132 133
		if thread.blocked() {
			return Stackframe{}, ThreadBlockedError{}
134
		}
135
		frames, err = thread.Stacktrace(0)
136
	} else {
137
		frames, err = g.Stacktrace(0)
138
	}
D
Derek Parker 已提交
139
	if err != nil {
140
		return Stackframe{}, err
D
Derek Parker 已提交
141
	}
142
	if len(frames) < 1 {
143 144 145 146 147 148 149 150 151 152
		return Stackframe{}, errors.New("empty stack trace")
	}
	return frames[0], nil
}

// Set breakpoints for potential next lines.
func (dbp *Process) setNextBreakpoints() (err error) {
	topframe, err := topframe(dbp.SelectedGoroutine, dbp.CurrentThread)
	if err != nil {
		return err
153
	}
D
Derek Parker 已提交
154

155
	if filepath.Ext(topframe.Current.File) != ".go" {
156
		return dbp.cnext(topframe)
D
Derek Parker 已提交
157
	}
158

159
	return dbp.next(dbp.SelectedGoroutine, topframe)
160 161
}

D
Derek Parker 已提交
162 163
// Set breakpoints at every line, and the return address. Also look for
// a deferred function and set a breakpoint there too.
164
func (dbp *Process) next(g *G, topframe Stackframe) error {
165 166 167 168 169 170 171 172 173 174 175 176 177
	cond := sameGoroutineCondition(dbp.SelectedGoroutine)

	// Disassembles function to find all runtime.deferreturn locations
	// See documentation of Breakpoint.DeferCond for why this is necessary
	deferreturns := []uint64{}
	text, err := dbp.CurrentThread.Disassemble(topframe.FDE.Begin(), topframe.FDE.End(), false)
	if err == nil {
		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)
			}
		}
	}
178

179
	// Set breakpoint on the most recently deferred function (if any)
180 181 182 183 184 185 186
	var deferpc uint64 = 0
	if g != nil && g.DeferPC != 0 {
		_, _, deferfn := dbp.goSymTable.PCToLine(g.DeferPC)
		var err error
		deferpc, err = dbp.FirstPCAfterPrologue(deferfn, false)
		if err != nil {
			return err
187
		}
D
Derek Parker 已提交
188
	}
189 190 191 192 193 194 195 196 197 198 199 200 201 202
	if deferpc != 0 {
		bp, err := dbp.SetTempBreakpoint(deferpc, cond)
		if err != nil {
			if _, ok := err.(BreakpointExistsError); !ok {
				dbp.ClearTempBreakpoints()
				return err
			}
		}
		bp.DeferCond = true
		bp.DeferReturns = deferreturns
	}

	// Add breakpoints on all the lines in the current function
	pcs := dbp.lineInfo.AllPCsBetween(topframe.FDE.Begin(), topframe.FDE.End()-1, topframe.Current.File)
D
Derek Parker 已提交
203

204 205
	var covered bool
	for i := range pcs {
206
		if topframe.FDE.Cover(pcs[i]) {
207 208
			covered = true
			break
D
Derek Parker 已提交
209
		}
210
	}
211 212

	if !covered {
213 214 215
		fn := dbp.goSymTable.PCToFunc(topframe.Ret)
		if g != nil && fn != nil && fn.Name == "runtime.goexit" {
			return nil
D
Derek Parker 已提交
216
		}
217
	}
218 219

	// Add a breakpoint on the return address for the current frame
220
	pcs = append(pcs, topframe.Ret)
221
	return dbp.setTempBreakpoints(topframe.Current.PC, pcs, cond)
222 223 224 225 226
}

// Set a breakpoint at every reachable location, as well as the return address. Without
// the benefit of an AST we can't be sure we're not at a branching statement and thus
// cannot accurately predict where we may end up.
227 228
func (dbp *Process) cnext(topframe Stackframe) error {
	pcs := dbp.lineInfo.AllPCsBetween(topframe.FDE.Begin(), topframe.FDE.End(), topframe.Current.File)
229 230
	pcs = append(pcs, topframe.Ret)
	return dbp.setTempBreakpoints(topframe.Current.PC, pcs, sameGoroutineCondition(dbp.SelectedGoroutine))
231
}
232

233 234 235
// setTempBreakpoints sets a breakpoint to all addresses specified in pcs
// skipping over curpc and curpc-1
func (dbp *Process) setTempBreakpoints(curpc uint64, pcs []uint64, cond ast.Expr) error {
236 237
	for i := range pcs {
		if pcs[i] == curpc || pcs[i] == curpc-1 {
238 239
			continue
		}
240
		if _, err := dbp.SetTempBreakpoint(pcs[i], cond); err != nil {
D
Derek Parker 已提交
241
			if _, ok := err.(BreakpointExistsError); !ok {
242
				dbp.ClearTempBreakpoints()
243 244 245
				return err
			}
		}
D
Derek Parker 已提交
246 247 248 249
	}
	return nil
}

D
Derek Parker 已提交
250
// SetPC sets the PC for this thread.
D
Derek Parker 已提交
251
func (thread *Thread) SetPC(pc uint64) error {
252 253 254 255 256 257 258
	regs, err := thread.Registers()
	if err != nil {
		return err
	}
	return regs.SetPC(thread, pc)
}

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
func (thread *Thread) getGVariable() (*Variable, error) {
	regs, err := thread.Registers()
	if err != nil {
		return nil, err
	}

	if thread.dbp.arch.GStructOffset() == 0 {
		// GetG was called through SwitchThread / updateThreadList during initialization
		// thread.dbp.arch isn't setup yet (it needs a CurrentThread to read global variables from)
		return nil, fmt.Errorf("g struct offset not initialized")
	}

	gaddrbs, err := thread.readMemory(uintptr(regs.TLS()+thread.dbp.arch.GStructOffset()), thread.dbp.arch.PtrSize())
	if err != nil {
		return nil, err
	}
	gaddr := uintptr(binary.LittleEndian.Uint64(gaddrbs))
A
go fmt  
aarzilli 已提交
276 277

	// On Windows, the value at TLS()+GStructOffset() is a
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
	// pointer to the G struct.
	needsDeref := runtime.GOOS == "windows"

	return thread.newGVariable(gaddr, needsDeref)
}

func (thread *Thread) newGVariable(gaddr uintptr, deref bool) (*Variable, error) {
	typ, err := thread.dbp.findType("runtime.g")
	if err != nil {
		return nil, err
	}

	name := ""

	if deref {
293
		typ = &dwarf.PtrType{dwarf.CommonType{int64(thread.dbp.arch.PtrSize()), "", reflect.Ptr, 0}, typ}
294 295 296 297 298 299 300
	} else {
		name = "runtime.curg"
	}

	return thread.newVariable(name, gaddr, typ), nil
}

D
Derek Parker 已提交
301
// GetG returns information on the G (goroutine) that is executing on this thread.
302
//
D
Derek Parker 已提交
303 304
// 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.
305 306 307 308 309 310 311 312
//
// 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.
//
313 314
// 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.
A
aarzilli 已提交
315
func (thread *Thread) GetG() (g *G, err error) {
316
	gaddr, err := thread.getGVariable()
317 318 319
	if err != nil {
		return nil, err
	}
320 321

	g, err = gaddr.parseG()
322 323 324
	if err == nil {
		g.thread = thread
	}
325
	return
D
Derek Parker 已提交
326
}
327

D
Derek Parker 已提交
328
// Stopped returns whether the thread is stopped at
329 330 331 332 333
// the operating system level. Actual implementation
// is OS dependant, look in OS thread file.
func (thread *Thread) Stopped() bool {
	return thread.stopped()
}
334

D
Derek Parker 已提交
335
// Halt stops this thread from executing. Actual
336 337 338 339 340 341 342 343 344 345 346 347 348 349
// implementation is OS dependant. Look in OS
// thread file.
func (thread *Thread) Halt() (err error) {
	defer func() {
		if err == nil {
			thread.running = false
		}
	}()
	if thread.Stopped() {
		return
	}
	err = thread.halt()
	return
}
350

D
Derek Parker 已提交
351
// Scope returns the current EvalScope for this thread.
352 353 354 355 356
func (thread *Thread) Scope() (*EvalScope, error) {
	locations, err := thread.Stacktrace(0)
	if err != nil {
		return nil, err
	}
357 358 359
	if len(locations) < 1 {
		return nil, errors.New("could not decode first frame")
	}
360
	return locations[0].Scope(thread), nil
361
}
362

D
Derek Parker 已提交
363 364
// SetCurrentBreakpoint sets the current breakpoint that this
// thread is stopped at as CurrentBreakpoint on the thread struct.
365 366 367 368 369 370 371 372 373 374 375
func (thread *Thread) SetCurrentBreakpoint() error {
	thread.CurrentBreakpoint = nil
	pc, err := thread.PC()
	if err != nil {
		return err
	}
	if bp, ok := thread.dbp.FindBreakpoint(pc); ok {
		thread.CurrentBreakpoint = bp
		if err = thread.SetPC(bp.Addr); err != nil {
			return err
		}
376
		thread.BreakpointConditionMet, thread.BreakpointConditionError = bp.checkCondition(thread)
A
aarzilli 已提交
377 378
		if thread.onTriggeredBreakpoint() {
			if g, err := thread.GetG(); err == nil {
D
Derek Parker 已提交
379
				thread.CurrentBreakpoint.HitCount[g.ID]++
A
aarzilli 已提交
380 381
			}
			thread.CurrentBreakpoint.TotalHitCount++
382 383 384 385
		}
	}
	return nil
}
386

D
Derek Parker 已提交
387 388
func (thread *Thread) onTriggeredBreakpoint() bool {
	return (thread.CurrentBreakpoint != nil) && thread.BreakpointConditionMet
389 390
}

D
Derek Parker 已提交
391 392
func (thread *Thread) onTriggeredTempBreakpoint() bool {
	return thread.onTriggeredBreakpoint() && thread.CurrentBreakpoint.Temp
393
}
A
aarzilli 已提交
394

D
Derek Parker 已提交
395 396
func (thread *Thread) onRuntimeBreakpoint() bool {
	loc, err := thread.Location()
A
aarzilli 已提交
397 398 399 400 401
	if err != nil {
		return false
	}
	return loc.Fn != nil && loc.Fn.Name == "runtime.breakpoint"
}
402

H
Hubert Krauze 已提交
403 404
// onNextGorutine returns true if this thread is on the goroutine requested by the current 'next' command
func (thread *Thread) onNextGoroutine() (bool, error) {
405
	var bp *Breakpoint
H
Hubert Krauze 已提交
406 407 408
	for i := range thread.dbp.Breakpoints {
		if thread.dbp.Breakpoints[i].Temp {
			bp = thread.dbp.Breakpoints[i]
409 410 411
		}
	}
	if bp == nil {
412
		return false, nil
413
	}
414 415 416 417 418 419
	// we just want to check the condition on the goroutine id here
	dc := bp.DeferCond
	bp.DeferCond = false
	defer func() {
		bp.DeferCond = dc
	}()
H
Hubert Krauze 已提交
420
	return bp.checkCondition(thread)
421
}