mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	Begin thread code isolation
This commit is contained in:
		| @ -3,7 +3,6 @@ package proctl_test | |||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"syscall" |  | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/derekparker/delve/helper" | 	"github.com/derekparker/delve/helper" | ||||||
| @ -12,7 +11,7 @@ import ( | |||||||
|  |  | ||||||
| func dataAtAddr(pid int, addr uint64) ([]byte, error) { | func dataAtAddr(pid int, addr uint64) ([]byte, error) { | ||||||
| 	data := make([]byte, 1) | 	data := make([]byte, 1) | ||||||
| 	_, err := syscall.PtracePeekData(pid, uintptr(addr), data) | 	_, err := proctl.ReadMemory(pid, uintptr(addr), data) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | |||||||
							
								
								
									
										315
									
								
								proctl/threads.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								proctl/threads.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,315 @@ | |||||||
|  | package proctl | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"syscall" | ||||||
|  |  | ||||||
|  | 	"github.com/derekparker/delve/dwarf/frame" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ThreadContext represents a single thread of execution in the | ||||||
|  | // traced program. | ||||||
|  | type ThreadContext struct { | ||||||
|  | 	Id      int | ||||||
|  | 	Process *DebuggedProcess | ||||||
|  | 	Status  *syscall.WaitStatus | ||||||
|  | 	Regs    *syscall.PtraceRegs | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Obtains register values from the debugged process. | ||||||
|  | func (thread *ThreadContext) Registers() (*syscall.PtraceRegs, error) { | ||||||
|  | 	err := syscall.PtraceGetRegs(thread.Id, thread.Regs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("could not get registers %s", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return thread.Regs, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Returns the current PC for this thread id. | ||||||
|  | func (thread *ThreadContext) CurrentPC() (uint64, error) { | ||||||
|  | 	regs, err := thread.Registers() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return regs.PC(), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // PrintInfo prints out the thread status | ||||||
|  | // including: PC, tid, file, line, and function. | ||||||
|  | func (thread *ThreadContext) PrintInfo() error { | ||||||
|  | 	pc, err := thread.CurrentPC() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	f, l, fn := thread.Process.GoSymTable.PCToLine(pc) | ||||||
|  | 	if fn != nil { | ||||||
|  | 		fmt.Printf("Thread %d at %#v %s:%d %s\n", thread.Id, pc, f, l, fn.Name) | ||||||
|  | 	} else { | ||||||
|  | 		fmt.Printf("Thread %d at %#v\n", thread.Id, pc) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Sets a software breakpoint at addr, and stores it in the process wide | ||||||
|  | // break point table. Setting a break point must be thread specific due to | ||||||
|  | // ptrace actions needing the thread to be in a signal-delivery-stop in order | ||||||
|  | // to initiate any ptrace command. Otherwise, it really doesn't matter | ||||||
|  | // as we're only dealing with threads. | ||||||
|  | func (thread *ThreadContext) Break(addr uintptr) (*BreakPoint, error) { | ||||||
|  | 	var ( | ||||||
|  | 		int3         = []byte{0xCC} | ||||||
|  | 		f, l, fn     = thread.Process.GoSymTable.PCToLine(uint64(addr)) | ||||||
|  | 		originalData = make([]byte, 1) | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	if fn == nil { | ||||||
|  | 		return nil, InvalidAddressError{address: addr} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := ReadMemory(thread.Id, addr, originalData) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("PEEK ERR") | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if bytes.Equal(originalData, int3) { | ||||||
|  | 		return nil, BreakPointExistsError{f, l, addr} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err = WriteMemory(thread.Id, addr, int3) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("POKE ERR") | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	breakpoint := &BreakPoint{ | ||||||
|  | 		FunctionName: fn.Name, | ||||||
|  | 		File:         f, | ||||||
|  | 		Line:         l, | ||||||
|  | 		Addr:         uint64(addr), | ||||||
|  | 		OriginalData: originalData, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	thread.Process.BreakPoints[uint64(addr)] = breakpoint | ||||||
|  |  | ||||||
|  | 	return breakpoint, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Clears a software breakpoint, and removes it from the process level | ||||||
|  | // break point table. | ||||||
|  | func (thread *ThreadContext) Clear(pc uint64) (*BreakPoint, error) { | ||||||
|  | 	bp, ok := thread.Process.BreakPoints[pc] | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, fmt.Errorf("No breakpoint currently set for %#v", pc) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if _, err := WriteMemory(thread.Id, uintptr(bp.Addr), bp.OriginalData); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("could not clear breakpoint %s", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	delete(thread.Process.BreakPoints, pc) | ||||||
|  |  | ||||||
|  | 	return bp, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (thread *ThreadContext) Continue() error { | ||||||
|  | 	// Check whether we are stopped at a breakpoint, and | ||||||
|  | 	// if so, single step over it before continuing. | ||||||
|  | 	regs, err := thread.Registers() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("could not get registers %s", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if _, ok := thread.Process.BreakPoints[regs.PC()-1]; ok { | ||||||
|  | 		err := thread.Step() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("could not step %s", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return syscall.PtraceCont(thread.Id, 0) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Single steps this thread a single instruction, ensuring that | ||||||
|  | // we correctly handle the likely case that we are at a breakpoint. | ||||||
|  | func (thread *ThreadContext) Step() (err error) { | ||||||
|  | 	regs, err := thread.Registers() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bp, ok := thread.Process.BreakPoints[regs.PC()-1] | ||||||
|  | 	if ok { | ||||||
|  | 		// Clear the breakpoint so that we can continue execution. | ||||||
|  | 		_, err = thread.Clear(bp.Addr) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Reset program counter to our restored instruction. | ||||||
|  | 		regs.SetPC(bp.Addr) | ||||||
|  | 		err = syscall.PtraceSetRegs(thread.Id, regs) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("could not set registers %s", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Restore breakpoint now that we have passed it. | ||||||
|  | 		defer func() { | ||||||
|  | 			_, err = thread.Break(uintptr(bp.Addr)) | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = syscall.PtraceSingleStep(thread.Id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("step failed: %s", err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, _, err = wait(thread.Id, 0) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Step to next source line. Next will step over functions, | ||||||
|  | // and will follow through to the return address of a function. | ||||||
|  | // Next is implemented on the thread context, however during the | ||||||
|  | // course of this function running, it's very likely that the | ||||||
|  | // goroutine our M is executing will switch to another M, therefore | ||||||
|  | // this function cannot assume all execution will happen on this thread | ||||||
|  | // in the traced process. | ||||||
|  | func (thread *ThreadContext) Next() (err error) { | ||||||
|  | 	pc, err := thread.CurrentPC() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if _, ok := thread.Process.BreakPoints[pc-1]; ok { | ||||||
|  | 		pc-- // Decrement PC to account for BreakPoint | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, l, _ := thread.Process.GoSymTable.PCToLine(pc) | ||||||
|  | 	fde, err := thread.Process.FrameEntries.FDEForPC(pc) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	step := func() (uint64, error) { | ||||||
|  | 		err = thread.Step() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return thread.CurrentPC() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret := thread.ReturnAddressFromOffset(fde.ReturnAddressOffset(pc)) | ||||||
|  | 	for { | ||||||
|  | 		pc, err = step() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !fde.Cover(pc) && pc != ret { | ||||||
|  | 			err := thread.continueToReturnAddress(pc, fde) | ||||||
|  | 			if err != nil { | ||||||
|  | 				if _, ok := err.(InvalidAddressError); !ok { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			pc, _ = thread.CurrentPC() | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		_, nl, _ := thread.Process.GoSymTable.PCToLine(pc) | ||||||
|  | 		if nl != l { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (thread *ThreadContext) continueToReturnAddress(pc uint64, fde *frame.FrameDescriptionEntry) error { | ||||||
|  | 	for !fde.Cover(pc) { | ||||||
|  | 		// Our offset here is be 0 because we | ||||||
|  | 		// have stepped into the first instruction | ||||||
|  | 		// of this function. Therefore the function | ||||||
|  | 		// has not had a chance to modify its' stack | ||||||
|  | 		// and change our offset. | ||||||
|  | 		addr := thread.ReturnAddressFromOffset(0) | ||||||
|  | 		bp, err := thread.Break(uintptr(addr)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if _, ok := err.(BreakPointExistsError); !ok { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		bp.temp = true | ||||||
|  | 		// Ensure we cleanup after ourselves no matter what. | ||||||
|  | 		defer thread.clearTempBreakpoint(bp.Addr) | ||||||
|  |  | ||||||
|  | 		for { | ||||||
|  | 			err = thread.Continue() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			// We wait on -1 here because once we continue this | ||||||
|  | 			// thread, it's very possible the scheduler could of | ||||||
|  | 			// change the goroutine context on us, we there is | ||||||
|  | 			// no guarantee that waiting on this tid will ever | ||||||
|  | 			// return. | ||||||
|  | 			wpid, _, err := trapWait(thread.Process, -1, 0) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			if wpid != thread.Id { | ||||||
|  | 				thread = thread.Process.Threads[wpid] | ||||||
|  | 			} | ||||||
|  | 			pc, _ = thread.CurrentPC() | ||||||
|  | 			if (pc - 1) == bp.Addr { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Takes an offset from RSP and returns the address of the | ||||||
|  | // instruction the currect function is going to return to. | ||||||
|  | func (thread *ThreadContext) ReturnAddressFromOffset(offset int64) uint64 { | ||||||
|  | 	regs, err := thread.Registers() | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("Could not obtain register values") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	retaddr := int64(regs.Rsp) + offset | ||||||
|  | 	data := make([]byte, 8) | ||||||
|  | 	syscall.PtracePeekText(thread.Id, uintptr(retaddr), data) | ||||||
|  | 	return binary.LittleEndian.Uint64(data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (thread *ThreadContext) clearTempBreakpoint(pc uint64) error { | ||||||
|  | 	if bp, ok := thread.Process.BreakPoints[pc]; ok { | ||||||
|  | 		_, err := thread.Clear(bp.Addr) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Reset program counter to our restored instruction. | ||||||
|  | 		regs, err := thread.Registers() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		regs.SetPC(bp.Addr) | ||||||
|  | 		return syscall.PtraceSetRegs(thread.Id, regs) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @ -1,315 +1,11 @@ | |||||||
| package proctl | package proctl | ||||||
|  |  | ||||||
| import ( | import "syscall" | ||||||
| 	"bytes" |  | ||||||
| 	"encoding/binary" |  | ||||||
| 	"fmt" |  | ||||||
|  |  | ||||||
| 	"syscall" | func WriteMemory(tid int, addr uintptr, data []byte) (int, error) { | ||||||
|  | 	return syscall.PtracePokeData(tid, addr, data) | ||||||
| 	"github.com/derekparker/delve/dwarf/frame" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // ThreadContext represents a single thread of execution in the |  | ||||||
| // traced program. |  | ||||||
| type ThreadContext struct { |  | ||||||
| 	Id      int |  | ||||||
| 	Process *DebuggedProcess |  | ||||||
| 	Status  *syscall.WaitStatus |  | ||||||
| 	Regs    *syscall.PtraceRegs |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Obtains register values from the debugged process. | func ReadMemory(tid int, addr uintptr, data []byte) (int, error) { | ||||||
| func (thread *ThreadContext) Registers() (*syscall.PtraceRegs, error) { | 	return syscall.PtracePeekData(tid, addr, data) | ||||||
| 	err := syscall.PtraceGetRegs(thread.Id, thread.Regs) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, fmt.Errorf("could not get registers %s", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return thread.Regs, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Returns the current PC for this thread id. |  | ||||||
| func (thread *ThreadContext) CurrentPC() (uint64, error) { |  | ||||||
| 	regs, err := thread.Registers() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return 0, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return regs.PC(), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // PrintInfo prints out the thread status |  | ||||||
| // including: PC, tid, file, line, and function. |  | ||||||
| func (thread *ThreadContext) PrintInfo() error { |  | ||||||
| 	pc, err := thread.CurrentPC() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	f, l, fn := thread.Process.GoSymTable.PCToLine(pc) |  | ||||||
| 	if fn != nil { |  | ||||||
| 		fmt.Printf("Thread %d at %#v %s:%d %s\n", thread.Id, pc, f, l, fn.Name) |  | ||||||
| 	} else { |  | ||||||
| 		fmt.Printf("Thread %d at %#v\n", thread.Id, pc) |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Sets a software breakpoint at addr, and stores it in the process wide |  | ||||||
| // break point table. Setting a break point must be thread specific due to |  | ||||||
| // ptrace actions needing the thread to be in a signal-delivery-stop in order |  | ||||||
| // to initiate any ptrace command. Otherwise, it really doesn't matter |  | ||||||
| // as we're only dealing with threads. |  | ||||||
| func (thread *ThreadContext) Break(addr uintptr) (*BreakPoint, error) { |  | ||||||
| 	var ( |  | ||||||
| 		int3         = []byte{0xCC} |  | ||||||
| 		f, l, fn     = thread.Process.GoSymTable.PCToLine(uint64(addr)) |  | ||||||
| 		originalData = make([]byte, 1) |  | ||||||
| 	) |  | ||||||
|  |  | ||||||
| 	if fn == nil { |  | ||||||
| 		return nil, InvalidAddressError{address: addr} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	_, err := syscall.PtracePeekData(thread.Id, addr, originalData) |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Println("PEEK ERR") |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if bytes.Equal(originalData, int3) { |  | ||||||
| 		return nil, BreakPointExistsError{f, l, addr} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	_, err = syscall.PtracePokeData(thread.Id, addr, int3) |  | ||||||
| 	if err != nil { |  | ||||||
| 		fmt.Println("POKE ERR") |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	breakpoint := &BreakPoint{ |  | ||||||
| 		FunctionName: fn.Name, |  | ||||||
| 		File:         f, |  | ||||||
| 		Line:         l, |  | ||||||
| 		Addr:         uint64(addr), |  | ||||||
| 		OriginalData: originalData, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	thread.Process.BreakPoints[uint64(addr)] = breakpoint |  | ||||||
|  |  | ||||||
| 	return breakpoint, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Clears a software breakpoint, and removes it from the process level |  | ||||||
| // break point table. |  | ||||||
| func (thread *ThreadContext) Clear(pc uint64) (*BreakPoint, error) { |  | ||||||
| 	bp, ok := thread.Process.BreakPoints[pc] |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil, fmt.Errorf("No breakpoint currently set for %#v", pc) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if _, err := syscall.PtracePokeData(thread.Id, uintptr(bp.Addr), bp.OriginalData); err != nil { |  | ||||||
| 		return nil, fmt.Errorf("could not clear breakpoint %s", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	delete(thread.Process.BreakPoints, pc) |  | ||||||
|  |  | ||||||
| 	return bp, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (thread *ThreadContext) Continue() error { |  | ||||||
| 	// Check whether we are stopped at a breakpoint, and |  | ||||||
| 	// if so, single step over it before continuing. |  | ||||||
| 	regs, err := thread.Registers() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("could not get registers %s", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if _, ok := thread.Process.BreakPoints[regs.PC()-1]; ok { |  | ||||||
| 		err := thread.Step() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("could not step %s", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return syscall.PtraceCont(thread.Id, 0) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Single steps this thread a single instruction, ensuring that |  | ||||||
| // we correctly handle the likely case that we are at a breakpoint. |  | ||||||
| func (thread *ThreadContext) Step() (err error) { |  | ||||||
| 	regs, err := thread.Registers() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	bp, ok := thread.Process.BreakPoints[regs.PC()-1] |  | ||||||
| 	if ok { |  | ||||||
| 		// Clear the breakpoint so that we can continue execution. |  | ||||||
| 		_, err = thread.Clear(bp.Addr) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Reset program counter to our restored instruction. |  | ||||||
| 		regs.SetPC(bp.Addr) |  | ||||||
| 		err = syscall.PtraceSetRegs(thread.Id, regs) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return fmt.Errorf("could not set registers %s", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Restore breakpoint now that we have passed it. |  | ||||||
| 		defer func() { |  | ||||||
| 			_, err = thread.Break(uintptr(bp.Addr)) |  | ||||||
| 		}() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	err = syscall.PtraceSingleStep(thread.Id) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("step failed: %s", err.Error()) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	_, _, err = wait(thread.Id, 0) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Step to next source line. Next will step over functions, |  | ||||||
| // and will follow through to the return address of a function. |  | ||||||
| // Next is implemented on the thread context, however during the |  | ||||||
| // course of this function running, it's very likely that the |  | ||||||
| // goroutine our M is executing will switch to another M, therefore |  | ||||||
| // this function cannot assume all execution will happen on this thread |  | ||||||
| // in the traced process. |  | ||||||
| func (thread *ThreadContext) Next() (err error) { |  | ||||||
| 	pc, err := thread.CurrentPC() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if _, ok := thread.Process.BreakPoints[pc-1]; ok { |  | ||||||
| 		pc-- // Decrement PC to account for BreakPoint |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	_, l, _ := thread.Process.GoSymTable.PCToLine(pc) |  | ||||||
| 	fde, err := thread.Process.FrameEntries.FDEForPC(pc) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	step := func() (uint64, error) { |  | ||||||
| 		err = thread.Step() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return 0, err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return thread.CurrentPC() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ret := thread.ReturnAddressFromOffset(fde.ReturnAddressOffset(pc)) |  | ||||||
| 	for { |  | ||||||
| 		pc, err = step() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !fde.Cover(pc) && pc != ret { |  | ||||||
| 			err := thread.continueToReturnAddress(pc, fde) |  | ||||||
| 			if err != nil { |  | ||||||
| 				if _, ok := err.(InvalidAddressError); !ok { |  | ||||||
| 					return err |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			pc, _ = thread.CurrentPC() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		_, nl, _ := thread.Process.GoSymTable.PCToLine(pc) |  | ||||||
| 		if nl != l { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (thread *ThreadContext) continueToReturnAddress(pc uint64, fde *frame.FrameDescriptionEntry) error { |  | ||||||
| 	for !fde.Cover(pc) { |  | ||||||
| 		// Our offset here is be 0 because we |  | ||||||
| 		// have stepped into the first instruction |  | ||||||
| 		// of this function. Therefore the function |  | ||||||
| 		// has not had a chance to modify its' stack |  | ||||||
| 		// and change our offset. |  | ||||||
| 		addr := thread.ReturnAddressFromOffset(0) |  | ||||||
| 		bp, err := thread.Break(uintptr(addr)) |  | ||||||
| 		if err != nil { |  | ||||||
| 			if _, ok := err.(BreakPointExistsError); !ok { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		bp.temp = true |  | ||||||
| 		// Ensure we cleanup after ourselves no matter what. |  | ||||||
| 		defer thread.clearTempBreakpoint(bp.Addr) |  | ||||||
|  |  | ||||||
| 		for { |  | ||||||
| 			err = thread.Continue() |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			// We wait on -1 here because once we continue this |  | ||||||
| 			// thread, it's very possible the scheduler could of |  | ||||||
| 			// change the goroutine context on us, we there is |  | ||||||
| 			// no guarantee that waiting on this tid will ever |  | ||||||
| 			// return. |  | ||||||
| 			wpid, _, err := trapWait(thread.Process, -1, 0) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			if wpid != thread.Id { |  | ||||||
| 				thread = thread.Process.Threads[wpid] |  | ||||||
| 			} |  | ||||||
| 			pc, _ = thread.CurrentPC() |  | ||||||
| 			if (pc - 1) == bp.Addr { |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Takes an offset from RSP and returns the address of the |  | ||||||
| // instruction the currect function is going to return to. |  | ||||||
| func (thread *ThreadContext) ReturnAddressFromOffset(offset int64) uint64 { |  | ||||||
| 	regs, err := thread.Registers() |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic("Could not obtain register values") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	retaddr := int64(regs.Rsp) + offset |  | ||||||
| 	data := make([]byte, 8) |  | ||||||
| 	syscall.PtracePeekText(thread.Id, uintptr(retaddr), data) |  | ||||||
| 	return binary.LittleEndian.Uint64(data) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (thread *ThreadContext) clearTempBreakpoint(pc uint64) error { |  | ||||||
| 	if bp, ok := thread.Process.BreakPoints[pc]; ok { |  | ||||||
| 		_, err := thread.Clear(bp.Addr) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Reset program counter to our restored instruction. |  | ||||||
| 		regs, err := thread.Registers() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		regs.SetPC(bp.Addr) |  | ||||||
| 		return syscall.PtraceSetRegs(thread.Id, regs) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Derek Parker
					Derek Parker