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: Examine memory:
examinemem [-fmt <format>] [-count|-len <count>] [-size <size>] <address> 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). 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. 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'. 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: 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: x 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: {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>] <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). 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. 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'. 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: 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. {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() return cmd.Run()
} }
func examineMemoryCmd(t *Term, ctx callContext, args string) error { func examineMemoryCmd(t *Term, ctx callContext, argstr string) error {
v := strings.FieldsFunc(args, func(c rune) bool {
return c == ' '
})
var ( var (
address uint64 address uint64
err error err error
ok bool ok bool
args = strings.Split(argstr, " ")
) )
// Default value // Default value
priFmt := byte('x') priFmt := byte('x')
count := 1 count := 1
size := 1 size := 1
isExpr := false
for i := 0; i < len(v); i++ { // nextArg returns the next argument that is not an empty string, if any, and
switch v[i] { // 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": case "-fmt":
i++ arg := nextArg()
if i >= len(v) { if arg == "" {
return fmt.Errorf("expected argument after -fmt") return fmt.Errorf("expected argument after -fmt")
} }
fmtMapToPriFmt := map[string]byte{ fmtMapToPriFmt := map[string]byte{
@ -1692,39 +1712,39 @@ func examineMemoryCmd(t *Term, ctx callContext, args string) error {
"bin": 'b', "bin": 'b',
"binary": 'b', "binary": 'b',
} }
priFmt, ok = fmtMapToPriFmt[v[i]] priFmt, ok = fmtMapToPriFmt[arg]
if !ok { 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": case "-count", "-len":
i++ arg := nextArg()
if i >= len(v) { if arg == "" {
return fmt.Errorf("expected argument after -count/-len") return fmt.Errorf("expected argument after -count/-len")
} }
var err error var err error
count, err = strconv.Atoi(v[i]) count, err = strconv.Atoi(arg)
if err != nil || count <= 0 { if err != nil || count <= 0 {
return fmt.Errorf("count/len must be a positive integer") return fmt.Errorf("count/len must be a positive integer")
} }
case "-size": case "-size":
i++ arg := nextArg()
if i >= len(v) { if arg == "" {
return fmt.Errorf("expected argument after -size") return fmt.Errorf("expected argument after -size")
} }
var err error var err error
size, err = strconv.Atoi(v[i]) size, err = strconv.Atoi(arg)
if err != nil || size <= 0 || size > 8 { if err != nil || size <= 0 || size > 8 {
return fmt.Errorf("size must be a positive integer (<=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: default:
if i != len(v)-1 { if len(args) > 0 {
return fmt.Errorf("unknown option %q", v[i]) return fmt.Errorf("unknown option %q", args[0])
}
// 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)
} }
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") 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") 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) memArea, isLittleEndian, err := t.client.ExamineMemory(address, count*size)
if err != nil { if err != nil {
return err return err

@ -1029,6 +1029,66 @@ func TestExamineMemoryCmd(t *testing.T) {
if !strings.Contains(res, firstLine) { if !strings.Contains(res, firstLine) {
t.Fatalf("expected first line: %s", 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)
}
}
}) })
} }