Skip to content

Instantly share code, notes, and snippets.

@AR2000AR
Created September 29, 2022 22:46
debugger for OpenComputer
local libdbg = require("libdbg")
local dbgInstance = libdbg.Debugger.new(nil,nil,true) --default to localhost:4545
while true do
dbgInstance.breakpoint() --will break
os.sleep()
end
-- On the machine, open a listening socket (nc -k -N -l 4545)
-- available cmd are : break,continue,getenv,getproc,getall
local component = require("component")
local event = require("event")
local os = require("os")
local s = require("serialization").serialize
local internet = require("internet")
local process = require("process")
assert(component.isAvailable("internet"), "No internet component")
assert(component.internet.isTcpEnabled(), "Cannot open TPC sockets. See mod config")
local libdbgu = {}
libdbgu.LOG_LEVEL = {}
libdbgu.LOG_LEVEL.DEBUG = 1
libdbgu.LOG_LEVEL.INFO = libdbgu.LOG_LEVEL.DEBUG + 1
libdbgu.LOG_LEVEL.WARNING = libdbgu.LOG_LEVEL.INFO + 1
libdbgu.LOG_LEVEL.ERROR = libdbgu.LOG_LEVEL.WARNING + 1
libdbgu.LOG_LEVEL[libdbgu.LOG_LEVEL.DEBUG] = "DEBUG"
libdbgu.LOG_LEVEL[libdbgu.LOG_LEVEL.INFO] = "INFO"
libdbgu.LOG_LEVEL[libdbgu.LOG_LEVEL.WARNING] = "WARNING"
libdbgu.LOG_LEVEL[libdbgu.LOG_LEVEL.ERROR] = "ERROR"
libdbgu.Debugger = {}
function libdbgu.Debugger.new(addr, port, waitForDebugger, env)
local self = {type = "Debugger"}
addr = addr or "localhost"
port = port or 4545
local doBreak = false
local socket, rawSocket, timer
env = env or _ENV
local function dumpAll()
if (not rawSocket.finishConnect()) then return end
socket:setvbuf("full")
socket:write("_ENV====================================\n")
for k, val in pairs(_ENV) do
local v = "yolo"
socket:write(string.format("%s\t%q\n", k, type(val)))
end
socket:flush()
socket:write("_G======================================\n")
for k, val in pairs(_G) do
local v = "yole"
socket:write(string.format("%s\t%q\n", k, type(val)))
end
socket:flush()
socket:write("debug.getinfo()=========================\n")
local l = 0
while (debug.getinfo(l, "nSlufL")) do
local info = debug.getinfo(l, "nSlufL")
socket:write(string.format("%d :\n", l))
for key, val in pairs(info) do
socket:write(string.format("\t%s\t%q\n", key, tostring(val)))
end
socket:write("\n\tdebug.getlocal()\n")
local i = 1
while (debug.getlocal(l, i)) do
socket:write(string.format("\t%d\t%q\n", i, s(table.pack(debug.getlocal(l, i)))))
i = i + 1
end
l = l + 1
socket:flush()
end
socket:flush()
local info = debug.getinfo(dumpAll)
info.func = dumpAll
if (info.func and info.nups) then
for i = 0, info.nups do
socket:write(string.format("%d\t%q\n", i, s(table.pack(debug.getupvalue(info.func, i)))))
end
end
socket:flush()
socket:setvbuf("no")
end
local function dumpEnv()
for k, v in pairs(env) do
socket:write(string.format("[ENV] %s\t%s\t%q\n", k, type(v), tostring(v)))
end
end
local function doEval(data)
local f, err = load(data, "t", _ENV)
if (f) then
socket:write(string.format("[EVAL]\t%s\n", s(table.pack(pcall(f)))))
else
socket:write(string.format("[EVAL]\t%s\n", err))
end
end
local function dumpProc()
for k, v in pairs(process.findProcess()) do
socket:write(string.format("[PROC] %q\t%s\t%q\n", k, type(v), tostring(v)))
end
for k, v in pairs(process.findProcess().env) do
socket:write(string.format("[PROC][ENV] %q\t%s\t%q\n", k, type(v), tostring(v)))
end
for k, v in pairs(process.findProcess().data) do
socket:write(string.format("[PROC][DATA] %q\t%s\t%q\n", k, type(v), tostring(v)))
end
for k, v in pairs(process.findProcess().instances) do
socket:write(string.format("[PROC][INSTANCES] %q\t%s\t%q\n", k, type(v), tostring(v)))
end
end
function self.tick()
local data, status
repeat
status, data = pcall(socket.read, socket) --hide buffer timeouts
if (status and data) then
if (data:sub(1, 1) == "=") then
doEval(data:sub(2))
end
if (data == "break" or data == "b") then
doBreak = true
self.breakpoint()
elseif (data == "continue" or data == "c") then
doBreak = false
elseif (data == "getenv") then
dumpEnv()
elseif (data == "getproc") then
dumpProc()
elseif (data == "getall") then
dumpAll()
end
end
until (not data or not status)
end
function self.breakpoint()
self.log("Breaking", libdbgu.LOG_LEVEL.DEBUG)
dumpAll()
--[[ for key, val in pairs(debug.getinfo(2)) do
socket:write(string.format("[DEBUG_GETINFO] %s\t%q\n", key, tostring(val)))
end
local i = 1
while (debug.getlocal(2, i)) do
socket:write(string.format("[DEBUG_GETLOCAL] %d\t%q\n", i, s(table.pack(debug.getlocal(2, i)))))
i = i + 1
end ]]
while doBreak do
---@diagnostic disable-next-line: undefined-field
os.sleep()
end
end
local varCache = {} --key = tostring(var). only for function and tables
function self.dumpVar(value, varName)
--get caller info
--cache the var if table or function
local valueType = type(value)
if (valueType == "table" or valueType == "function") then
varCache[tostring(value)] = {valueType, value, varName or "unknown"}
end
--TODO : handle table
socket:write(string.format("[VAR] %q,%q,%q\n", varName or "unknonw", valueType, tostring(value)))
end
function self.close()
self.log("Closing debugger session")
if (timer) then event.cancel(timer) end
socket:close()
end
function self.log(msg, level)
checkArg(1, msg, "string")
checkArg(2, level, "number", "nil")
level = level or libdbgu.LOG_LEVEL.INFO
socket:write(string.format("[%s] %s\n", libdbgu.LOG_LEVEL[level], msg))
end
socket = internet.open(addr, port)
rawSocket = socket.stream.socket
socket:setTimeout(0)
socket:setvbuf("no")
while waitForDebugger do
waitForDebugger = not rawSocket.finishConnect()
event.pull(0, "dummy")
end
timer = event.timer(0.5, self.tick, math.huge)
self.log("Debugger attached")
return self
end
return libdbgu
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment