未验证 提交 a5d9dbee 编写于 作者: C chainhelen 提交者: GitHub

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.
上级 5b4f4a81
......@@ -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)
......
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
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册