Skip to content

Commit

Permalink
udap: debug adapter protocol implementation
Browse files Browse the repository at this point in the history
Attempt to start udb in adapter mode
Debuggee communication reconfiguration
Add debuggee error handling
Add debug console handling
Respond to stackTrace request
Respond to scope and variables requests
Respond to continue, next, stepIn, and stepOut
Add runInTerminal features
Add udb and socket error handling

Signed-off-by: mstreeter10 <[email protected]>
  • Loading branch information
mstreeter10 authored and Jafaral committed Jan 9, 2024
1 parent 773ed0d commit d55ba84
Show file tree
Hide file tree
Showing 9 changed files with 966 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ uni/progs/uget
uni/progs/umake
uni/progs/uprof
uni/udb/udb
uni/udb/udap/udap
uni/udb/udap/dapcom
uni/unicon/unicon
uni/unidep/unidep
uni/unidoc/.lastbuild
Expand All @@ -81,6 +83,8 @@ uni/xml/testinvalid
uni/xml/testnotwf
uni/xml/testvalid
uni/ulsp/ulsp
uni/ulsp/udap/udap
uni/ulsp/udap/progcom

# uflex
uni/uflex/flexgram.icn
Expand Down
7 changes: 6 additions & 1 deletion uni/udb/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CLEAN = clean
cflags = $(UFLAGS) -c
ldflags = -u

.PHONY: all udap
#
# Add dependency on system include "evdefs.icn" because it is really sad
# if udb gets broken.
Expand Down Expand Up @@ -39,7 +40,10 @@ DTA = dta
DTA_UFILES = $(DTA)/atomic_agent.u $(DTA)/temporals.u


all: Libfiles Assertions $(EXEs)
all: Libfiles Assertions udap $(EXEs)

udap:
cd udap; $(MAKE)

Libfiles:
cd $(LIB); $(MAKE)
Expand Down Expand Up @@ -134,6 +138,7 @@ clean:
cd $(DTA); $(MAKE) clean
$(DEL) ../../bin/$(EXEs)
cd lib; $(MAKE) clean
cd udap; $(MAKE) clean

cleantools:
cd $(LIB); $(MAKE) cleantools
29 changes: 29 additions & 0 deletions uni/udb/udap/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
BASE = ../../..
include $(BASE)/Makedefs.uni

UFLAGS=-s -u

SRC=launch-dap.icn server.icn communicator.icn
OBJ=launch-dap.u server.u communicator.u

.PHONY:all

all: dapcom udap

udap: $(OBJ)
$(UC) $(DASHG) -o udap $(OBJ)
$(CP) udap$(EXE) ../../../bin

launch-dap.u:launch-dap.icn server.u
server.u:server.icn communicator.u
communicator.u:communicator.icn

dapcom:dapcom.icn
$(UC) $(DASHG) -o dapcom dapcom.icn
$(CP) dapcom$(EXE) ../../../bin

zip:
zip udap.zip Makefile *.icn

clean:
$(RM) -f *.u $(prog)$(EXE) uniclass* dapcom$(EXE)
295 changes: 295 additions & 0 deletions uni/udb/udap/communicator.icn
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
package udap

link findre
import json

class Communicator(udb, udbSock, tpsock, dapcomSock, filePath, tpArgs)

# Attempt to start udb if not already active and connect to it.
# Returns "success" if successful and an appropriate error string if otherwise.
method start_debugger(port)
local udbPath, dir, result

udbPath := find_debugger()
if /udbPath then return "udap could not find udb"

udb := system(udbPath || " -adapter " || port, &null, &null, &null, "nowait")

udbSock := open_sock(port)
if /udbSock then return "udap failed to open udbSock: " || port

tpsock := open_sock(port + 10)
if /tpsock then return "udap failed to open tpsock: " || port + 10

return "success"
end

# Send a termination signal to udb.
method end_debugger()
kill(\udb, 9)
end

# Returns udb's absolute path.
method find_debugger()
return pathfind("udb")
end

# Returns a list of tables containing stack trace information.
# A table of with key "type" set to "crash" is returned if udb experiences an error.
method stack_trace()
local udbResTable, udbResTableList, i, frames

