bee/vendor/github.com/go-delve/delve/pkg/proc/core/core.go

499 lines
15 KiB
Go

package core
import (
"errors"
"fmt"
"go/ast"
"io"
"github.com/go-delve/delve/pkg/proc"
)
// A SplicedMemory represents a memory space formed from multiple regions,
// each of which may override previously regions. For example, in the following
// core, the program text was loaded at 0x400000:
// Start End Page Offset
// 0x0000000000400000 0x000000000044f000 0x0000000000000000
// but then it's partially overwritten with an RW mapping whose data is stored
// in the core file:
// Type Offset VirtAddr PhysAddr
// FileSiz MemSiz Flags Align
// LOAD 0x0000000000004000 0x000000000049a000 0x0000000000000000
// 0x0000000000002000 0x0000000000002000 RW 1000
// This can be represented in a SplicedMemory by adding the original region,
// then putting the RW mapping on top of it.
type SplicedMemory struct {
readers []readerEntry
}
type readerEntry struct {
offset uintptr
length uintptr
reader proc.MemoryReader
}
// Add adds a new region to the SplicedMemory, which may override existing regions.
func (r *SplicedMemory) Add(reader proc.MemoryReader, off, length uintptr) {
if length == 0 {
return
}
end := off + length - 1
newReaders := make([]readerEntry, 0, len(r.readers))
add := func(e readerEntry) {
if e.length == 0 {
return
}
newReaders = append(newReaders, e)
}
inserted := false
// Walk through the list of regions, fixing up any that overlap and inserting the new one.
for _, entry := range r.readers {
entryEnd := entry.offset + entry.length - 1
switch {
case entryEnd < off:
// Entry is completely before the new region.
add(entry)
case end < entry.offset:
// Entry is completely after the new region.
if !inserted {
add(readerEntry{off, length, reader})
inserted = true
}
add(entry)
case off <= entry.offset && entryEnd <= end:
// Entry is completely overwritten by the new region. Drop.
case entry.offset < off && entryEnd <= end:
// New region overwrites the end of the entry.
entry.length = off - entry.offset
add(entry)
case off <= entry.offset && end < entryEnd:
// New reader overwrites the beginning of the entry.
if !inserted {
add(readerEntry{off, length, reader})
inserted = true
}
overlap := entry.offset - off
entry.offset += overlap
entry.length -= overlap
add(entry)
case entry.offset < off && end < entryEnd:
// New region punches a hole in the entry. Split it in two and put the new region in the middle.
add(readerEntry{entry.offset, off - entry.offset, entry.reader})
add(readerEntry{off, length, reader})
add(readerEntry{end + 1, entryEnd - end, entry.reader})
inserted = true
default:
panic(fmt.Sprintf("Unhandled case: existing entry is %v len %v, new is %v len %v", entry.offset, entry.length, off, length))
}
}
if !inserted {
newReaders = append(newReaders, readerEntry{off, length, reader})
}
r.readers = newReaders
}
// ReadMemory implements MemoryReader.ReadMemory.
func (r *SplicedMemory) ReadMemory(buf []byte, addr uintptr) (n int, err error) {
started := false
for _, entry := range r.readers {
if entry.offset+entry.length < addr {
if !started {
continue
}
return n, fmt.Errorf("hit unmapped area at %v after %v bytes", addr, n)
}
// Don't go past the region.
pb := buf
if addr+uintptr(len(buf)) > entry.offset+entry.length {
pb = pb[:entry.offset+entry.length-addr]
}
pn, err := entry.reader.ReadMemory(pb, addr)
n += pn
if err != nil || pn != len(pb) {
return n, err
}
buf = buf[pn:]
addr += uintptr(pn)
if len(buf) == 0 {
// Done, don't bother scanning the rest.
return n, nil
}
}
if n == 0 {
return 0, fmt.Errorf("offset %v did not match any regions", addr)
}
return n, nil
}
// OffsetReaderAt wraps a ReaderAt into a MemoryReader, subtracting a fixed
// offset from the address. This is useful to represent a mapping in an address
// space. For example, if program text is mapped in at 0x400000, an
// OffsetReaderAt with offset 0x400000 can be wrapped around file.Open(program)
// to return the results of a read in that part of the address space.
type OffsetReaderAt struct {
reader io.ReaderAt
offset uintptr
}
// ReadMemory will read the memory at addr-offset.
func (r *OffsetReaderAt) ReadMemory(buf []byte, addr uintptr) (n int, err error) {
return r.reader.ReadAt(buf, int64(addr-r.offset))
}
// Process represents a core file.
type Process struct {
mem proc.MemoryReader
Threads map[int]*Thread
pid int
entryPoint uint64
bi *proc.BinaryInfo
breakpoints proc.BreakpointMap
currentThread *Thread
selectedGoroutine *proc.G
common proc.CommonProcess
}
// Thread represents a thread in the core file being debugged.
type Thread struct {
th osThread
p *Process
common proc.CommonThread
}
type osThread interface {
registers(floatingPoint bool) (proc.Registers, error)
pid() int
}
var (
// ErrWriteCore is returned when attempting to write to the core
// process memory.
ErrWriteCore = errors.New("can not write to core process")
// ErrShortRead is returned on a short read.
ErrShortRead = errors.New("short read")
// ErrContinueCore is returned when trying to continue execution of a core process.
ErrContinueCore = errors.New("can not continue execution of core process")
// ErrChangeRegisterCore is returned when trying to change register values for core files.
ErrChangeRegisterCore = errors.New("can not change register values of core process")
)
type openFn func(string, string) (*Process, error)
var openFns = []openFn{readLinuxAMD64Core, readAMD64Minidump}
// ErrUnrecognizedFormat is returned when the core file is not recognized as
// any of the supported formats.
var ErrUnrecognizedFormat = errors.New("unrecognized core format")
// OpenCore will open the core file and return a Process struct.
// If the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in.
func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error) {
var p *Process
var err error
for _, openFn := range openFns {
p, err = openFn(corePath, exePath)
if err != ErrUnrecognizedFormat {
break
}
}
if err != nil {
return nil, err
}
if err := p.initialize(exePath, debugInfoDirs); err != nil {
return nil, err
}
return p, nil
}
// initialize for core files doesn't do much
// aside from call the post initialization setup.
func (p *Process) initialize(path string, debugInfoDirs []string) error {
return proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint)
}
// BinInfo will return the binary info.
func (p *Process) BinInfo() *proc.BinaryInfo {
return p.bi
}
// SetSelectedGoroutine will set internally the goroutine that should be
// the default for any command executed, the goroutine being actively
// followed.
func (p *Process) SetSelectedGoroutine(g *proc.G) {
p.selectedGoroutine = g
}
// EntryPoint will return the entry point address for this core file.
func (p *Process) EntryPoint() (uint64, error) {
return p.entryPoint, nil
}
// writeBreakpoint is a noop function since you
// cannot write breakpoints into core files.
func (p *Process) writeBreakpoint(addr uint64) (file string, line int, fn *proc.Function, originalData []byte, err error) {
return "", 0, nil, nil, errors.New("cannot write a breakpoint to a core file")
}
// Recorded returns whether this is a live or recorded process. Always returns true for core files.
func (p *Process) Recorded() (bool, string) { return true, "" }
// Restart will only return an error for core files, as they are not executing.
func (p *Process) Restart(string) error { return ErrContinueCore }
// Direction will only return an error as you cannot continue a core process.
func (p *Process) Direction(proc.Direction) error { return ErrContinueCore }
// When does not apply to core files, it is to support the Mozilla 'rr' backend.
func (p *Process) When() (string, error) { return "", nil }
// Checkpoint for core files returns an error, there is no execution of a core file.
func (p *Process) Checkpoint(string) (int, error) { return -1, ErrContinueCore }
// Checkpoints returns nil on core files, you cannot set checkpoints when debugging core files.
func (p *Process) Checkpoints() ([]proc.Checkpoint, error) { return nil, nil }
// ClearCheckpoint clears a checkpoint, but will only return an error for core files.
func (p *Process) ClearCheckpoint(int) error { return errors.New("checkpoint not found") }
// ReadMemory will return memory from the core file at the specified location and put the
// read memory into `data`, returning the length read, and returning an error if
// the length read is shorter than the length of the `data` buffer.
func (t *Thread) ReadMemory(data []byte, addr uintptr) (n int, err error) {
n, err = t.p.mem.ReadMemory(data, addr)
if err == nil && n != len(data) {
err = ErrShortRead
}
return n, err
}
// WriteMemory will only return an error for core files, you cannot write
// to the memory of a core process.
func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) {
return 0, ErrWriteCore
}
// Location returns the location of this thread based on
// the value of the instruction pointer register.
func (t *Thread) Location() (*proc.Location, error) {
regs, err := t.th.registers(false)
if err != nil {
return nil, err
}
pc := regs.PC()
f, l, fn := t.p.bi.PCToLine(pc)
return &proc.Location{PC: pc, File: f, Line: l, Fn: fn}, nil
}
// Breakpoint returns the current breakpoint this thread is stopped at.
// For core files this always returns an empty BreakpointState struct, as
// there are no breakpoints when debugging core files.
func (t *Thread) Breakpoint() proc.BreakpointState {
return proc.BreakpointState{}
}
// ThreadID returns the ID for this thread.
func (t *Thread) ThreadID() int {
return int(t.th.pid())
}
// Registers returns the current value of the registers for this thread.
func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) {
return t.th.registers(floatingPoint)
}
// RestoreRegisters will only return an error for core files,
// you cannot change register values for core files.
func (t *Thread) RestoreRegisters(proc.Registers) error {
return ErrChangeRegisterCore
}
// Arch returns the architecture the target is built for and executing on.
func (t *Thread) Arch() proc.Arch {
return t.p.bi.Arch
}
// BinInfo returns information about the binary.
func (t *Thread) BinInfo() *proc.BinaryInfo {
return t.p.bi
}
// StepInstruction will only return an error for core files,
// you cannot execute a core file.
func (t *Thread) StepInstruction() error {
return ErrContinueCore
}
// Blocked will return false always for core files as there is
// no execution.
func (t *Thread) Blocked() bool {
return false
}
// SetCurrentBreakpoint will always just return nil
// for core files, as there are no breakpoints in core files.
func (t *Thread) SetCurrentBreakpoint() error {
return nil
}
// Common returns a struct containing common information
// across thread implementations.
func (t *Thread) Common() *proc.CommonThread {
return &t.common
}
// SetPC will always return an error, you cannot
// change register values when debugging core files.
func (t *Thread) SetPC(uint64) error {
return ErrChangeRegisterCore
}
// SetSP will always return an error, you cannot
// change register values when debugging core files.
func (t *Thread) SetSP(uint64) error {
return ErrChangeRegisterCore
}
// SetDX will always return an error, you cannot
// change register values when debugging core files.
func (t *Thread) SetDX(uint64) error {
return ErrChangeRegisterCore
}
// Breakpoints will return all breakpoints for the process.
func (p *Process) Breakpoints() *proc.BreakpointMap {
return &p.breakpoints
}
// ClearBreakpoint will always return an error as you cannot set or clear
// breakpoints on core files.
func (p *Process) ClearBreakpoint(addr uint64) (*proc.Breakpoint, error) {
return nil, proc.NoBreakpointError{Addr: addr}
}
// ClearInternalBreakpoints will always return nil and have no
// effect since you cannot set breakpoints on core files.
func (p *Process) ClearInternalBreakpoints() error {
return nil
}
// ContinueOnce will always return an error because you
// cannot control execution of a core file.
func (p *Process) ContinueOnce() (proc.Thread, error) {
return nil, ErrContinueCore
}
// StepInstruction will always return an error
// as you cannot control execution of a core file.
func (p *Process) StepInstruction() error {
return ErrContinueCore
}
// RequestManualStop will return nil and have no effect
// as you cannot control execution of a core file.
func (p *Process) RequestManualStop() error {
return nil
}
// CheckAndClearManualStopRequest will always return false and
// have no effect since there are no manual stop requests as
// there is no controlling execution of a core file.
func (p *Process) CheckAndClearManualStopRequest() bool {
return false
}
// CurrentThread returns the current active thread.
func (p *Process) CurrentThread() proc.Thread {
return p.currentThread
}
// Detach will always return nil and have no
// effect as you cannot detach from a core file
// and have it continue execution or exit.
func (p *Process) Detach(bool) error {
return nil
}
// Valid returns whether the process is active. Always returns true
// for core files as it cannot exit or be otherwise detached from.
func (p *Process) Valid() (bool, error) {
return true, nil
}
// Common returns common information across Process
// implementations.
func (p *Process) Common() *proc.CommonProcess {
return &p.common
}
// Pid returns the process ID of this process.
func (p *Process) Pid() int {
return p.pid
}
// ResumeNotify is a no-op on core files as we cannot
// control execution.
func (p *Process) ResumeNotify(chan<- struct{}) {
}
// SelectedGoroutine returns the current active and selected
// goroutine.
func (p *Process) SelectedGoroutine() *proc.G {
return p.selectedGoroutine
}
// SetBreakpoint will always return an error for core files as you cannot write memory or control execution.
func (p *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) {
return nil, ErrWriteCore
}
// SwitchGoroutine will change the selected and active goroutine.
func (p *Process) SwitchGoroutine(gid int) error {
g, err := proc.FindGoroutine(p, gid)
if err != nil {
return err
}
if g == nil {
// user specified -1 and selectedGoroutine is nil
return nil
}
if g.Thread != nil {
return p.SwitchThread(g.Thread.ThreadID())
}
p.selectedGoroutine = g
return nil
}
// SwitchThread will change the selected and active thread.
func (p *Process) SwitchThread(tid int) error {
if th, ok := p.Threads[tid]; ok {
p.currentThread = th
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
// ThreadList will return a list of all threads currently in the process.
func (p *Process) ThreadList() []proc.Thread {
r := make([]proc.Thread, 0, len(p.Threads))
for _, v := range p.Threads {
r = append(r, v)
}
return r
}
// FindThread will return the thread with the corresponding thread ID.
func (p *Process) FindThread(threadID int) (proc.Thread, bool) {
t, ok := p.Threads[threadID]
return t, ok
}