github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/service/rpc2/client.go (about) 1 package rpc2 2 3 import ( 4 "fmt" 5 "log" 6 "net" 7 "net/rpc" 8 "net/rpc/jsonrpc" 9 "time" 10 11 "github.com/go-delve/delve/service" 12 "github.com/go-delve/delve/service/api" 13 ) 14 15 // RPCClient is a RPC service.Client. 16 type RPCClient struct { 17 client *rpc.Client 18 19 retValLoadCfg *api.LoadConfig 20 } 21 22 // Ensure the implementation satisfies the interface. 23 var _ service.Client = &RPCClient{} 24 25 // NewClient creates a new RPCClient. 26 func NewClient(addr string) *RPCClient { 27 client, err := jsonrpc.Dial("tcp", addr) 28 if err != nil { 29 log.Fatal("dialing:", err) 30 } 31 return newFromRPCClient(client) 32 } 33 34 func newFromRPCClient(client *rpc.Client) *RPCClient { 35 c := &RPCClient{client: client} 36 c.call("SetApiVersion", api.SetAPIVersionIn{APIVersion: 2}, &api.SetAPIVersionOut{}) 37 return c 38 } 39 40 // NewClientFromConn creates a new RPCClient from the given connection. 41 func NewClientFromConn(conn net.Conn) *RPCClient { 42 return newFromRPCClient(jsonrpc.NewClient(conn)) 43 } 44 45 func (c *RPCClient) ProcessPid() int { 46 out := new(ProcessPidOut) 47 c.call("ProcessPid", ProcessPidIn{}, out) 48 return out.Pid 49 } 50 51 func (c *RPCClient) BuildID() string { 52 out := new(BuildIDOut) 53 c.call("BuildID", BuildIDIn{}, out) 54 return out.BuildID 55 } 56 57 func (c *RPCClient) LastModified() time.Time { 58 out := new(LastModifiedOut) 59 c.call("LastModified", LastModifiedIn{}, out) 60 return out.Time 61 } 62 63 func (c *RPCClient) Detach(kill bool) error { 64 defer c.client.Close() 65 out := new(DetachOut) 66 return c.call("Detach", DetachIn{kill}, out) 67 } 68 69 func (c *RPCClient) Restart(rebuild bool) ([]api.DiscardedBreakpoint, error) { 70 out := new(RestartOut) 71 err := c.call("Restart", RestartIn{"", false, nil, false, rebuild, [3]string{}}, out) 72 return out.DiscardedBreakpoints, err 73 } 74 75 func (c *RPCClient) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, newRedirects [3]string, rebuild bool) ([]api.DiscardedBreakpoint, error) { 76 out := new(RestartOut) 77 err := c.call("Restart", RestartIn{pos, resetArgs, newArgs, rerecord, rebuild, newRedirects}, out) 78 return out.DiscardedBreakpoints, err 79 } 80 81 func (c *RPCClient) GetState() (*api.DebuggerState, error) { 82 var out StateOut 83 err := c.call("State", StateIn{NonBlocking: false}, &out) 84 return out.State, err 85 } 86 87 func (c *RPCClient) GetStateNonBlocking() (*api.DebuggerState, error) { 88 var out StateOut 89 err := c.call("State", StateIn{NonBlocking: true}, &out) 90 return out.State, err 91 } 92 93 func (c *RPCClient) Continue() <-chan *api.DebuggerState { 94 return c.continueDir(api.Continue) 95 } 96 97 func (c *RPCClient) Rewind() <-chan *api.DebuggerState { 98 return c.continueDir(api.Rewind) 99 } 100 101 func (c *RPCClient) DirectionCongruentContinue() <-chan *api.DebuggerState { 102 return c.continueDir(api.DirectionCongruentContinue) 103 } 104 105 func (c *RPCClient) continueDir(cmd string) <-chan *api.DebuggerState { 106 ch := make(chan *api.DebuggerState) 107 go func() { 108 for { 109 out := new(CommandOut) 110 err := c.call("Command", &api.DebuggerCommand{Name: cmd, ReturnInfoLoadConfig: c.retValLoadCfg}, &out) 111 state := out.State 112 if err != nil { 113 state.Err = err 114 } 115 if state.Exited { 116 // Error types apparently cannot be marshalled by Go correctly. Must reset error here. 117 //lint:ignore ST1005 backwards compatibility 118 state.Err = fmt.Errorf("Process %d has exited with status %d", c.ProcessPid(), state.ExitStatus) 119 } 120 ch <- &state 121 if err != nil || state.Exited { 122 close(ch) 123 return 124 } 125 126 isbreakpoint := false 127 istracepoint := true 128 for i := range state.Threads { 129 if state.Threads[i].Breakpoint != nil { 130 isbreakpoint = true 131 istracepoint = istracepoint && (state.Threads[i].Breakpoint.Tracepoint || state.Threads[i].Breakpoint.TraceReturn) 132 } 133 } 134 135 if !isbreakpoint || !istracepoint { 136 close(ch) 137 return 138 } 139 } 140 }() 141 return ch 142 } 143 144 func (c *RPCClient) Next() (*api.DebuggerState, error) { 145 var out CommandOut 146 err := c.call("Command", api.DebuggerCommand{Name: api.Next, ReturnInfoLoadConfig: c.retValLoadCfg}, &out) 147 return &out.State, err 148 } 149 150 func (c *RPCClient) ReverseNext() (*api.DebuggerState, error) { 151 var out CommandOut 152 err := c.call("Command", api.DebuggerCommand{Name: api.ReverseNext, ReturnInfoLoadConfig: c.retValLoadCfg}, &out) 153 return &out.State, err 154 } 155 156 func (c *RPCClient) Step() (*api.DebuggerState, error) { 157 var out CommandOut 158 err := c.call("Command", api.DebuggerCommand{Name: api.Step, ReturnInfoLoadConfig: c.retValLoadCfg}, &out) 159 return &out.State, err 160 } 161 162 func (c *RPCClient) ReverseStep() (*api.DebuggerState, error) { 163 var out CommandOut 164 err := c.call("Command", api.DebuggerCommand{Name: api.ReverseStep, ReturnInfoLoadConfig: c.retValLoadCfg}, &out) 165 return &out.State, err 166 } 167 168 func (c *RPCClient) StepOut() (*api.DebuggerState, error) { 169 var out CommandOut 170 err := c.call("Command", api.DebuggerCommand{Name: api.StepOut, ReturnInfoLoadConfig: c.retValLoadCfg}, &out) 171 return &out.State, err 172 } 173 174 func (c *RPCClient) ReverseStepOut() (*api.DebuggerState, error) { 175 var out CommandOut 176 err := c.call("Command", api.DebuggerCommand{Name: api.ReverseStepOut, ReturnInfoLoadConfig: c.retValLoadCfg}, &out) 177 return &out.State, err 178 } 179 180 func (c *RPCClient) Call(goroutineID int64, expr string, unsafe bool) (*api.DebuggerState, error) { 181 var out CommandOut 182 err := c.call("Command", api.DebuggerCommand{Name: api.Call, ReturnInfoLoadConfig: c.retValLoadCfg, Expr: expr, UnsafeCall: unsafe, GoroutineID: goroutineID}, &out) 183 return &out.State, err 184 } 185 186 func (c *RPCClient) StepInstruction() (*api.DebuggerState, error) { 187 var out CommandOut 188 err := c.call("Command", api.DebuggerCommand{Name: api.StepInstruction}, &out) 189 return &out.State, err 190 } 191 192 func (c *RPCClient) ReverseStepInstruction() (*api.DebuggerState, error) { 193 var out CommandOut 194 err := c.call("Command", api.DebuggerCommand{Name: api.ReverseStepInstruction}, &out) 195 return &out.State, err 196 } 197 198 func (c *RPCClient) SwitchThread(threadID int) (*api.DebuggerState, error) { 199 var out CommandOut 200 cmd := api.DebuggerCommand{ 201 Name: api.SwitchThread, 202 ThreadID: threadID, 203 } 204 err := c.call("Command", cmd, &out) 205 return &out.State, err 206 } 207 208 func (c *RPCClient) SwitchGoroutine(goroutineID int64) (*api.DebuggerState, error) { 209 var out CommandOut 210 cmd := api.DebuggerCommand{ 211 Name: api.SwitchGoroutine, 212 GoroutineID: goroutineID, 213 } 214 err := c.call("Command", cmd, &out) 215 return &out.State, err 216 } 217 218 func (c *RPCClient) Halt() (*api.DebuggerState, error) { 219 var out CommandOut 220 err := c.call("Command", api.DebuggerCommand{Name: api.Halt}, &out) 221 return &out.State, err 222 } 223 224 func (c *RPCClient) GetBufferedTracepoints() ([]api.TracepointResult, error) { 225 var out GetBufferedTracepointsOut 226 err := c.call("GetBufferedTracepoints", GetBufferedTracepointsIn{}, &out) 227 return out.TracepointResults, err 228 } 229 230 func (c *RPCClient) GetBreakpoint(id int) (*api.Breakpoint, error) { 231 var out GetBreakpointOut 232 err := c.call("GetBreakpoint", GetBreakpointIn{id, ""}, &out) 233 return &out.Breakpoint, err 234 } 235 236 func (c *RPCClient) GetBreakpointByName(name string) (*api.Breakpoint, error) { 237 var out GetBreakpointOut 238 err := c.call("GetBreakpoint", GetBreakpointIn{0, name}, &out) 239 return &out.Breakpoint, err 240 } 241 242 // CreateBreakpoint will send a request to the RPC server to create a breakpoint. 243 // Please refer to the documentation for `Debugger.CreateBreakpoint` for a description of how 244 // the requested breakpoint parameters are interpreted and used: 245 // https://pkg.go.dev/github.com/go-delve/delve/service/debugger#Debugger.CreateBreakpoint 246 func (c *RPCClient) CreateBreakpoint(breakPoint *api.Breakpoint) (*api.Breakpoint, error) { 247 var out CreateBreakpointOut 248 err := c.call("CreateBreakpoint", CreateBreakpointIn{*breakPoint, "", nil, false}, &out) 249 return &out.Breakpoint, err 250 } 251 252 // CreateBreakpointWithExpr is like CreateBreakpoint but will also set a 253 // location expression to be used to restore the breakpoint after it is 254 // disabled. 255 func (c *RPCClient) CreateBreakpointWithExpr(breakPoint *api.Breakpoint, locExpr string, substitutePathRules [][2]string, suspended bool) (*api.Breakpoint, error) { 256 var out CreateBreakpointOut 257 err := c.call("CreateBreakpoint", CreateBreakpointIn{*breakPoint, locExpr, substitutePathRules, suspended}, &out) 258 return &out.Breakpoint, err 259 } 260 261 func (c *RPCClient) CreateEBPFTracepoint(fnName string) error { 262 var out CreateEBPFTracepointOut 263 return c.call("CreateEBPFTracepoint", CreateEBPFTracepointIn{FunctionName: fnName}, &out) 264 } 265 266 func (c *RPCClient) CreateWatchpoint(scope api.EvalScope, expr string, wtype api.WatchType) (*api.Breakpoint, error) { 267 var out CreateWatchpointOut 268 err := c.call("CreateWatchpoint", CreateWatchpointIn{scope, expr, wtype}, &out) 269 return out.Breakpoint, err 270 } 271 272 func (c *RPCClient) ListBreakpoints(all bool) ([]*api.Breakpoint, error) { 273 var out ListBreakpointsOut 274 err := c.call("ListBreakpoints", ListBreakpointsIn{all}, &out) 275 return out.Breakpoints, err 276 } 277 278 func (c *RPCClient) ClearBreakpoint(id int) (*api.Breakpoint, error) { 279 var out ClearBreakpointOut 280 err := c.call("ClearBreakpoint", ClearBreakpointIn{id, ""}, &out) 281 return out.Breakpoint, err 282 } 283 284 func (c *RPCClient) ClearBreakpointByName(name string) (*api.Breakpoint, error) { 285 var out ClearBreakpointOut 286 err := c.call("ClearBreakpoint", ClearBreakpointIn{0, name}, &out) 287 return out.Breakpoint, err 288 } 289 290 func (c *RPCClient) ToggleBreakpoint(id int) (*api.Breakpoint, error) { 291 var out ToggleBreakpointOut 292 err := c.call("ToggleBreakpoint", ToggleBreakpointIn{id, ""}, &out) 293 return out.Breakpoint, err 294 } 295 296 func (c *RPCClient) ToggleBreakpointByName(name string) (*api.Breakpoint, error) { 297 var out ToggleBreakpointOut 298 err := c.call("ToggleBreakpoint", ToggleBreakpointIn{0, name}, &out) 299 return out.Breakpoint, err 300 } 301 302 func (c *RPCClient) AmendBreakpoint(bp *api.Breakpoint) error { 303 out := new(AmendBreakpointOut) 304 err := c.call("AmendBreakpoint", AmendBreakpointIn{*bp}, out) 305 return err 306 } 307 308 func (c *RPCClient) CancelNext() error { 309 var out CancelNextOut 310 return c.call("CancelNext", CancelNextIn{}, &out) 311 } 312 313 func (c *RPCClient) ListThreads() ([]*api.Thread, error) { 314 var out ListThreadsOut 315 err := c.call("ListThreads", ListThreadsIn{}, &out) 316 return out.Threads, err 317 } 318 319 func (c *RPCClient) GetThread(id int) (*api.Thread, error) { 320 var out GetThreadOut 321 err := c.call("GetThread", GetThreadIn{id}, &out) 322 return out.Thread, err 323 } 324 325 func (c *RPCClient) EvalVariable(scope api.EvalScope, expr string, cfg api.LoadConfig) (*api.Variable, error) { 326 var out EvalOut 327 err := c.call("Eval", EvalIn{scope, expr, &cfg}, &out) 328 return out.Variable, err 329 } 330 331 func (c *RPCClient) SetVariable(scope api.EvalScope, symbol, value string) error { 332 out := new(SetOut) 333 return c.call("Set", SetIn{scope, symbol, value}, out) 334 } 335 336 func (c *RPCClient) ListSources(filter string) ([]string, error) { 337 sources := new(ListSourcesOut) 338 err := c.call("ListSources", ListSourcesIn{filter}, sources) 339 return sources.Sources, err 340 } 341 342 func (c *RPCClient) ListFunctions(filter string) ([]string, error) { 343 funcs := new(ListFunctionsOut) 344 err := c.call("ListFunctions", ListFunctionsIn{filter}, funcs) 345 return funcs.Funcs, err 346 } 347 348 func (c *RPCClient) ListTypes(filter string) ([]string, error) { 349 types := new(ListTypesOut) 350 err := c.call("ListTypes", ListTypesIn{filter}, types) 351 return types.Types, err 352 } 353 354 func (c *RPCClient) ListPackageVariables(filter string, cfg api.LoadConfig) ([]api.Variable, error) { 355 var out ListPackageVarsOut 356 err := c.call("ListPackageVars", ListPackageVarsIn{filter, cfg}, &out) 357 return out.Variables, err 358 } 359 360 func (c *RPCClient) ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) { 361 var out ListLocalVarsOut 362 err := c.call("ListLocalVars", ListLocalVarsIn{scope, cfg}, &out) 363 return out.Variables, err 364 } 365 366 func (c *RPCClient) ListThreadRegisters(threadID int, includeFp bool) (api.Registers, error) { 367 out := new(ListRegistersOut) 368 err := c.call("ListRegisters", ListRegistersIn{ThreadID: threadID, IncludeFp: includeFp, Scope: nil}, out) 369 return out.Regs, err 370 } 371 372 func (c *RPCClient) ListScopeRegisters(scope api.EvalScope, includeFp bool) (api.Registers, error) { 373 out := new(ListRegistersOut) 374 err := c.call("ListRegisters", ListRegistersIn{ThreadID: 0, IncludeFp: includeFp, Scope: &scope}, out) 375 return out.Regs, err 376 } 377 378 func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) { 379 var out ListFunctionArgsOut 380 err := c.call("ListFunctionArgs", ListFunctionArgsIn{scope, cfg}, &out) 381 return out.Args, err 382 } 383 384 func (c *RPCClient) ListGoroutines(start, count int) ([]*api.Goroutine, int, error) { 385 var out ListGoroutinesOut 386 err := c.call("ListGoroutines", ListGoroutinesIn{start, count, nil, api.GoroutineGroupingOptions{}, nil}, &out) 387 return out.Goroutines, out.Nextg, err 388 } 389 390 func (c *RPCClient) ListGoroutinesWithFilter(start, count int, filters []api.ListGoroutinesFilter, group *api.GoroutineGroupingOptions, scope *api.EvalScope) ([]*api.Goroutine, []api.GoroutineGroup, int, bool, error) { 391 if group == nil { 392 group = &api.GoroutineGroupingOptions{} 393 } 394 var out ListGoroutinesOut 395 err := c.call("ListGoroutines", ListGoroutinesIn{start, count, filters, *group, scope}, &out) 396 return out.Goroutines, out.Groups, out.Nextg, out.TooManyGroups, err 397 } 398 399 func (c *RPCClient) Stacktrace(goroutineId int64, depth int, opts api.StacktraceOptions, cfg *api.LoadConfig) ([]api.Stackframe, error) { 400 var out StacktraceOut 401 err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, false, false, opts, cfg}, &out) 402 return out.Locations, err 403 } 404 405 func (c *RPCClient) Ancestors(goroutineID int64, numAncestors int, depth int) ([]api.Ancestor, error) { 406 var out AncestorsOut 407 err := c.call("Ancestors", AncestorsIn{goroutineID, numAncestors, depth}, &out) 408 return out.Ancestors, err 409 } 410 411 func (c *RPCClient) AttachedToExistingProcess() bool { 412 out := new(AttachedToExistingProcessOut) 413 c.call("AttachedToExistingProcess", AttachedToExistingProcessIn{}, out) 414 return out.Answer 415 } 416 417 func (c *RPCClient) FindLocation(scope api.EvalScope, loc string, findInstructions bool, substitutePathRules [][2]string) ([]api.Location, string, error) { 418 var out FindLocationOut 419 err := c.call("FindLocation", FindLocationIn{scope, loc, !findInstructions, substitutePathRules}, &out) 420 return out.Locations, out.SubstituteLocExpr, err 421 } 422 423 // DisassembleRange disassembles code between startPC and endPC 424 func (c *RPCClient) DisassembleRange(scope api.EvalScope, startPC, endPC uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error) { 425 var out DisassembleOut 426 err := c.call("Disassemble", DisassembleIn{scope, startPC, endPC, flavour}, &out) 427 return out.Disassemble, err 428 } 429 430 // DisassemblePC disassembles function containing pc 431 func (c *RPCClient) DisassemblePC(scope api.EvalScope, pc uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error) { 432 var out DisassembleOut 433 err := c.call("Disassemble", DisassembleIn{scope, pc, 0, flavour}, &out) 434 return out.Disassemble, err 435 } 436 437 // Recorded returns true if the debugger target is a recording. 438 func (c *RPCClient) Recorded() bool { 439 out := new(RecordedOut) 440 c.call("Recorded", RecordedIn{}, out) 441 return out.Recorded 442 } 443 444 // TraceDirectory returns the path to the trace directory for a recording. 445 func (c *RPCClient) TraceDirectory() (string, error) { 446 var out RecordedOut 447 err := c.call("Recorded", RecordedIn{}, &out) 448 return out.TraceDirectory, err 449 } 450 451 // Checkpoint sets a checkpoint at the current position. 452 func (c *RPCClient) Checkpoint(where string) (checkpointID int, err error) { 453 var out CheckpointOut 454 err = c.call("Checkpoint", CheckpointIn{where}, &out) 455 return out.ID, err 456 } 457 458 // ListCheckpoints gets all checkpoints. 459 func (c *RPCClient) ListCheckpoints() ([]api.Checkpoint, error) { 460 var out ListCheckpointsOut 461 err := c.call("ListCheckpoints", ListCheckpointsIn{}, &out) 462 return out.Checkpoints, err 463 } 464 465 // ClearCheckpoint removes a checkpoint 466 func (c *RPCClient) ClearCheckpoint(id int) error { 467 var out ClearCheckpointOut 468 err := c.call("ClearCheckpoint", ClearCheckpointIn{id}, &out) 469 return err 470 } 471 472 func (c *RPCClient) SetReturnValuesLoadConfig(cfg *api.LoadConfig) { 473 c.retValLoadCfg = cfg 474 } 475 476 func (c *RPCClient) FunctionReturnLocations(fnName string) ([]uint64, error) { 477 var out FunctionReturnLocationsOut 478 err := c.call("FunctionReturnLocations", FunctionReturnLocationsIn{fnName}, &out) 479 return out.Addrs, err 480 } 481 482 func (c *RPCClient) IsMulticlient() bool { 483 var out IsMulticlientOut 484 c.call("IsMulticlient", IsMulticlientIn{}, &out) 485 return out.IsMulticlient 486 } 487 488 func (c *RPCClient) Disconnect(cont bool) error { 489 if cont { 490 out := new(CommandOut) 491 c.client.Go("RPCServer.Command", &api.DebuggerCommand{Name: api.Continue, ReturnInfoLoadConfig: c.retValLoadCfg}, &out, nil) 492 } 493 return c.client.Close() 494 } 495 496 func (c *RPCClient) ListDynamicLibraries() ([]api.Image, error) { 497 var out ListDynamicLibrariesOut 498 c.call("ListDynamicLibraries", ListDynamicLibrariesIn{}, &out) 499 return out.List, nil 500 } 501 502 func (c *RPCClient) ExamineMemory(address uint64, count int) ([]byte, bool, error) { 503 out := &ExaminedMemoryOut{} 504 505 err := c.call("ExamineMemory", ExamineMemoryIn{Length: count, Address: address}, out) 506 if err != nil { 507 return nil, false, err 508 } 509 return out.Mem, out.IsLittleEndian, nil 510 } 511 512 func (c *RPCClient) StopRecording() error { 513 return c.call("StopRecording", StopRecordingIn{}, &StopRecordingOut{}) 514 } 515 516 func (c *RPCClient) CoreDumpStart(dest string) (api.DumpState, error) { 517 out := &DumpStartOut{} 518 err := c.call("DumpStart", DumpStartIn{Destination: dest}, out) 519 return out.State, err 520 } 521 522 func (c *RPCClient) CoreDumpWait(msec int) api.DumpState { 523 out := &DumpWaitOut{} 524 _ = c.call("DumpWait", DumpWaitIn{Wait: msec}, out) 525 return out.State 526 } 527 528 func (c *RPCClient) CoreDumpCancel() error { 529 out := &DumpCancelOut{} 530 return c.call("DumpCancel", DumpCancelIn{}, out) 531 } 532 533 // ListTargets returns the current list of debug targets. 534 func (c *RPCClient) ListTargets() ([]api.Target, error) { 535 out := &ListTargetsOut{} 536 err := c.call("ListTargets", ListTargetsIn{}, out) 537 return out.Targets, err 538 } 539 540 // FollowExec enabled or disabled follow exec mode. When follow exec is 541 // enabled Delve will automatically attach to new subprocesses with a 542 // command line matched by regex, if regex is nil all new subprocesses are 543 // automatically debugged. 544 func (c *RPCClient) FollowExec(v bool, regex string) error { 545 out := &FollowExecOut{} 546 err := c.call("FollowExec", FollowExecIn{Enable: v, Regex: regex}, out) 547 return err 548 } 549 550 // FollowExecEnabled returns true if follow exec mode is enabled. 551 func (c *RPCClient) FollowExecEnabled() bool { 552 out := &FollowExecEnabledOut{} 553 _ = c.call("FollowExecEnabled", FollowExecEnabledIn{}, out) 554 return out.Enabled 555 } 556 557 func (c *RPCClient) SetDebugInfoDirectories(v []string) error { 558 return c.call("DebugInfoDirectories", DebugInfoDirectoriesIn{Set: true, List: v}, &DebugInfoDirectoriesOut{}) 559 } 560 561 func (c *RPCClient) GetDebugInfoDirectories() ([]string, error) { 562 out := &DebugInfoDirectoriesOut{} 563 err := c.call("DebugInfoDirectories", DebugInfoDirectoriesIn{Set: false, List: nil}, out) 564 return out.List, err 565 } 566 567 func (c *RPCClient) call(method string, args, reply interface{}) error { 568 return c.client.Call("RPCServer."+method, args, reply) 569 } 570 571 func (c *RPCClient) CallAPI(method string, args, reply interface{}) error { 572 return c.call(method, args, reply) 573 }