udbResTableList := list()
frames := list()

every i := udb_input("bt", 1) do {
put(udbResTableList, \i)
}
if *udbResTableList ~= 0 then {
every udbResTable := !udbResTableList do {
if member(udbResTable, "type") then {
if udbResTable["type"] == "frame" then {
udbResTable["name"] := replace(udbResTable["name"], "\"", "\\\"")
udbResTable["consoleMsg"] := replace(udbResTable["consoleMsg"], "\"", "\\\"")
put(frames, udbResTable)
}
if udbResTable["type"] == "crash" then return udbResTable
}
}
}

return frames
end

# Returns a list of tables containing scope information.
# A table of with key "type" set to "crash" is returned if udb experiences an error.
method get_scopes(frame)
local udbResTableList, udbResTable, scopes, i, j, k, l, m

udbResTableList := list()
scopes := list()

every i := udb_input("frame " || frame, 1) do put(udbResTableList, \i)
every j := udb_input("print -g", 1) do put(udbResTableList, \j)
every k := udb_input("print -l", 1) do put(udbResTableList, \k)
every l := udb_input("print -s", 1) do put(udbResTableList, \l)
every m := udb_input("print -p", 1) do put(udbResTableList, \m)

if *udbResTableList ~= 0 then {
every udbResTable := !udbResTableList do {
if member(udbResTable, "type") then {
if member(udbResTable, "variables") then {
if udbResTable["type"] == "globals" & *udbResTable["variables"] > 0 then {
put(scopes, table("name", "Globals", "variablesReference", 1))
}
if udbResTable["type"] == "locals" & *udbResTable["variables"] > 0 then {
put(scopes, table("name", "Locals", "variablesReference", 2))
}
if udbResTable["type"] == "statics" & *udbResTable["variables"] > 0 then {
put(scopes, table("name", "Statics", "variablesReference", 3))
}
if udbResTable["type"] == "params" & *udbResTable["variables"] > 0 then {
put(scopes, table("name", "Parameters", "variablesReference", 4))
}
}
if udbResTable["type"] == "crash" then return udbResTable
}
}
}

return scopes
end

# Returns a list of tables containing variable information.
# A table of with key "type" set to "crash" is returned if udb experiences an error.
method get_variables(variablesReference)
local udbResTable, udbResTableList, variables, cmd, i

udbResTableList := list()
variables := list()

if variablesReference = 1 then cmd := "print -g"
else if variablesReference = 2 then cmd := "print -l"
else if variablesReference = 3 then cmd := "print -s"
else if variablesReference = 4 then cmd := "print -p"

every i := udb_input(cmd, 1) do put(udbResTableList, \i)

if *udbResTableList ~= 0 then {
every udbResTable := !udbResTableList do {
if member(udbResTable, "type") then {
if member(udbResTable, "variables") then {
if udbResTable["type"] == "globals" | udbResTable["type"] == "locals" | udbResTable["type"] == "statics" | udbResTable["type"] == "params" then {
variables := udbResTable["variables"]
}
}
if udbResTable["type"] == "crash" then return udbResTable
}
}
}

every variable := !variables do {
variable["value"] := replace(variable["value"], "\"", "\\\"")
variable["type"] := replace(variable["type"], "\"", "\\\"")
variable["variablesReference"] := 0
}

return variables
end

# Generator that suspends all udb commands needed for loading debuggee.
method load_cmds()
local dir, procs

filePath ? dir := tab(findre("\/[^\/]+$"))
suspend "dir args " || dir

if procs := find_debugger() then {
procs ? procs := tab(findre("unicon") + 6)
procs ||:= "/ipl/procs"
suspend "dir args " || procs
}

if \tpArgs then suspend "load " || filePath || " " || tpArgs
else suspend "load " || filePath
end

# Sets the file path of the debuggee.
method set_filepath(fpath)
filePath := fpath
end

# Attempts to open a specified port. Returns communication source if successful.
method open_sock(port)
local sock
if /port then return "udb communication port not declared"

every 1 to 5 do {
if sock := open(":" || port, "na") then {
return sock
}
else {
write("Attempting to open sock on port " || port || " again.")
delay(1000)
}
}

