// Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package debug provides the portable interface to a program being debugged. package debug import ( "fmt" "io" "strings" ) // Program is the interface to a (possibly remote) program being debugged. // The process (if any) and text file associated with it may change during // the session, but many resources are associated with the Program rather // than process or text file so they persist across debuggging runs. type Program interface { // Open opens a virtual file associated with the process. // Names are things like "text", "mem", "fd/2". // Mode is one of "r", "w", "rw". // Return values are open File and error. // When the target binary is re-run, open files are // automatically updated to refer to the corresponding // file in the new process. Open(name string, mode string) (File, error) // Run abandons the current running process, if any, // and execs a new instance of the target binary file // (which may have changed underfoot). // Breakpoints and open files are re-established. // The call hangs until the program stops executing, // at which point it returns the program status. // args contains the command-line arguments for the process. Run(args ...string) (Status, error) // Stop stops execution of the current process but // does not kill it. Stop() (Status, error) // Resume resumes execution of a stopped process. // The call hangs until the program stops executing, // at which point it returns the program status. Resume() (Status, error) // TODO: Step(). Where does the granularity happen, // on the proxy end or the debugging control end? // Kill kills the current process. Kill() (Status, error) // Breakpoint sets a breakpoint at the specified address. Breakpoint(address uint64) (PCs []uint64, err error) // BreakpointAtFunction sets a breakpoint at the start of the specified function. BreakpointAtFunction(name string) (PCs []uint64, err error) // BreakpointAtLine sets a breakpoint at the specified source line. BreakpointAtLine(file string, line uint64) (PCs []uint64, err error) // DeleteBreakpoints removes the breakpoints at the specified addresses. // Addresses where no breakpoint is set are ignored. DeleteBreakpoints(pcs []uint64) error // Eval evaluates the expression (typically an address) and returns // its string representation(s). Multivalued expressions such as // matches for regular expressions return multiple values. // TODO: change this to multiple functions with more specific names. // Syntax: // re:regexp // Returns a list of symbol names that match the expression // addr:symbol // Returns a one-element list holding the hexadecimal // ("0x1234") value of the address of the symbol // val:symbol // Returns a one-element list holding the formatted // value of the symbol // 0x1234, 01234, 467 // Returns a one-element list holding the name of the // symbol ("main.foo") at that address (hex, octal, decimal). Eval(expr string) ([]string, error) // Evaluate evaluates an expression. Accepts a subset of Go expression syntax: // basic literals, identifiers, parenthesized expressions, and most operators. // Only the len function call is available. // // The expression can refer to local variables and function parameters of the // function where the program is stopped. // // On success, the type of the value returned will be one of: // int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, // complex64, complex128, bool, Pointer, Array, Slice, String, Map, Struct, // Channel, Func, or Interface. Evaluate(e string) (Value, error) // Frames returns up to count stack frames from where the program // is currently stopped. Frames(count int) ([]Frame, error) // VarByName returns a Var referring to a global variable with the given name. // TODO: local variables VarByName(name string) (Var, error) // Value gets the value of a variable by reading the program's memory. Value(v Var) (Value, error) // MapElement returns Vars for the key and value of a map element specified by // a 0-based index. MapElement(m Map, index uint64) (Var, Var, error) // Goroutines gets the current goroutines. Goroutines() ([]*Goroutine, error) } type Goroutine struct { ID int64 Status GoroutineStatus StatusString string // A human-readable string explaining the status in more detail. Function string // Name of the goroutine function. Caller string // Name of the function that created this goroutine. StackFrames []Frame } type GoroutineStatus byte const ( Running GoroutineStatus = iota Queued Blocked ) func (g GoroutineStatus) String() string { switch g { case Running: return "running" case Queued: return "queued" case Blocked: return "blocked" } return "invalid status" } func (g *Goroutine) String() string { return fmt.Sprintf("goroutine %d [%s] %s -> %s", g.ID, g.StatusString, g.Caller, g.Function) } // A reference to a variable in a program. // TODO: handle variables stored in registers type Var struct { TypeID uint64 // A type identifier, opaque to the user. Address uint64 // The address of the variable. } // A value read from a remote program. type Value interface{} // Pointer is a Value representing a pointer. // Note that the TypeID field will be the type of the variable being pointed to, // not the type of this pointer. type Pointer struct { TypeID uint64 // A type identifier, opaque to the user. Address uint64 // The address of the variable. } // Array is a Value representing an array. type Array struct { ElementTypeID uint64 Address uint64 Length uint64 // Number of elements in the array StrideBits uint64 // Number of bits between array entries } // Len returns the number of elements in the array. func (a Array) Len() uint64 { return a.Length } // Element returns a Var referring to the given element of the array. func (a Array) Element(index uint64) Var { return Var{ TypeID: a.ElementTypeID, Address: a.Address + index*(a.StrideBits/8), } } // Slice is a Value representing a slice. type Slice struct { Array Capacity uint64 } // String is a Value representing a string. // TODO: a method to access more of a truncated string. type String struct { // Length contains the length of the remote string, in bytes. Length uint64 // String contains the string itself; it may be truncated to fewer bytes than the value of the Length field. String string } // Map is a Value representing a map. type Map struct { TypeID uint64 Address uint64 Length uint64 // Number of elements in the map. } // Struct is a Value representing a struct. type Struct struct { Fields []StructField } // StructField represents a field in a struct object. type StructField struct { Name string Var Var } // Channel is a Value representing a channel. type Channel struct { ElementTypeID uint64 Address uint64 // Location of the channel struct in memory. Buffer uint64 // Location of the buffer; zero for nil channels. Length uint64 // Number of elements stored in the channel buffer. Capacity uint64 // Capacity of the buffer; zero for unbuffered channels. Stride uint64 // Number of bytes between buffer entries. BufferStart uint64 // Index in the buffer of the element at the head of the queue. } // Element returns a Var referring to the given element of the channel's queue. // If the channel is unbuffered, nil, or if the index is too large, returns a Var with Address == 0. func (m Channel) Element(index uint64) Var { if index >= m.Length { return Var{ TypeID: m.ElementTypeID, Address: 0, } } if index < m.Capacity-m.BufferStart { // The element is in the part of the queue that occurs later in the buffer // than the head of the queue. return Var{ TypeID: m.ElementTypeID, Address: m.Buffer + (m.BufferStart+index)*m.Stride, } } // The element is in the part of the queue that has wrapped around to the // start of the buffer. return Var{ TypeID: m.ElementTypeID, Address: m.Buffer + (m.BufferStart+index-m.Capacity)*m.Stride, } } // Func is a Value representing a func. type Func struct { Address uint64 } // Interface is a Value representing an interface. type Interface struct{} // The File interface provides access to file-like resources in the program. // It implements only ReaderAt and WriterAt, not Reader and Writer, because // random access is a far more common pattern for things like symbol tables, // and because enormous address space of virtual memory makes routines // like io.Copy dangerous. type File interface { io.ReaderAt io.WriterAt io.Closer } type Status struct { PC, SP uint64 } type Frame struct { // PC is the hardware program counter. PC uint64 // SP is the hardware stack pointer. SP uint64 // File and Line are the source code location of the PC. File string Line uint64 // Function is the name of this frame's function. Function string // FunctionStart is the starting PC of the function. FunctionStart uint64 // Params contains the function's parameters. Params []Param // Vars contains the function's local variables. Vars []LocalVar } func (f Frame) String() string { params := make([]string, len(f.Params)) for i, p := range f.Params { params[i] = p.Name // TODO: more information } p := strings.Join(params, ", ") off := f.PC - f.FunctionStart return fmt.Sprintf("%s(%s)\n\t%s:%d +0x%x", f.Function, p, f.File, f.Line, off) } // Param is a parameter of a function. type Param struct { Name string Var Var } // LocalVar is a local variable of a function. type LocalVar struct { Name string Var Var }