Skip to content

Commit

Permalink
pkg,service: add cmd x for examining memory.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
chainhelen committed Jan 7, 2020
1 parent 059c51e commit 9c8bf7b
Show file tree
Hide file tree
Showing 10 changed files with 332 additions and 0 deletions.
17 changes: 17 additions & 0 deletions Documentation/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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: x /[<FMT>] ADDRESS.
[exit](#exit) | Exit the debugger.
[frame](#frame) | Set the current frame, or execute command on a different frame.
[funcs](#funcs) | Print list of functions.
Expand Down Expand Up @@ -213,6 +214,22 @@ If locspec is omitted edit will open the current source file in the editor, othe

Aliases: ed

## examinemem
Examine memory: x /[<FMT>] ADDRESS.
FMT is a count followed by a format letter and a size letter.

Count number is the number of examining Size.
Format letter is one of o(octal), x(hex), d(decimal), u(unsigned decimal).
Size letter is one of b(byte), h(halfword), w(word), g(giant, 8 bytes).

ADDRESS is an expression for the memory address to examine.

Default for FMT is 8xb.
For example: x /8xb 0xc00008af38


Aliases: x

## exit
Exit the debugger.

Expand Down
25 changes: 25 additions & 0 deletions _fixtures/examinememory.go
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 12 additions & 0 deletions pkg/proc/mem.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,15 @@ func DereferenceMemory(mem MemoryReadWriter) MemoryReadWriter {
}
return mem
}

func ExamineMemory(mem MemoryReadWriter, address uintptr, num int) ([]byte, error) {
data := make([]byte, num)
rn, err := mem.ReadMemory(data, address)
if err != nil {
return nil, err
}
if num != rn {
return nil, errors.New("the specific range has exceeded readable area")
}
return data, nil
}
156 changes: 156 additions & 0 deletions pkg/terminal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package terminal
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"go/parser"
Expand Down Expand Up @@ -368,6 +369,19 @@ 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: x /[<FMT>] ADDRESS.
FMT is a count followed by a format letter and a size letter.
Count number is the number of examining Size.
Format letter is one of o(octal), x(hex), d(decimal), u(unsigned decimal).
Size letter is one of b(byte), h(halfword), w(word), g(giant, 8 bytes).
ADDRESS is an expression for the memory address to examine.
Default for FMT is 8xb.
For example: x /8xb 0xc00008af38
`},
}

if client == nil || client.Recorded() {
Expand Down Expand Up @@ -1341,6 +1355,148 @@ func edit(t *Term, ctx callContext, args string) error {
return cmd.Run()
}

func parseFmtStr(fmtStr string) (count int, format byte, size int, err error) {
if len(fmtStr) <= 2 {
return 0, ' ', 0, fmt.Errorf("invalid argument for FMT")
}
if fmtStr[0] != '/' {
return 0, ' ', 0, fmt.Errorf("the first letter of FMT should be \"/\"")
}
i := 1
for ; i < len(fmtStr); i++ {
if fmtStr[i] < '0' || fmtStr[i] > '9' {
break
}
}
if i == 1 {
return 0, ' ', 0, fmt.Errorf("the \"count\" of FMT should be a number")
}
count, err = strconv.Atoi(fmtStr[1:i])
if err != nil {
return 0, ' ', 0, fmt.Errorf("strconv the \"count\" into number of FMT get %s", err)
}

formatSupportedArrForErr := func(arr []byte) string {
str := "[ "
for i, v := range arr {
str += "\"" + string(v) + "\""
if i != len(arr)-1 {
str += " "
}
}
str += "]"
return str
}

// if no format and size, use default argument 'xb'
if i == len(fmtStr) {
return count, 'x', 1, nil
}

supportedFormat := []byte{'o', 'x', 'd'}
format = fmtStr[i]
hitSupportedFlag := false
for _, v := range supportedFormat {
if v == format {
hitSupportedFlag = true
break
}
}
if !hitSupportedFlag {
return 0, ' ', 0, fmt.Errorf("the \"format\" letter should be one of %s", formatSupportedArrForErr(supportedFormat))
}

i++
// if no size, use default argument 'b'
if i == len(fmtStr) {
return count, format, 1, nil
}
supportedSize := []byte{'b', 'h', 'w', 'g'}
hitSupportedFlag = false
var index int
for index = 0; index < len(supportedSize); index++ {
if supportedSize[index] == fmtStr[i] {
hitSupportedFlag = true
break
}
}
if !hitSupportedFlag {
return 0, ' ', 0, fmt.Errorf("the \"size\" letter should be one of %s", formatSupportedArrForErr(supportedSize))
}
size = 1 << uint(index)

i++
// if redundant parameters, should report error
if i != len(fmtStr) {
return 0, ' ', 0, fmt.Errorf("some redundant parameters for FMT")
}
return count, format, size, nil
}

func computeNumForMemoryArea(data []byte) uint64 {
size := len(data)
num := uint64(0)
switch size {
case 1:
num = uint64(data[0])
case 2:
num = uint64(binary.BigEndian.Uint16(data))
case 4:
num = uint64(binary.BigEndian.Uint32(data))
case 8:
num = binary.BigEndian.Uint64(data)
default:
panic(fmt.Errorf("the len of data = %d, is not expected", size))
}
return num
}

func examineMemoryCmd(t *Term, ctx callContext, args string) error {
fmtStr := "/8xb"
addressStr := args

v := strings.SplitN(args, " ", 2)
if len(v) == 0 || v[0] == "" {
return fmt.Errorf("not enough arguments")
}

if len(v) == 2 {
fmtStr = v[0]
addressStr = v[1]
}

count, format, size, err := parseFmtStr(fmtStr)
if err != nil {
return fmt.Errorf("parse FMT \"%s\" failed, %s", fmtStr, err)
}

// TODO support negative
if count < 0 {
return fmt.Errorf("not support \"count\" < 0")
}
if count == 0 {
return nil
}

address, err := strconv.ParseInt(addressStr, 0, 64)
if err != nil {
return fmt.Errorf("convert address into uintptr type failed, %s", err)
}

memArea, err := t.client.ExamineMemory(count*size, uintptr(address))
if err != nil {
return err
}

res := make([]uint64, 0, count)
for i := 0; i < count; i++ {
res = append(res, computeNumForMemoryArea(memArea[i*size:(i+1)*size]))
}

fmt.Println(api.PrettyExamineMemory(uintptr(address), res, size, format))
return nil
}

func printVar(t *Term, ctx callContext, args string) error {
if len(args) == 0 {
return fmt.Errorf("not enough arguments")
Expand Down
38 changes: 38 additions & 0 deletions pkg/terminal/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -993,3 +993,41 @@ 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")
// remove "\n"
addressStr := strings.TrimSpace(term.MustExec("p bspUintptr"))
res := term.MustExec("x /52xb " + addressStr)
t.Logf("the result of examining memory \n%s", res)
address, err := strconv.ParseInt(addressStr, 0, 64)
if err != nil {
t.Fatalf("could convert %s into int64, err %s", addressStr, err)
}
// 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 /10og " + addressStr)
t.Logf("the second result of examining memory result \n%s", res)

// check first line
firstLine = fmt.Sprintf("%#x: 01774130300641603610021 0110230501242605614031", address)
if !strings.Contains(res, firstLine) {
t.Fatalf("expected first line: %s", firstLine)
}
})
}
37 changes: 37 additions & 0 deletions service/api/prettyprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"reflect"
"strconv"
"strings"
)

Expand Down Expand Up @@ -354,3 +355,39 @@ func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent stri

fmt.Fprint(buf, "]")
}

func PrettyExamineMemory(address uintptr, res []uint64, size int, format byte) string {
cols := int(8)
if size == 4 {
cols = 4
} else if size == 8 {
cols = 2
}
l := len(res)
rows := l / cols
if l%cols != 0 {
rows++
}

maxColCharNum := int(0)
for i := 0; i < rows; i++ {
for j := 0; j < cols && i*cols+j < l; j++ {
curColCharNum := len(fmt.Sprintf("%#"+string(format), res[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++ {
lines += fmt.Sprintf(colFormat, res[i*cols+j])
}
lines += "\n"
address += uintptr(size * cols)
}
return lines
}
3 changes: 3 additions & 0 deletions service/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ type Client interface {
// ListDynamicLibraries returns a list of loaded dynamic libraries.
ListDynamicLibraries() ([]api.Image, error)

// ExamineMemory returns a list of specific address memory that len is num.
ExamineMemory(num int, address uintptr) ([]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
Expand Down
10 changes: 10 additions & 0 deletions service/debugger/debugger.go
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,16 @@ func (d *Debugger) ListDynamicLibraries() []api.Image {
return r
}

// ExamineMemory returns a list of specific address memory that len is num.
func (d *Debugger) ExamineMemory(address uintptr, num int) ([]byte, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()

thread := d.target.CurrentThread()

return proc.ExamineMemory(thread, address, num)
}

func (d *Debugger) GetVersion(out *api.GetVersionOut) error {
if d.config.CoreFile != "" {
if d.config.Backend == "rr" {
Expand Down
11 changes: 11 additions & 0 deletions service/rpc2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,17 @@ func (c *RPCClient) ListDynamicLibraries() ([]api.Image, error) {
return out.List, nil
}

func (c *RPCClient) ExamineMemory(num int, address uintptr) ([]byte, error) {
out := &ExaminedMemoryOut{}

err := c.call("ExamineMemory", ExamineMemoryIn{Num: num, Address: address}, &out)
if err != nil {
return nil, err
}

return out.MemData, nil
}

func (c *RPCClient) call(method string, args, reply interface{}) error {
return c.client.Call("RPCServer."+method, args, reply)
}
Expand Down
Loading

0 comments on commit 9c8bf7b

Please sign in to comment.