examinememory: evaluate addr as expression (#2385)

* examinememory: evaluate addr as expression

This makes it easy to read memory locations at an offset of a known
address, e.g.:

x 0xc000046800 + 32

* use feedback from @aarzilli

- expression mode is now enabled via -x flag
- support "-x var", "-x &var" in addition to "-x <addr expr>"
- some refactoring

* add test cases

* deal with double spaces

* update docs

* add new failing test

* fix docs

* simplify implementation, update test & docs

* Fix docs
This commit is contained in:
Felix Geisendörfer 2021-04-26 19:36:24 +02:00 committed by GitHub
parent bbae9a9d12
commit 7bf5482b32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 140 additions and 26 deletions

@ -280,14 +280,19 @@ Aliases: ed
Examine memory:
examinemem [-fmt <format>] [-count|-len <count>] [-size <size>] <address>
examinemem [-fmt <format>] [-count|-len <count>] [-size <size>] -x <expression>
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. Please note '-len' is deprecated by '-count and -size'.
Expression can be an integer expression or pointer value of the memory location to examine.
For example:
x -fmt hex -count 20 -size 1 0xc00008af38
x -fmt hex -count 20 -size 1 -x 0xc00008af38 + 8
x -fmt hex -count 20 -size 1 -x &myVar
x -fmt hex -count 20 -size 1 -x myPtrVar
Aliases: x

@ -408,14 +408,19 @@ 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>] [-count|-len <count>] [-size <size>] <address>
examinemem [-fmt <format>] [-count|-len <count>] [-size <size>] -x <expression>
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. Please note '-len' is deprecated by '-count and -size'.
Expression can be an integer expression or pointer value of the memory location to examine.
For example:
x -fmt hex -count 20 -size 1 0xc00008af38`},
x -fmt hex -count 20 -size 1 0xc00008af38
x -fmt hex -count 20 -size 1 -x 0xc00008af38 + 8
x -fmt hex -count 20 -size 1 -x &myVar
x -fmt hex -count 20 -size 1 -x myPtrVar`},
{aliases: []string{"display"}, group: dataCmds, cmdFn: display, helpMsg: `Print value of an expression every time the program stops.
@ -1659,27 +1664,42 @@ 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 == ' '
})
func examineMemoryCmd(t *Term, ctx callContext, argstr string) error {
var (
address uint64
err error
ok bool
args = strings.Split(argstr, " ")
)
// Default value
priFmt := byte('x')
count := 1
size := 1
isExpr := false
for i := 0; i < len(v); i++ {
switch v[i] {
// nextArg returns the next argument that is not an empty string, if any, and
// advances the args slice to the position after that.
nextArg := func() string {
for len(args) > 0 {
arg := args[0]
args = args[1:]
if arg != "" {
return arg
}
}
return ""
}
loop:
for {
switch cmd := nextArg(); cmd {
case "":
// no more arguments
break loop
case "-fmt":
i++
if i >= len(v) {
arg := nextArg()
if arg == "" {
return fmt.Errorf("expected argument after -fmt")
}
fmtMapToPriFmt := map[string]byte{
@ -1692,39 +1712,39 @@ func examineMemoryCmd(t *Term, ctx callContext, args string) error {
"bin": 'b',
"binary": 'b',
}
priFmt, ok = fmtMapToPriFmt[v[i]]
priFmt, ok = fmtMapToPriFmt[arg]
if !ok {
return fmt.Errorf("%q is not a valid format", v[i])
return fmt.Errorf("%q is not a valid format", arg)
}
case "-count", "-len":
i++
if i >= len(v) {
arg := nextArg()
if arg == "" {
return fmt.Errorf("expected argument after -count/-len")
}
var err error
count, err = strconv.Atoi(v[i])
count, err = strconv.Atoi(arg)
if err != nil || count <= 0 {
return fmt.Errorf("count/len must be a positive integer")
}
case "-size":
i++
if i >= len(v) {
arg := nextArg()
if arg == "" {
return fmt.Errorf("expected argument after -size")
}
var err error
size, err = strconv.Atoi(v[i])
size, err = strconv.Atoi(arg)
if err != nil || size <= 0 || size > 8 {
return fmt.Errorf("size must be a positive integer (<=8)")
}
case "-x":
isExpr = true
break loop // remaining args are going to be interpreted as expression
default:
if i != len(v)-1 {
return fmt.Errorf("unknown option %q", v[i])
}
// TODO, maybe we can support expression.
address, err = strconv.ParseUint(v[len(v)-1], 0, 64)
if err != nil {
return fmt.Errorf("convert address into uintptr type failed, %s", err)
if len(args) > 0 {
return fmt.Errorf("unknown option %q", args[0])
}
args = []string{cmd}
break loop // only one arg left to be evaluated as a uint
}
}
@ -1733,10 +1753,39 @@ func examineMemoryCmd(t *Term, ctx callContext, args string) error {
return fmt.Errorf("read memory range (count*size) must be less than or equal to 1000 bytes")
}
if address == 0 {
if len(args) == 0 {
return fmt.Errorf("no address specified")
}
if isExpr {
expr := strings.Join(args, " ")
val, err := t.client.EvalVariable(ctx.Scope, expr, t.loadConfig())
if err != nil {
return err
}
// "-x &myVar" or "-x myPtrVar"
if val.Kind == reflect.Ptr {
if len(val.Children) < 1 {
return fmt.Errorf("bug? invalid pointer: %#v", val)
}
address = val.Children[0].Addr
// "-x 0xc000079f20 + 8" or -x 824634220320 + 8
} else if val.Kind == reflect.Int && val.Value != "" {
address, err = strconv.ParseUint(val.Value, 0, 64)
if err != nil {
return fmt.Errorf("bad expression result: %q: %s", val.Value, err)
}
} else {
return fmt.Errorf("unsupported expression type: %s", val.Kind)
}
} else {
address, err = strconv.ParseUint(args[0], 0, 64)
if err != nil {
return fmt.Errorf("convert address into uintptr type failed, %s", err)
}
}
memArea, isLittleEndian, err := t.client.ExamineMemory(address, count*size)
if err != nil {
return err

@ -1029,6 +1029,66 @@ func TestExamineMemoryCmd(t *testing.T) {
if !strings.Contains(res, firstLine) {
t.Fatalf("expected first line: %s", firstLine)
}
// third examining memory: -x addr
res = term.MustExec("examinemem -x " + addressStr)
t.Logf("the third result of examining memory result \n%s", res)
firstLine = fmt.Sprintf("%#x: 0xff", address)
if !strings.Contains(res, firstLine) {
t.Fatalf("expected first line: %s", firstLine)
}
// fourth examining memory: -x addr + offset
res = term.MustExec("examinemem -x " + addressStr + " + 8")
t.Logf("the fourth result of examining memory result \n%s", res)
firstLine = fmt.Sprintf("%#x: 0x12", address+8)
if !strings.Contains(res, firstLine) {
t.Fatalf("expected first line: %s", firstLine)
}
// fifth examining memory: -x &var
res = term.MustExec("examinemem -x &bs[0]")
t.Logf("the fifth result of examining memory result \n%s", res)
firstLine = fmt.Sprintf("%#x: 0xff", address)
if !strings.Contains(res, firstLine) {
t.Fatalf("expected first line: %s", firstLine)
}
// sixth examining memory: -fmt and double spaces
res = term.MustExec("examinemem -fmt hex -x &bs[0]")
t.Logf("the sixth result of examining memory result \n%s", res)
firstLine = fmt.Sprintf("%#x: 0xff", address)
if !strings.Contains(res, firstLine) {
t.Fatalf("expected first line: %s", firstLine)
}
})
withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
tests := []struct {
Expr string
Want int
}{
{Expr: "&i1", Want: 1},
{Expr: "&i2", Want: 2},
{Expr: "p1", Want: 1},
{Expr: "*pp1", Want: 1},
{Expr: "&str1[1]", Want: '1'},
{Expr: "c1.pb", Want: 1},
{Expr: "&c1.pb.a", Want: 1},
{Expr: "&c1.pb.a.A", Want: 1},
{Expr: "&c1.pb.a.B", Want: 2},
}
term.MustExec("continue")
for _, test := range tests {
res := term.MustExec("examinemem -fmt dec -x " + test.Expr)
// strip addr from output, e.g. "0xc0000160b8: 023" -> "023"
res = strings.TrimSpace(strings.Split(res, ":")[1])
got, err := strconv.Atoi(res)
if err != nil {
t.Fatalf("expr=%q err=%s", test.Expr, err)
} else if got != test.Want {
t.Errorf("expr=%q got=%d want=%d", test.Expr, got, test.Want)
}
}
})
}