terminal: add -size argument to examinemem command

Adds a -size argument to examinemem that specifies how to group bytes on output.
This commit is contained in:
hitzhangjie
2020-09-11 14:21:11 +08:00
committed by GitHub
parent 6ef7aa8743
commit 37d1e0100a
8 changed files with 104 additions and 40 deletions

View File

@ -259,15 +259,15 @@ Aliases: ed
## examinemem
Examine memory:
examinemem [-fmt <format>] [-len <length>] <address>
examinemem [-fmt <format>] [-count|-len <count>] [-size <size>] <address>
Format represents the data format and the value is one of this list (default hex): bin(binary), oct(octal), dec(decimal), hex(hexadecimal),.
Format represents the data format and the value is one of this list (default hex): bin(binary), oct(octal), dec(decimal), hex(hexadecimal), addr(address).
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.
Address is the memory location of the target to examine. Please note '-len' is deprecated by '-count and -size'.
For example:
x -fmt hex -len 20 0xc00008af38
x -fmt hex -count 20 -size 1 0xc00008af38
Aliases: x

View File

@ -390,15 +390,15 @@ If locspec is omitted edit will open the current source file in the editor, othe
{aliases: []string{"examinemem", "x"}, group: dataCmds, cmdFn: examineMemoryCmd, helpMsg: `Examine memory:
examinemem [-fmt <format>] [-len <length>] <address>
examinemem [-fmt <format>] [-count|-len <count>] [-size <size>] <address>
Format represents the data format and the value is one of this list (default hex): bin(binary), oct(octal), dec(decimal), hex(hexadecimal),.
Format represents the data format and the value is one of this list (default hex): bin(binary), oct(octal), dec(decimal), hex(hexadecimal), addr(address).
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.
Address is the memory location of the target to examine. Please note '-len' is deprecated by '-count and -size'.
For example:
x -fmt hex -len 20 0xc00008af38`},
x -fmt hex -count 20 -size 1 0xc00008af38`},
{aliases: []string{"display"}, group: dataCmds, cmdFn: display, helpMsg: `Print value of an expression every time the program stops.
@ -1562,7 +1562,8 @@ func examineMemoryCmd(t *Term, ctx callContext, args string) error {
// Default value
priFmt := byte('x')
length := 1
count := 1
size := 1
for i := 0; i < len(v); i++ {
switch v[i] {
@ -1585,19 +1586,25 @@ func examineMemoryCmd(t *Term, ctx callContext, args string) error {
if !ok {
return fmt.Errorf("%q is not a valid format", v[i])
}
case "-len":
case "-count", "-len":
i++
if i >= len(v) {
return fmt.Errorf("expected argument after -len")
return fmt.Errorf("expected argument after -count/-len")
}
var err error
length, err = strconv.Atoi(v[i])
if err != nil || length <= 0 {
return fmt.Errorf("len must be an positive integer")
count, err = strconv.Atoi(v[i])
if err != nil || count <= 0 {
return fmt.Errorf("count/len must be a positive integer")
}
// TODO, maybe configured by user.
if length > 1000 {
return fmt.Errorf("len must be less than or equal to 1000")
case "-size":
i++
if i >= len(v) {
return fmt.Errorf("expected argument after -size")
}
var err error
size, err = strconv.Atoi(v[i])
if err != nil || size <= 0 || size > 8 {
return fmt.Errorf("size must be a positive integer (<=8)")
}
default:
if i != len(v)-1 {
@ -1611,16 +1618,20 @@ func examineMemoryCmd(t *Term, ctx callContext, args string) error {
}
}
// TODO, maybe configured by user.
if count*size > 1000 {
return fmt.Errorf("read memory range (count*size) must be less than or equal to 1000 bytes")
}
if address == 0 {
return fmt.Errorf("no address specified")
}
memArea, err := t.client.ExamineMemory(address, length)
memArea, isLittleEndian, err := t.client.ExamineMemory(address, count*size)
if err != nil {
return err
}
fmt.Print(api.PrettyExamineMemory(uintptr(address), memArea, priFmt))
fmt.Print(api.PrettyExamineMemory(uintptr(address), memArea, isLittleEndian, priFmt, size))
return nil
}

View File

@ -1093,7 +1093,7 @@ func TestExamineMemoryCmd(t *testing.T) {
t.Fatalf("could convert %s into int64, err %s", addressStr, err)
}
res := term.MustExec("examinemem -len 52 -fmt hex " + addressStr)
res := term.MustExec("examinemem -count 52 -fmt hex " + addressStr)
t.Logf("the result of examining memory \n%s", res)
// check first line
firstLine := fmt.Sprintf("%#x: 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11", address)
@ -1109,7 +1109,7 @@ func TestExamineMemoryCmd(t *testing.T) {
// second examining memory
term.MustExec("continue")
res = term.MustExec("x -len 52 -fmt bin " + addressStr)
res = term.MustExec("x -count 52 -fmt bin " + addressStr)
t.Logf("the second result of examining memory result \n%s", res)
// check first line

View File

@ -360,11 +360,17 @@ func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent stri
fmt.Fprint(buf, "]")
}
func PrettyExamineMemory(address uintptr, memArea []byte, format byte) string {
// PrettyExamineMemory examine the memory and format data
//
// `format` specifies the data format (or data type), `size` specifies size of each data,
// like 4byte integer, 1byte character, etc. `count` specifies the number of values.
func PrettyExamineMemory(address uintptr, memArea []byte, isLittleEndian bool, format byte, size int) string {
var (
cols int
colFormat string
colBytes = size
addrLen int
addrFmt string
)
@ -374,24 +380,24 @@ func PrettyExamineMemory(address uintptr, memArea []byte, format byte) string {
switch format {
case 'b':
cols = 4 // Avoid emitting rows that are too long when using binary format
colFormat = "%08b"
colFormat = fmt.Sprintf("%%0%db", colBytes*8)
case 'o':
cols = 8
colFormat = "%04o" // Always keep one leading zero for octal.
colFormat = fmt.Sprintf("0%%0%do", colBytes*3) // Always keep one leading zero for octal.
case 'd':
cols = 8
colFormat = "%03d"
colFormat = fmt.Sprintf("%%0%dd", colBytes*3)
case 'x':
cols = 8
colFormat = "0x%02x" // Always keep one leading '0x' for hex.
colFormat = fmt.Sprintf("0x%%0%dx", colBytes*2) // Always keep one leading '0x' for hex.
default:
return fmt.Sprintf("not supprted format %q\n", string(format))
}
colFormat += "\t"
l := len(memArea)
rows := l / cols
if l%cols != 0 {
rows := l / (cols * colBytes)
if l%(cols*colBytes) != 0 {
rows++
}
@ -403,10 +409,16 @@ func PrettyExamineMemory(address uintptr, memArea []byte, format byte) string {
var b strings.Builder
w := tabwriter.NewWriter(&b, 0, 0, 3, ' ', 0)
for i := 0; i < rows; i++ {
fmt.Fprintf(w, addrFmt, address)
for j := 0; j < cols && i*cols+j < l; j++ {
fmt.Fprintf(w, colFormat, memArea[i*cols+j])
for j := 0; j < cols; j++ {
offset := i*(cols*colBytes) + j*colBytes
if offset+colBytes <= len(memArea) {
n := byteArrayToUInt64(memArea[offset:offset+colBytes], isLittleEndian)
fmt.Fprintf(w, colFormat, n)
}
}
fmt.Fprintln(w, "")
address += uintptr(cols)
@ -414,3 +426,17 @@ func PrettyExamineMemory(address uintptr, memArea []byte, format byte) string {
w.Flush()
return b.String()
}
func byteArrayToUInt64(buf []byte, isLittleEndian bool) uint64 {
var n uint64
if isLittleEndian {
for i := len(buf) - 1; i >= 0; i-- {
n = n<<8 + uint64(buf[i])
}
} else {
for i := 0; i < len(buf); i++ {
n = n<<8 + uint64(buf[i])
}
}
return n
}

View File

@ -2,6 +2,7 @@ package api
import (
"fmt"
"math"
"strings"
"testing"
)
@ -17,7 +18,7 @@ func TestPrettyExamineMemory(t *testing.T) {
"0x10007: 0151 0152 0153 0154 0155 0156 0157 0160 ",
"0x1000f: 0161 0162 0163 0164 0165 0166 0167 0170 ",
"0x10017: 0171 0172"}
res := strings.Split(strings.TrimSpace(PrettyExamineMemory(addr, memArea, format)), "\n")
res := strings.Split(strings.TrimSpace(PrettyExamineMemory(addr, memArea, true, format, 1)), "\n")
if len(display) != len(res) {
t.Fatalf("wrong lines return, expected %d but got %d", len(display), len(res))
@ -32,3 +33,27 @@ func TestPrettyExamineMemory(t *testing.T) {
}
}
}
func Test_byteArrayToUInt64(t *testing.T) {
tests := []struct {
name string
args []byte
want uint64
}{
{"case-nil", nil, 0},
{"case-empty", []byte{}, 0},
{"case-1", []byte{0x1}, 1},
{"case-2", []byte{0x12}, 18},
{"case-3", []byte{0x1, 0x2}, 513},
{"case-4", []byte{0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2}, 144397766876004609},
{"case-5", []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, math.MaxUint64},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := byteArrayToUInt64(tt.args, true)
if got != tt.want {
t.Errorf("byteArrayToUInt64() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -160,7 +160,7 @@ type Client interface {
// 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 uint64, length int) ([]byte, error)
ExamineMemory(address uint64, length int) ([]byte, bool, error)
// StopRecording stops a recording if one is in progress.
StopRecording() error

View File

@ -441,15 +441,14 @@ func (c *RPCClient) ListDynamicLibraries() ([]api.Image, error) {
return out.List, nil
}
func (c *RPCClient) ExamineMemory(address uint64, count int) ([]byte, error) {
func (c *RPCClient) ExamineMemory(address uint64, count int) ([]byte, bool, error) {
out := &ExaminedMemoryOut{}
err := c.call("ExamineMemory", ExamineMemoryIn{Length: count, Address: address}, out)
if err != nil {
return nil, err
return nil, false, err
}
return out.Mem, nil
return out.Mem, out.IsLittleEndian, nil
}
func (c *RPCClient) StopRecording() error {

View File

@ -834,6 +834,7 @@ type ExamineMemoryIn struct {
// ExaminedMemoryOut holds the return values of ExamineMemory
type ExaminedMemoryOut struct {
Mem []byte
IsLittleEndian bool
}
func (s *RPCServer) ExamineMemory(arg ExamineMemoryIn, out *ExaminedMemoryOut) error {
@ -846,6 +847,8 @@ func (s *RPCServer) ExamineMemory(arg ExamineMemoryIn, out *ExaminedMemoryOut) e
}
out.Mem = Mem
out.IsLittleEndian = true //TODO: get byte order from debugger.target.BinInfo().Arch
return nil
}