From e978a6e19f9ab4ba2fa044b87a51667e059ecd96 Mon Sep 17 00:00:00 2001 From: mstreeter10 Date: Sun, 17 Dec 2023 17:01:57 -0500 Subject: [PATCH] udap: Added udb and socket error handling Signed-off-by: mstreeter10 --- .gitignore | 1 + uni/ulsp/udap/Makefile | 2 +- uni/ulsp/udap/communicator.icn | 264 +++++++++++++++++++++------------ uni/ulsp/udap/progcom | Bin 2009 -> 0 bytes uni/ulsp/udap/progcom.icn | 32 ++-- uni/ulsp/udap/server.icn | 235 ++++++++++++++++++----------- 6 files changed, 344 insertions(+), 190 deletions(-) delete mode 100755 uni/ulsp/udap/progcom diff --git a/.gitignore b/.gitignore index 178641ec7..ae7afa4e8 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,7 @@ uni/xml/testnotwf uni/xml/testvalid uni/ulsp/ulsp uni/ulsp/udap/udap +uni/ulsp/udap/progcom # uflex uni/uflex/flexgram.icn diff --git a/uni/ulsp/udap/Makefile b/uni/ulsp/udap/Makefile index 1a7952b9b..6e3ed6c0b 100644 --- a/uni/ulsp/udap/Makefile +++ b/uni/ulsp/udap/Makefile @@ -28,4 +28,4 @@ zip: zip udap.zip Makefile *.icn clean: - $(RM) -f *.u $(prog) uniclass* \ No newline at end of file + $(RM) -f *.u $(prog) uniclass* progcom \ No newline at end of file diff --git a/uni/ulsp/udap/communicator.icn b/uni/ulsp/udap/communicator.icn index 3f399bc8f..77e1e48c4 100644 --- a/uni/ulsp/udap/communicator.icn +++ b/uni/ulsp/udap/communicator.icn @@ -3,92 +3,96 @@ package udap link findre import json -class Communicator(udb, udbSock, progSock, filePath, debuggerActive) +class Communicator(udb, udbSock, progSock, progComSock, filePath, progArgs) + # 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 - if debuggerActive == "n" then { - udbPath := find_debugger() - if /udbPath then return "udap could not find udb" + udbPath := find_debugger() + if /udbPath then return "udap could not find udb" - if udb := open(udbPath || " -adapter " || port, "pw") then debuggerActive := "y" - else return "udap could not start udb" - } + udb := system(udbPath || " -adapter " || port, &null, &null, &null, "nowait") + + udbSock := open_sock(port) + if /udbSock then return "udap failed to open udbSock: " || port - result := connect_udbsock(port) - if result ~== "success" then { - return result - } + progSock := open_sock(port + 10) + if /progSock then return "udap failed to open progSock: " || port + 10 return "success" end + # Send a termination signal to udb. method end_debugger() - close(udb) - debuggerActive := "n" + kill(\udb, 9) end + # Returns udb's absolute path. method find_debugger() return pathfind("udb") end - method stackTrace() - local udbRes, udbResTable, udbResList - - udbRes := "" - udbResList := list() + # 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 - write(udbSock, "bt") + udbResTableList := list() + frames := list() - #in json - udbRes := udb_output() - - if udbRes ~== "" then { - every udbResTable := jtou(udbRes) do { + 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(udbResList, udbResTable) + put(frames, udbResTable) } + if udbResTable["type"] == "crash" then return udbResTable } } } - return udbResList + return frames end - method getScopes(frame) - local udbRes, udbResTable, scopes + # 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 - udbRes := "" + udbResTableList := list() scopes := list() - write(udbSock, "frame " || frame) - write(udbSock, "print -g") - write(udbSock, "print -l") - write(udbSock, "print -s") - write(udbSock, "print -p") + 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) - #in json - udbRes := udb_output() - - if udbRes ~== "" then { - every udbResTable := jtou(udbRes) do { - if member(udbResTable, "type") & 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 *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 } } } @@ -96,26 +100,30 @@ class Communicator(udb, udbSock, progSock, filePath, debuggerActive) return scopes end - method getVariables(variablesReference) - local udbRes, udbResTable, variables + # 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 - udbRes := "" + udbResTableList := list() variables := list() - if variablesReference = 1 then write(udbSock, "print -g") - if variablesReference = 2 then write(udbSock, "print -l") - if variablesReference = 3 then write(udbSock, "print -s") - if variablesReference = 4 then write(udbSock, "print -p") + 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" - #in json - udbRes := udb_output() + every i := udb_input(cmd, 1) do put(udbResTableList, \i) - if udbRes ~== "" then { - every udbResTable := jtou(udbRes) do { - if member(udbResTable, "type") & member(udbResTable, "variables") then { - if udbResTable["type"] == "globals" | udbResTable["type"] == "locals" | udbResTable["type"] == "statics" | udbResTable["type"] == "params" then { - variables := udbResTable["variables"] + 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 } } } @@ -129,69 +137,141 @@ class Communicator(udb, udbSock, progSock, filePath, debuggerActive) return variables end + # Generator that suspends all udb commands needed for loading debuggee. method load_cmds() - local dir + local dir, procs filePath ? dir := tab(findre("\/[^\/]+$")) suspend "dir args " || dir - suspend "load " || filePath + + if procs := find_debugger() then { + procs ? procs := tab(findre("unicon") + 6) + procs ||:= "/ipl/procs" + suspend "dir args " || procs + } + + if \progArgs then suspend "load " || filePath || " " || progArgs + else suspend "load " || filePath end + # Sets the file path of the debuggee. method set_filepath(fpath) filePath := fpath end - method connect_udbsock(port) + # 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" - if \udbSock then return "success" - every 1 to 5 do { - if udbSock := open(":" || port, "n") then { - return "success" + if sock := open(":" || port, "na") then { + return sock } else { + write("Attempting to open sock on port " || port || " again.") delay(1000) } } - return "udap failed to connect to udb on port: " || port + write("udap failed to open port: " || port) end + # Disconnects from udb. method disconnect_udbsock() - close(udbSock) + close(\udbSock) udbSock := &null end - method udb_output() - local msg + # 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 := "" - while *select(udbSock, 200) > 0 do { - msg ||:= ready(udbSock) + 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 - method udb_input(exp) - local udbRes := "", resultTable + # 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() + 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 suspend resultTable + 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, res, resultTable + local breakpoints, bp, line, cond, udbResTable, udbResTableList, i + + udbResTableList := list() - write(udbSock, "clear break") - udb_output() + udb_input("clear break", 1) breakpoints := arguments["breakpoints"] if *breakpoints = 0 then fail @@ -200,14 +280,16 @@ class Communicator(udb, udbSock, progSock, filePath, debuggerActive) line := breakpoints[bp]["line"] cond := \breakpoints[bp]["condition"] - write(udbSock, "b " || arguments["source"]["name"] || ":" || line) - res := udb_output() + every i := udb_input("b " || arguments["source"]["name"] || ":" || line, 1) do put(udbResTableList, \i) - if res ~== "" & resultTable := jtou(res) then breakpoints[bp]["verified"] := resultTable["success"] + 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 - - initially - debuggerActive := "n" end diff --git a/uni/ulsp/udap/progcom b/uni/ulsp/udap/progcom deleted file mode 100755 index 7e8b1c19562e68bc3099c3a53a5ec4fbc30d2b05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2009 zcmb_cy-yTT5PuwkvQcAWLX5@pxBz}|%K;i=I70*yFG0}o5fXyAV|j8H?{+V{2ONGx ztfaIu78X<{cE(s3Z77Kqc9wKn8U6xw=DnG9eF;`hGR*tU{N8-LxuxrzyHxSrsO%Kx zZxkl;ZaJtaw-z-*r5Y-9#{zG)ru;_ahN>P!?y6t$0$<<3LsK3ikZxjhB41QqIgput z`A9};S@uNvg}d(Jg-l*w&i51@XF>M)GMy=mPEF3I<)ZBFma3_|KdyA9KP@v4a~IQ@ zk@REaH!+C$ExvIb#)t82zIIuBUEIuOMVtkUtu{(aRV52By@aPLg&Xo-P^|{*(V}A> z!#VDVH*MlCIn`=l=OMO9{;y=qbf58e@)vRg+1c!1zdJbKUKtYN!N)H%xj7+P-x)hS z^Lg$06H}`N%~WORh4}WP_mhcl{xXs>l=$z+v+g;}^RIkJZl@bf%j#O-v-1*79N`05 z#%T-pSojj*C9tEQHwlk}ou)GyjL(2_ABgXOvLMcbavbd>DCgyQI4|=!Zwk057{3L| z@f*Mrl|An#c@-AKgK(H^iEpXQL(LYZ;bc#{i}rAMMj^733pJv z`513%ely0V{#J}l{g*K|pTr(vM-=tb?#I~FKZyCJzR*?hd&=rbjuAc~q<-02sp5-z xP!~a6`65zPAxh!O8Zg3!hyw3E{78Wx1}k1r%T_#J{01o<2W|iW diff --git a/uni/ulsp/udap/progcom.icn b/uni/ulsp/udap/progcom.icn index 1e356b82a..63f7db488 100644 --- a/uni/ulsp/udap/progcom.icn +++ b/uni/ulsp/udap/progcom.icn @@ -1,13 +1,21 @@ +global sock + procedure main(argv) - port := pop(argv) - sock := open(":" || port, "n") - - repeat { - if *select(sock, 50) > 0 then { - writes(ready(sock)) - } - if *select(&input, 50) > 0 then { - writes(sock, ready()) - } - } -end \ No newline at end of file + port := (if &features == "MacOS" then "127.0.0.1" else "") || ":" || pop(argv) + every trap(!["SIGINT", "SIGHUP", "SIGPIPE"], progcomExit) + + 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 progcomExit(non) + close(\sock) + stop() +end diff --git a/uni/ulsp/udap/server.icn b/uni/ulsp/udap/server.icn index 937295da9..56e825ebd 100644 --- a/uni/ulsp/udap/server.icn +++ b/uni/ulsp/udap/server.icn @@ -5,8 +5,9 @@ link strings import json global seq, lastUdbCmd, waitingForTerminal, requestQueue -class Server(port, sock, communicator, shellProcessId, clientDetails) +class Server(port, sock, communicator, shellProcessId, clientDetails, currentRequestBody, udbError) + # Main loop udap process. method run() local request_body, jsontable, request_seq, request_command, request_arguments, cmd @@ -31,33 +32,34 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) } end + # Given a DAP client request body, process that request. method process_request(request_body) local jsontable, request_seq, request_command, request_arguments, response_body jsontable := jtou(request_body) + if jsontable["type"] == "request" then currentRequestBody := jsontable + request_seq := jsontable["seq"] request_command := jsontable["command"] request_arguments := jsontable["arguments"] response_body := jsontable["body"] - write("vscode: " || request_body) + write("client -> udap: " || request_body) case request_command of { "initialize": { initialize(request_seq, request_command, request_arguments) } "launch" : { launch(request_seq, request_command, request_arguments) } "setBreakpoints" : { set_breakpoints(request_seq, request_command, request_arguments) } - "configurationDone" : { acknowledge(request_seq, request_command); progress("run") } - "threads" : { threads(request_seq, request_command) } - "continue" : { acknowledge(request_seq, request_command); progress("cont") } - "next" : { acknowledge(request_seq, request_command); progress("next") } - "stepIn" : { acknowledge(request_seq, request_command); progress("step") } - "stepOut" : { acknowledge(request_seq, request_command); progress("return") } + "configurationDone" : { udb_input("run", 1); if /udbError then acknowledge(request_seq, request_command) } "threads" : { threads(request_seq, request_command) } + "continue" : { udb_input("cont", 1); if /udbError then acknowledge(request_seq, request_command) } + "next" : { udb_input("next", 1); if /udbError then acknowledge(request_seq, request_command) } + "stepIn" : { udb_input("step", 1); if /udbError then acknowledge(request_seq, request_command) } + "stepOut" : { udb_input("return", 1); if /udbError then acknowledge(request_seq, request_command) } "stackTrace" : { stackTrace(request_seq, request_command, request_arguments) } "scopes" : { scopes(request_seq, request_command, request_arguments) } "variables" : { variables(request_seq, request_command, request_arguments) } - "restart" : { restart(request_seq, request_command) } "disconnect" : { acknowledge(request_seq, request_command); disconnect() } "runInTerminal" : { if member(response_body, "shellProcessId") then shellProcessId := response_body["shellProcessId"] } "evaluate" : { evaluate(request_seq, request_command, request_arguments) } @@ -69,13 +71,14 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) # Get Request # ################################################################################# + # Attempt to read messages from client given a socket and returns each message as a generator. method get_request(sock) local request_body, msg, len while /request_body | request_body == "" do { # Even while waiting for request, listen to udb - if \communicator.udbSock & \communicator.debuggerActive == "y" then { + if \communicator.udbSock then { select([sock, communicator.udbSock]) udb_listen() } @@ -106,6 +109,7 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) # Build Response # ################################################################################# + # Create and return a response for client in json format. method build_response(request_seq, success, request_command, body, message) local responseTable, responseBody, responseHeader @@ -121,7 +125,7 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) responseBody := tojson(responseTable) responseHeader := "Content-Length:" || *responseBody || "\r\n\r\n" - write("udap: " || responseBody) + write("udap -> client: " || responseBody) seq +:= 1 @@ -132,6 +136,7 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) # Build Request # ################################################################################# + # Create and return a request for client in json format. method build_request(command, arguments) local requestTable, requestBody, requestHeader @@ -144,7 +149,7 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) requestBody := tojson(requestTable) requestHeader := "Content-Length:" || *requestBody || "\r\n\r\n" - write("udap: " || requestBody) + write("udap -> client: " || requestBody) seq +:= 1 @@ -155,6 +160,7 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) # Build Event # ################################################################################# + # Create and return an event for client in json format. method build_event(event, body) local eventTable, eventBody, eventHeader @@ -167,7 +173,7 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) eventBody := tojson(eventTable) eventHeader := "Content-Length:" || *eventBody || "\r\n\r\n" - write("udap: " || eventBody) + write("udap -> client: " || eventBody) seq +:= 1 @@ -178,6 +184,7 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) # initialize # ######################################################## + # Handles all the things required from a client "initialize" request. method initialize(request_seq, request_command, request_arguments) local capabilitiesTable, res, udbPort, startRes, event, req @@ -199,7 +206,6 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) capabilitiesTable := table() capabilitiesTable["supportsConfigurationDoneRequest"] := "__true__" - capabilitiesTable["supportsRestartRequest"] := "__true__" res := build_response(request_seq, "__true__", request_command, capabilitiesTable) writes(sock, res) @@ -212,61 +218,72 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) # launch # ######################################################## + # Handles all the things required from a client "launch" request. method launch(request_seq, request_command, request_arguments) local res, initEvent, pth, event, progPort pth := request_arguments["program"] pth ? { - tab(match("^\"\\")) pth := tab(find(".icn")) } + communicator.set_filepath(pth) + + if member(request_arguments, "args") then { + communicator.progArgs := request_arguments["args"] + } port ? { move() progPort := tab(0) } progPort := integer(progPort) - progPort +:= 20 + progPort +:= 30 args := ["progcom " || progPort] event := build_request("runInTerminal", table("kind", "integrated", "cwd", "", "title", "udbTerminal", "args", args, "argsCanBeInterpretedByShell", "__true__")) writes(sock, event) waitingForTerminal := 1 - communicator.set_filepath(pth) + communicator.progComSock := communicator.open_sock(progPort) + if /communicator.progComSock then { + res := build_response(request_seq, "__false__", request_command, &null, "udap failed to open progComSock: " || port + 10) + writes(sock, res) + return + } res := build_response(request_seq, "__true__", request_command) writes(sock, res) end -######################################################## -# progress # -######################################################## - - method progress(cmd) - udb_input(cmd) - end - ######################################################## # set breakpoints # ######################################################## + # Handles all the things required from a client "setBreakpoints" request. method set_breakpoints(request_seq, request_command, request_arguments) - local res, breakpointTable, breakpoint + local res, breakpointTable, breakpoint, resTable breakpointTable := table() breakpointTable["breakpoints"] := request_arguments["breakpoints"] - communicator.set_breakpoints(request_arguments) + resTable := communicator.set_breakpoints(request_arguments) - res := build_response(request_seq, "__true__", request_command, breakpointTable) - writes(sock, res) + if type(\resTable) == "table" then { + if member(resTable, "type") then + if resTable["type"] == "crash" then + handle_error(resTable) + } + else { + res := build_response(request_seq, "__true__", request_command, breakpointTable) + writes(sock, res) + } end ######################################################## # threads # ######################################################## + # Handles all the things required from a client "threads" request. method threads(request_seq, request_command) local res, threadsTable @@ -281,46 +298,70 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) # stackTrace # ######################################################## + # Handles all the things required from a client "stackTrace" request. method stackTrace(request_seq, request_command, request_arguments) local res, stackList - stackList := communicator.stackTrace() + stackList := communicator.stack_trace() - res := build_response(request_seq, "__true__", request_command, table("stackFrames", stackList)) - writes(sock, res) + if type(stackList) == "table" then { + if member(stackList, "type") then + if stackList["type"] == "crash" then + handle_error(stackList) + } + else { + res := build_response(request_seq, "__true__", request_command, table("stackFrames", stackList)) + writes(sock, res) + } end ######################################################## # scopes # ######################################################## + # Handles all the things required from a client "scopes" request. method scopes(request_seq, request_command, request_arguments) local res, scopes - scopes := communicator.getScopes(request_arguments["frameId"]) + scopes := communicator.get_scopes(request_arguments["frameId"]) - res := build_response(request_seq, "__true__", request_command, table("scopes", scopes)) - writes(sock, res) + if type(scopes) == "table" then { + if member(scopes, "type") then + if scopes["type"] == "crash" then + handle_error(scopes) + } + else { + res := build_response(request_seq, "__true__", request_command, table("scopes", scopes)) + writes(sock, res) + } end ######################################################## # variables # ######################################################## + # Handles all the things required from a client "variables" request. method variables(request_seq, request_command, request_arguments) local res, variables - variables := communicator.getVariables(request_arguments["variablesReference"]) + variables := communicator.get_variables(request_arguments["variablesReference"]) - res := build_response(request_seq, "__true__", request_command, table("variables", variables)) - writes(sock, res) + if type(variables) == "table" then { + if member(variables, "type") then + if variables["type"] == "crash" then + handle_error(variables) + } + else { + res := build_response(request_seq, "__true__", request_command, table("variables", variables)) + writes(sock, res) + } end ######################################################## # acknowledge # ######################################################## - # default response for request that only requires an acknowledgement + # Default response for request that only requires an acknowledgement. method acknowledge(request_seq, request_command) local res @@ -328,49 +369,16 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) writes(sock, res) end -######################################################## -# restart # -######################################################## - - method restart(request_seq, request_command) - local initEvent, cmd, progPort, event - - udb_input("reset") - system("kill -9 " || shellProcessId) - - every cmd := communicator.load_cmds() do udb_input(cmd) - acknowledge(request_seq, request_command) - - if member(\clientDetails, "supportsInvalidatedEvent") then if clientDetails["supportsInvalidatedEvent"] == "__true__" then { - event := build_event("invalidated", table("areas", ["all"])) - writes(sock, event) - } - - port ? { - move() - progPort := tab(0) - } - progPort := integer(progPort) - progPort +:= 20 - args := ["progcom " || progPort] - - event := build_request("runInTerminal", table("kind", "integrated", "cwd", "", "title", "udbTerminal", "args", args, "argsCanBeInterpretedByShell", "__true__")) - writes(sock, event) - waitingForTerminal := 1 - - initEvent := build_event("initialized") - writes(sock, initEvent) - end - ######################################################## # udb_listen # ######################################################## + # Reads udb's communication socket and processes any output collected. method udb_listen() local outputTable, udbRes udbRes := communicator.udb_output() - if udbRes ~== "" then { + if \udbRes ~== "" then { every outputTable := jtou(udbRes) do { process_udb_output_table(outputTable) } @@ -381,9 +389,12 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) # udb_input # ######################################################## - method udb_input(expression) + # Sends udb a command as 'expression' and processes any output collected. + # 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_input(expression, wait) local outputTable - every outputTable := communicator.udb_input(expression) do { + every outputTable := communicator.udb_input(expression, wait) do { process_udb_output_table(outputTable) } end @@ -392,13 +403,14 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) # process_udb_output_table # ######################################################## + # Given a udb output table as 'outputTable', process that table. method process_udb_output_table(outputTable) local udbRes := "", event udbRes := replacem(\outputTable["consoleMsg"], "\\\\\\\"", "\\\"", "\\\\\"", "\\\"", "\"", "\\\"") /outputTable["type"] := "console" - if not (outputTable["type"] == ("exited" | "stderr")) then { + if not (outputTable["type"] == ("exited" | "stderr" | "crash")) then { event := build_event("output", table("category", "console", "output", udbRes)) writes(sock, event) if outputTable["type"] == "breakpoint" then { @@ -430,6 +442,10 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) event := build_event("exited", table("exitCode", outputTable["exitCode"])) writes(sock, event) } + else if outputTable["type"] == "crash" then { + handle_error(outputTable) + return + } if \outputTable["requireResponse"] then { event := build_event("output", table("category", "console", "output", "[answered Y; input not from terminal]\n")) writes(sock, event) @@ -441,6 +457,7 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) # evaluate # ######################################################## + # Handles all the things required from a client "evaluate" request. method evaluate(request_seq, request_command, request_arguments) local res, expression, result := "", isEvalExp @@ -462,19 +479,63 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) end ######################################################## -# disconnect # +# handle_error # ######################################################## - method disconnect(msg) - local event - /msg := "UDAP disconnected" - udb_input("reset") + # Handle a udb output table of "type" set to "crash". + method handle_error(outputTable) + local errorText, errorMessageTable, res, event + + udbError := "__true__" + + errorText := "Unicon Debugger experienced an error during " || currentRequestBody["command"] || " request. " + if \outputTable then { + errorText ||:= "Error number: " || outputTable["errornumber"] || ". " + if member(outputTable, "errortext") then errorText ||:= "Error text: " || outputTable["errortext"] || ". " + if member(outputTable, "errorvalue") then errorText ||:= "Error value: " || outputTable["errorvalue"] || ". " + } + errorText ||:= "Closing Unicon Debugger." + + errorMessageTable := table("format", errorText, "showUser", "__true__") + if \outputTable then errorMessageTable["id"] := outputTable["errornumber"] + else errorMessageTable["id"] := 0 - system("kill -9 " || shellProcessId) - close(sock) + res := build_response(currentRequestBody["seq"], "__false__", currentRequestBody["command"], table("error", errorMessageTable)) + writes(sock, res) + + event := build_event("terminated") + writes(sock, event) + end + + # Enable communication between progcom and debugee. + method debuggee_progcom_communication() + local resList, res + repeat { + if \communicator then { + (resList := select([\communicator.progSock, \communicator.progComSock])) | next + every res := !resList do { + if res === communicator.progSock then writes(communicator.progComSock, ready(communicator.progSock)) + if res === communicator.progComSock then writes(communicator.progSock, ready(communicator.progComSock)) + } + } + } + end + +######################################################## +# disconnect # +######################################################## + + # Disconnect from client and udb and startup as a fresh session. + method disconnect() + communicator.disconnect_udbsock() + communicator.end_debugger() + communicator.udb_input(&null, &null, 1) + kill(\shellProcessId, 9) + close(\sock) startup() end + # Attempt to open communication port for client and set default class parameters. method startup() every 1 to 5 do if sock := open(port, "na") then { @@ -487,13 +548,15 @@ class Server(port, sock, communicator, shellProcessId, clientDetails) if /sock then stop("failed to connect to ",port) - seq := 1 + communicator := Communicator() + seq := 1 + udbError := &null lastUdbCmd := "" requestQueue := [] end initially + thread debuggee_progcom_communication() startup() - communicator := Communicator() end \ No newline at end of file