diff --git a/_fixtures/testvariables3.go b/_fixtures/testvariables3.go new file mode 100644 index 00000000..8ce2dd80 --- /dev/null +++ b/_fixtures/testvariables3.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "runtime" +) + +func main() { + i1 := 1 + i2 := 2 + p1 := &i1 + runtime.Breakpoint() + fmt.Printf("%d %d %v\n", i1, i2, p1) +} diff --git a/proc/variables.go b/proc/variables.go index 3a2e14ba..fabb96e1 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -6,6 +6,9 @@ import ( "debug/gosym" "encoding/binary" "fmt" + "go/ast" + "go/parser" + "go/token" "strconv" "strings" "unsafe" @@ -337,6 +340,15 @@ func (scope *EvalScope) EvalVariable(name string) (*Variable, error) { return v, err } +// Sets the value of the named variable +func (scope *EvalScope) SetVariable(name, value string) error { + addr, err := scope.ExtractVariableInfo(name) + if err != nil { + return err + } + return addr.setValue(value) +} + func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, error) { rdr := scope.DwarfReader() v, err := scope.extractVarInfoFromEntry(entry, rdr) @@ -627,6 +639,27 @@ func (v *Variable) loadValueInternal(printStructName bool, recurseLevel int) (st return "", fmt.Errorf("could not find value for type %s", v.dwarfType) } +func (v *Variable) setValue(value string) error { + v = v.resolveTypedefs() + + switch t := v.dwarfType.(type) { + case *dwarf.PtrType: + return v.writeUint(false, value, int64(v.thread.dbp.arch.PtrSize())) + case *dwarf.ComplexType: + return v.writeComplex(value, t.ByteSize) + case *dwarf.IntType: + return v.writeUint(true, value, t.ByteSize) + case *dwarf.UintType: + return v.writeUint(false, value, t.ByteSize) + case *dwarf.FloatType: + return v.writeFloat(value, t.ByteSize) + case *dwarf.BoolType: + return v.writeBool(value) + default: + return fmt.Errorf("Can not set value of variables of type: %T\n", t) + } +} + func (thread *Thread) readString(addr uintptr) (string, error) { // string data structure is always two ptrs in size. Addr, followed by len // http://research.swtch.com/godata @@ -775,6 +808,81 @@ func (v *Variable) readComplex(size int64) (string, error) { return fmt.Sprintf("(%s + %si)", r, i), nil } +func (v *Variable) writeComplex(value string, size int64) error { + var real, imag float64 + + expr, err := parser.ParseExpr(value) + if err != nil { + return err + } + + var lits []*ast.BasicLit + + if e, ok := expr.(*ast.ParenExpr); ok { + expr = e.X + } + + switch e := expr.(type) { + case *ast.BinaryExpr: // " + i" or "i + " + x, xok := e.X.(*ast.BasicLit) + y, yok := e.Y.(*ast.BasicLit) + if e.Op != token.ADD || !xok || !yok { + return fmt.Errorf("Not a complex constant: %s", value) + } + lits = []*ast.BasicLit{x, y} + case *ast.CallExpr: // "complex(, )" + tname, ok := e.Fun.(*ast.Ident) + if !ok { + return fmt.Errorf("Not a complex constant: %s", value) + } + if (tname.Name != "complex64") && (tname.Name != "complex128") { + return fmt.Errorf("Not a complex constant: %s", value) + } + if len(e.Args) != 2 { + return fmt.Errorf("Not a complex constant: %s", value) + } + for i := range e.Args { + lit, ok := e.Args[i].(*ast.BasicLit) + if !ok { + return fmt.Errorf("Not a complex constant: %s", value) + } + lits = append(lits, lit) + } + lits[1].Kind = token.IMAG + lits[1].Value = lits[1].Value + "i" + case *ast.BasicLit: // "" or "i" + lits = []*ast.BasicLit{e} + default: + return fmt.Errorf("Not a complex constant: %s", value) + } + + for _, lit := range lits { + var err error + var v float64 + switch lit.Kind { + case token.FLOAT, token.INT: + v, err = strconv.ParseFloat(lit.Value, int(size/2)) + real += v + case token.IMAG: + v, err = strconv.ParseFloat(lit.Value[:len(lit.Value)-1], int(size/2)) + imag += v + default: + return fmt.Errorf("Not a complex constant: %s", value) + } + if err != nil { + return err + } + } + + err = v.writeFloatRaw(real, int64(size/2)) + if err != nil { + return err + } + imagaddr := *v + imagaddr.Addr += uintptr(size / 2) + return imagaddr.writeFloatRaw(imag, int64(size/2)) +} + func (v *Variable) readInt(size int64) (string, error) { n, err := v.thread.readIntRaw(v.Addr, size) if err != nil { @@ -813,6 +921,37 @@ func (v *Variable) readUint(size int64) (string, error) { return strconv.FormatUint(n, 10), nil } +func (v *Variable) writeUint(signed bool, value string, size int64) error { + var n uint64 + var err error + if signed { + var m int64 + m, err = strconv.ParseInt(value, 0, int(size*8)) + n = uint64(m) + } else { + n, err = strconv.ParseUint(value, 0, int(size*8)) + } + if err != nil { + return err + } + + val := make([]byte, size) + + switch size { + case 1: + val[0] = byte(n) + case 2: + binary.LittleEndian.PutUint16(val, uint16(n)) + case 4: + binary.LittleEndian.PutUint32(val, uint32(n)) + case 8: + binary.LittleEndian.PutUint64(val, uint64(n)) + } + + _, err = v.thread.writeMemory(v.Addr, val) + return err +} + func (thread *Thread) readUintRaw(addr uintptr, size int64) (uint64, error) { var n uint64 @@ -856,6 +995,30 @@ func (v *Variable) readFloat(size int64) (string, error) { return "", fmt.Errorf("could not read float") } +func (v *Variable) writeFloat(value string, size int64) error { + f, err := strconv.ParseFloat(value, int(size*8)) + if err != nil { + return err + } + return v.writeFloatRaw(f, size) +} + +func (v *Variable) writeFloatRaw(f float64, size int64) error { + buf := bytes.NewBuffer(make([]byte, 0, size)) + + switch size { + case 4: + n := float32(f) + binary.Write(buf, binary.LittleEndian, n) + case 8: + n := float64(f) + binary.Write(buf, binary.LittleEndian, n) + } + + _, err := v.thread.writeMemory(v.Addr, buf.Bytes()) + return err +} + func (v *Variable) readBool() (string, error) { val, err := v.thread.readMemory(v.Addr, 1) if err != nil { @@ -869,6 +1032,19 @@ func (v *Variable) readBool() (string, error) { return "true", nil } +func (v *Variable) writeBool(value string) error { + b, err := strconv.ParseBool(value) + if err != nil { + return err + } + val := []byte{0} + if b { + val[0] = *(*byte)(unsafe.Pointer(&b)) + } + _, err = v.thread.writeMemory(v.Addr, val) + return err +} + func (v *Variable) readFunctionPtr() (string, error) { val, err := v.thread.readMemory(v.Addr, v.thread.dbp.arch.PtrSize()) if err != nil { diff --git a/proc/variables_test.go b/proc/variables_test.go index 8ede437a..c0d303fd 100644 --- a/proc/variables_test.go +++ b/proc/variables_test.go @@ -12,6 +12,7 @@ import ( type varTest struct { name string value string + setTo string varType string err error } @@ -38,48 +39,67 @@ func evalVariable(p *Process, symbol string) (*Variable, error) { return scope.EvalVariable(symbol) } +func (tc *varTest) settable() bool { + return tc.setTo != "" +} + +func (tc *varTest) afterSet() varTest { + r := *tc + r.value = r.setTo + return r +} + +func setVariable(p *Process, symbol, value string) error { + scope, err := p.CurrentThread.Scope() + if err != nil { + return err + } + return scope.SetVariable(symbol, value) +} + const varTestBreakpointLineNumber = 59 func TestVariableEvaluation(t *testing.T) { testcases := []varTest{ - {"a1", "foofoofoofoofoofoo", "struct string", nil}, - {"a10", "ofo", "struct string", nil}, - {"a11", "[3]main.FooBar [{Baz: 1, Bur: a},{Baz: 2, Bur: b},{Baz: 3, Bur: c}]", "[3]main.FooBar", nil}, - {"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: d},{Baz: 5, Bur: e}]", "struct []main.FooBar", nil}, - {"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: f},*{Baz: 7, Bur: g},*{Baz: 8, Bur: h}]", "struct []*main.FooBar", nil}, - {"a2", "6", "int", nil}, - {"a3", "7.23", "float64", nil}, - {"a4", "[2]int [1,2]", "[2]int", nil}, - {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "struct []int", nil}, - {"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar", nil}, - {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar", nil}, - {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil}, - {"a9", "*main.FooBar nil", "*main.FooBar", nil}, - {"baz", "bazburzum", "struct string", nil}, - {"neg", "-1", "int", nil}, - {"f32", "1.2", "float32", nil}, - {"c64", "(1 + 2i)", "complex64", nil}, - {"c128", "(2 + 3i)", "complex128", nil}, - {"a6.Baz", "8", "int", nil}, - {"a7.Baz", "5", "int", nil}, - {"a8.Baz", "feh", "struct string", nil}, - {"a9.Baz", "nil", "int", fmt.Errorf("a9 is nil")}, - {"a9.NonExistent", "nil", "int", fmt.Errorf("a9 has no member NonExistent")}, - {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil}, // reread variable after member - {"i32", "[2]int32 [1,2]", "[2]int32", nil}, - {"b1", "true", "bool", nil}, - {"b2", "false", "bool", nil}, {"i8", "1", "int8", nil}, - {"u16", "65535", "uint16", nil}, - {"u32", "4294967295", "uint32", nil}, - {"u64", "18446744073709551615", "uint64", nil}, - {"u8", "255", "uint8", nil}, - {"up", "5", "uintptr", nil}, - {"f", "main.barfoo", "func()", nil}, - {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "struct []int", nil}, - {"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *main.Nest {...}}}", "main.Nest", nil}, - {"main.p1", "10", "int", nil}, - {"p1", "10", "int", nil}, - {"NonExistent", "", "", fmt.Errorf("could not find symbol value for NonExistent")}, + {"a1", "foofoofoofoofoofoo", "", "struct string", nil}, + {"a10", "ofo", "", "struct string", nil}, + {"a11", "[3]main.FooBar [{Baz: 1, Bur: a},{Baz: 2, Bur: b},{Baz: 3, Bur: c}]", "", "[3]main.FooBar", nil}, + {"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: d},{Baz: 5, Bur: e}]", "", "struct []main.FooBar", nil}, + {"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: f},*{Baz: 7, Bur: g},*{Baz: 8, Bur: h}]", "", "struct []*main.FooBar", nil}, + {"a2", "6", "10", "int", nil}, + {"a3", "7.23", "3.1", "float64", nil}, + {"a4", "[2]int [1,2]", "", "[2]int", nil}, + {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, + {"a6", "main.FooBar {Baz: 8, Bur: word}", "", "main.FooBar", nil}, + {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "", "*main.FooBar", nil}, + {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "", "main.FooBar2", nil}, + {"a9", "*main.FooBar nil", "", "*main.FooBar", nil}, + {"baz", "bazburzum", "", "struct string", nil}, + {"neg", "-1", "-20", "int", nil}, + {"f32", "1.2", "1.1", "float32", nil}, + {"c64", "(1 + 2i)", "(4 + 5i)", "complex64", nil}, + {"c128", "(2 + 3i)", "(6.3 + 7i)", "complex128", nil}, + {"a6.Baz", "8", "20", "int", nil}, + {"a7.Baz", "5", "25", "int", nil}, + {"a8.Baz", "feh", "", "struct string", nil}, + {"a9.Baz", "nil", "", "int", fmt.Errorf("a9 is nil")}, + {"a9.NonExistent", "nil", "", "int", fmt.Errorf("a9 has no member NonExistent")}, + {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "", "main.FooBar2", nil}, // reread variable after member + {"i32", "[2]int32 [1,2]", "", "[2]int32", nil}, + {"b1", "true", "false", "bool", nil}, + {"b2", "false", "true", "bool", nil}, + {"i8", "1", "2", "int8", nil}, + {"u16", "65535", "0", "uint16", nil}, + {"u32", "4294967295", "1", "uint32", nil}, + {"u64", "18446744073709551615", "2", "uint64", nil}, + {"u8", "255", "3", "uint8", nil}, + {"up", "5", "4", "uintptr", nil}, + {"f", "main.barfoo", "", "func()", nil}, + {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, + {"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *main.Nest {...}}}", "", "main.Nest", nil}, + {"main.p1", "10", "12", "int", nil}, + {"p1", "10", "13", "int", nil}, + {"NonExistent", "", "", "", fmt.Errorf("could not find symbol value for NonExistent")}, } withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { @@ -101,6 +121,18 @@ func TestVariableEvaluation(t *testing.T) { t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) } } + + if tc.settable() { + assertNoError(setVariable(p, tc.name, tc.setTo), t, "SetVariable()") + variable, err = evalVariable(p, tc.name) + assertNoError(err, t, "EvalVariable()") + assertVariable(t, variable, tc.afterSet()) + + assertNoError(setVariable(p, tc.name, tc.value), t, "SetVariable()") + variable, err := evalVariable(p, tc.name) + assertNoError(err, t, "EvalVariable()") + assertVariable(t, variable, tc) + } } }) } @@ -165,39 +197,39 @@ func TestLocalVariables(t *testing.T) { }{ {(*EvalScope).LocalVariables, []varTest{ - {"a1", "foofoofoofoofoofoo", "struct string", nil}, - {"a10", "ofo", "struct string", nil}, - {"a11", "[3]main.FooBar [{Baz: 1, Bur: a},{Baz: 2, Bur: b},{Baz: 3, Bur: c}]", "[3]main.FooBar", nil}, - {"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: d},{Baz: 5, Bur: e}]", "struct []main.FooBar", nil}, - {"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: f},*{Baz: 7, Bur: g},*{Baz: 8, Bur: h}]", "struct []*main.FooBar", nil}, - {"a2", "6", "int", nil}, - {"a3", "7.23", "float64", nil}, - {"a4", "[2]int [1,2]", "[2]int", nil}, - {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "struct []int", nil}, - {"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar", nil}, - {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar", nil}, - {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil}, - {"a9", "*main.FooBar nil", "*main.FooBar", nil}, - {"b1", "true", "bool", nil}, - {"b2", "false", "bool", nil}, - {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "struct []int", nil}, - {"c128", "(2 + 3i)", "complex128", nil}, - {"c64", "(1 + 2i)", "complex64", nil}, - {"f", "main.barfoo", "func()", nil}, - {"f32", "1.2", "float32", nil}, - {"i32", "[2]int32 [1,2]", "[2]int32", nil}, - {"i8", "1", "int8", nil}, - {"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *main.Nest {...}}}", "main.Nest", nil}, - {"neg", "-1", "int", nil}, - {"u16", "65535", "uint16", nil}, - {"u32", "4294967295", "uint32", nil}, - {"u64", "18446744073709551615", "uint64", nil}, - {"u8", "255", "uint8", nil}, - {"up", "5", "uintptr", nil}}}, + {"a1", "foofoofoofoofoofoo", "", "struct string", nil}, + {"a10", "ofo", "", "struct string", nil}, + {"a11", "[3]main.FooBar [{Baz: 1, Bur: a},{Baz: 2, Bur: b},{Baz: 3, Bur: c}]", "", "[3]main.FooBar", nil}, + {"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: d},{Baz: 5, Bur: e}]", "", "struct []main.FooBar", nil}, + {"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: f},*{Baz: 7, Bur: g},*{Baz: 8, Bur: h}]", "", "struct []*main.FooBar", nil}, + {"a2", "6", "", "int", nil}, + {"a3", "7.23", "", "float64", nil}, + {"a4", "[2]int [1,2]", "", "[2]int", nil}, + {"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil}, + {"a6", "main.FooBar {Baz: 8, Bur: word}", "", "main.FooBar", nil}, + {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "", "*main.FooBar", nil}, + {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "", "main.FooBar2", nil}, + {"a9", "*main.FooBar nil", "", "*main.FooBar", nil}, + {"b1", "true", "", "bool", nil}, + {"b2", "false", "", "bool", nil}, + {"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil}, + {"c128", "(2 + 3i)", "", "complex128", nil}, + {"c64", "(1 + 2i)", "", "complex64", nil}, + {"f", "main.barfoo", "", "func()", nil}, + {"f32", "1.2", "", "float32", nil}, + {"i32", "[2]int32 [1,2]", "", "[2]int32", nil}, + {"i8", "1", "", "int8", nil}, + {"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *main.Nest {...}}}", "", "main.Nest", nil}, + {"neg", "-1", "", "int", nil}, + {"u16", "65535", "", "uint16", nil}, + {"u32", "4294967295", "", "uint32", nil}, + {"u64", "18446744073709551615", "", "uint64", nil}, + {"u8", "255", "", "uint8", nil}, + {"up", "5", "", "uintptr", nil}}}, {(*EvalScope).FunctionArguments, []varTest{ - {"bar", "main.FooBar {Baz: 10, Bur: lorem}", "main.FooBar", nil}, - {"baz", "bazburzum", "struct string", nil}}}, + {"bar", "main.FooBar {Baz: 10, Bur: lorem}", "", "main.FooBar", nil}, + {"baz", "bazburzum", "", "struct string", nil}}}, } withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { @@ -302,3 +334,57 @@ func TestFrameEvaluation(t *testing.T) { } }) } + +func TestComplexSetting(t *testing.T) { + withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) { + pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber) + + _, err := p.SetBreakpoint(pc) + assertNoError(err, t, "SetBreakpoint() returned an error") + + err = p.Continue() + assertNoError(err, t, "Continue() returned an error") + + h := func(setExpr, value string) { + assertNoError(setVariable(p, "c128", setExpr), t, "SetVariable()") + variable, err := evalVariable(p, "c128") + assertNoError(err, t, "EvalVariable()") + if variable.Value != value { + t.Fatalf("Wrong value of c128: \"%s\", expected \"%s\" after setting it to \"%s\"", variable.Value, value, setExpr) + } + } + + h("3.2i", "(0 + 3.2i)") + h("1.1", "(1.1 + 0i)") + h("1 + 3.3i", "(1 + 3.3i)") + h("complex128(1.2, 3.4)", "(1.2 + 3.4i)") + }) +} + +func TestPointerSetting(t *testing.T) { + withTestProcess("testvariables3", t, func(p *Process, fixture protest.Fixture) { + assertNoError(p.Continue(), t, "Continue() returned an error") + + pval := func(value string) { + variable, err := evalVariable(p, "p1") + assertNoError(err, t, "EvalVariable()") + if variable.Value != value { + t.Fatalf("Wrong value of p1, \"%s\" expected \"%s\"", variable.Value, value) + } + } + + pval("*1") + + // change p1 to point to i2 + scope, err := p.CurrentThread.Scope() + assertNoError(err, t, "Scope()") + i2addr, err := scope.ExtractVariableInfo("i2") + assertNoError(err, t, "EvalVariableAddr()") + assertNoError(setVariable(p, "p1", strconv.Itoa(int(i2addr.Addr))), t, "SetVariable()") + pval("*2") + + // change the value of i2 check that p1 also changes + assertNoError(setVariable(p, "i2", "5"), t, "SetVariable()") + pval("*5") + }) +} diff --git a/service/client.go b/service/client.go index 410993e3..28c937f1 100644 --- a/service/client.go +++ b/service/client.go @@ -53,6 +53,9 @@ type Client interface { // ListPackageVariablesFor lists all package variables in the context of a thread. ListPackageVariablesFor(threadID int, filter string) ([]api.Variable, error) + // SetVariable sets the value of a variable + SetVariable(scope api.EvalScope, symbol, value string) error + // ListSources lists all source files in the process matching filter. ListSources(filter string) ([]string, error) // ListFunctions lists all functions in the process matching filter. diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 41079591..4b9c9f1f 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -442,6 +442,14 @@ func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api return &converted, err } +func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol, value string) error { + s, err := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame) + if err != nil { + return err + } + return s.SetVariable(symbol, value) +} + func (d *Debugger) Goroutines() ([]*api.Goroutine, error) { goroutines := []*api.Goroutine{} gs, err := d.process.GoroutinesInfo() diff --git a/service/rpc/client.go b/service/rpc/client.go index 5561cc6b..32a35bcb 100644 --- a/service/rpc/client.go +++ b/service/rpc/client.go @@ -155,6 +155,11 @@ func (c *RPCClient) EvalVariable(scope api.EvalScope, symbol string) (*api.Varia return v, err } +func (c *RPCClient) SetVariable(scope api.EvalScope, symbol, value string) error { + var unused int + return c.call("SetSymbol", SetSymbolArgs{scope, symbol, value}, &unused) +} + func (c *RPCClient) ListSources(filter string) ([]string, error) { var sources []string err := c.call("ListSources", filter, &sources) diff --git a/service/rpc/server.go b/service/rpc/server.go index 242419af..ef209481 100644 --- a/service/rpc/server.go +++ b/service/rpc/server.go @@ -249,6 +249,17 @@ func (s *RPCServer) EvalSymbol(args EvalSymbolArgs, variable *api.Variable) erro return nil } +type SetSymbolArgs struct { + Scope api.EvalScope + Symbol string + Value string +} + +func (s *RPCServer) SetSymbol(args SetSymbolArgs, unused *int) error { + *unused = 0 + return s.debugger.SetVariableInScope(args.Scope, args.Symbol, args.Value) +} + func (s *RPCServer) ListSources(filter string, sources *[]string) error { ss, err := s.debugger.Sources(filter) if err != nil { diff --git a/service/test/integration_test.go b/service/test/integration_test.go index bd9901e1..e5e2abdb 100644 --- a/service/test/integration_test.go +++ b/service/test/integration_test.go @@ -629,7 +629,31 @@ func TestClientServer_EvalVariable(t *testing.T) { t.Logf("var1: <%s>", var1.Value) if var1.Value != "foofoofoofoofoofoo" { - t.Fatalf("Wrong variable value (EvalVariable)", var1.Value) + t.Fatalf("Wrong variable value: %v", var1.Value) + } + }) +} + +func TestClientServer_SetVariable(t *testing.T) { + withTestClient("testvariables", t, func(c service.Client) { + fp := testProgPath(t, "testvariables") + _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 59}) + assertNoError(err, t, "CreateBreakpoint()") + + state := <-c.Continue() + + if state.Err != nil { + t.Fatalf("Continue(): %v\n", state.Err) + } + + assertNoError(c.SetVariable(api.EvalScope{ -1, 0 }, "a2", "8"), t, "SetVariable()") + + a2, err := c.EvalVariable(api.EvalScope{ -1, 0 }, "a2") + + t.Logf("a2: <%s>", a2.Value) + + if a2.Value != "8" { + t.Fatalf("Wrong variable value: %v", a2.Value) } }) } diff --git a/terminal/command.go b/terminal/command.go index aa66fe0d..6860c7c7 100644 --- a/terminal/command.go +++ b/terminal/command.go @@ -67,6 +67,7 @@ func DebugCommands(client service.Client) *Commands { {aliases: []string{"goroutine"}, cmdFn: goroutine, helpMsg: "Sets current goroutine."}, {aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."}, {aliases: []string{"print", "p"}, cmdFn: g0f0(printVar), helpMsg: "Evaluate a variable."}, + {aliases: []string{"set"}, cmdFn: g0f0(setVar), helpMsg: "Changes the value of a variable."}, {aliases: []string{"sources"}, cmdFn: filterSortAndOutput(sources), helpMsg: "Print list of source files, optionally filtered by a regexp."}, {aliases: []string{"funcs"}, cmdFn: filterSortAndOutput(funcs), helpMsg: "Print list of functions, optionally filtered by a regexp."}, {aliases: []string{"args"}, cmdFn: filterSortAndOutput(g0f0filter(args)), helpMsg: "Print function arguments, optionally filtered by a regexp."}, @@ -557,6 +558,14 @@ func printVar(client service.Client, scope api.EvalScope, args ...string) error return nil } +func setVar(client service.Client, scope api.EvalScope, args ...string) error { + if len(args) != 2 { + return fmt.Errorf("wrong number of arguments") + } + + return client.SetVariable(scope, args[0], args[1]) +} + func filterVariables(vars []api.Variable, filter string) []string { reg, err := regexp.Compile(filter) if err != nil {