diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index 847e32e07e..9b6cc24875 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -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 /[] 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. @@ -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 /[] 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. diff --git a/_fixtures/examinememory.go b/_fixtures/examinememory.go new file mode 100644 index 0000000000..7e6dada645 --- /dev/null +++ b/_fixtures/examinememory.go @@ -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 +} diff --git a/pkg/proc/mem.go b/pkg/proc/mem.go index 6dee7c95cc..1189c36f0e 100644 --- a/pkg/proc/mem.go +++ b/pkg/proc/mem.go @@ -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 +} diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index e918a4e330..fa805a0bb9 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -5,6 +5,7 @@ package terminal import ( "bufio" "bytes" + "encoding/binary" "errors" "fmt" "go/parser" @@ -368,6 +369,19 @@ Defines as an alias to 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 /[] 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() { @@ -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") diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index b9b9f0be40..0b392634fc 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -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) + } + }) +} diff --git a/service/api/prettyprint.go b/service/api/prettyprint.go index 5100bf8f46..1b36cc7fcb 100644 --- a/service/api/prettyprint.go +++ b/service/api/prettyprint.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "reflect" + "strconv" "strings" ) @@ -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 +} diff --git a/service/client.go b/service/client.go index 6b617cd72c..a3272c8045 100644 --- a/service/client.go +++ b/service/client.go @@ -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 diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 7eafe5d760..1e99073eab 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -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" { diff --git a/service/rpc2/client.go b/service/rpc2/client.go index 6f2f31db24..d35b6a0843 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -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) } diff --git a/service/rpc2/server.go b/service/rpc2/server.go index 730f1e9cfe..8230ca5d1e 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -731,3 +731,26 @@ func (s *RPCServer) ListDynamicLibraries(in ListDynamicLibrariesIn, out *ListDyn out.List = s.debugger.ListDynamicLibraries() return nil } + +// ExamineMemoryIn holds the arguments of ExamineMemory +type ExamineMemoryIn struct { + Num int + Address uintptr +} + +// ExaminedMemoryOut holds the return values of ExamineMemory +type ExaminedMemoryOut struct { + Address uintptr + MemData []byte +} + +func (s *RPCServer) ExamineMemory(arg ExamineMemoryIn, out *ExaminedMemoryOut) error { + MemData, err := s.debugger.ExamineMemory(arg.Address, arg.Num) + if err != nil { + return err + } + + out.MemData = MemData + out.Address = arg.Address + return nil +}