Introduce JSON-RPC service
This commit is contained in:
parent
5642e0a106
commit
687dc4172d
4
Makefile
4
Makefile
@ -31,7 +31,7 @@ ifeq "$(CERT)" ""
|
|||||||
endif
|
endif
|
||||||
go test $(PREFIX)/terminal $(PREFIX)/dwarf/frame $(PREFIX)/dwarf/op $(PREFIX)/dwarf/util $(PREFIX)/source $(PREFIX)/dwarf/line
|
go test $(PREFIX)/terminal $(PREFIX)/dwarf/frame $(PREFIX)/dwarf/op $(PREFIX)/dwarf/util $(PREFIX)/source $(PREFIX)/dwarf/line
|
||||||
go test -c $(PREFIX)/proc && codesign -s $(CERT) ./proc.test && ./proc.test $(TESTFLAGS) && rm ./proc.test
|
go test -c $(PREFIX)/proc && codesign -s $(CERT) ./proc.test && ./proc.test $(TESTFLAGS) && rm ./proc.test
|
||||||
go test -c $(PREFIX)/service/rest && codesign -s $(CERT) ./rest.test && ./rest.test $(TESTFLAGS) && rm ./rest.test
|
go test -c $(PREFIX)/service/test && codesign -s $(CERT) ./test.test && ./test.test $(TESTFLAGS) && rm ./test.test
|
||||||
else
|
else
|
||||||
go test -v ./...
|
go test -v ./...
|
||||||
endif
|
endif
|
||||||
@ -51,7 +51,7 @@ ifeq "$(UNAME)" "Darwin"
|
|||||||
ifeq "$(CERT)" ""
|
ifeq "$(CERT)" ""
|
||||||
$(error You must provide a CERT env var)
|
$(error You must provide a CERT env var)
|
||||||
endif
|
endif
|
||||||
go test -c $(PREFIX)/service/rest && codesign -s $(CERT) ./rest.test && ./rest.test -test.run $(RUN) && rm ./rest.test
|
go test -c $(PREFIX)/service/test && codesign -s $(CERT) ./test.test && ./test.test -test.run $(RUN) && rm ./test.test
|
||||||
else
|
else
|
||||||
go test $(PREFIX)/service/rest -run $(RUN)
|
go test $(PREFIX)/service/rest -run $(RUN)
|
||||||
endif
|
endif
|
||||||
|
@ -13,7 +13,7 @@ func anotherthread(wg *sync.WaitGroup) {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i := 0; i < 100000; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go anotherthread(&wg)
|
go anotherthread(&wg)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,9 @@ import (
|
|||||||
|
|
||||||
sys "golang.org/x/sys/unix"
|
sys "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"github.com/derekparker/delve/service"
|
||||||
"github.com/derekparker/delve/service/rest"
|
"github.com/derekparker/delve/service/rest"
|
||||||
|
"github.com/derekparker/delve/service/rpc"
|
||||||
"github.com/derekparker/delve/terminal"
|
"github.com/derekparker/delve/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,11 +40,13 @@ func main() {
|
|||||||
var addr string
|
var addr string
|
||||||
var logEnabled bool
|
var logEnabled bool
|
||||||
var headless bool
|
var headless bool
|
||||||
|
var http bool
|
||||||
|
|
||||||
flag.BoolVar(&printv, "version", false, "Print version number and exit.")
|
flag.BoolVar(&printv, "version", false, "Print version number and exit.")
|
||||||
flag.StringVar(&addr, "addr", "localhost:0", "Debugging server listen address.")
|
flag.StringVar(&addr, "addr", "localhost:0", "Debugging server listen address.")
|
||||||
flag.BoolVar(&logEnabled, "log", false, "Enable debugging server logging.")
|
flag.BoolVar(&logEnabled, "log", false, "Enable debugging server logging.")
|
||||||
flag.BoolVar(&headless, "headless", false, "Run in headless mode.")
|
flag.BoolVar(&headless, "headless", false, "Run in headless mode.")
|
||||||
|
flag.BoolVar(&http, "http", false, "Start HTTP server instead of RPC.")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if flag.NFlag() == 0 && len(flag.Args()) == 0 {
|
if flag.NFlag() == 0 && len(flag.Args()) == 0 {
|
||||||
@ -60,12 +64,12 @@ func main() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
status := run(addr, logEnabled, headless)
|
status := run(addr, logEnabled, headless, http)
|
||||||
fmt.Println("[Hope I was of service hunting your bug!]")
|
fmt.Println("[Hope I was of service hunting your bug!]")
|
||||||
os.Exit(status)
|
os.Exit(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(addr string, logEnabled, headless bool) int {
|
func run(addr string, logEnabled, headless, http bool) int {
|
||||||
// Collect launch arguments
|
// Collect launch arguments
|
||||||
var processArgs []string
|
var processArgs []string
|
||||||
var attachPid int
|
var attachPid int
|
||||||
@ -121,18 +125,32 @@ func run(addr string, logEnabled, headless bool) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and start a REST debugger server
|
// Create and start a debugger server
|
||||||
server := rest.NewServer(&rest.Config{
|
var server service.Server
|
||||||
Listener: listener,
|
if http {
|
||||||
ProcessArgs: processArgs,
|
server = rest.NewServer(&service.Config{
|
||||||
AttachPid: attachPid,
|
Listener: listener,
|
||||||
}, logEnabled)
|
ProcessArgs: processArgs,
|
||||||
|
AttachPid: attachPid,
|
||||||
|
}, logEnabled)
|
||||||
|
} else {
|
||||||
|
server = rpc.NewServer(&service.Config{
|
||||||
|
Listener: listener,
|
||||||
|
ProcessArgs: processArgs,
|
||||||
|
AttachPid: attachPid,
|
||||||
|
}, logEnabled)
|
||||||
|
}
|
||||||
go server.Run()
|
go server.Run()
|
||||||
|
|
||||||
var status int
|
var status int
|
||||||
if !headless {
|
if !headless {
|
||||||
// Create and start a terminal
|
// Create and start a terminal
|
||||||
client := rest.NewClient(listener.Addr().String())
|
var client service.Client
|
||||||
|
if http {
|
||||||
|
client = rest.NewClient(listener.Addr().String())
|
||||||
|
} else {
|
||||||
|
client = rpc.NewClient(listener.Addr().String())
|
||||||
|
}
|
||||||
term := terminal.New(client)
|
term := terminal.New(client)
|
||||||
err, status = term.Run()
|
err, status = term.Run()
|
||||||
} else {
|
} else {
|
||||||
|
@ -101,7 +101,10 @@ func (dbp *Process) Detach(kill bool) (err error) {
|
|||||||
// Clean up any breakpoints we've set.
|
// Clean up any breakpoints we've set.
|
||||||
for _, bp := range dbp.Breakpoints {
|
for _, bp := range dbp.Breakpoints {
|
||||||
if bp != nil {
|
if bp != nil {
|
||||||
dbp.ClearBreakpoint(bp.Addr)
|
_, err := dbp.ClearBreakpoint(bp.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dbp.execPtraceFunc(func() {
|
dbp.execPtraceFunc(func() {
|
||||||
|
@ -3,6 +3,7 @@ package proc
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
@ -15,7 +16,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
protest.RunTestsWithFixtures(m)
|
os.Exit(protest.RunTestsWithFixtures(m))
|
||||||
}
|
}
|
||||||
|
|
||||||
func withTestProcess(name string, t *testing.T, fn func(p *Process, fixture protest.Fixture)) {
|
func withTestProcess(name string, t *testing.T, fn func(p *Process, fixture protest.Fixture)) {
|
||||||
|
@ -55,13 +55,12 @@ func BuildFixture(name string) Fixture {
|
|||||||
|
|
||||||
// RunTestsWithFixtures will pre-compile test fixtures before running test
|
// RunTestsWithFixtures will pre-compile test fixtures before running test
|
||||||
// methods. Test binaries are deleted before exiting.
|
// methods. Test binaries are deleted before exiting.
|
||||||
func RunTestsWithFixtures(m *testing.M) {
|
func RunTestsWithFixtures(m *testing.M) int {
|
||||||
status := m.Run()
|
status := m.Run()
|
||||||
|
|
||||||
// Remove the fixtures.
|
// Remove the fixtures.
|
||||||
for _, f := range Fixtures {
|
for _, f := range Fixtures {
|
||||||
os.Remove(f.Path)
|
os.Remove(f.Path)
|
||||||
}
|
}
|
||||||
|
return status
|
||||||
os.Exit(status)
|
|
||||||
}
|
}
|
||||||
|
19
service/config.go
Normal file
19
service/config.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
// Config provides the configuration to start a Debugger and expose it with a
|
||||||
|
// service.
|
||||||
|
//
|
||||||
|
// Only one of ProcessArgs or AttachPid should be specified. If ProcessArgs is
|
||||||
|
// provided, a new process will be launched. Otherwise, the debugger will try
|
||||||
|
// to attach to an existing process with AttachPid.
|
||||||
|
type Config struct {
|
||||||
|
// Listener is used to serve requests.
|
||||||
|
Listener net.Listener
|
||||||
|
// ProcessArgs are the arguments to launch a new process.
|
||||||
|
ProcessArgs []string
|
||||||
|
// AttachPid is the PID of an existing process to which the debugger should
|
||||||
|
// attach.
|
||||||
|
AttachPid int
|
||||||
|
}
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
restful "github.com/emicklei/go-restful"
|
restful "github.com/emicklei/go-restful"
|
||||||
|
|
||||||
|
"github.com/derekparker/delve/service"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
"github.com/derekparker/delve/service/debugger"
|
"github.com/derekparker/delve/service/debugger"
|
||||||
)
|
)
|
||||||
@ -16,31 +17,15 @@ import (
|
|||||||
// RESTServer exposes a Debugger via a HTTP REST API.
|
// RESTServer exposes a Debugger via a HTTP REST API.
|
||||||
type RESTServer struct {
|
type RESTServer struct {
|
||||||
// config is all the information necessary to start the debugger and server.
|
// config is all the information necessary to start the debugger and server.
|
||||||
config *Config
|
config *service.Config
|
||||||
// listener is used to serve HTTP.
|
// listener is used to serve HTTP.
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
// debugger is a debugger service.
|
// debugger is a debugger service.
|
||||||
debugger *debugger.Debugger
|
debugger *debugger.Debugger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config provides the configuration to start a Debugger and expose it with a
|
|
||||||
// RESTServer.
|
|
||||||
//
|
|
||||||
// Only one of ProcessArgs or AttachPid should be specified. If ProcessArgs is
|
|
||||||
// provided, a new process will be launched. Otherwise, the debugger will try
|
|
||||||
// to attach to an existing process with AttachPid.
|
|
||||||
type Config struct {
|
|
||||||
// Listener is used to serve HTTP.
|
|
||||||
Listener net.Listener
|
|
||||||
// ProcessArgs are the arguments to launch a new process.
|
|
||||||
ProcessArgs []string
|
|
||||||
// AttachPid is the PID of an existing process to which the debugger should
|
|
||||||
// attach.
|
|
||||||
AttachPid int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewServer creates a new RESTServer.
|
// NewServer creates a new RESTServer.
|
||||||
func NewServer(config *Config, logEnabled bool) *RESTServer {
|
func NewServer(config *service.Config, logEnabled bool) *RESTServer {
|
||||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||||||
if !logEnabled {
|
if !logEnabled {
|
||||||
log.SetOutput(ioutil.Discard)
|
log.SetOutput(ioutil.Discard)
|
||||||
|
186
service/rpc/client.go
Normal file
186
service/rpc/client.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/rpc"
|
||||||
|
"net/rpc/jsonrpc"
|
||||||
|
|
||||||
|
"github.com/derekparker/delve/service"
|
||||||
|
"github.com/derekparker/delve/service/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is a RPC service.Client.
|
||||||
|
type RPCClient struct {
|
||||||
|
addr string
|
||||||
|
client *rpc.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the implementation satisfies the interface.
|
||||||
|
var _ service.Client = &RPCClient{}
|
||||||
|
|
||||||
|
// NewClient creates a new RPCClient.
|
||||||
|
func NewClient(addr string) *RPCClient {
|
||||||
|
client, err := jsonrpc.Dial("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("dialing:", err)
|
||||||
|
}
|
||||||
|
return &RPCClient{
|
||||||
|
addr: addr,
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) Detach(kill bool) error {
|
||||||
|
return c.call("Detach", kill, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) GetState() (*api.DebuggerState, error) {
|
||||||
|
state := new(api.DebuggerState)
|
||||||
|
err := c.call("State", nil, state)
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) Continue() (*api.DebuggerState, error) {
|
||||||
|
state := new(api.DebuggerState)
|
||||||
|
err := c.call("Command", &api.DebuggerCommand{Name: api.Continue}, state)
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) Next() (*api.DebuggerState, error) {
|
||||||
|
state := new(api.DebuggerState)
|
||||||
|
err := c.call("Command", &api.DebuggerCommand{Name: api.Next}, state)
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) Step() (*api.DebuggerState, error) {
|
||||||
|
state := new(api.DebuggerState)
|
||||||
|
err := c.call("Command", &api.DebuggerCommand{Name: api.Step}, state)
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) SwitchThread(threadID int) (*api.DebuggerState, error) {
|
||||||
|
state := new(api.DebuggerState)
|
||||||
|
cmd := &api.DebuggerCommand{
|
||||||
|
Name: api.SwitchThread,
|
||||||
|
ThreadID: threadID,
|
||||||
|
}
|
||||||
|
err := c.call("Command", cmd, state)
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) Halt() (*api.DebuggerState, error) {
|
||||||
|
state := new(api.DebuggerState)
|
||||||
|
err := c.call("Command", &api.DebuggerCommand{Name: api.Halt}, state)
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) GetBreakpoint(id int) (*api.Breakpoint, error) {
|
||||||
|
breakpoint := new(api.Breakpoint)
|
||||||
|
err := c.call("GetBreakpoint", id, breakpoint)
|
||||||
|
return breakpoint, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) CreateBreakpoint(breakPoint *api.Breakpoint) (*api.Breakpoint, error) {
|
||||||
|
newBreakpoint := new(api.Breakpoint)
|
||||||
|
err := c.call("CreateBreakpoint", breakPoint, &newBreakpoint)
|
||||||
|
return newBreakpoint, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ListBreakpoints() ([]*api.Breakpoint, error) {
|
||||||
|
var breakpoints []*api.Breakpoint
|
||||||
|
err := c.call("ListBreakpoints", nil, &breakpoints)
|
||||||
|
return breakpoints, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ClearBreakpoint(id int) (*api.Breakpoint, error) {
|
||||||
|
bp := new(api.Breakpoint)
|
||||||
|
err := c.call("ClearBreakpoint", id, bp)
|
||||||
|
return bp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ListThreads() ([]*api.Thread, error) {
|
||||||
|
var threads []*api.Thread
|
||||||
|
err := c.call("ListThreads", nil, &threads)
|
||||||
|
return threads, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) GetThread(id int) (*api.Thread, error) {
|
||||||
|
thread := new(api.Thread)
|
||||||
|
err := c.call("GetThread", id, &thread)
|
||||||
|
return thread, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) EvalVariable(symbol string) (*api.Variable, error) {
|
||||||
|
v := new(api.Variable)
|
||||||
|
err := c.call("EvalSymbol", symbol, v)
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) EvalVariableFor(threadID int, symbol string) (*api.Variable, error) {
|
||||||
|
v := new(api.Variable)
|
||||||
|
err := c.call("EvalThreadSymbol", threadID, v)
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ListSources(filter string) ([]string, error) {
|
||||||
|
var sources []string
|
||||||
|
err := c.call("ListSources", filter, &sources)
|
||||||
|
return sources, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ListFunctions(filter string) ([]string, error) {
|
||||||
|
var funcs []string
|
||||||
|
err := c.call("ListFunctions", filter, &funcs)
|
||||||
|
return funcs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ListPackageVariables(filter string) ([]api.Variable, error) {
|
||||||
|
var vars []api.Variable
|
||||||
|
err := c.call("ListPackageVars", filter, &vars)
|
||||||
|
return vars, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ListPackageVariablesFor(threadID int, filter string) ([]api.Variable, error) {
|
||||||
|
var vars []api.Variable
|
||||||
|
err := c.call("ListThreadPackageVars", &ThreadListArgs{Id: threadID, Filter: filter}, &vars)
|
||||||
|
return vars, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ListLocalVariables() ([]api.Variable, error) {
|
||||||
|
var vars []api.Variable
|
||||||
|
err := c.call("ListLocalVars", nil, &vars)
|
||||||
|
return vars, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ListRegisters() (string, error) {
|
||||||
|
var regs string
|
||||||
|
err := c.call("ListRegisters", nil, ®s)
|
||||||
|
return regs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ListFunctionArgs() ([]api.Variable, error) {
|
||||||
|
var vars []api.Variable
|
||||||
|
err := c.call("ListFunctionArgs", nil, &vars)
|
||||||
|
return vars, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ListGoroutines() ([]*api.Goroutine, error) {
|
||||||
|
var goroutines []*api.Goroutine
|
||||||
|
err := c.call("ListGoroutines", nil, &goroutines)
|
||||||
|
return goroutines, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) Stacktrace(goroutineId, depth int) ([]*api.Location, error) {
|
||||||
|
var locations []*api.Location
|
||||||
|
err := c.call("StacktraceGoroutine", &StacktraceGoroutineArgs{Id: 1, Depth: depth}, &locations)
|
||||||
|
return locations, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) url(path string) string {
|
||||||
|
return fmt.Sprintf("http://%s%s", c.addr, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) call(method string, args, reply interface{}) error {
|
||||||
|
return c.client.Call("RPCServer."+method, args, reply)
|
||||||
|
}
|
289
service/rpc/server.go
Normal file
289
service/rpc/server.go
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
grpc "net/rpc"
|
||||||
|
"net/rpc/jsonrpc"
|
||||||
|
|
||||||
|
"github.com/derekparker/delve/service"
|
||||||
|
"github.com/derekparker/delve/service/api"
|
||||||
|
"github.com/derekparker/delve/service/debugger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RPCServer struct {
|
||||||
|
// config is all the information necessary to start the debugger and server.
|
||||||
|
config *service.Config
|
||||||
|
// listener is used to serve HTTP.
|
||||||
|
listener net.Listener
|
||||||
|
// debugger is a debugger service.
|
||||||
|
debugger *debugger.Debugger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates a new RPCServer.
|
||||||
|
func NewServer(config *service.Config, logEnabled bool) *RPCServer {
|
||||||
|
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||||||
|
if !logEnabled {
|
||||||
|
log.SetOutput(ioutil.Discard)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RPCServer{
|
||||||
|
config: config,
|
||||||
|
listener: config.Listener,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop detaches from the debugger and waits for it to stop.
|
||||||
|
func (s *RPCServer) Stop(kill bool) error {
|
||||||
|
return s.debugger.Detach(kill)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts a debugger and exposes it with an HTTP server. The debugger
|
||||||
|
// itself can be stopped with the `detach` API. Run blocks until the HTTP
|
||||||
|
// server stops.
|
||||||
|
func (s *RPCServer) Run() error {
|
||||||
|
c, err := s.listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and start the debugger
|
||||||
|
if s.debugger, err = debugger.New(&debugger.Config{
|
||||||
|
ProcessArgs: s.config.ProcessArgs,
|
||||||
|
AttachPid: s.config.AttachPid,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcs := grpc.NewServer()
|
||||||
|
rpcs.Register(s)
|
||||||
|
rpcs.ServeCodec(jsonrpc.NewServerCodec(c))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) Detach(kill bool, ret *int) error {
|
||||||
|
return s.debugger.Detach(kill)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) State(arg interface{}, state *api.DebuggerState) error {
|
||||||
|
st, err := s.debugger.State()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*state = *st
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) Command(command *api.DebuggerCommand, state *api.DebuggerState) error {
|
||||||
|
st, err := s.debugger.Command(command)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*state = *st
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) GetBreakpoint(id int, breakpoint *api.Breakpoint) error {
|
||||||
|
bp := s.debugger.FindBreakpoint(id)
|
||||||
|
if bp == nil {
|
||||||
|
return fmt.Errorf("no breakpoint with id %d", id)
|
||||||
|
}
|
||||||
|
*breakpoint = *bp
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StacktraceGoroutineArgs struct {
|
||||||
|
Id, Depth int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) StacktraceGoroutine(args *StacktraceGoroutineArgs, locations *[]api.Location) error {
|
||||||
|
locs, err := s.debugger.Stacktrace(args.Id, args.Depth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*locations = locs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) ListBreakpoints(arg interface{}, breakpoints *[]*api.Breakpoint) error {
|
||||||
|
*breakpoints = s.debugger.Breakpoints()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) CreateBreakpoint(bp, newBreakpoint *api.Breakpoint) error {
|
||||||
|
createdbp, err := s.debugger.CreateBreakpoint(bp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*newBreakpoint = *createdbp
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) ClearBreakpoint(id int, breakpoint *api.Breakpoint) error {
|
||||||
|
bp := s.debugger.FindBreakpoint(id)
|
||||||
|
if bp == nil {
|
||||||
|
return fmt.Errorf("no breakpoint with id %d", id)
|
||||||
|
}
|
||||||
|
deleted, err := s.debugger.ClearBreakpoint(bp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*breakpoint = *deleted
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) ListThreads(arg interface{}, threads *[]*api.Thread) error {
|
||||||
|
*threads = s.debugger.Threads()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) GetThread(id int, thread *api.Thread) error {
|
||||||
|
t := s.debugger.FindThread(id)
|
||||||
|
if t == nil {
|
||||||
|
return fmt.Errorf("no thread with id %d", id)
|
||||||
|
}
|
||||||
|
*thread = *t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) ListPackageVars(filter string, variables *[]api.Variable) error {
|
||||||
|
state, err := s.debugger.State()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
current := state.CurrentThread
|
||||||
|
if current == nil {
|
||||||
|
return fmt.Errorf("no current thread")
|
||||||
|
}
|
||||||
|
|
||||||
|
vars, err := s.debugger.PackageVariables(current.ID, filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*variables = vars
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThreadListArgs struct {
|
||||||
|
Id int
|
||||||
|
Filter string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) ListThreadPackageVars(args *ThreadListArgs, variables *[]api.Variable) error {
|
||||||
|
if thread := s.debugger.FindThread(args.Id); thread == nil {
|
||||||
|
return fmt.Errorf("no thread with id %d", args.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
vars, err := s.debugger.PackageVariables(args.Id, args.Filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*variables = vars
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) ListRegisters(arg interface{}, registers *string) error {
|
||||||
|
state, err := s.debugger.State()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
regs, err := s.debugger.Registers(state.CurrentThread.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*registers = regs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) ListLocalVars(arg interface{}, variables *[]api.Variable) error {
|
||||||
|
state, err := s.debugger.State()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
vars, err := s.debugger.LocalVariables(state.CurrentThread.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*variables = vars
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) ListFunctionArgs(arg interface{}, variables *[]api.Variable) error {
|
||||||
|
state, err := s.debugger.State()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
vars, err := s.debugger.FunctionArguments(state.CurrentThread.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*variables = vars
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) EvalSymbol(symbol string, variable *api.Variable) error {
|
||||||
|
state, err := s.debugger.State()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
current := state.CurrentThread
|
||||||
|
if current == nil {
|
||||||
|
return errors.New("no current thread")
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := s.debugger.EvalVariableInThread(current.ID, symbol)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*variable = *v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThreadSymbolArgs struct {
|
||||||
|
Id int
|
||||||
|
Symbol string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) EvalThreadSymbol(args *ThreadSymbolArgs, variable *api.Variable) error {
|
||||||
|
v, err := s.debugger.EvalVariableInThread(args.Id, args.Symbol)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*variable = *v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) ListSources(filter string, sources *[]string) error {
|
||||||
|
ss, err := s.debugger.Sources(filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*sources = ss
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) ListFunctions(filter string, funcs *[]string) error {
|
||||||
|
fns, err := s.debugger.Functions(filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*funcs = fns
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RPCServer) ListGoroutines(arg interface{}, goroutines *[]*api.Goroutine) error {
|
||||||
|
gs, err := s.debugger.Goroutines()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*goroutines = gs
|
||||||
|
return nil
|
||||||
|
}
|
6
service/server.go
Normal file
6
service/server.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
type Server interface {
|
||||||
|
Run() error
|
||||||
|
Stop(bool) error
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package rest
|
package servicetest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -8,8 +9,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
protest "github.com/derekparker/delve/proc/test"
|
protest "github.com/derekparker/delve/proc/test"
|
||||||
|
|
||||||
"github.com/derekparker/delve/service"
|
"github.com/derekparker/delve/service"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
|
"github.com/derekparker/delve/service/rest"
|
||||||
|
"github.com/derekparker/delve/service/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -17,7 +21,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
protest.RunTestsWithFixtures(m)
|
os.Exit(protest.RunTestsWithFixtures(m))
|
||||||
}
|
}
|
||||||
|
|
||||||
func withTestClient(name string, t *testing.T, fn func(c service.Client)) {
|
func withTestClient(name string, t *testing.T, fn func(c service.Client)) {
|
||||||
@ -25,14 +29,33 @@ func withTestClient(name string, t *testing.T, fn func(c service.Client)) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("couldn't start listener: %s\n", err)
|
t.Fatalf("couldn't start listener: %s\n", err)
|
||||||
}
|
}
|
||||||
server := NewServer(&Config{
|
defer listener.Close()
|
||||||
Listener: listener,
|
// Test REST service
|
||||||
ProcessArgs: []string{protest.BuildFixture(name).Path},
|
restService := func() {
|
||||||
}, false)
|
fmt.Println("---- RUNNING TEST WITH REST CLIENT ----")
|
||||||
go server.Run()
|
server := rest.NewServer(&service.Config{
|
||||||
client := NewClient(listener.Addr().String())
|
Listener: listener,
|
||||||
defer client.Detach(true)
|
ProcessArgs: []string{protest.BuildFixture(name).Path},
|
||||||
fn(client)
|
}, false)
|
||||||
|
go server.Run()
|
||||||
|
client := rest.NewClient(listener.Addr().String())
|
||||||
|
defer client.Detach(true)
|
||||||
|
fn(client)
|
||||||
|
}
|
||||||
|
// Test RPC service
|
||||||
|
rpcService := func() {
|
||||||
|
fmt.Println("---- RUNNING TEST WITH RPC CLIENT ----")
|
||||||
|
server := rpc.NewServer(&service.Config{
|
||||||
|
Listener: listener,
|
||||||
|
ProcessArgs: []string{protest.BuildFixture(name).Path},
|
||||||
|
}, false)
|
||||||
|
go server.Run()
|
||||||
|
client := rpc.NewClient(listener.Addr().String())
|
||||||
|
defer client.Detach(true)
|
||||||
|
fn(client)
|
||||||
|
}
|
||||||
|
rpcService()
|
||||||
|
restService()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientServer_exit(t *testing.T) {
|
func TestClientServer_exit(t *testing.T) {
|
Loading…
Reference in New Issue
Block a user