write("udap failed to open port: " || port)
end

# Disconnects from udb.
method disconnect_udbsock()
close(\udbSock)
udbSock := &null
end

# Attempts to read udb socket output.
# Returns what was read or fails if reading isn't possible.
# If 'wait' is not null, process will wait a maximum of 5 seconds for udb to respond.
# 'wait' should be not null if udap needs a response from udb.
method udb_output(wait)
local msg, i, seg, failsafe

failsafe := 0
msg := ""
if /wait then {
while *select(udbSock, 200) > 0 do {
if failsafe > 50 then fail
seg := ready(udbSock)
if /seg then fail
msg ||:= seg
failsafe +:= 1
}
}
else {
if *select(udbSock, 5000) > 0 then {
seg := ready(udbSock)
if /seg then fail
msg ||:= seg
}
while *select(udbSock, 200) > 0 do {
if failsafe > 50 then fail
seg := ready(udbSock)
if /seg then fail
msg ||:= seg
failsafe +:= 1
}
}

write("udb -> udap: " || msg)
return msg
end

# Sends udb a command as 'exp' and returns what was output.
# A table of with key "type" set to "crash" is returned if udb experiences an error or if reading isn't possible.
# If 'wait' is not null, process will wait a maximum of 5 seconds for udb to respond.
# 'wait' should be not null if udap needs a response from udb.
# Will always return a crash table if a crash has happened unless if a call with 'resetError' not null has been executed.
method udb_input(exp, wait, resetError)
local udbRes, resultTable
static errorCalled
initial errorCalled := &null

if \resetError then {
errorCalled := &null
return
}

if \errorCalled then return errorCalled

write("udap -> udb: " || exp)
write(udbSock, exp)
udbRes := udb_output(wait)

if /udbRes then {
resultTable := table("type", "crash", "errornumber", 1040)
resultTable["errortext"] := "socket error"
errorCalled := resultTable
return resultTable
}

if udbRes ~== "" then {
every resultTable := jtou(udbRes) do {
if member(resultTable, "type") then
if resultTable["type"] == "crash" then
errorCalled := resultTable
suspend resultTable
}
}
end

# Returns the communcation source used to communicate with udb.
method get_communication_source()
if \udbSock then return udbSock
end

# Attempts to set breakpoints given DAP setBreakpoints request as 'arguments'.
# Gives a "verified" key to each breakpoint table and sets it based on if the breakpoint was successfully set or not.
# A table of with key "type" set to "crash" is returned if udb experiences an error.
method set_breakpoints(arguments)
local breakpoints, bp, line, cond, udbResTable, udbResTableList, i

udbResTableList := list()

udb_input("clear break", 1)

breakpoints := arguments["breakpoints"]
if *breakpoints = 0 then fail

every bp := 1 to *breakpoints do {
line := breakpoints[bp]["line"]
cond := \breakpoints[bp]["condition"]

every i := udb_input("b " || arguments["source"]["name"] || ":" || line, 1) do put(udbResTableList, \i)

if *udbResTableList ~= 0 then {
every udbResTable := !udbResTableList do {
if member(udbResTable, "type") then
if udbResTable["type"] == "crash" then return udbResTable
breakpoints[bp]["verified"] := udbResTable["success"]
}
}
else breakpoints[bp]["verified"] := "__false__"
}
end
end
23 changes: 23 additions & 0 deletions uni/udb/udap/dapcom.icn
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#
# relay communication between the target program's std in/out, and udap socket.
#
global sock
procedure main(argv)
port := (if &features == "MacOS" then "127.0.0.1" else "") || ":" || pop(argv)
every trap("SIGINT" | "SIGHUP" | "SIGPIPE", onExit)

every !5 do
if sock := open(port, "n") then
break
else
delay(1000)

\sock | stop("failed to connect to ",port, " ", &errortext )
repeat every s := !select([sock, &input]) do
writes(ready(s === sock)) | writes(sock, ready())
end

procedure onExit(non)
close(\sock)
stop()
end
Loading

0 comments on commit d55ba84

Please sign in to comment.