pkg,service: add cmd examinemem
(x
) for examining memory. (#1814)
According to #1800 #1584 #1038, `dlv` should enable the user to dive into memory. User can print binary data in specific memory address range. But not support for sepecific variable name or structures temporarily.(Because I have no idea that modify `print` command.) Close #1584.
This commit is contained in:
parent
5b4f4a81b1
commit
a5d9dbee79
@ -24,6 +24,7 @@ Command | Description
|
||||
[disassemble](#disassemble) | Disassembler.
|
||||
[down](#down) | Move the current frame down.
|
||||
[edit](#edit) | Open where you are in $DELVE_EDITOR or $EDITOR
|
||||
[examinemem](#examinemem) | Examine memory:
|
||||
[exit](#exit) | Exit the debugger.
|
||||
[frame](#frame) | Set the current frame, or execute command on a different frame.
|
||||
[funcs](#funcs) | Print list of functions.
|
||||
@ -210,6 +211,18 @@ If locspec is omitted edit will open the current source file in the editor, othe
|
||||
|
||||
Aliases: ed
|
||||
|
||||
## examinemem
|
||||
Examine memory:
|
||||
|
||||
examinemem [-fmt <format>] [-len <length>] <address>
|
||||
|
||||
Format represents the data format and the value is one of this list (default hex): oct(octal), hex(hexadecimal), dec(decimal), bin(binary).
|
||||
Length is the number of bytes (default 1) and must be less than or equal to 1000.
|
||||
Address is the memory location of the target to examine.
|
||||
For example: x -fmt hex -len 20 0xc00008af38
|
||||
|
||||
Aliases: x
|
||||
|
||||
## exit
|
||||
Exit the debugger.
|
||||
|
||||
|
@ -29,6 +29,7 @@ create_breakpoint(Breakpoint) | Equivalent to API call [CreateBreakpoint](https:
|
||||
detach(Kill) | Equivalent to API call [Detach](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Detach)
|
||||
disassemble(Scope, StartPC, EndPC, Flavour) | Equivalent to API call [Disassemble](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Disassemble)
|
||||
eval(Scope, Expr, Cfg) | Equivalent to API call [Eval](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Eval)
|
||||
examine_memory(Address, Length) | Equivalent to API call [ExamineMemory](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ExamineMemory)
|
||||
find_location(Scope, Loc, IncludeNonExecutableLines) | Equivalent to API call [FindLocation](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.FindLocation)
|
||||
function_return_locations(FnName) | Equivalent to API call [FunctionReturnLocations](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.FunctionReturnLocations)
|
||||
get_breakpoint(Id, Name) | Equivalent to API call [GetBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.GetBreakpoint)
|
||||
|
25
_fixtures/examinememory.go
Normal file
25
_fixtures/examinememory.go
Normal file
@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func main() {
|
||||
l := int(51)
|
||||
bs := make([]byte, l)
|
||||
for i := 0; i < l; i++ {
|
||||
bs[i] = byte(i + int(10))
|
||||
}
|
||||
|
||||
bsp := (*byte)(unsafe.Pointer(&bs[0]))
|
||||
|
||||
bspUintptr := uintptr(unsafe.Pointer(bsp))
|
||||
|
||||
fmt.Printf("%#x\n", bspUintptr)
|
||||
_ = *bsp
|
||||
|
||||
bs[0] = 255
|
||||
|
||||
_ = *bsp
|
||||
}
|
@ -7,6 +7,10 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/cosiner/argv"
|
||||
"github.com/go-delve/delve/service"
|
||||
"github.com/go-delve/delve/service/api"
|
||||
"github.com/go-delve/delve/service/debugger"
|
||||
"go/parser"
|
||||
"go/scanner"
|
||||
"io"
|
||||
@ -20,11 +24,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/cosiner/argv"
|
||||
"github.com/go-delve/delve/service"
|
||||
"github.com/go-delve/delve/service/api"
|
||||
"github.com/go-delve/delve/service/debugger"
|
||||
)
|
||||
|
||||
const optimizedFunctionWarning = "Warning: debugging optimized function"
|
||||
@ -368,6 +367,15 @@ Defines <alias> as an alias to <command> or removes an alias.`},
|
||||
|
||||
If locspec is omitted edit will open the current source file in the editor, otherwise it will open the specified location.`},
|
||||
{aliases: []string{"libraries"}, cmdFn: libraries, helpMsg: `List loaded dynamic libraries`},
|
||||
|
||||
{aliases: []string{"examinemem", "x"}, cmdFn: examineMemoryCmd, helpMsg: `Examine memory:
|
||||
|
||||
examinemem [-fmt <format>] [-len <length>] <address>
|
||||
|
||||
Format represents the data format and the value is one of this list (default hex): oct(octal), hex(hexadecimal), dec(decimal), bin(binary).
|
||||
Length is the number of bytes (default 1) and must be less than or equal to 1000.
|
||||
Address is the memory location of the target to examine.
|
||||
For example: x -fmt hex -len 20 0xc00008af38`},
|
||||
}
|
||||
|
||||
if client == nil || client.Recorded() {
|
||||
@ -1349,6 +1357,81 @@ func edit(t *Term, ctx callContext, args string) error {
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func examineMemoryCmd(t *Term, ctx callContext, args string) error {
|
||||
v := strings.FieldsFunc(args, func(c rune) bool {
|
||||
return c == ' '
|
||||
})
|
||||
|
||||
var (
|
||||
address int64
|
||||
err error
|
||||
ok bool
|
||||
)
|
||||
|
||||
// Default value
|
||||
priFmt := byte('x')
|
||||
length := 1
|
||||
|
||||
for i := 0; i < len(v); i++ {
|
||||
switch v[i] {
|
||||
case "-fmt":
|
||||
i++
|
||||
if i >= len(v) {
|
||||
return fmt.Errorf("expected argument after -fmt")
|
||||
}
|
||||
fmtMapToPriFmt := map[string]byte{
|
||||
"oct": 'o',
|
||||
"octal": 'o',
|
||||
"hex": 'x',
|
||||
"hexadecimal": 'x',
|
||||
"dec": 'd',
|
||||
"decimal": 'd',
|
||||
"bin": 'b',
|
||||
"binary": 'b',
|
||||
}
|
||||
priFmt, ok = fmtMapToPriFmt[v[i]]
|
||||
if !ok {
|
||||
return fmt.Errorf("%q is not a valid format", v[i])
|
||||
}
|
||||
case "-len":
|
||||
i++
|
||||
if i >= len(v) {
|
||||
return fmt.Errorf("expected argument after -len")
|
||||
}
|
||||
var err error
|
||||
length, err = strconv.Atoi(v[i])
|
||||
if err != nil || length <= 0 {
|
||||
return fmt.Errorf("len must be an positive integer")
|
||||
}
|
||||
// TODO, maybe configured by user.
|
||||
if length > 1000 {
|
||||
return fmt.Errorf("len must be less than or equal to 1000")
|
||||
}
|
||||
default:
|
||||
if i != len(v)-1 {
|
||||
return fmt.Errorf("unknown option %q", v[i])
|
||||
}
|
||||
// TODO, maybe we can support expression.
|
||||
address, err = strconv.ParseInt(v[len(v)-1], 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert address into uintptr type failed, %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if address == 0 {
|
||||
return fmt.Errorf("no address specified")
|
||||
}
|
||||
|
||||
memArea, err := t.client.ExamineMemory(uintptr(address), length)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(api.PrettyExamineMemory(uintptr(address), memArea, priFmt))
|
||||
return nil
|
||||
}
|
||||
|
||||
func printVar(t *Term, ctx callContext, args string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("not enough arguments")
|
||||
|
@ -993,3 +993,42 @@ func TestIssue1598(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestExamineMemoryCmd(t *testing.T) {
|
||||
withTestTerminal("examinememory", t, func(term *FakeTerminal) {
|
||||
term.MustExec("break examinememory.go:19")
|
||||
term.MustExec("break examinememory.go:24")
|
||||
term.MustExec("continue")
|
||||
|
||||
addressStr := strings.TrimSpace(term.MustExec("p bspUintptr"))
|
||||
address, err := strconv.ParseInt(addressStr, 0, 64)
|
||||
if err != nil {
|
||||
t.Fatalf("could convert %s into int64, err %s", addressStr, err)
|
||||
}
|
||||
|
||||
res := term.MustExec("examinemem -len 52 -fmt hex " + addressStr)
|
||||
t.Logf("the result of examining memory \n%s", res)
|
||||
// check first line
|
||||
firstLine := fmt.Sprintf("%#x: 0xa 0xb 0xc 0xd 0xe 0xf 0x10 0x11", address)
|
||||
if !strings.Contains(res, firstLine) {
|
||||
t.Fatalf("expected first line: %s", firstLine)
|
||||
}
|
||||
|
||||
// check last line
|
||||
lastLine := fmt.Sprintf("%#x: 0x3a 0x3b 0x3c 0x0", address+6*8)
|
||||
if !strings.Contains(res, lastLine) {
|
||||
t.Fatalf("expected last line: %s", lastLine)
|
||||
}
|
||||
|
||||
// second examining memory
|
||||
term.MustExec("continue")
|
||||
res = term.MustExec("x -len 52 -fmt bin " + addressStr)
|
||||
t.Logf("the second result of examining memory result \n%s", res)
|
||||
|
||||
// check first line
|
||||
firstLine = fmt.Sprintf("%#x: 11111111 00001011 00001100 00001101", address)
|
||||
if !strings.Contains(res, firstLine) {
|
||||
t.Fatalf("expected first line: %s", firstLine)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -450,6 +450,44 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["examine_memory"] = starlark.NewBuiltin("examine_memory", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
if err := isCancelled(thread); err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
var rpcArgs rpc2.ExamineMemoryIn
|
||||
var rpcRet rpc2.ExaminedMemoryOut
|
||||
if len(args) > 0 && args[0] != starlark.None {
|
||||
err := unmarshalStarlarkValue(args[0], &rpcArgs.Address, "Address")
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
if len(args) > 1 && args[1] != starlark.None {
|
||||
err := unmarshalStarlarkValue(args[1], &rpcArgs.Length, "Length")
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
for _, kv := range kwargs {
|
||||
var err error
|
||||
switch kv[0].(starlark.String) {
|
||||
case "Address":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Address, "Address")
|
||||
case "Length":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Length, "Length")
|
||||
default:
|
||||
err = fmt.Errorf("unknown argument %q", kv[0])
|
||||
}
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
err := env.ctx.Client().CallAPI("ExamineMemory", &rpcArgs, &rpcRet)
|
||||
if err != nil {
|
||||
return starlark.None, err
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["find_location"] = starlark.NewBuiltin("find_location", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
if err := isCancelled(thread); err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -354,3 +355,53 @@ func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent stri
|
||||
|
||||
fmt.Fprint(buf, "]")
|
||||
}
|
||||
|
||||
func PrettyExamineMemory(address uintptr, memArea []byte, format byte) string {
|
||||
cols := 8
|
||||
// Avoid emitting rows that are too long when using binary format
|
||||
if format == 'b' {
|
||||
cols = 4
|
||||
}
|
||||
|
||||
l := len(memArea)
|
||||
rows := l / cols
|
||||
if l%cols != 0 {
|
||||
rows++
|
||||
}
|
||||
|
||||
var colFormat string
|
||||
// Leading zero and occupy 8 for binary
|
||||
if format == 'b' {
|
||||
colFormat = " %#08b"
|
||||
} else {
|
||||
var maxColCharNum int
|
||||
for i := 0; i < rows; i++ {
|
||||
for j := 0; j < cols && i*cols+j < l; j++ {
|
||||
curColCharNum := len(fmt.Sprintf("%#"+string(format), memArea[i*cols+j]))
|
||||
if curColCharNum > maxColCharNum {
|
||||
maxColCharNum = curColCharNum
|
||||
}
|
||||
}
|
||||
}
|
||||
colFormat = " %#-" + strconv.Itoa(maxColCharNum) + string(format)
|
||||
}
|
||||
|
||||
lines := ""
|
||||
for i := 0; i < rows; i++ {
|
||||
lines += fmt.Sprintf("%#x:", address)
|
||||
for j := 0; j < cols && i*cols+j < l; j++ {
|
||||
curOutput := fmt.Sprintf(colFormat, memArea[i*cols+j])
|
||||
|
||||
// Diffrent versions of golang output differently if binary.
|
||||
// See https://ci.appveyor.com/project/derekparker/delve-facy3/builds/30179356.
|
||||
// Remove prefix `0b` if binary in some versions of golang because it is not graceful.
|
||||
if format == 'b' && strings.Contains(curOutput, "0b") {
|
||||
curOutput = " " + curOutput[6:]
|
||||
}
|
||||
lines += curOutput
|
||||
}
|
||||
lines += "\n"
|
||||
address += uintptr(cols)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
@ -147,6 +147,11 @@ type Client interface {
|
||||
// ListDynamicLibraries returns a list of loaded dynamic libraries.
|
||||
ListDynamicLibraries() ([]api.Image, error)
|
||||
|
||||
// ExamineMemory returns the raw memory stored at the given address.
|
||||
// The amount of data to be read is specified by length which must be less than or equal to 1000.
|
||||
// This function will return an error if it reads less than `length` bytes.
|
||||
ExamineMemory(address uintptr, length int) ([]byte, error)
|
||||
|
||||
// Disconnect closes the connection to the server without sending a Detach request first.
|
||||
// If cont is true a continue command will be sent instead.
|
||||
Disconnect(cont bool) error
|
||||
|
@ -1313,6 +1313,25 @@ func (d *Debugger) ListDynamicLibraries() []api.Image {
|
||||
return r
|
||||
}
|
||||
|
||||
// ExamineMemory returns the raw memory stored at the given address.
|
||||
// The amount of data to be read is specified by length.
|
||||
// This function will return an error if it reads less than `length` bytes.
|
||||
func (d *Debugger) ExamineMemory(address uintptr, length int) ([]byte, error) {
|
||||
d.processMutex.Lock()
|
||||
defer d.processMutex.Unlock()
|
||||
|
||||
thread := d.target.CurrentThread()
|
||||
data := make([]byte, length)
|
||||
n, err := thread.ReadMemory(data, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length != n {
|
||||
return nil, errors.New("the specific range has exceeded readable area")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (d *Debugger) GetVersion(out *api.GetVersionOut) error {
|
||||
if d.config.CoreFile != "" {
|
||||
if d.config.Backend == "rr" {
|
||||
|
@ -413,6 +413,17 @@ func (c *RPCClient) ListDynamicLibraries() ([]api.Image, error) {
|
||||
return out.List, nil
|
||||
}
|
||||
|
||||
func (c *RPCClient) ExamineMemory(address uintptr, count int) ([]byte, error) {
|
||||
out := &ExaminedMemoryOut{}
|
||||
|
||||
err := c.call("ExamineMemory", ExamineMemoryIn{Length: count, Address: address}, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return out.Mem, nil
|
||||
}
|
||||
|
||||
func (c *RPCClient) call(method string, args, reply interface{}) error {
|
||||
return c.client.Call("RPCServer."+method, args, reply)
|
||||
}
|
||||
|
@ -751,3 +751,27 @@ func (s *RPCServer) ListPackagesBuildInfo(in ListPackagesBuildInfoIn, out *ListP
|
||||
out.List = s.debugger.ListPackagesBuildInfo(in.IncludeFiles)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExamineMemoryIn holds the arguments of ExamineMemory
|
||||
type ExamineMemoryIn struct {
|
||||
Address uintptr
|
||||
Length int
|
||||
}
|
||||
|
||||
// ExaminedMemoryOut holds the return values of ExamineMemory
|
||||
type ExaminedMemoryOut struct {
|
||||
Mem []byte
|
||||
}
|
||||
|
||||
func (s *RPCServer) ExamineMemory(arg ExamineMemoryIn, out *ExaminedMemoryOut) error {
|
||||
if arg.Length > 1000 {
|
||||
return fmt.Errorf("len must be less than or equal to 1000")
|
||||
}
|
||||
Mem, err := s.debugger.ExamineMemory(arg.Address, arg.Length)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.Mem = Mem
|
||||
return nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user