You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
809 lines
22 KiB
809 lines
22 KiB
8 years ago
|
-- see copyright notice in grldc.h
|
||
|
|
||
|
local debug = require( "debug" )
|
||
|
local net = require( "grldc.net" )
|
||
|
local socket = require( "grldc.socket" )
|
||
|
local utilities = require( "grldc.utilities" )
|
||
|
|
||
|
local assert = assert
|
||
|
local print = print
|
||
|
local type = type
|
||
|
local debug = debug
|
||
|
local xpcall = xpcall
|
||
|
local pcall = pcall
|
||
|
local tostring = tostring
|
||
|
local string = string
|
||
|
local table = table
|
||
|
local unpack = unpack
|
||
|
local error = error
|
||
|
local setmetatable = setmetatable
|
||
|
local getmetatable = getmetatable
|
||
|
local coroutine = coroutine
|
||
|
local pairs = pairs
|
||
|
local ipairs = ipairs
|
||
|
local loadstring = loadstring
|
||
|
local originalCoroutineCreate = coroutine.create
|
||
|
local globals = _G
|
||
|
local getfenv = getfenv
|
||
|
local setfenv = setfenv
|
||
|
local findfenv = getfenv
|
||
|
|
||
|
-- luabind functions
|
||
|
local class_info = class_info
|
||
|
|
||
|
if getfenv == nil then
|
||
|
-- lua 5.2: no get/set environment
|
||
|
findfenv = function( f )
|
||
|
local idx = 1
|
||
|
while true do
|
||
|
local name, value = debug.getupvalue( f, idx )
|
||
|
if name == nil then break end
|
||
|
--print( "\""..name.."\"" )
|
||
|
if name == "_ENV" then return value end
|
||
|
idx = idx + 1
|
||
|
end
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
getfenv = function( f )
|
||
|
local env = findfenv( f )
|
||
|
if env == nil then
|
||
|
error( "Can't find function environment" )
|
||
|
end
|
||
|
return env
|
||
|
end
|
||
|
|
||
|
assert( setfenv == nil )
|
||
|
setfenv = function( f, env )
|
||
|
local idx = 1
|
||
|
while true do
|
||
|
local name, value = debug.getupvalue( f, idx )
|
||
|
if name == nil then break end
|
||
|
--print( "\""..name.."\"" )
|
||
|
if name == "_ENV" then
|
||
|
debug.setupvalue( f, idx, env )
|
||
|
return
|
||
|
end
|
||
|
idx = idx + 1
|
||
|
end
|
||
|
error( "Can't find function environment" )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
module( "grldc" )
|
||
|
|
||
|
local server = nil
|
||
|
local status = "running"
|
||
|
|
||
|
local hookActiveCount = 0
|
||
|
|
||
|
local callstack
|
||
|
local coroutines = {}
|
||
|
setmetatable( coroutines, { __mode = "k" } )
|
||
|
|
||
|
local commands = {}
|
||
|
local runningCommands = {} -- commands that can be issued even when the debugged code is running
|
||
|
|
||
|
local breakPoints = {}
|
||
|
local breakPointAliases = {}
|
||
|
|
||
|
internal_.init( breakPointAliases )
|
||
|
|
||
|
local values = {}
|
||
|
local proxyMeta = {}
|
||
|
|
||
|
local envMeta = {}
|
||
|
|
||
|
local function releaseValue( id )
|
||
|
--print( "Releasing value with ID "..id )
|
||
|
assert( values[id] ~= nil )
|
||
|
values[id] = nil
|
||
|
end
|
||
|
|
||
|
local function splitValue( value )
|
||
|
local t = type( value )
|
||
|
if t == "nil" then
|
||
|
return { type = t, short = tostring( value ) }
|
||
|
elseif t == "number" then
|
||
|
return value
|
||
|
elseif t == "string" then
|
||
|
local maxStringLength = 48
|
||
|
if #value > maxStringLength then
|
||
|
local id = #values + 1 -- table 'values' can have holes, but this will always give a free id
|
||
|
--print( "Created string value with ID "..id )
|
||
|
values[id] = value
|
||
|
return { type = t, short = "\""..string.sub( value, 1, maxStringLength-3 ).."\"...", id = id }
|
||
|
else
|
||
|
return value
|
||
|
end
|
||
|
elseif t == "boolean" then
|
||
|
return value
|
||
|
elseif t == "table" then
|
||
|
local id = #values + 1 -- table 'values' can have holes, but this will always give a free id
|
||
|
--print( "Created table value with ID "..id )
|
||
|
values[id] = value
|
||
|
local res = { type = t, short = tostring( value ), id = id }
|
||
|
if getmetatable( value ) == proxyMeta then
|
||
|
res.type = "proxy"
|
||
|
res.short = value.short
|
||
|
end
|
||
|
return res
|
||
|
elseif t == "function" then
|
||
|
local id = #values + 1 -- table 'values' can have holes, but this will always give a free id
|
||
|
--print( "Created function value with ID "..id )
|
||
|
values[id] = value
|
||
|
return { type = t, short = tostring( value ), id = id }
|
||
|
elseif t == "thread" then
|
||
|
return { type = t, short = tostring( value ) }
|
||
|
elseif t == "userdata" then
|
||
|
local m = getmetatable(value)
|
||
|
if m and m.__luabind_class then
|
||
|
local tostr = m.__tostring
|
||
|
m.__tostring = nil -- temporarily disable tostring, so that we can get native lua info
|
||
|
local ok, ptr = pcall(tostring,value)
|
||
|
m.__tostring = tostr
|
||
|
_, _, ptr = string.find( ptr, "userdata: (.+)" )
|
||
|
local info = class_info( value )
|
||
|
local id = #values + 1 -- table 'values' can have holes, but this will always give a free id
|
||
|
values[id] = value
|
||
|
return { type = t, short = "[luabind] "..info.name..": "..ptr, id = id }
|
||
|
end
|
||
|
return { type = t, short = tostring(value) }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function getValue( id )
|
||
|
local value = assert( values[id], "No value associated to ID "..tostring(id) )
|
||
|
local t = type( value )
|
||
|
local res
|
||
|
if t == "table" and getmetatable( value ) == proxyMeta then
|
||
|
res = {}
|
||
|
for _, entry in ipairs( value ) do
|
||
|
table.insert( res, { name = entry.name, value = splitValue( entry.value ) } )
|
||
|
end
|
||
|
elseif t == "table" then
|
||
|
res = {}
|
||
|
local meta = getmetatable( value )
|
||
|
if meta ~= nil then
|
||
|
table.insert( res, { name = "<metatable>", value = splitValue( meta ) } )
|
||
|
end
|
||
|
for k, v in pairs( value ) do
|
||
|
local key = splitValue(k)
|
||
|
local val = splitValue(v)
|
||
|
if type( key ) == "table" and key.id ~= nil then -- the key is a complex value
|
||
|
local proxy = { { name = "<key>", value = k }, { name = "<value>", value = v } }
|
||
|
if type( val ) == "table" then
|
||
|
proxy.short = val.short
|
||
|
else
|
||
|
if type( val ) == "string" then
|
||
|
proxy.short = "\""..val.."\""
|
||
|
else
|
||
|
proxy.short = tostring( val )
|
||
|
end
|
||
|
end
|
||
|
setmetatable( proxy, proxyMeta )
|
||
|
proxy = splitValue( proxy )
|
||
|
table.insert( res, { name = "["..key.short.."]", value = proxy } )
|
||
|
releaseValue( key.id )
|
||
|
if type( val ) == "table" and val.id ~= nil then
|
||
|
releaseValue( val.id )
|
||
|
end
|
||
|
else
|
||
|
local name
|
||
|
if type( key ) == "table" then
|
||
|
name = "["..key.short.."]"
|
||
|
else
|
||
|
local simpleKey = false
|
||
|
if type( key ) == "string" then
|
||
|
simpleKey = (string.find( key, "^[%a_][%a%d_]*$" ) ~= nil)
|
||
|
end
|
||
|
if simpleKey then
|
||
|
name = key
|
||
|
else
|
||
|
local keyStr = tostring( key )
|
||
|
if type( key ) == "string" then
|
||
|
keyStr = "\""..keyStr.."\""
|
||
|
end
|
||
|
name = "["..keyStr.."]"
|
||
|
end
|
||
|
end
|
||
|
table.insert( res, { name = name, value = val } )
|
||
|
end
|
||
|
end
|
||
|
elseif t == "function" then
|
||
|
res = {}
|
||
|
local upvaluesProxy = {}
|
||
|
setmetatable( upvaluesProxy, proxyMeta )
|
||
|
local upIdx = 1
|
||
|
while true do
|
||
|
local upName, upValue = debug.getupvalue( value, upIdx )
|
||
|
if upName == nil then break end
|
||
|
table.insert( upvaluesProxy, { name = upIdx..": "..upName, value = upValue } )
|
||
|
upIdx = upIdx + 1
|
||
|
end
|
||
|
local info = debug.getinfo( value, "S" )
|
||
|
table.insert( res, { name = "<what>", value = splitValue( info.what ) } )
|
||
|
if string.sub( info.source, 1, 1 ) == "@" then
|
||
|
table.insert( res, { name = "<source>", value = splitValue( info.source.."("..info.linedefined..")" ) } )
|
||
|
else
|
||
|
table.insert( res, { name = "<source>", value = splitValue( info.source ) } )
|
||
|
end
|
||
|
table.insert( res, { name = "<environment>", value = splitValue( findfenv( value ) ) } )
|
||
|
table.insert( res, { name = "<upvalues>", value = splitValue( upvaluesProxy ) } )
|
||
|
elseif t == "string" then
|
||
|
res = { { name = "<value>", value = value } }
|
||
|
elseif t == "userdata" then
|
||
|
local m = getmetatable( value )
|
||
|
if m and m.__luabind_class then
|
||
|
local info = class_info( value )
|
||
|
local res = {}
|
||
|
table.insert( res, { name = "<class methods>", value = splitValue( info.methods ) } )
|
||
|
for _, attrName in pairs( info.attributes ) do
|
||
|
table.insert( res, { name = attrName, value = splitValue( value[attrName] ) } )
|
||
|
end
|
||
|
return res
|
||
|
else
|
||
|
error( "Unknown value type: "..t.." (value = "..tostring( value )..")" )
|
||
|
end
|
||
|
else
|
||
|
error( "Unknown value type: "..t.." (value = "..tostring( value )..")" )
|
||
|
end
|
||
|
if res[1] == nil then
|
||
|
res[1] = { name = "<empty>" }
|
||
|
end
|
||
|
return res
|
||
|
end
|
||
|
|
||
|
local function checkClosed( f, ... )
|
||
|
local results = { xpcall( f, function( msg ) if msg == "closed" then return msg end return debug.traceback( msg ) end ) }
|
||
|
if not results[1] then
|
||
|
if results[2] == "closed" then
|
||
|
print( "Connection with debugger lost" )
|
||
|
server = nil
|
||
|
else
|
||
|
error( results[2], 0 )
|
||
|
end
|
||
|
return ...
|
||
|
end
|
||
|
return unpack( results, 2 )
|
||
|
end
|
||
|
|
||
|
local function synchronize()
|
||
|
print( "sending synchronization request..." )
|
||
|
server:send( "synchronize" )
|
||
|
print( "receiving breakpoints..." )
|
||
|
local numBreakpoints = server:receive()
|
||
|
print( tostring(numBreakpoints).." breakpoint(s)" )
|
||
|
assert( numBreakpoints == 0 ) -- not yet implemented
|
||
|
local breakOnConnection = server:receive()
|
||
|
return breakOnConnection
|
||
|
end
|
||
|
|
||
|
function updateRunningRequests_()
|
||
|
while true do
|
||
|
if server == nil then break end
|
||
|
local command = server:tryReceive( "running" )
|
||
|
if command == nil then break end
|
||
|
local func = runningCommands[command]
|
||
|
assert( func ~= nil, "Unknown running command: "..tostring( command ) )
|
||
|
local ok, msg = xpcall( func, debug.traceback )
|
||
|
if not ok then
|
||
|
print( "Error processing running command "..command..": "..tostring( msg ) )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function registerSourceFile_( fileName )
|
||
|
local nsource = "@"..utilities.normalizePath( string.sub( fileName, 2 ) )
|
||
|
s = breakPoints[nsource]
|
||
|
if s == nil then
|
||
|
s = {}
|
||
|
breakPoints[nsource] = s
|
||
|
end
|
||
|
breakPointAliases[fileName] = s
|
||
|
return s
|
||
|
end
|
||
|
|
||
|
local function getinfo( thread, level, what )
|
||
|
if type(level) ~= "function" then
|
||
|
level = level + 1 -- do not count ourself
|
||
|
end
|
||
|
if thread == nil then
|
||
|
thread = getmainthread()
|
||
|
end
|
||
|
return debug.getinfo( thread, level, what )
|
||
|
end
|
||
|
|
||
|
local function getlocal( thread, level, idx )
|
||
|
level = level + 1 -- do not count ourself
|
||
|
if thread == nil then
|
||
|
thread = getmainthread()
|
||
|
end
|
||
|
return debug.getlocal( thread, level, idx )
|
||
|
end
|
||
|
|
||
|
local function setlocal( thread, level, idx, value )
|
||
|
level = level + 1 -- do not count ourself
|
||
|
if thread == nil then
|
||
|
thread = getmainthread()
|
||
|
end
|
||
|
return debug.setlocal( thread, level, idx, value )
|
||
|
end
|
||
|
|
||
|
function getAppLevel_( thread, fromLevel )
|
||
|
-- find where the application code starts in the callstack (we want to ignore grldc functions)
|
||
|
local level = (fromLevel or 1) + 1
|
||
|
local appLevel
|
||
|
local grldcFunction = { [breakNow] = true, [connect] = true, [globals.coroutine.create] = true, [updateRunningRequests_] = true }
|
||
|
--[[print( "GRLDC functions:" )
|
||
|
for f in pairs( grldcFunction ) do
|
||
|
print( "\t"..tostring(f).." ("..tostring(getinfo(thread,f,"nf").name)..")" )
|
||
|
end
|
||
|
print( "Current stack:" )]]
|
||
|
while true do
|
||
|
local info = getinfo( thread, level, "f" )
|
||
|
if info == nil then break end
|
||
|
--print( "\t"..level.." "..tostring(info.func).." ("..tostring(info.name)..")" )
|
||
|
if grldcFunction[info.func] then appLevel = level end -- actual appLevel is level + 1, but we don't count ourself
|
||
|
level = level + 1
|
||
|
end
|
||
|
return appLevel
|
||
|
end
|
||
|
|
||
|
local function getCallstack( thread )
|
||
|
local appLevel = getAppLevel_( thread )
|
||
|
if appLevel == nil then appLevel = 0 end
|
||
|
|
||
|
local callstack = {}
|
||
|
local level = appLevel
|
||
|
while true do
|
||
|
local info = getinfo( thread, level, "nSl" )
|
||
|
if info == nil then break end
|
||
|
level = level + 1
|
||
|
local data =
|
||
|
{
|
||
|
name = info.name,
|
||
|
namewhat = info.namewhat,
|
||
|
what = info.what,
|
||
|
source = info.source,
|
||
|
line = info.currentline,
|
||
|
}
|
||
|
table.insert( callstack, data )
|
||
|
end
|
||
|
return callstack
|
||
|
end
|
||
|
|
||
|
local function setHook()
|
||
|
hookActiveCount = hookActiveCount + 1
|
||
|
if hookActiveCount == 1 then
|
||
|
internal_.setHookActive( true )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function removeHook()
|
||
|
hookActiveCount = hookActiveCount - 1
|
||
|
if hookActiveCount == 0 then
|
||
|
internal_.setHookActive( false )
|
||
|
end
|
||
|
--debug.sethook( nil )
|
||
|
end
|
||
|
|
||
|
function suspendHook()
|
||
|
removeHook()
|
||
|
end
|
||
|
|
||
|
function resumeHook()
|
||
|
setHook()
|
||
|
end
|
||
|
|
||
|
local function registerCoroutine( co )
|
||
|
--debug.sethook( co, hook, "crl" )
|
||
|
internal_.setHook( co )
|
||
|
coroutines[co] = {}
|
||
|
end
|
||
|
|
||
|
globals.coroutine.create = function( f )
|
||
|
local co = originalCoroutineCreate( f )
|
||
|
registerCoroutine( co )
|
||
|
return co
|
||
|
end
|
||
|
|
||
|
local function setBreakPoint( source, line, value )
|
||
|
assert( string.sub( source, 1, 1 ) == "@" )
|
||
|
local nsource = "@"..utilities.normalizePath( string.sub( source, 2 ) )
|
||
|
assert( nsource == source, "Source must be normalized before setting a breakpoint, but source "..source.." is not normalized to "..nsource )
|
||
|
print( "Setting breakpoint at "..source.."("..line..") to "..tostring( value ) )
|
||
|
local s = breakPoints[source]
|
||
|
if s == nil then s = {} breakPoints[source] = s end
|
||
|
if value then
|
||
|
s[line] = true
|
||
|
else
|
||
|
s[line] = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function connect( address, port, name, maxRetry )
|
||
|
local retryCount = maxRetry
|
||
|
assert( name ~= nil )
|
||
|
assert( server == nil, "Already connected" )
|
||
|
print( "grldc: connecting to GRLD server..." )
|
||
|
while true do
|
||
|
local ok, msg = pcall( function()
|
||
|
server = net.connect( address, port )
|
||
|
end )
|
||
|
if ok then break end
|
||
|
if not ok and msg ~= "connection refused" then
|
||
|
error( msg )
|
||
|
end
|
||
|
if maxRetry ~= nil then
|
||
|
retryCount = retryCount - 1
|
||
|
if retryCount < 0 then
|
||
|
print( "grldc: can't connect to GRLD server after "..(maxRetry+1).." attempt(s) ; debugging disabled" )
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
print( "grldc module connected to the GRLD server" )
|
||
|
checkClosed( function()
|
||
|
print( "sending client name..." )
|
||
|
server:send( name )
|
||
|
print( "synchronizing with server..." )
|
||
|
local breakOnConnection = synchronize()
|
||
|
local co, mainthread = coroutine.running()
|
||
|
assert( co == nil or mainthread, "Connection to the debugger must be done from the main thread" )
|
||
|
if mainthread then
|
||
|
-- lua 5.2: we can access the main thread directly
|
||
|
getmainthread = function() return co end
|
||
|
end
|
||
|
print( "setting debug hook..." )
|
||
|
internal_.setHook( getmainthread() )
|
||
|
print( "hook set" )
|
||
|
setHook()
|
||
|
if breakOnConnection then
|
||
|
breakNow()
|
||
|
end
|
||
|
end )
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
local function breakNowImpl()
|
||
|
assert( status == "running" )
|
||
|
status = "break"
|
||
|
internal_.setStepMode( 0, nil )
|
||
|
server:send( "break" )
|
||
|
--assert( server:receive() == "ack_break" )
|
||
|
|
||
|
callstack = getCallstack( coroutine.running() )
|
||
|
|
||
|
server:send( callstack[1].source )
|
||
|
server:send( callstack[1].line )
|
||
|
while status == "break" do
|
||
|
--print( "waiting data..." )
|
||
|
server:waitData()
|
||
|
--print( "received data" )
|
||
|
updateRunningRequests_()
|
||
|
local command = server:tryReceive()
|
||
|
if command ~= nil then
|
||
|
assert( commands[command] ~= nil, "Received unknown command: "..tostring(command) )
|
||
|
commands[command]()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
callstack = nil
|
||
|
end
|
||
|
|
||
|
local function getCoroutineId( co )
|
||
|
if co == nil then
|
||
|
return "main"
|
||
|
else
|
||
|
local _, _, id = string.find( tostring( co ), "thread: (.*)" )
|
||
|
assert( id ~= nil )
|
||
|
return id
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function getCoroutineFromId( id )
|
||
|
if id == "current" then
|
||
|
return coroutine.running()
|
||
|
else
|
||
|
local co = nil
|
||
|
if id ~= "main" then
|
||
|
for c, info in pairs( coroutines ) do
|
||
|
if coroutine.status( c ) ~= "dead" and id == getCoroutineId( c ) then
|
||
|
co = c
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
if co == nil then
|
||
|
return "no such coroutine"
|
||
|
end
|
||
|
end
|
||
|
return co
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function commands.run()
|
||
|
--server:send( "ack_run" )
|
||
|
status = "running"
|
||
|
--assert( stepMode == nil )
|
||
|
end
|
||
|
|
||
|
function commands.stepover()
|
||
|
--server:send( "ack_stepover" )
|
||
|
status = "running"
|
||
|
internal_.setStepMode( 2, coroutine.running() or getmainthread() )
|
||
|
end
|
||
|
|
||
|
function commands.stepin()
|
||
|
--server:send( "ack_stepin" )
|
||
|
status = "running"
|
||
|
internal_.setStepMode( 1, nil )
|
||
|
end
|
||
|
|
||
|
function commands.stepout()
|
||
|
--server:send( "ack_stepout" )
|
||
|
status = "running"
|
||
|
internal_.setStepMode( 3, coroutine.running() or getmainthread() )
|
||
|
end
|
||
|
|
||
|
function commands.callstack()
|
||
|
local thread = server:receive()
|
||
|
if thread == "current" then
|
||
|
server:send( callstack )
|
||
|
else
|
||
|
local co = getCoroutineFromId( thread )
|
||
|
if type( co ) ~= "string" then
|
||
|
server:send( getCallstack( co ) )
|
||
|
else
|
||
|
server:send( co )
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function commands.coroutines()
|
||
|
local res = {}
|
||
|
for co, info in pairs( coroutines ) do
|
||
|
if coroutine.status( co ) ~= "dead" then
|
||
|
local id = getCoroutineId( co )
|
||
|
table.insert( res, { id = id } )
|
||
|
end
|
||
|
end
|
||
|
server:send( res )
|
||
|
end
|
||
|
|
||
|
function commands.currentthread()
|
||
|
server:send( getCoroutineId( coroutine.running() ) )
|
||
|
end
|
||
|
|
||
|
function commands.breakpoints()
|
||
|
server:send( breakPoints )
|
||
|
end
|
||
|
|
||
|
function commands.locals()
|
||
|
local res = {}
|
||
|
local thread = server:receive()
|
||
|
local level = server:receive()
|
||
|
local co = getCoroutineFromId( thread )
|
||
|
if type( co ) ~= "string" then
|
||
|
local idx = 1
|
||
|
local appLevel = getAppLevel_( co, 1 )
|
||
|
if appLevel == nil then
|
||
|
appLevel = 0
|
||
|
end
|
||
|
level = level + appLevel - 1
|
||
|
while true do
|
||
|
local name, value = getlocal( co, level, idx )
|
||
|
if name == nil then break end
|
||
|
if name ~= "(*temporary)" then
|
||
|
table.insert( res, { name = name, value = splitValue( value ) } )
|
||
|
end
|
||
|
idx = idx + 1
|
||
|
end
|
||
|
server:send( res )
|
||
|
else
|
||
|
server:send( "no such coroutine" )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function commands.upvalues()
|
||
|
local res = {}
|
||
|
local thread = server:receive()
|
||
|
local level = server:receive()
|
||
|
local co = getCoroutineFromId( thread )
|
||
|
if type( co ) ~= "string" then
|
||
|
local idx = 1
|
||
|
local appLevel = getAppLevel_( co, 1 )
|
||
|
if appLevel == nil then
|
||
|
appLevel = 0
|
||
|
end
|
||
|
level = level + appLevel - 1
|
||
|
local info = getinfo( co, level, "f" )
|
||
|
while true do
|
||
|
local name, value = debug.getupvalue( info.func, idx )
|
||
|
if name == nil then break end
|
||
|
table.insert( res, { name = name, value = splitValue( value ) } )
|
||
|
idx = idx + 1
|
||
|
end
|
||
|
server:send( res )
|
||
|
else
|
||
|
server:send( "no such coroutine" )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function commands.evaluate()
|
||
|
local expr = server:receive()
|
||
|
local thread = server:receive()
|
||
|
local level = server:receive()
|
||
|
local co = getCoroutineFromId( thread )
|
||
|
if type( co ) ~= "string" then
|
||
|
if string.sub( expr, 1, 1 ) == "=" then
|
||
|
expr = "return "..string.sub( expr, 2 )
|
||
|
end
|
||
|
local ok, results = pcall( function()
|
||
|
local f = assert( loadstring( expr ) )
|
||
|
|
||
|
local appLevel = getAppLevel_( co, 1 )
|
||
|
if appLevel == nil then
|
||
|
appLevel = 0
|
||
|
end
|
||
|
local orgLevel = level
|
||
|
level = level + appLevel - 1
|
||
|
local info = getinfo( co, level, "f" )
|
||
|
|
||
|
local upvalues = {}
|
||
|
local idx = 1
|
||
|
while true do
|
||
|
local name, value = debug.getupvalue( info.func, idx )
|
||
|
if name == nil then break end
|
||
|
upvalues[name] = idx
|
||
|
idx = idx + 1
|
||
|
end
|
||
|
|
||
|
local locals = {}
|
||
|
idx = 1
|
||
|
while true do
|
||
|
local name, value = getlocal( co, level, idx )
|
||
|
if name == nil then break end
|
||
|
locals[name] = idx
|
||
|
idx = idx + 1
|
||
|
end
|
||
|
|
||
|
local env = setmetatable( { func = info.func, thread = co, level = orgLevel, locals = locals, upvalues = upvalues, environment = getfenv( info.func ) }, envMeta )
|
||
|
setfenv( f, env )
|
||
|
return { f() }
|
||
|
end )
|
||
|
if ok then
|
||
|
local res = {}
|
||
|
local lastResult = 0 -- TODO : check if there is a way to know the actual number of results, even if the last ones are nil values
|
||
|
for idx, value in pairs( results ) do
|
||
|
if idx > lastResult then lastResult = idx end
|
||
|
res[idx] = { name = "result #"..tostring(idx), value = splitValue( value ) }
|
||
|
end
|
||
|
for idx = 1, lastResult - 1 do
|
||
|
if res[idx] == nil then
|
||
|
res[idx] = { name = "result #"..tostring(idx), value = splitValue( nil ) }
|
||
|
end
|
||
|
end
|
||
|
if res[1] == nil then
|
||
|
res[1] = { name = "<no result>" }
|
||
|
end
|
||
|
server:send( res )
|
||
|
else
|
||
|
server:send( { { name = "<error>", value = splitValue( results ) } } )
|
||
|
end
|
||
|
else
|
||
|
server:send( { { name = "<error>", value = "no such coroutine" } } )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
envMeta.__index = function( self, key )
|
||
|
if key == "__globals__" then
|
||
|
return globals
|
||
|
elseif key == "_G" then
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
local lv = self.locals[key]
|
||
|
if lv ~= nil then
|
||
|
local appLevel = getAppLevel_( self.thread, 1 )
|
||
|
if appLevel == nil then
|
||
|
appLevel = 0
|
||
|
end
|
||
|
level = self.level + appLevel - 1
|
||
|
local k, v = getlocal( self.thread, level, lv )
|
||
|
return v
|
||
|
end
|
||
|
|
||
|
local uv = self.upvalues[key]
|
||
|
if uv ~= nil then
|
||
|
local k, v = debug.getupvalue( self.func, uv )
|
||
|
return v
|
||
|
end
|
||
|
|
||
|
return self.environment[key]
|
||
|
end
|
||
|
|
||
|
envMeta.__newindex = function( self, key, value )
|
||
|
if key == "__globals__" then
|
||
|
globals[key] = value
|
||
|
return
|
||
|
elseif key == "_G" then
|
||
|
error( "Can't override _G when remotely evaluating an expression" )
|
||
|
end
|
||
|
|
||
|
local lv = self.locals[key]
|
||
|
if lv ~= nil then
|
||
|
local appLevel = getAppLevel_( self.thread, 1 )
|
||
|
if appLevel == nil then
|
||
|
appLevel = 0
|
||
|
end
|
||
|
level = self.level + appLevel - 1
|
||
|
setlocal( self.thread, level, lv, value )
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local uv = self.upvalues[key]
|
||
|
if uv ~= nil then
|
||
|
debug.setupvalue( self.func, uv, value )
|
||
|
return
|
||
|
end
|
||
|
|
||
|
self.environment[key] = value
|
||
|
end
|
||
|
|
||
|
function commands.getValue()
|
||
|
local id = server:receive()
|
||
|
server:send( getValue( id ) )
|
||
|
end
|
||
|
|
||
|
function runningCommands.releaseValue()
|
||
|
local id = server:receive( "running" )
|
||
|
releaseValue( id )
|
||
|
end
|
||
|
|
||
|
runningCommands["break"] = function()
|
||
|
if status == "running" then
|
||
|
breakNow()
|
||
|
else
|
||
|
print( "Break command ignored: already breaked" )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
runningCommands.setbreakpoint = function()
|
||
|
local data = server:receive( "running" )
|
||
|
setBreakPoint( data.source, data.line, data.value )
|
||
|
end
|
||
|
|
||
|
function breakNow()
|
||
|
removeHook()
|
||
|
print( "Breaking execution..." )
|
||
|
while true do
|
||
|
if server == nil then
|
||
|
print( "Can't break execution: not connected to a debugger" )
|
||
|
return
|
||
|
end
|
||
|
local ok, msg = xpcall( breakNowImpl,
|
||
|
function( msg )
|
||
|
if msg == "closed" then return msg end
|
||
|
return debug.traceback( msg )
|
||
|
end
|
||
|
)
|
||
|
if ok then
|
||
|
break
|
||
|
else
|
||
|
if msg == "closed" then
|
||
|
print( "Connection with debugger lost" )
|
||
|
server = nil
|
||
|
break
|
||
|
else
|
||
|
print( "Error during break: "..msg )
|
||
|
end
|
||
|
end
|
||
|
socket.sleep( 0.1 )
|
||
|
status = "running"
|
||
|
end
|
||
|
print( "Resuming execution..." )
|
||
|
internal_.setStepDepth( 0 )
|
||
|
setHook()
|
||
|
end
|