From 37d1e0100a7b19aa47870196ed1fc0766ffa4eb6 Mon Sep 17 00:00:00 2001 From: hitzhangjie Date: Fri, 11 Sep 2020 14:21:11 +0800 Subject: [PATCH] terminal: add -size argument to examinemem command Adds a -size argument to examinemem that specifies how to group bytes on output. --- Documentation/cli/README.md | 8 +++--- pkg/terminal/command.go | 43 ++++++++++++++++++----------- pkg/terminal/command_test.go | 4 +-- service/api/prettyprint.go | 48 +++++++++++++++++++++++++-------- service/api/prettyprint_test.go | 27 ++++++++++++++++++- service/client.go | 2 +- service/rpc2/client.go | 7 +++-- service/rpc2/server.go | 5 +++- 8 files changed, 104 insertions(+), 40 deletions(-) diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index 89e5691c..1f6022eb 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -259,15 +259,15 @@ Aliases: ed ## examinemem Examine memory: - examinemem [-fmt ] [-len ]
+ examinemem [-fmt ] [-count|-len ] [-size ]
-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 diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 6af3268c..25423619 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -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 ] [-len ]
+ examinemem [-fmt ] [-count|-len ] [-size ]
-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 } diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index da9d553b..78ad1a61 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -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 diff --git a/service/api/prettyprint.go b/service/api/prettyprint.go index 45571def..564bae57 100644 --- a/service/api/prettyprint.go +++ b/service/api/prettyprint.go @@ -360,13 +360,19 @@ 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 - addrLen int - addrFmt string + colBytes = size + + addrLen int + addrFmt string ) // Diffrent versions of golang output differently about '#'. @@ -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 +} diff --git a/service/api/prettyprint_test.go b/service/api/prettyprint_test.go index 9ec90686..c4070ed1 100644 --- a/service/api/prettyprint_test.go +++ b/service/api/prettyprint_test.go @@ -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) + } + }) + } +} diff --git a/service/client.go b/service/client.go index 7de94b3e..8fd863b8 100644 --- a/service/client.go +++ b/service/client.go @@ -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 diff --git a/service/rpc2/client.go b/service/rpc2/client.go index cb46be71..7de4dcb9 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -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 { diff --git a/service/rpc2/server.go b/service/rpc2/server.go index b5719a58..5d8c51c4 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -833,7 +833,8 @@ type ExamineMemoryIn struct { // ExaminedMemoryOut holds the return values of ExamineMemory type ExaminedMemoryOut struct { - Mem []byte + 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 }