mirror of https://github.com/axmolengine/axmol.git
3504 lines
150 KiB
C
3504 lines
150 KiB
C
|
|
/* lua_debugger.c */
|
|
|
|
#include "lua.h"
|
|
#include "lauxlib.h"
|
|
#include "lua_debugger.h"
|
|
|
|
/* debugger */
|
|
static const char *lua_m_debugger =
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2011-2012 Sierra Wireless and others.\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Sierra Wireless - initial API and implementation\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Debugger using DBGp protocol.\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- The module returns a single init function which takes 6 parameters (IDEHOST, IDEPORT, IDEKEY, TRANSPORT, PLATFORM, WORKINGDIR).\n"
|
|
"--\n"
|
|
"-- IDEHOST: the host name or the ip address of the DBGP server (so your ide)\n"
|
|
"-- if HOST is nil, the DBGP_IDEHOST env var is used.\n"
|
|
"-- if the env var is nil, the default value '127.0.0.1' is used.\n"
|
|
"--\n"
|
|
"-- IDEPORT: the port of the DBGP server (must be configure in the IDE)\n"
|
|
"-- if PORT is nil, the DBGP_IDEPORT env var is used.\n"
|
|
"-- if the env var is nil, the default value '10000' is used.\n"
|
|
"--\n"
|
|
"-- IDEIDEKEY: a string which is used as session key\n"
|
|
"-- if IDEKEY is nil, the DBGP_IDEKEY env var is used.\n"
|
|
"-- if the env var is nil, the default value 'luaidekey' is used.\n"
|
|
"--\n"
|
|
"-- TRANSPORT: (advanced optional parameter) the module name of which implement the transport interface used to do the connection with the server.\n"
|
|
"-- by default the debugger use an internal implementation based on luasocket, but if can not use it, you could implement or use another transport layer implementation.\n"
|
|
"-- if TRANSPORT is nil, the DBGP_TRANSPORT env var is used.\n"
|
|
"-- if the env var is nil, the default value 'debugger.transport.luasocket' is used : this is the default implementation based on luasocket.\n"
|
|
"--\n"
|
|
"-- PLATFORM: (advanced optional parameter) 'unix' or 'win32' string which define the kind of platform on which the program to debug is executed.\n"
|
|
"-- by default the debugger will try to guess it and surely success, if for some reasons it fails you could help it by precise the execution platform.\n"
|
|
"-- if PLATFORM is nil, the DBGP_PLATFORM env var is used.\n"
|
|
"-- if the env var is nil, the debugger will try to guess it.\n"
|
|
"--\n"
|
|
"-- WORKINGDIR: (advanced optional parameter) the working directory in which the program to debug is executed.\n"
|
|
"-- by default the debugger will try to guess it and surely success, if for some reasons it fails you could help it by precise the working directory.\n"
|
|
"-- if WORKINGDIR is nil, the DBGP_WORKINGDIR env var is used.\n"
|
|
"-- if the env var is nil, the debugger will try to guess it.\n"
|
|
"--\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Known Issues:\n"
|
|
"-- * Functions cannot be created using the debugger and then called in program because their environment is mapped directly to\n"
|
|
"-- a debugger internal structure which cannot be persisted (i.e. used outside of the debug_hook).\n"
|
|
"-- * The DLTK client implementation does not handle context for properties. As a workaround, the context is encoded into the\n"
|
|
"-- fullname attribute of each property and is used likewise in property_get commands. The syntax is \"<context ID>|<full name>\"\n"
|
|
"-- * Dynamic code (compiled with load or loadstring) is not handled (the debugger will step over it, like C code)\n"
|
|
"-- Design notes:\n"
|
|
"-- * The whole debugger state is kept in a (currently) unique session table in order to ease eventual adaptation to a multi-threaded\n"
|
|
"-- model, as DBGp needs one connection per thread.\n"
|
|
"-- * Full names of properties are base64 encoded because they can contain arbitrary data (spaces, escape characters, ...), this makes\n"
|
|
"-- command parsing munch easier and faster\n"
|
|
"-- * This debugger supports asynchronous commands: any command can be done at any time, but some of them (continuations) can lead to\n"
|
|
"-- inconsistent states. In addition, this have a quite big overhead (~66%), if performance is an issue, a custom command to disable\n"
|
|
"-- async mode could be done.\n"
|
|
"-- * All commands are implemented in table commands, see this comments on this table to additional details about commands implementation\n"
|
|
"-- * The environments in which are evaluated user code (property_* and eval commands, conditional breakpoints, ...) is a read/write\n"
|
|
"-- mapping of the local environment of a given stack level (can be accessed with variable names). See Context for additional details.\n"
|
|
"-- Context instantiation is pooled inside a debugging loop with ContextManager (each stack level is instantiated only once).\n"
|
|
"-- * Output redirection is done by redefining print and some values inside the io table. See \"Output redirection handling\" for details.\n"
|
|
"-- Todo list:\n"
|
|
"-- * Override I/O in init function instead of on module loading.\n"
|
|
"-- * Allow to break programatically (debugger.break()).\n"
|
|
"-- * Break-on-error feature (break if an error is thrown and there is no pcall in stack to handle it).\n"
|
|
"-- * Use new 5.2 facilities to provide informations about function (arguments names, vararg, ...)\n"
|
|
"-- * Allow to see ... content for vararg functions (5.2 only)\n"
|
|
"-- * Inspect LuaJIT C data (http://lua-users.org/lists/lua-l/2011-02/msg01012.html)-- /!\\ This file is auto-generated. Do not alter manually /!\\\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Submodules body\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.transport.apr\n"
|
|
"package.preload[\"debugger.transport.apr\"] = function(...)\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2011-2012 Sierra Wireless and others.\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Sierra Wireless - initial API and implementation\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Apache Portable Runtime backend for DBGP debugger.\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"local apr = require \"apr\"\n"
|
|
"\n"
|
|
"-- base 64 wrapping\n"
|
|
"function b64_wrap(src)\n"
|
|
" local t = {}\n"
|
|
" local b64_src = mime.b64(src)\n"
|
|
" for i=1, #b64_src, 76 do t[#t+1] = b64_src:sub(i, i+75)..\"\\r\\n\" end\n"
|
|
" return table.concat(t)\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- implements a subset of LuaSocket API using APR\n"
|
|
"local SOCKET_MT = {\n"
|
|
" connect = function(self, address, port) return self.skt:connect(address, port) end,\n"
|
|
" receive = function(self, n) return self.skt:read(n) end, -- only numeric read is used\n"
|
|
" send = function(self, data) return self.skt:write(data) end,\n"
|
|
" close = function(self) return self.skt:close() end,\n"
|
|
" settimeout = function(self, sec)\n"
|
|
" if sec == nil then self.skt:timeout_set(true)\n"
|
|
" elseif sec == 0 then self.skt:timeout_set(false)\n"
|
|
" else self.skt:timeout_set(math.floor(sec * 1000000)) end\n"
|
|
" end\n"
|
|
"}\n"
|
|
"SOCKET_MT.__index = SOCKET_MT\n"
|
|
"\n"
|
|
"return {\n"
|
|
" create = function()\n"
|
|
" local skt, err = apr.socket_create('tcp')\n"
|
|
" if not skt then return nil, err end\n"
|
|
" return setmetatable({skt = skt}, SOCKET_MT)\n"
|
|
" end,\n"
|
|
" sleep = apr.sleep, -- exact same API as LuaSocket\n"
|
|
"\n"
|
|
" -- Base64 related functions\n"
|
|
" --- Encodes a string into Base64 with line wrapping\n"
|
|
" -- @param data (string) data to encode\n"
|
|
" -- @return base64 encoded string\n"
|
|
" b64 = function(data)\n"
|
|
" t = {}\n"
|
|
" local b64_data = apr.base64_encode(data)\n"
|
|
" for i=1, #b64_data, 76 do t[#t+1] = b64_data:sub(i, i+75)..\"\\r\\n\" end\n"
|
|
" return table.concat(t)\n"
|
|
" end,\n"
|
|
"\n"
|
|
" --- Encodes a string into Base64, without any extra parsing (wrapping, ...)\n"
|
|
" -- @param data (string) data to encode\n"
|
|
" -- @return decoded string\n"
|
|
" rawb64 = apr.base64_encode,\n"
|
|
"\n"
|
|
" --- Decodes base64 data\n"
|
|
" -- @param data (string) base64 encoded data\n"
|
|
" -- @return decoded string\n"
|
|
" unb64 = apr.base64_decode,\n"
|
|
"}\n"
|
|
"\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.transport.apr\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.transport.luasocket\n"
|
|
"package.preload[\"debugger.transport.luasocket\"] = function(...)\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2011-2012 Sierra Wireless and others.\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Sierra Wireless - initial API and implementation\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- LuaSocket backend for DBGP debugger.\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"-- in order to be as lightweight as possible with Luasocket, core API is used\n"
|
|
"-- directly (to no add yet another layer)\n"
|
|
"\n"
|
|
"--FIXME: remove this hack as soon as luasocket officially support 5.2\n"
|
|
"if _VERSION == \"Lua 5.2\" then\n"
|
|
" table.getn = function(t) return t and #t end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local socket = require \"socket\"\n"
|
|
"local mime = require \"mime\"\n"
|
|
"local ltn12 = require \"ltn12\"\n"
|
|
"local reg = debug.getregistry()\n"
|
|
"\n"
|
|
"\n"
|
|
"return {\n"
|
|
" create = socket.tcp,\n"
|
|
" sleep = socket.sleep,\n"
|
|
"\n"
|
|
" -- Base64 related functions\n"
|
|
" --- Encodes a string into Base64 with line wrapping\n"
|
|
" -- @param data (string) data to encode\n"
|
|
" -- @return base64 encoded string\n"
|
|
" b64 = function(data)\n"
|
|
" local filter = ltn12.filter.chain(mime.encode(\"base64\"), mime.wrap(\"base64\"))\n"
|
|
" local sink, output = ltn12.sink.table()\n"
|
|
" ltn12.pump.all(ltn12.source.string(data), ltn12.sink.chain(filter, sink))\n"
|
|
" return table.concat(output)\n"
|
|
" end,\n"
|
|
"\n"
|
|
" --- Encodes a string into Base64, without any extra parsing (wrapping, ...)\n"
|
|
" -- @param data (string) data to encode\n"
|
|
" -- @return decoded string\n"
|
|
" rawb64 = function(data)\n"
|
|
" return (mime.b64(data)) -- first result of the low-level function is fine here\n"
|
|
" end,\n"
|
|
"\n"
|
|
" --- Decodes base64 data\n"
|
|
" -- @param data (string) base64 encoded data\n"
|
|
" -- @return decoded string\n"
|
|
" unb64 = function(data)\n"
|
|
" return (mime.unb64(data)) -- first result of the low-level function is fine here\n"
|
|
" end,\n"
|
|
"}\n"
|
|
"\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.transport.luasocket\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.transport.luasocket_sched\n"
|
|
"package.preload[\"debugger.transport.luasocket_sched\"] = function(...)\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2011-2012 Sierra Wireless and others.\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Sierra Wireless - initial API and implementation\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- LuaSocket with LuaSched backend for DBGP debugger.\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"-- As LuaShed totally hides blocking functions, this module MUST be loaded on the very start of the program\n"
|
|
"-- (before loading sched) to catch references to blocking functions.\n"
|
|
"\n"
|
|
"local socketcore = require\"socket.core\"\n"
|
|
"local debug = require \"debug\"\n"
|
|
"local reg = debug.getregistry()\n"
|
|
"\n"
|
|
"local blockingcreate = socketcore.tcp\n"
|
|
"local blockingsleep = socketcore.sleep\n"
|
|
"\n"
|
|
"local blockingconnect = reg[\"tcp{master}\"].__index.connect\n"
|
|
"local blockingreceive = reg[\"tcp{client}\"].__index.receive\n"
|
|
"local blockingsend = reg[\"tcp{client}\"].__index.send\n"
|
|
"local blockingsettimeout = reg[\"tcp{master}\"].__index.settimeout\n"
|
|
"local blockingclose = reg[\"tcp{master}\"].__index.close\n"
|
|
"\n"
|
|
"-- we cannot set a new metatable directly on socket object, so wrap it into a new table\n"
|
|
"-- and forward all calls.\n"
|
|
"local blockingtcp = {\n"
|
|
" connect = function(self, address, port) return blockingconnect(self.skt, address, port) end,\n"
|
|
" receive = function(self, n) return blockingreceive(self.skt, n) end,\n"
|
|
" send = function(self, data) return blockingsend(self.skt, data) end,\n"
|
|
" settimeout = function(self, sec) return blockingsettimeout(self.skt, sec) end,\n"
|
|
" close = function(self) return blockingclose(self.skt) end,\n"
|
|
"}\n"
|
|
"\n"
|
|
"blockingtcp.__index = blockingtcp\n"
|
|
"\n"
|
|
"local mime = require \"mime\"\n"
|
|
"local ltn12 = require \"ltn12\"\n"
|
|
"\n"
|
|
"-- verify that the socket function are the real ones and not sched not blocking versions\n"
|
|
"assert(debug.getinfo(blockingcreate, \"S\").what == \"C\", \"The debugger needs the real socket functions !\")\n"
|
|
"-- cleanup the package.loaded table (socket.core adds socket field into it)\n"
|
|
"package.loaded.socket = nil\n"
|
|
"\n"
|
|
"return {\n"
|
|
" create = function() return setmetatable({ skt = blockingcreate() }, blockingtcp) end,\n"
|
|
" sleep = blockingsleep,\n"
|
|
"\n"
|
|
" -- Base64 related functions\n"
|
|
" --- Encodes a string into Base64 with line wrapping\n"
|
|
" -- @param data (string) data to encode\n"
|
|
" -- @return base64 encoded string\n"
|
|
" b64 = function(data)\n"
|
|
" local filter = ltn12.filter.chain(mime.encode(\"base64\"), mime.wrap(\"base64\"))\n"
|
|
" local sink, output = ltn12.sink.table()\n"
|
|
" ltn12.pump.all(ltn12.source.string(data), ltn12.sink.chain(filter, sink))\n"
|
|
" return table.concat(output)\n"
|
|
" end,\n"
|
|
"\n"
|
|
" --- Encodes a string into Base64, without any extra parsing (wrapping, ...)\n"
|
|
" -- @param data (string) data to encode\n"
|
|
" -- @return decoded string\n"
|
|
" rawb64 = function(data)\n"
|
|
" return (mime.b64(data)) -- first result of the low-level function is fine here\n"
|
|
" end,\n"
|
|
"\n"
|
|
" --- Decodes base64 data\n"
|
|
" -- @param data (string) base64 encoded data\n"
|
|
" -- @return decoded string\n"
|
|
" unb64 = function(data)\n"
|
|
" return (mime.unb64(data)) -- first result of the low-level function is fine here\n"
|
|
" end,\n"
|
|
"}\n"
|
|
"\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.transport.luasocket_sched\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.commands\n"
|
|
"package.preload[\"debugger.commands\"] = function(...)\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2011-2012 Sierra Wireless and others.\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Sierra Wireless - initial API and implementation\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Commands handlers for DBGp protocol.\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Debugger command functions. Each function handle a different command.\n"
|
|
"-- A command function is called with 3 arguments\n"
|
|
"-- 1. the debug session instance\n"
|
|
"-- 2. the command arguments as table\n"
|
|
"-- 3. the command data, if any\n"
|
|
"-- The result is either :\n"
|
|
"-- * true (or any value evaluated to true) : the debugger will resume the execution of the application (continuation command)\n"
|
|
"-- * false : only in async mode, the debugger WILL wait for further commands instead of continuing (typically, break command)\n"
|
|
"-- * nil/no return : in sync mode, the debugger will wait for another command. In async mode the debugger will continue the execution\n"
|
|
"\n"
|
|
"local cowrap, coyield = coroutine.wrap, coroutine.yield\n"
|
|
"local debug = require \"debug\"\n"
|
|
"\n"
|
|
"local core = require \"debugger.core\"\n"
|
|
"local dbgp = require \"debugger.dbgp\"\n"
|
|
"local util = require \"debugger.util\"\n"
|
|
"local platform = require \"debugger.platform\"\n"
|
|
"local introspection = require \"debugger.introspection\"\n"
|
|
"local context = require \"debugger.context\"\n"
|
|
"local log = util.log\n"
|
|
"\n"
|
|
"local M = { } -- command handlers table\n"
|
|
"\n"
|
|
"--- Gets the coroutine behind an id\n"
|
|
"-- Throws errors on unknown identifiers\n"
|
|
"-- @param coro_id (string or nil) Coroutine identifier or nil (current coroutine)\n"
|
|
"-- @return Coroutine instance or nil (if coro_id was nil or if coroutine is the current coroutine)\n"
|
|
"local function get_coroutine(self, coro_id)\n"
|
|
" if coro_id then\n"
|
|
" local coro = dbgp.assert(399, core.active_coroutines.from_id[tonumber(coro_id)], \"No such coroutine\")\n"
|
|
" dbgp.assert(399, coroutine.status(coro) ~= \"dead\", \"Coroutine is dead\")\n"
|
|
" if coro ~= self.coro[1] then return util.ForeignThread(coro) end\n"
|
|
" end\n"
|
|
" return self.coro\n"
|
|
"end\n"
|
|
"\n"
|
|
"M[\"break\"] = function(self, args)\n"
|
|
" self.state = \"break\"\n"
|
|
" -- send response to previous command\n"
|
|
" core.previous_context_response(self)\n"
|
|
" -- and then response to break command itself\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = { command = \"break\", transaction_id = args.i, success = 1 } } )\n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.status(self, args)\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = {\n"
|
|
" command = \"status\",\n"
|
|
" reason = \"ok\",\n"
|
|
" status = self.state,\n"
|
|
" transaction_id = args.i } } )\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.stop(self, args)\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = {\n"
|
|
" command = \"stop\",\n"
|
|
" reason = \"ok\",\n"
|
|
" status = \"stopped\",\n"
|
|
" transaction_id = args.i } } )\n"
|
|
" self.skt:close()\n"
|
|
" os.exit(1)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.feature_get(self, args)\n"
|
|
" local name = args.n\n"
|
|
" local response = util.features[name] or (not not M[name])\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = {\n"
|
|
" command = \"feature_get\",\n"
|
|
" feature_name = name,\n"
|
|
" supported = response and \"1\" or \"0\",\n"
|
|
" transaction_id = args.i },\n"
|
|
" tostring(response) } )\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.feature_set(self, args)\n"
|
|
" local name, value = args.n, args.v\n"
|
|
" local success = pcall(function() util.features[name] = value end)\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = {\n"
|
|
" command = \"feature_set\",\n"
|
|
" feature = name,\n"
|
|
" success = success and 1 or 0,\n"
|
|
" transaction_id = args.i\n"
|
|
" } } )\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.typemap_get(self, args)\n"
|
|
" local function gentype(name, type, xsdtype)\n"
|
|
" return { tag = \"map\", atts = { name = name, type = type, [\"xsi:type\"] = xsdtype } }\n"
|
|
" end\n"
|
|
"\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = {\n"
|
|
" command = \"typemap_get\",\n"
|
|
" transaction_id = args.i,\n"
|
|
" [\"xmlns:xsi\"] = \"http://www.w3.org/2001/XMLSchema-instance\",\n"
|
|
" [\"xmlns:xsd\"] = \"http://www.w3.org/2001/XMLSchema\",\n"
|
|
" },\n"
|
|
" gentype(\"nil\", \"null\"),\n"
|
|
" gentype(\"boolean\", \"bool\", \"xsd:boolean\"),\n"
|
|
" gentype(\"number\", \"float\", \"xsd:float\"),\n"
|
|
" gentype(\"string\", \"string\", \"xsd:string\"),\n"
|
|
" gentype(\"function\", \"resource\"),\n"
|
|
" gentype(\"userdata\", \"resource\"),\n"
|
|
" gentype(\"thread\", \"resource\"),\n"
|
|
" gentype(\"table\", \"hash\"),\n"
|
|
" gentype(\"sequence\", \"array\"), -- artificial type to represent sequences (1-n continuous indexes)\n"
|
|
" gentype(\"multival\", \"array\"), -- used to represent return values\n"
|
|
" } )\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.run(self)\n"
|
|
" core.prev_break_line = nil\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.step_over(self)\n"
|
|
" core.events.register(\"over\")\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.step_out(self)\n"
|
|
" core.events.register(\"out\")\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.step_into(self)\n"
|
|
" core.events.register(\"into\")\n"
|
|
" return true\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.eval(self, args, data)\n"
|
|
" log(\"DEBUG\", \"Going to eval \"..data)\n"
|
|
" local result, err, success\n"
|
|
" local env = self.stack(self.coro, 0)\n"
|
|
" -- first, try to load as expression\n"
|
|
" -- DBGp does not support stack level here, see http://bugs.activestate.com/show_bug.cgi?id=81178\n"
|
|
" local func, err = util.loadin(\"return \"..data, env)\n"
|
|
"\n"
|
|
" -- if it is not an expression, try as statement (assignment, ...)\n"
|
|
" if not func then\n"
|
|
" func, err = util.loadin(data, env)\n"
|
|
" end\n"
|
|
"\n"
|
|
" if func then\n"
|
|
" success, result = pcall(function() return introspection.Multival(func()) end)\n"
|
|
" if not success then err = result end\n"
|
|
" end\n"
|
|
"\n"
|
|
" local response = { tag = \"response\", attr = { command = \"eval\", transaction_id = args.i } }\n"
|
|
" if not err then\n"
|
|
" local nresults = result.n\n"
|
|
" if nresults == 1 then result = result[1] end\n"
|
|
"\n"
|
|
" -- store result for further use (property_*)\n"
|
|
" -- TODO: this could be optimized: this is only used for Expressions view and totally useless for interactive console,\n"
|
|
" -- so storing result or not could be set by an argument\n"
|
|
" local idx\n"
|
|
" if nresults > 0 then\n"
|
|
" local cache = env[context.Context[-1]]\n"
|
|
" idx = #cache + 1\n"
|
|
" cache[idx] = result\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- As of Lua 5.1, the maximum stack size (and result count) is 8000, this limit is used to fit all results in one page\n"
|
|
" response[1] = introspection.make_property(-1, result, idx or \"\", nil, 1, 8000, 0, nil)\n"
|
|
" response.attr.success = 1\n"
|
|
" else\n"
|
|
" response.attr.success = 0\n"
|
|
" response[1] = dbgp.make_error(206, err)\n"
|
|
" end\n"
|
|
" dbgp.send_xml(self.skt, response)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.breakpoint_set(self, args, data)\n"
|
|
" if args.o and not core.breakpoints.hit_conditions[args.o] then dbgp.error(200, \"Invalid hit_condition operator: \"..args.o) end\n"
|
|
" local filename, lineno = args.f, tonumber(args.n)\n"
|
|
" --if it is not in current project path, ignore the breakpoint_set command, send empty response to avoid blocking (add by guanyu)\n"
|
|
" if not filename:find(platform.base_dir,1,true) then\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = { command = \"breakpoint_set\", transaction_id = args.i} } )\n"
|
|
" return nil;\n"
|
|
" end\n"
|
|
" local bp = {\n"
|
|
" type = args.t,\n"
|
|
" state = args.s or \"enabled\",\n"
|
|
" temporary = args.r == \"1\", -- \"0\" or nil makes this property false\n"
|
|
" hit_count = 0,\n"
|
|
" filename = filename,\n"
|
|
" lineno = lineno,\n"
|
|
" hit_value = tonumber(args.h or 0),\n"
|
|
" hit_condition = args.o or \">=\",\n"
|
|
" }\n"
|
|
"\n"
|
|
" if args.t == \"conditional\" then\n"
|
|
" bp.expression = data\n"
|
|
" -- the expression is compiled only once\n"
|
|
" bp.condition = dbgp.assert(207, loadstring(\"return (\" .. data .. \")\"))\n"
|
|
" elseif args.t ~= \"line\" then dbgp.error(201, \"BP type \" .. args.t .. \" not yet supported\") end\n"
|
|
"\n"
|
|
" local bpid = core.breakpoints.insert(bp)\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = { command = \"breakpoint_set\", transaction_id = args.i, state = bp.state, id = bpid } } )\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.breakpoint_get(self, args)\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\",\n"
|
|
" attr = { command = \"breakpoint_get\", transaction_id = args.i },\n"
|
|
" dbgp.assert(205, core.breakpoints.get_xml(tonumber(args.d))) })\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.breakpoint_list(self, args)\n"
|
|
" local bps = { tag = \"response\", attr = { command = \"breakpoint_list\", transaction_id = args.i } }\n"
|
|
" for id, bp in pairs(core.breakpoints.get()) do bps[#bps + 1] = core.breakpoints.get_xml(id) end\n"
|
|
" dbgp.send_xml(self.skt, bps)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.breakpoint_update(self, args)\n"
|
|
" local bp = core.breakpoints.get(tonumber(args.d))\n"
|
|
" if not bp then dbgp.error(205, \"No such breakpint \"..args.d) end\n"
|
|
" if args.o and not core.breakpoints.hit_conditions[args.o] then dbgp.error(200, \"Invalid hit_condition operator: \"..args.o) end\n"
|
|
" local response = { tag = \"response\", attr = { command = \"breakpoint_update\", transaction_id = args.i } }\n"
|
|
" local needUpdate = false\n"
|
|
" local oldline = bp.lineno\n"
|
|
" if tonumber(args.n) ~= tonumber(bp.lineno) then\n"
|
|
" needUpdate = true\n"
|
|
" end\n"
|
|
" bp.state = args.s or bp.state\n"
|
|
" bp.lineno = tonumber(args.n or bp.lineno)\n"
|
|
" bp.hit_value = tonumber(args.h or bp.hit_value)\n"
|
|
" bp.hit_condition = args.o or bp.hit_condition\n"
|
|
" if needUpdate then\n"
|
|
" core.breakpoints.update(oldline, bp)\n"
|
|
" end\n"
|
|
" dbgp.send_xml(self.skt, response)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.breakpoint_remove(self, args)\n"
|
|
" local response = { tag = \"response\", attr = { command = \"breakpoint_remove\", transaction_id = args.i } }\n"
|
|
" -- args.d may be nil, because the breakpoint does not belong to current project (add by guanyu)\n"
|
|
" if args.d and not core.breakpoints.remove(tonumber(args.d)) then dbgp.error(205, \"No such breakpint \"..args.d) end\n"
|
|
" dbgp.send_xml(self.skt, response)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.stack_depth(self, args)\n"
|
|
" local depth = 0\n"
|
|
" local coro = get_coroutine(self, args.o)\n"
|
|
" for level = 0, math.huge do\n"
|
|
" local info = coro:getinfo(level, \"St\")\n"
|
|
" if not info then break end -- end of stack\n"
|
|
" depth = depth + 1\n"
|
|
" if info.istailcall then depth = depth + 1 end -- a 'fake' level is added in that case\n"
|
|
" if info.what == \"main\" then break end -- levels below main chunk are not interesting\n"
|
|
" end\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = { command = \"stack_depth\", transaction_id = args.i, depth = depth} } )\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.stack_get(self, args) -- TODO: dynamic code\n"
|
|
" -- special URIs to identify unreachable stack levels\n"
|
|
" local what2uri = {\n"
|
|
" tail = \"tailreturn:/\",\n"
|
|
" C = \"ccode:/\",\n"
|
|
" }\n"
|
|
"\n"
|
|
" local function make_level(info, level)\n"
|
|
" local attr = { level = level, where = info.name, type=\"file\" }\n"
|
|
" local uri = platform.get_uri(info.source)\n"
|
|
" if uri and info.currentline then -- reachable level\n"
|
|
" attr.filename = uri\n"
|
|
" attr.lineno = info.currentline\n"
|
|
" else\n"
|
|
" attr.filename = what2uri[info.what] or \"unknown:/\"\n"
|
|
" attr.lineno = -1\n"
|
|
" end\n"
|
|
" return { tag = \"stack\", attr = attr }\n"
|
|
" end\n"
|
|
"\n"
|
|
" local node = { tag = \"response\", attr = { command = \"stack_get\", transaction_id = args.i} }\n"
|
|
" local coro = get_coroutine(self, args.o)\n"
|
|
"\n"
|
|
" if args.d then\n"
|
|
" local stack_level = tonumber(args.d)\n"
|
|
" node[#node+1] = make_level(coro:getinfo(stack_level, \"nSl\"), stack_level)\n"
|
|
" else\n"
|
|
" for i=0, math.huge do\n"
|
|
" local info = coro:getinfo(i, \"nSlt\")\n"
|
|
" if not info then break end\n"
|
|
" node[#node+1] = make_level(info, i)\n"
|
|
" -- add a fake level of stack for tail calls (tells user that the function has not been called directly)\n"
|
|
" if info.istailcall then\n"
|
|
" node[#node+1] = { tag = \"stack\", attr = { level=i, type=\"file\", filename=\"tailreturn:/\", lineno=-1 } }\n"
|
|
" end\n"
|
|
" if info.what == \"main\" then break end -- levels below main chunk are not interesting\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" dbgp.send_xml(self.skt, node)\n"
|
|
"end\n"
|
|
"\n"
|
|
"--- Lists all active coroutines.\n"
|
|
"-- Returns a list of active coroutines with their id (an arbitrary string) to query stack and properties. The id is\n"
|
|
"-- guaranteed to be unique and stable for all coroutine life (they can be reused as long as coroutine exists).\n"
|
|
"-- Others commands such as stack_get or property_* commands takes an additional -o switch to query a particular cOroutine.\n"
|
|
"-- If the switch is not given, running coroutine will be used.\n"
|
|
"-- In case of error on coroutines (most likely coroutine not found or dead), an error 399 is thrown.\n"
|
|
"-- Note there is an important limitation due to Lua 5.1 coroutine implementation: you cannot query main \"coroutine\" from\n"
|
|
"-- another one, so main coroutine is not in returned list (this will change with Lua 5.2).\n"
|
|
"--\n"
|
|
"-- This is a non-standard command. The returned XML has the following strucuture:\n"
|
|
"-- <response command=\"coroutine_list\" transaction_id=\"0\">\n"
|
|
"-- <coroutine name=\"<some printtable name>\" id=\"<coroutine id>\" running=\"0|1\" />\n"
|
|
"-- ...\n"
|
|
"-- </response>\n"
|
|
"function M.coroutine_list(self, args)\n"
|
|
" local running = self.coro[1]\n"
|
|
" local coroutines = { tag = \"response\", attr = { command = \"coroutine_list\", transaction_id = args.i } }\n"
|
|
" -- as any operation on main coroutine will fail, it is not yet listed\n"
|
|
" -- coroutines[1] = { name = \"coroutine\", attr = { id = 0, name = \"main\", running = (running == nil) and \"1\" or \"0\" } }\n"
|
|
" for id, coro in pairs(core.active_coroutines.from_id) do\n"
|
|
" if id ~= \"n\" then\n"
|
|
" coroutines[#coroutines + 1] = { tag = \"coroutine\", attr = { id = id, name = tostring(coro), running = (coro == running) and \"1\" or \"0\" } }\n"
|
|
" end\n"
|
|
" end\n"
|
|
" dbgp.send_xml(self.skt, coroutines)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.context_names(self, args)\n"
|
|
" local coro = get_coroutine(self, args.o)\n"
|
|
" local level = tonumber(args.d or 0)\n"
|
|
" local info = coro:getinfo(level, \"f\") or dbgp.error(301, \"No such stack level \"..tostring(level))\n"
|
|
"\n"
|
|
" -- All contexts are always passed, even if empty. This is how DLTK expect context, what about others ?\n"
|
|
" local contexts = {\n"
|
|
" tag = \"response\", attr = { command = \"context_names\", transaction_id = args.i },\n"
|
|
" { tag = \"context\", attr = { name = \"Local\", id = 0 } },\n"
|
|
" { tag = \"context\", attr = { name = \"Upvalue\", id = 2 } },\n"
|
|
" { tag = \"context\", attr = { name = \"Global\", id = 1 } },\n"
|
|
" }\n"
|
|
"\n"
|
|
" dbgp.send_xml(self.skt, contexts)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.context_get(self, args)\n"
|
|
" local cxt_num = tonumber(args.c or 0)\n"
|
|
" local cxt_id = context.Context[cxt_num] or dbgp.error(302, \"No such context: \"..tostring(cxt_num))\n"
|
|
" local level = tonumber(args.d or 0)\n"
|
|
" local coro = get_coroutine(self, args.o)\n"
|
|
" local cxt = self.stack(coro, level)\n"
|
|
"\n"
|
|
" local properties = { tag = \"response\", attr = { command = \"context_get\", transaction_id = args.i, context = context} }\n"
|
|
" -- iteration over global is different (this could be unified in Lua 5.2 thanks to __pairs metamethod)\n"
|
|
" for name, val in (cxt_num == 1 and next or getmetatable(cxt[cxt_id]).iterator), cxt[cxt_id], nil do\n"
|
|
" -- the DBGp specification is not clear about the depth of a context_get, but a recursive get could be *really* slow in Lua\n"
|
|
" properties[#properties + 1] = introspection.make_property(cxt_num, val, name, nil, 0, util.features.max_children, 0,\n"
|
|
" util.features.max_data, cxt_num ~= 1)\n"
|
|
" end\n"
|
|
"\n"
|
|
" dbgp.send_xml(self.skt, properties)\n"
|
|
"end\n"
|
|
"\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Property_* commands\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- This in the environment in which properties are get or set.\n"
|
|
"-- It notably contain a collection of proxy table which handle transparentely get/set operations on special fields\n"
|
|
"-- and the cache of complex keys.\n"
|
|
"local property_evaluation_environment = {\n"
|
|
" key_cache = introspection.key_cache,\n"
|
|
" metatable = setmetatable({ }, {\n"
|
|
" __index = function(self, tbl) return getmetatable(tbl) end,\n"
|
|
" __newindex = function(self, tbl, mt) return setmetatable(tbl, mt) end,\n"
|
|
" }),\n"
|
|
" environment = util.eval_env,\n"
|
|
"}\n"
|
|
"-- to allows to be set as metatable\n"
|
|
"property_evaluation_environment.__index = property_evaluation_environment\n"
|
|
"\n"
|
|
"function M.property_get(self, args)\n"
|
|
" --TODO BUG ECLIPSE TOOLSLINUX-99 352316\n"
|
|
" local cxt_num, name = assert(util.unb64(args.n):match(\"^(%-?%d+)|(.*)$\"))\n"
|
|
" cxt_num = tonumber(args.c or cxt_num)\n"
|
|
" local cxt_id = context.Context[cxt_num] or dbgp.error(302, \"No such context: \"..tostring(cxt_num))\n"
|
|
" local level = tonumber(args.d or 0)\n"
|
|
" local coro = get_coroutine(self, args.o)\n"
|
|
" local size = tonumber(args.m or util.features.max_data)\n"
|
|
" if size < 0 then size = nil end -- call from property_value\n"
|
|
" local page = tonumber(args.p or 0)\n"
|
|
" local cxt = self.stack(coro, level)\n"
|
|
" local chunk = dbgp.assert(206, util.loadin(\"return \"..name, property_evaluation_environment))\n"
|
|
" local prop = select(2, dbgp.assert(300, pcall(chunk, cxt[cxt_id])))\n"
|
|
" local response = introspection.make_property(cxt_num, prop, name, name, util.features.max_depth, util.features.max_children, page, size)\n"
|
|
" -- make_property is not able to flag special variables as such when they are at root of property\n"
|
|
" -- special variables queries are in the form \"<proxy name>[(...)[a][b]<...>]\"\n"
|
|
" -- TODO: such parsing is far from perfect\n"
|
|
" if name:match(\"^[%w_]+%[.-%b[]%]$\") == name then response.attr.type = \"special\" end\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\",\n"
|
|
" attr = { command = \"property_get\", transaction_id = args.i, context = context},\n"
|
|
" response } )\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.property_value(self, args)\n"
|
|
" args.m = -1\n"
|
|
" M.property_get(self, args)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.property_set(self, args, data)\n"
|
|
" local cxt_num, name = assert(util.unb64(args.n):match(\"^(%-?%d+)|(.*)$\"))\n"
|
|
" cxt_num = tonumber(args.c or cxt_num)\n"
|
|
" local cxt_id = context.Context[cxt_num] or dbgp.error(302, \"No such context: \"..tostring(cxt_num))\n"
|
|
" local level = tonumber(args.d or 0)\n"
|
|
" local coro = get_coroutine(self, args.o)\n"
|
|
" local cxt = self.stack(coro, level)\n"
|
|
"\n"
|
|
" -- evaluate the new value in the local context\n"
|
|
" local value = select(2, dbgp.assert(206, pcall(dbgp.assert(206, util.loadin(\"return \"..data, cxt)))))\n"
|
|
"\n"
|
|
" local chunk = dbgp.assert(206, util.loadin(name .. \" = value\", setmetatable({ value = value }, property_evaluation_environment)))\n"
|
|
" dbgp.assert(206, pcall(chunk, cxt[cxt_id]))\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = { success = 1, transaction_id = args.i } } )\n"
|
|
"end\n"
|
|
"\n"
|
|
"--TODO dynamic code handling\n"
|
|
"-- The DBGp specification is not clear about the line number meaning, this implementation is 1-based and numbers are inclusive\n"
|
|
"function M.source(self, args)\n"
|
|
" local path\n"
|
|
" if args.f then\n"
|
|
" path = platform.get_path(args.f)\n"
|
|
" else\n"
|
|
" path = self.coro:getinfo(0, \"S\").source\n"
|
|
" assert(path:sub(1,1) == \"@\")\n"
|
|
" path = path:sub(2)\n"
|
|
" end\n"
|
|
" local file, err = io.open(path)\n"
|
|
" if not file then dbgp.error(100, err, { success = 0 }) end\n"
|
|
" -- Try to identify compiled files\n"
|
|
" if file:read(1) == \"\\033\" then dbgp.error(100, args.f..\" is bytecode\", { success = 0 }) end\n"
|
|
" file:seek(\"set\", 0)\n"
|
|
"\n"
|
|
"\n"
|
|
" local srclines = { }\n"
|
|
" local beginline, endline, currentline = tonumber(args.b or 0), tonumber(args.e or math.huge), 0\n"
|
|
" for line in file:lines() do\n"
|
|
" currentline = currentline + 1\n"
|
|
" if currentline >= beginline and currentline <= endline then\n"
|
|
" srclines[#srclines + 1] = line\n"
|
|
" elseif currentline >= endline then break end\n"
|
|
" end\n"
|
|
" file:close()\n"
|
|
" srclines[#srclines + 1] = \"\" -- to add a trailing \\n\n"
|
|
"\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\",\n"
|
|
" attr = { command = \"source\", transaction_id = args.i, success = 1},\n"
|
|
" util.b64(table.concat(srclines, \"\\n\")) })\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Factory for both stdout and stderr commands, change file descriptor in io\n"
|
|
"local function output_command_handler_factory(mode)\n"
|
|
" return function(self, args)\n"
|
|
" if args.c == \"0\" then -- disable\n"
|
|
" io[mode] = io.base[mode]\n"
|
|
" else\n"
|
|
" io[mode] = setmetatable({ skt = self.skt, mode = mode }, args.c == \"1\" and core.copy_output or core.redirect_output)\n"
|
|
" end\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = { command = mode, transaction_id = args.i, success = \"1\" } } )\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"M.stdout = output_command_handler_factory(\"stdout\")\n"
|
|
"M.stderr = output_command_handler_factory(\"stderr\")\n"
|
|
"\n"
|
|
"\n"
|
|
"return M\n"
|
|
"\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.commands\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.context\n"
|
|
"package.preload[\"debugger.context\"] = function(...)\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2011-2012 Sierra Wireless and others.\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Sierra Wireless - initial API and implementation\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Context handling: allows to evaluate code snippets in the context of a function\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"local M = { }\n"
|
|
"\n"
|
|
"local dbgp = require \"debugger.dbgp\"\n"
|
|
"local util = require \"debugger.util\"\n"
|
|
"\n"
|
|
"-- make unique object to access contexts\n"
|
|
"local LOCAL, UPVAL, GLOBAL, EVAL, STORE, HANDLE = {}, {}, {}, {}, {}, {}\n"
|
|
"\n"
|
|
"local getglobals\n"
|
|
"if _VERSION == \"Lua 5.1\" then\n"
|
|
" getglobals = function(f) return getfenv(f) end\n"
|
|
"elseif _VERSION == \"Lua 5.2\" then\n"
|
|
" getglobals = function(f, cxt)\n"
|
|
" -- 'global' environment: this is either the local _ENV or upvalue _ENV. A special case happen when a\n"
|
|
" -- function does not reference any global variable: the upvalue _ENV may not exist at all. In this case,\n"
|
|
" -- global environment is not relevant so it is fixed to an empty table. Another solution would be to set it\n"
|
|
" -- to the environment from above stack level but it would require some overhead (especially if multiple\n"
|
|
" -- levels must be instantiated)\n"
|
|
" if cxt[LOCAL][STORE][\"_ENV\"] then return cxt[LOCAL][\"_ENV\"]\n"
|
|
" elseif cxt[UPVAL][STORE][\"_ENV\"] then return cxt[UPVAL][\"_ENV\"]\n"
|
|
" else return { } end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"--- Captures variables for given stack level. The capture contains local, upvalues and global variables.\n"
|
|
"-- The capture can be seen as a proxy table to the stack level: any value can be queried or set no matter\n"
|
|
"-- it is a local or an upvalue.\n"
|
|
"-- The individual local and upvalues context are also available and can be queried and modified with indexed notation too.\n"
|
|
"-- These objects are NOT persistant and must not be used outside the debugger loop which instanciated them !\n"
|
|
"M.Context = {\n"
|
|
" -- Context identifiers can be accessed by their DBGp context ID\n"
|
|
" [0] = LOCAL,\n"
|
|
" [1] = GLOBAL, -- DLTK internal ID for globals is 1\n"
|
|
" [2] = UPVAL,\n"
|
|
" -- EVAL is used to keep results from eval in cache in order to browse or modify them, results are stored as sequence\n"
|
|
" [-1] = EVAL,\n"
|
|
" STORE = STORE,\n"
|
|
"\n"
|
|
" -- gets a variable by name with correct handling of Lua scope chain\n"
|
|
" -- the or chain does not work here beacause __index metamethod would raise an error instead of returning nil\n"
|
|
" __index = function(self, k)\n"
|
|
" if self[LOCAL][STORE][k] then return self[LOCAL][k]\n"
|
|
" elseif self[UPVAL][STORE][k] then return self[UPVAL][k]\n"
|
|
" else return self[GLOBAL][k] end\n"
|
|
" end,\n"
|
|
" __newindex = function(self, k, v)\n"
|
|
" if self[LOCAL][STORE][k] then self[LOCAL][k] = v\n"
|
|
" elseif self[UPVAL][STORE][k] then self[UPVAL][k] = v\n"
|
|
" else self[GLOBAL][k] = v end\n"
|
|
" end,\n"
|
|
"\n"
|
|
" -- debug only !!\n"
|
|
" __tostring = function(self)\n"
|
|
" local buf = { \"Locals: \\n\" }\n"
|
|
" for k,v in pairs(self[LOCAL][STORE]) do\n"
|
|
" buf[#buf+1] = \"\\t\"..tostring(k)..\"(\"..tostring(v)..\")=\"..tostring(self[LOCAL][k])..\"\\n\"\n"
|
|
" end\n"
|
|
" buf[#buf+1] = \"Upvalues: \\n\"\n"
|
|
" for k,v in pairs(self[UPVAL][STORE]) do\n"
|
|
" buf[#buf+1] = \"\\t\"..tostring(k)..\"(\"..tostring(v)..\")=\"..tostring(self[UPVAL][k])..\"\\n\"\n"
|
|
" end\n"
|
|
" return table.concat(buf)\n"
|
|
" end,\n"
|
|
"\n"
|
|
" LocalContext = {\n"
|
|
" __index = function(self, k)\n"
|
|
" local index = self[STORE][k]\n"
|
|
" if not index then error(\"The local \"..tostring(k)..\" does not exists.\") end\n"
|
|
" local handle = self[HANDLE]\n"
|
|
" return select(2, handle.coro:getlocal(handle.level, index))\n"
|
|
" end,\n"
|
|
" __newindex = function(self, k, v)\n"
|
|
" local index = self[STORE][k]\n"
|
|
" if index then\n"
|
|
" local handle = self[HANDLE]\n"
|
|
" handle.coro:setlocal(handle.level, index, v)\n"
|
|
" else error(\"Cannot set local \" .. k) end\n"
|
|
" end,\n"
|
|
" -- Lua 5.2 ready :)\n"
|
|
" --__pairs = function(self) return getmetatable(self).iterator, self, nil end,\n"
|
|
" iterator = function(self, prev)\n"
|
|
" local key, index = next(self[STORE], prev)\n"
|
|
" if key then return key, self[key] else return nil end\n"
|
|
" end,\n"
|
|
" },\n"
|
|
"\n"
|
|
" UpvalContext = {\n"
|
|
" __index = function(self, k)\n"
|
|
" local index = self[STORE][k]\n"
|
|
" if not index then error(\"The local \"..tostring(k)..\" does not exitsts.\") end\n"
|
|
" return select(2, debug.getupvalue(self[HANDLE], index))\n"
|
|
" end,\n"
|
|
" __newindex = function(self, k, v)\n"
|
|
" local index = self[STORE][k]\n"
|
|
" if index then debug.setupvalue(self[HANDLE], index, v)\n"
|
|
" else error(\"Cannot set upvalue \" .. k) end\n"
|
|
" end,\n"
|
|
" -- Lua 5.2 ready :)\n"
|
|
" -- __pairs = function(self) return getmetatable(self).iterator, self, nil end,\n"
|
|
" iterator = function(self, prev)\n"
|
|
" local key, index = next(self[STORE], prev)\n"
|
|
" if key then return key, self[key] else return nil end\n"
|
|
" end,\n"
|
|
" },\n"
|
|
"\n"
|
|
" --- Context constructor\n"
|
|
" -- @param coro (util.*Thread instance) coroutine to map to\n"
|
|
" -- @param level (number) stack level do dump (script stack level)\n"
|
|
" new = function(cls, coro, level)\n"
|
|
" local locals, upvalues = {}, {}\n"
|
|
" if level < 0 then dbgp.error(301, \"No such stack level: \"..tostring(level)) end\n"
|
|
" local func = (coro:getinfo(level, \"f\") or dbgp.error(301, \"No such stack level: \"..tostring(level))).func\n"
|
|
"\n"
|
|
" -- local variables\n"
|
|
" for i=1, math.huge do\n"
|
|
" local name, val = coro:getlocal(level, i)\n"
|
|
" if not name then break\n"
|
|
" elseif name:sub(1,1) ~= \"(\" then -- skip internal values\n"
|
|
" locals[name] = i\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- upvalues\n"
|
|
" for i=1, math.huge do\n"
|
|
" local name, val = debug.getupvalue(func, i)\n"
|
|
" if not name then break end\n"
|
|
" upvalues[name] = i\n"
|
|
" end\n"
|
|
"\n"
|
|
" locals = setmetatable({ [STORE] = locals, [HANDLE] = { level = level, coro = coro } }, cls.LocalContext)\n"
|
|
" upvalues = setmetatable({ [STORE] = upvalues, [HANDLE] = func }, cls.UpvalContext)\n"
|
|
"\n"
|
|
" local result = setmetatable({ [LOCAL] = locals, [UPVAL] = upvalues, [EVAL] = {} }, cls)\n"
|
|
" rawset(result, GLOBAL, getglobals(func, result))\n"
|
|
" return result\n"
|
|
" end,\n"
|
|
"}\n"
|
|
"\n"
|
|
"--- Handle caching of all instantiated context.\n"
|
|
"-- Returns a function which takes 2 parameters: thread and stack level and returns the corresponding context. If this\n"
|
|
"-- context has been already queried there is no new instantiation. A ContextManager is valid only during the debug loop\n"
|
|
"-- on which it has been instantiated. References to a ContextManager must be lost after the end of debug loop (so\n"
|
|
"-- threads can be collected).\n"
|
|
"-- If a context cannot be instantiated, an 301 DBGP error is thrown.\n"
|
|
"function M.ContextManager()\n"
|
|
" local cache = { }\n"
|
|
" return function(thread, level)\n"
|
|
" -- the real coroutine is used as key (not the wrapped instance as its unicity is not guaranteed)\n"
|
|
" -- otherwise, true is used to identify current thread (as nil is not a valid table key)\n"
|
|
" local key = thread[1] or true\n"
|
|
" local thread_contexts = cache[key]\n"
|
|
" if not thread_contexts then\n"
|
|
" thread_contexts = { }\n"
|
|
" cache[key] = thread_contexts\n"
|
|
" end\n"
|
|
"\n"
|
|
" local context = thread_contexts[level]\n"
|
|
" if not context then\n"
|
|
" context = M.Context:new(thread, level)\n"
|
|
" thread_contexts[level] = context\n"
|
|
" end\n"
|
|
"\n"
|
|
" return context\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"return M\n"
|
|
"\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.context\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.dbgp\n"
|
|
"package.preload[\"debugger.dbgp\"] = function(...)\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2011-2012 Sierra Wireless and others.\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Sierra Wireless - initial API and implementation\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- DBGp protocol utility function (parsing, error handling, XML generation).\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"local util = require \"debugger.util\"\n"
|
|
"\n"
|
|
"local error, setmetatable, type, pairs, ipairs, tostring, tconcat =\n"
|
|
" error, setmetatable, type, pairs, ipairs, tostring, table.concat\n"
|
|
"\n"
|
|
"local M = { }\n"
|
|
"\n"
|
|
"--- Parses the DBGp command arguments and returns it as a Lua table with key/value pairs.\n"
|
|
"-- For example, the sequence <code>-i 5 -j foo</code> will result in <code>{i=5, j=foo}</code>\n"
|
|
"-- @param cmd_args (string) sequence of arguments\n"
|
|
"-- @return table described above\n"
|
|
"function M.arg_parse(cmd_args)\n"
|
|
" local args = {}\n"
|
|
" for arg, val in cmd_args:gmatch(\"%-(%w) (%S+)\") do\n"
|
|
" args[arg] = val\n"
|
|
" end\n"
|
|
" return args\n"
|
|
"end\n"
|
|
"\n"
|
|
"--- Parses a command line\n"
|
|
"-- @return commande name (string)\n"
|
|
"-- @retrun arguments (table)\n"
|
|
"-- @return data (string, optional)\n"
|
|
"function M.cmd_parse(cmd)\n"
|
|
" local cmd_name, args, data\n"
|
|
" if cmd:find(\"--\", 1, true) then -- there is a data part\n"
|
|
" cmd_name, args, data = cmd:match(\"^(%S+)%s+(.*)%s+%-%-%s*(.*)$\")\n"
|
|
" data = util.unb64(data)\n"
|
|
" else\n"
|
|
" cmd_name, args = cmd:match(\"^(%S+)%s+(.*)$\")\n"
|
|
" end\n"
|
|
" return cmd_name, M.arg_parse(args), data\n"
|
|
"end\n"
|
|
"\n"
|
|
"--- Returns the packet read from socket, or nil followed by an error message on errors.\n"
|
|
"function M.read_packet(skt)\n"
|
|
" local size = {}\n"
|
|
" while true do\n"
|
|
" local byte, err = skt:receive(1)\n"
|
|
" if not byte then return nil, err end\n"
|
|
" if byte == \"\\000\" then break end\n"
|
|
" size[#size+1] = byte\n"
|
|
" end\n"
|
|
" return tconcat(size)\n"
|
|
"end\n"
|
|
"\n"
|
|
"M.DBGP_ERR_METATABLE = {} -- unique object used to identify DBGp errors\n"
|
|
"\n"
|
|
"--- Throws a correct DBGp error which result in a fine tuned error message to the server.\n"
|
|
"-- It is intended to be called into a command to make a useful error message, a standard Lua error\n"
|
|
"-- result in a code 998 error (internal debugger error).\n"
|
|
"-- @param code numerical error code\n"
|
|
"-- @param message message string (optional)\n"
|
|
"-- @param attr extra attributes to add to the response tag (optional)\n"
|
|
"function M.error(code, message, attr)\n"
|
|
" error(setmetatable({ code = code, message = message, attr = attr or {} }, M.DBGP_ERR_METATABLE), 2)\n"
|
|
"end\n"
|
|
"\n"
|
|
"--- Like core assert but throws a DBGp error if condition is not met.\n"
|
|
"-- @param code numerical error code thrown if condition is not met.\n"
|
|
"-- @param message condition to test\n"
|
|
"-- @param ... will be used as error message if test fails.\n"
|
|
"function M.assert(code, success, ...)\n"
|
|
" if not success then M.error(code, (...)) end\n"
|
|
" return success, ...\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- -----------------\n"
|
|
"-- Outgoing data\n"
|
|
"-- -----------------\n"
|
|
"local xmlattr_specialchars = { ['\"'] = \""\", [\"<\"] = \"<\", [\"&\"] = \"&\" }\n"
|
|
"--- Very basic XML generator\n"
|
|
"-- Generates a XML string from a Lua Object Model (LOM) table.\n"
|
|
"-- See http://matthewwild.co.uk/projects/luaexpat/lom.html\n"
|
|
"function M.lom2str(xml)\n"
|
|
" local pieces = { } -- string buffer\n"
|
|
"\n"
|
|
" local function generate(node)\n"
|
|
" pieces[#pieces + 1] = \"<\"..node.tag\n"
|
|
" pieces[#pieces + 1] = \" \"\n"
|
|
" -- attribute ordering is not honored here\n"
|
|
" for attr, val in pairs(node.attr or {}) do\n"
|
|
" if type(attr) == \"string\" then\n"
|
|
" pieces[#pieces + 1] = attr .. '=\"' .. tostring(val):gsub('[\"&<]', xmlattr_specialchars) .. '\"'\n"
|
|
" pieces[#pieces + 1] = \" \"\n"
|
|
" end\n"
|
|
" end\n"
|
|
" pieces[#pieces] = nil -- remove the last separator (useless)\n"
|
|
"\n"
|
|
" if node[1] then\n"
|
|
" pieces[#pieces + 1] = \">\"\n"
|
|
" for _, child in ipairs(node) do\n"
|
|
" if type(child) == \"table\" then generate(child)\n"
|
|
" else pieces[#pieces + 1] = \"<![CDATA[\" .. tostring(child) .. \"]]>\" end\n"
|
|
" end\n"
|
|
" pieces[#pieces + 1] = \"</\" .. node.tag .. \">\"\n"
|
|
" else\n"
|
|
" pieces[#pieces + 1] = \"/>\"\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" generate(xml)\n"
|
|
" return tconcat(pieces)\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.send_xml(skt, resp)\n"
|
|
" if not resp.attr then resp.attr = {} end\n"
|
|
" resp.attr.xmlns = \"urn:debugger_protocol_v1\"\n"
|
|
"\n"
|
|
" local data = '<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\\n'..M.lom2str(resp)\n"
|
|
" util.log(\"DEBUG\", \"Send \" .. data)\n"
|
|
" skt:send(tostring(#data)..\"\\000\"..data..\"\\000\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"--- Return an XML tag describing a debugger error, with an optional message\n"
|
|
"-- @param code (number) error code (see DBGp specification)\n"
|
|
"-- @param msg (string, optional) textual description of error\n"
|
|
"-- @return table, suitable to be converted into XML\n"
|
|
"function M.make_error(code, msg)\n"
|
|
" local elem = { tag = \"error\", attr = { code = code } }\n"
|
|
" if msg then\n"
|
|
" elem[1] = { tostring(msg), tag = \"message\" }\n"
|
|
" end\n"
|
|
" return elem\n"
|
|
"end\n"
|
|
"\n"
|
|
"return M\n"
|
|
"\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.dbgp\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.introspection\n"
|
|
"package.preload[\"debugger.introspection\"] = function(...)\n"
|
|
"-- ----------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2011-2012 Sierra Wireless and others.\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Julien Desgats - initial API and implementation\n"
|
|
"-- ----------------------------------------------------------------------------\n"
|
|
"-- Properties generation. Generate a LOM table with data from introspection.\n"
|
|
"-- ----------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"local debug = require \"debug\"\n"
|
|
"local platform = require \"debugger.platform\"\n"
|
|
"local util = require \"debugger.util\"\n"
|
|
"\n"
|
|
"local tostring, type, assert, next, rawget, getmetatable, setmetatable, getfenv, select, coyield, cocreate, costatus, coresume, sformat, tconcat =\n"
|
|
" tostring, type, assert, next, rawget, getmetatable, setmetatable, getfenv, select, coroutine.yield, coroutine.create, coroutine.status, coroutine.resume, string.format, table.concat\n"
|
|
"\n"
|
|
"local MULTIVAL_MT = { __tostring = function() return \"\" end }\n"
|
|
"local probes = { }\n"
|
|
"\n"
|
|
"-- ---------- --\n"
|
|
"-- Public API --\n"
|
|
"-- ---------- --\n"
|
|
"\n"
|
|
"---\n"
|
|
"-- Introspection logic. This module implements Lua objects introspection and\n"
|
|
"-- generates a [DBGP](http://xdebug.org/docs-dbgp.php) compatible\n"
|
|
"-- [LOM](http://matthewwild.co.uk/projects/luaexpat/lom.html) data scructure.\n"
|
|
"-- @module debugger.introspection\n"
|
|
"local M = { }\n"
|
|
"\n"
|
|
"---\n"
|
|
"-- Represent the actual data to send to the debugger.\n"
|
|
"-- Full XML specification can be found in [DBGP specification](http://xdebug.org/docs-dbgp.php#properties-variables-and-values).\n"
|
|
"-- Modifying properties after their generation is possible (as actual data serialization/sending is delayed)\n"
|
|
"-- but should be used with care. The XML structure uses the [LOM](http://matthewwild.co.uk/projects/luaexpat/lom.html)\n"
|
|
"-- format, refer to these documents to get more informations about fields.\n"
|
|
"--\n"
|
|
"-- In addition to table fields, it has an array part, `[1]` being the string representation (base64 encoded),\n"
|
|
"-- possibly followed by chlid properties (@{#DBGPProperty} themselves)\n"
|
|
"--\n"
|
|
"-- @field #string tag Always \"property\"\n"
|
|
"-- @field #table attr XML attributes, see DBGP specification\n"
|
|
"-- @type DBGPProperty\n"
|
|
"\n"
|
|
"---\n"
|
|
"-- Inpectors table, contain all inspector functions.\n"
|
|
"-- Keys are either type names (`string`, `number`, ...) or metatables\n"
|
|
"-- that have a custom inspector attached.\n"
|
|
"-- @field [parent=#debugger.introspection] #table inspectors\n"
|
|
"M.inspectors = { }\n"
|
|
"\n"
|
|
"---\n"
|
|
"-- Generate a DBGP property if needed. If data is in data pagination and recursion depth ranges,\n"
|
|
"-- and send a property to the debugger, otherwise drop current property.\n"
|
|
"-- @param #string name Property name (displayed in IDE)\n"
|
|
"-- @param #string typename Type name (displayed in IDE)\n"
|
|
"-- @param #string repr Value string representation\n"
|
|
"-- @param #DBGPProperty parent Parent property\n"
|
|
"-- @param #string fullname Lua expression used to get value back in further calls\n"
|
|
"-- @return #table description\n"
|
|
"-- @function [parent=#debugger.introspection] property\n"
|
|
"M.property = coyield\n"
|
|
"\n"
|
|
"---\n"
|
|
"-- Adds a probe that will be called for every unknown table/userdata.\n"
|
|
"-- @param #function probe Inspector function to call.\n"
|
|
"-- @function [parent=#debugger.introspection] add_probe\n"
|
|
"M.add_probe = function(probe) probes[#probes + 1] = probe end\n"
|
|
"\n"
|
|
"---\n"
|
|
"-- Inspects a Lua value by dispatching it to correct inspector. Inspector functions have the same API.\n"
|
|
"-- @param #string name Property name (will be displayed by IDE)\n"
|
|
"-- @param value Value to inspect\n"
|
|
"-- @param #table parent Parent property (LOM table of the )\n"
|
|
"-- @param #string fullname Expression used to retrieve `value` for further debugger calls\n"
|
|
"-- @return #DBGPProperty The inspected value as returned by @{debugger.introspection#debugger.introspection.property}.\n"
|
|
"-- @return #nil If the value has not been inspected\n"
|
|
"-- @function [parent=#debugger.introspection] inspect\n"
|
|
"M.inspect = function(name, value, parent, fullname)\n"
|
|
" return (M.inspectors[type(value)] or M.inspectors.default)(name, value, parent, fullname)\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- ----------------- --\n"
|
|
"-- Utility functions --\n"
|
|
"-- ----------------- --\n"
|
|
"\n"
|
|
"local function default_inspector(name, value, parent, fullname)\n"
|
|
" return M.property(name, type(value), tostring(value), parent, fullname)\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Inspects types that can have a metatable (table and userdata). Returns\n"
|
|
"-- 1) generated property\n"
|
|
"-- 2) boolean indicating whether a custom inspector has been called (in that case, do not process value any further)\n"
|
|
"local function metatable_inspector(name, value, parent, fullname)\n"
|
|
" local mt = getmetatable(value)\n"
|
|
" do\n"
|
|
" -- find by metatable\n"
|
|
" local custom = M.inspectors[mt]\n"
|
|
" if custom then return custom(name, value, parent, fullname), true end\n"
|
|
" -- or else call probes\n"
|
|
" for i=1, #probes do\n"
|
|
" local prop = probes[i](name, value, parent, fullname)\n"
|
|
" if prop then return prop, true end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" local prop = default_inspector(name, value, parent, fullname)\n"
|
|
" if mt and prop then\n"
|
|
" local mtprop = M.inspect(\"metatable\", mt, prop, \"metatable[\"..prop.attr.fullname..\"]\")\n"
|
|
" if mtprop then mtprop.attr.type = \"special\" end\n"
|
|
" end\n"
|
|
" return prop, false\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function fancy_func_repr(f, info)\n"
|
|
" local args = {}\n"
|
|
" for i=1, info.nparams do\n"
|
|
" args[i] = debug.getlocal(f, i)\n"
|
|
" end\n"
|
|
"\n"
|
|
" if info.isvararg then\n"
|
|
" args[#args+1] = \"...\"\n"
|
|
" end\n"
|
|
"\n"
|
|
" return \"function(\" .. tconcat(args, \", \") .. \")\"\n"
|
|
"end\n"
|
|
"\n"
|
|
"--- Generate a name siutable for table index syntax\n"
|
|
"-- @param name Key name\n"
|
|
"-- @return #string A table index style index\n"
|
|
"-- @usage generate_printable_key('foo') => '[\"foo\"]'\n"
|
|
"-- @usage generate_printable_key(12) => '[12]'\n"
|
|
"-- @usage generate_printable_key({}) => '[table: 0x12345678]\n"
|
|
"-- @function [parent=#debugger.introspection] generate_printable_key\n"
|
|
"local function generate_printable_key(name)\n"
|
|
" return \"[\" .. (type(name) == \"string\" and sformat(\"%q\", name) or tostring(name)) .. \"]\"\n"
|
|
"end\n"
|
|
"M.generate_printable_key = generate_printable_key\n"
|
|
"\n"
|
|
"-- Used to store complex keys (other than string and number) as they cannot be passed in text\n"
|
|
"-- For these keys, the resulting expression will not be the key itself but \"key_cache[...]\"\n"
|
|
"-- where key_cache must be mapped to this table to resolve key correctly.\n"
|
|
"M.key_cache = setmetatable({ n=0 }, { __mode = \"v\" })\n"
|
|
"\n"
|
|
"local function generate_key(name)\n"
|
|
" local tname = type(name)\n"
|
|
" if tname == \"string\" then return sformat(\"%q\", name)\n"
|
|
" elseif tname == \"number\" or tname == \"boolean\" then return tostring(name)\n"
|
|
" else -- complex key, use key_cache for lookup\n"
|
|
" local i = M.key_cache.n\n"
|
|
" M.key_cache[i] = name\n"
|
|
" M.key_cache.n = i+1\n"
|
|
" return \"key_cache[\"..tostring(i)..\"]\"\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"--- Generate a usable fullname for a value.\n"
|
|
"-- Based on parent fullname and key value, return a valid Lua expression.\n"
|
|
"-- Key can be any value (as anything can act as table key). If it cannot\n"
|
|
"-- be serialized (only string, number and boolean can), it will be temporarly\n"
|
|
"-- stored in an internal cache to be retrieved later.\n"
|
|
"-- @param #string parent Parent fullname\n"
|
|
"-- @param key The child key to generate fullname for\n"
|
|
"-- @return #string A valid fullname expression\n"
|
|
"-- @function [parent=#debugger.introspection] make_fullname\n"
|
|
"local function make_fullname(parent, key)\n"
|
|
" return parent .. \"[\" .. generate_key(key) .. \"]\"\n"
|
|
"end\n"
|
|
"M.make_fullname = make_fullname\n"
|
|
"\n"
|
|
"-- ---------- --\n"
|
|
"-- Inspectors --\n"
|
|
"-- ---------- --\n"
|
|
"\n"
|
|
"M.inspectors.number = default_inspector\n"
|
|
"M.inspectors.boolean = default_inspector\n"
|
|
"M.inspectors[\"nil\"] = default_inspector\n"
|
|
"M.inspectors.userdata = default_inspector\n"
|
|
"M.inspectors.thread = default_inspector\n"
|
|
"M.inspectors.default = default_inspector -- allows 3rd party inspectors to use the default inspector if needed\n"
|
|
"\n"
|
|
"M.inspectors.userdata = function(name, value, parent, fullname)\n"
|
|
" return (metatable_inspector(name, value, parent, fullname)) -- drop second return value\n"
|
|
"end\n"
|
|
"\n"
|
|
"M.inspectors.string = function(name, value, parent, fullname)\n"
|
|
" -- escape linebreaks as \\n and not as \\<0x0A> like %q does\n"
|
|
" return M.property(name, \"string\", sformat(\"%q\", value):gsub(\"\\\\\\n\", \"\\\\n\"), parent, fullname)\n"
|
|
"end\n"
|
|
"\n"
|
|
"M.inspectors[\"function\"] = function(name, value, parent, fullname)\n"
|
|
" local info = debug.getinfo(value, \"nSflu\")\n"
|
|
" local prop\n"
|
|
" if info.what ~= \"C\" then\n"
|
|
" -- try to create a fancy representation if possible\n"
|
|
" local repr = info.nparams and fancy_func_repr(value, info) or tostring(value)\n"
|
|
" if info.source:sub(1,1) == \"@\" then\n"
|
|
" repr = repr .. \"\\n\" .. platform.get_uri(\"@\" .. info.source) .. \"\\n\" .. tostring(info.linedefined)\n"
|
|
" end\n"
|
|
" prop = M.property(name, \"function (Lua)\", repr, parent, fullname)\n"
|
|
" else\n"
|
|
" prop = M.property(name, \"function\", tostring(value), parent, fullname)\n"
|
|
" end\n"
|
|
" if not prop then return nil end\n"
|
|
"\n"
|
|
" -- (5.1 only) environment is dumped only if it is different from global environment\n"
|
|
" -- TODO: this is not a correct behavior: environment should be dumped if is different from current stack level one\n"
|
|
" local fenv = getfenv and getfenv(value)\n"
|
|
" if fenv and fenv ~= getfenv(0) then\n"
|
|
" local fenvprop = M.inspect(\"environment\", fenv, prop, \"environment[\"..prop.attr.fullname..\"]\")\n"
|
|
" if fenvprop then fenvprop.attr.type = \"special\" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" return prop\n"
|
|
"end\n"
|
|
"\n"
|
|
"\n"
|
|
"M.inspectors.table = function(name, value, parent, fullname)\n"
|
|
" local prop, iscustom = metatable_inspector(name, value, parent, fullname)\n"
|
|
" if not prop or iscustom then return prop end\n"
|
|
"\n"
|
|
" -- iterate over table values and detect arrays at the same time\n"
|
|
" -- next is used to circumvent __pairs metamethod in 5.2\n"
|
|
" local isarray, i = true, 1\n"
|
|
" for k,v in next, value, nil do\n"
|
|
" M.inspect(generate_printable_key(k), v, prop, make_fullname(fullname, k))\n"
|
|
" -- array detection: keys should be accessible by 1..n keys\n"
|
|
" isarray = isarray and rawget(value, i) ~= nil\n"
|
|
" i = i + 1\n"
|
|
" end\n"
|
|
" -- empty tables are considered as tables\n"
|
|
" if isarray and i > 1 then prop.attr.type = \"sequence\" end\n"
|
|
"\n"
|
|
" return prop\n"
|
|
"end\n"
|
|
"\n"
|
|
"M.inspectors[MULTIVAL_MT] = function(name, value, parent, fullname)\n"
|
|
" if value.n == 1 then\n"
|
|
" -- return directly the value as result\n"
|
|
" return M.inspect(name, value[1], parent, fullname)\n"
|
|
" else\n"
|
|
" -- wrap values inside a multival container\n"
|
|
" local prop = M.property(name, \"multival\", \"\", parent, fullname)\n"
|
|
" if not prop then return nil end\n"
|
|
" for i=1, value.n do\n"
|
|
" M.inspect(generate_printable_key(i), value[i], prop, fullname .. \"[\" .. i .. \"]\")\n"
|
|
" end\n"
|
|
" return prop\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- ------------ --\n"
|
|
"-- Internal API --\n"
|
|
"-- ------------ --\n"
|
|
"\n"
|
|
"-- Used to inspect \"multival\" or \"vararg\" values. The typical use is to pack function result(s) in a single\n"
|
|
"-- value to inspect. The Multival instances can be passed to make_property as a single value, they will be\n"
|
|
"-- correctly reported to debugger\n"
|
|
"function M.Multival(...)\n"
|
|
" return setmetatable({ n=select(\"#\", ...), ... }, MULTIVAL_MT)\n"
|
|
"end\n"
|
|
"\n"
|
|
"--- Makes a property form a name/value pair (and fullname). This is an **internal** function, and should not be used by 3rd party inspectors.\n"
|
|
"-- @param #number cxt_id Context ID in which this value resides (workaround bug 352316)\n"
|
|
"-- @param value The value to debug\n"
|
|
"-- @param name The name associated with value, passed through tostring, so it can be anything\n"
|
|
"-- @param #string fullname A Lua expression to eval to get that property again (if nil, computed automatically)\n"
|
|
"-- @param #number depth The maximum property depth (recursive calls)\n"
|
|
"-- @param #number pagesize maximum children to include\n"
|
|
"-- @param #number page The page to generate (0 based)\n"
|
|
"-- @param #number size_limit Optional, if set, the maximum size of the string representation (in bytes)\n"
|
|
"-- @param #boolean safe_name If true, does not encode the name as table key\n"
|
|
"-- @return #DBGPProperty root property\n"
|
|
"-- @function [parent=#debugger.introspection] make_property\n"
|
|
"--TODO BUG ECLIPSE TOOLSLINUX-99 352316 : as a workaround, context is encoded into the fullname property\n"
|
|
"M.make_property = function(cxt_id, value, name, fullname, depth, pagesize, page, size_limit, safe_name)\n"
|
|
" fullname = fullname or \"(...)[\" .. generate_key(name) .. \"]\"\n"
|
|
" if not safe_name then name = generate_printable_key(name) end\n"
|
|
"\n"
|
|
" local generator = cocreate(function() return M.inspect(name, value, nil, fullname) end)\n"
|
|
" local propstack = { }\n"
|
|
" local rootnode\n"
|
|
" local catchthis = true\n"
|
|
" local nodestoskip = page * pagesize -- nodes to skip at root level to respect pagination\n"
|
|
" local fullname_prefix = tostring(cxt_id)..\"|\"\n"
|
|
"\n"
|
|
" while true do\n"
|
|
" local succes, name, datatype, repr, parent, fullname = assert(coresume(generator, catchthis and propstack[#propstack] or nil))\n"
|
|
" -- finalize and pop all finished properties\n"
|
|
" while propstack[#propstack] ~= parent do\n"
|
|
" local topop = propstack[#propstack]\n"
|
|
" topop.attr.fullname = util.rawb64(fullname_prefix .. topop.attr.fullname)\n"
|
|
" propstack[#propstack] = nil\n"
|
|
" end\n"
|
|
" if costatus(generator) == \"dead\" then break end\n"
|
|
"\n"
|
|
" local prop = {\n"
|
|
" tag = \"property\",\n"
|
|
" attr = {\n"
|
|
" children = 0,\n"
|
|
" pagesize = pagesize,\n"
|
|
" page = parent and 0 or page,\n"
|
|
" type = datatype,\n"
|
|
" name = name,\n"
|
|
" fullname = fullname,\n"
|
|
" encoding = \"base64\",\n"
|
|
" size = #repr,\n"
|
|
" },\n"
|
|
" util.b64(size_limit and repr:sub(1, size_limit) or repr)\n"
|
|
" }\n"
|
|
"\n"
|
|
" if parent then\n"
|
|
" parent.attr.children = 1\n"
|
|
" parent.attr.numchildren = (parent.attr.numchildren or 0) + 1\n"
|
|
" -- take pagination into accont to know if node needs to be catched\n"
|
|
" catchthis = #parent <= pagesize and #propstack <= depth\n"
|
|
" if parent == rootnode then\n"
|
|
" catchthis = catchthis and nodestoskip <= 0\n"
|
|
" nodestoskip = nodestoskip - 1\n"
|
|
" end\n"
|
|
" -- add node to tree\n"
|
|
" if catchthis then\n"
|
|
" parent[#parent + 1] = prop\n"
|
|
" propstack[#propstack + 1] = prop\n"
|
|
" end\n"
|
|
" else\n"
|
|
" rootnode = prop\n"
|
|
" catchthis = true\n"
|
|
" propstack[#propstack + 1] = prop\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" return rootnode\n"
|
|
"end\n"
|
|
"\n"
|
|
"return M\n"
|
|
"\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.introspection\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.plugins.ffi\n"
|
|
"package.preload[\"debugger.plugins.ffi\"] = function(...)\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2012-2013 Julien Desgats\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Julien Desgats - initial API and implementation\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- LuaJIT cdata introspection library.\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"-- known issues:\n"
|
|
"-- * references are de-referenced event if inspect_references is unset\n"
|
|
"-- * is automatic pointer and reference de-referencing is possible ?\n"
|
|
"-- (only for first item in case of arrays). Possible leads:\n"
|
|
"-- http://stackoverflow.com/questions/7134590/how-to-test-if-an-address-is-readable-in-linux-userspace-app\n"
|
|
"-- http://www.softwareverify.com/blog/?p=319\n"
|
|
"-- * when setting a value from Eclipse, the type is sometimes changed (e.g. int => number)\n"
|
|
"\n"
|
|
"local introspection = require \"debugger.introspection\"\n"
|
|
"local reflect = require \"debugger.plugins.ffi.reflect\"\n"
|
|
"local ffi = require \"ffi\"\n"
|
|
"\n"
|
|
"local tostring, tonumber, type, assert, sformat, tconcat = tostring, tonumber, type, assert, string.format, table.concat\n"
|
|
"\n"
|
|
"local M = { }\n"
|
|
"\n"
|
|
"--- Whether the reference types are inspected. Usually references should be safe (at least a bit\n"
|
|
"-- safer than pointers) so they are inspected. If a reference points to unsafe memory, the whole\n"
|
|
"-- program could crash !\n"
|
|
"-- If this feature is disabled, deeply nested C types will not be displayed correctly as evaluation\n"
|
|
"-- has a recursion limit, any further evaluation is done through references.\n"
|
|
"M.inspect_references = true\n"
|
|
"\n"
|
|
"local function make_typename(refct)\n"
|
|
" local t = refct.what\n"
|
|
" if t == \"int\" then\n"
|
|
" if refct.bool then t = \"bool\"\n"
|
|
" else\n"
|
|
" -- use C99 type notation to give more details about acutal type\n"
|
|
" t = (refct.unsigned and \"uint\" or \"int\") .. tostring(refct.size * 8) .. \"_t\"\n"
|
|
" end\n"
|
|
" elseif t == \"float\" then\n"
|
|
" -- assume IEEE754\n"
|
|
" if refct.size == 8 then t = \"double\"\n"
|
|
" elseif refct.size == 16 then t = \"long double\" -- not really sure this one is always true\n"
|
|
" end\n"
|
|
" elseif t == \"struct\" or t == \"enum\" or t == \"union\" then\n"
|
|
" t = refct.name and (t .. \" \" .. refct.name) or (\"anonymous \"..t)\n"
|
|
" elseif t == \"func\" then\n"
|
|
" t = \"function (FFI)\"\n"
|
|
" elseif t == \"ptr\" then\n"
|
|
" t = make_typename(refct.element_type) .. \"*\"\n"
|
|
" elseif t == \"ref\" then\n"
|
|
" t = make_typename(refct.element_type) .. \"&\"\n"
|
|
" elseif t == \"field\" then\n"
|
|
" return make_typename(refct.type)\n"
|
|
" elseif t == \"bitfield\" then\n"
|
|
" t = (refct.type.unsigned and \"unsigned\" or \"signed\") .. \":\" .. tostring(refct.size * 8)\n"
|
|
" refct = refct.type\n"
|
|
" end\n"
|
|
"\n"
|
|
" if refct.const then t = \"const \" .. t end\n"
|
|
" if refct.volatile then t = \"volatile \" .. t end\n"
|
|
" return t\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- if cdatakind is unknown, this one will be called\n"
|
|
"local default_inspector = introspection.inspectors.number\n"
|
|
"local inspect\n"
|
|
"\n"
|
|
"-- recursion must be handled with some care: if we call regular introspection.inspect\n"
|
|
"-- we may create boxed references or Lua native objects which will be inspected as such\n"
|
|
"-- (leading to wrong type names).\n"
|
|
"local function recurse(name, value, parent, fullname, refct)\n"
|
|
" if type(value) == \"cdata\" then\n"
|
|
" return inspect(name, value, parent, fullname, refct)\n"
|
|
" else\n"
|
|
" local prop = introspection.inspect(name, value, parent, fullname)\n"
|
|
" if prop then\n"
|
|
" prop.attr.type = make_typename(refct)\n"
|
|
" end\n"
|
|
" return prop\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- cdata specific inspectors\n"
|
|
"local inspectors = {\n"
|
|
" struct = function(name, value, parent, fullname, refct)\n"
|
|
" local prop = introspection.property(name, make_typename(refct), tostring(value), parent, fullname)\n"
|
|
"\n"
|
|
" -- inspect children, if needed\n"
|
|
" if prop then\n"
|
|
" for member in refct:members() do\n"
|
|
" local mname = member.name\n"
|
|
" recurse(mname, value[mname], prop, fullname .. sformat('[%q]', mname), member)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return prop\n"
|
|
" end,\n"
|
|
"\n"
|
|
" array = function(name, value, parent, fullname, refct)\n"
|
|
" local etype = refct.element_type\n"
|
|
" -- for VLAs, reflect does not give size\n"
|
|
" local size = refct.size ~= \"none\" and refct.size or ffi.sizeof(value)\n"
|
|
" size = size and (size / etype.size) -- we've got the byte size, not element count\n"
|
|
"\n"
|
|
" local typename = make_typename(etype)\n"
|
|
" local prop = introspection.property(name, typename .. \"[\" .. (tostring(size) or \"\") .. \"]\", tostring(value), parent, fullname)\n"
|
|
"\n"
|
|
" if prop and size then\n"
|
|
" for i=0, size-1 do\n"
|
|
" local idx = \"[\"..tostring(i)..\"]\"\n"
|
|
" recurse(idx, value[i], prop, fullname .. idx, etype)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return prop\n"
|
|
" end,\n"
|
|
"\n"
|
|
" func = function(name, value, parent, fullname, refct)\n"
|
|
" local args = { }\n"
|
|
" for arg in refct:arguments() do\n"
|
|
" args[#args + 1] = make_typename(arg.type) .. \" \" .. arg.name\n"
|
|
" end\n"
|
|
"\n"
|
|
" if refct.vararg then\n"
|
|
" args[#args + 1] = \"...\"\n"
|
|
" end\n"
|
|
"\n"
|
|
" local repr = make_typename(refct.return_type) .. \" \" .. refct.name .. \"(\" .. tconcat(args, \", \") .. \")\"\n"
|
|
" return introspection.property(name, make_typename(refct), repr, parent, fullname)\n"
|
|
" end,\n"
|
|
"\n"
|
|
" enum = function(name, value, parent, fullname, refct)\n"
|
|
" local repr = tonumber(value)\n"
|
|
" -- try to convert numeric value into enum name\n"
|
|
" --TODO: is there a faster method to make it ?\n"
|
|
" for val in refct:values() do\n"
|
|
" if val.value == repr then\n"
|
|
" repr = val.name\n"
|
|
" break\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" return introspection.property(name, make_typename(refct), tostring(repr), parent, fullname)\n"
|
|
" end,\n"
|
|
"\n"
|
|
" ref = function(name, value, parent, fullname, refct)\n"
|
|
" -- this may be unsafe, see inspect_references setting\n"
|
|
" local typename = make_typename(refct)\n"
|
|
" if not M.inspect_references then\n"
|
|
" return introspection.property(name, typename, tostring(value), parent, fullname)\n"
|
|
" end\n"
|
|
"\n"
|
|
" local prop = recurse(name, value, parent, fullname, refct.element_type)\n"
|
|
" if prop then\n"
|
|
" prop.attr.type = typename\n"
|
|
" end\n"
|
|
" return prop\n"
|
|
" end,\n"
|
|
"\n"
|
|
" int = function(name, value, parent, fullname, refct)\n"
|
|
" return introspection.property(name, make_typename(refct), tostring(tonumber(value)), parent, fullname)\n"
|
|
" end,\n"
|
|
"\n"
|
|
" -- pointers are too unsafe, do not inspect them\n"
|
|
" ptr = function(name, value, parent, fullname, refct)\n"
|
|
" return introspection.property(name, make_typename(refct), tostring(value), parent, fullname)\n"
|
|
" end,\n"
|
|
"}\n"
|
|
"\n"
|
|
"inspectors.union = inspectors.struct\n"
|
|
"inspectors.float = inspectors.int\n"
|
|
"\n"
|
|
"-- for struct/union fields, the actual type is nested into the refct\n"
|
|
"inspectors.field = function(name, value, parent, fullname, refct)\n"
|
|
" return inspect(name, value, parent, fullname, refct.type)\n"
|
|
"end\n"
|
|
"inspectors.bitfield = inspectors.field\n"
|
|
"\n"
|
|
"inspect = function(name, value, parent, fullname, refct)\n"
|
|
" -- inspect only values, not ctypes\n"
|
|
" --FIXME: this cause references to be dereferenced and crash the process if they are wrong !\n"
|
|
" if ffi.typeof(value) ~= value then\n"
|
|
" refct = refct or reflect.typeof(value)\n"
|
|
" return (inspectors[refct.what] or default_inspector)(name, value, parent, fullname, refct)\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- return a simple property for ctypes\n"
|
|
" return introspection.property(name, \"ctype\", tostring(value), parent, fullname)\n"
|
|
"end\n"
|
|
"\n"
|
|
"introspection.inspectors.cdata = inspect\n"
|
|
"\n"
|
|
"return M\n"
|
|
"\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.plugins.ffi\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.plugins.ffi.reflect\n"
|
|
"package.preload[\"debugger.plugins.ffi.reflect\"] = function(...)\n"
|
|
"--[[ LuaJIT FFI reflection Library ]]--\n"
|
|
"--[[ Copyright (C) 2013 Peter Cawley <lua@corsix.org>. All rights reserved.\n"
|
|
"\n"
|
|
"Permission is hereby granted, free of charge, to any person obtaining a copy\n"
|
|
"of this software and associated documentation files (the \"Software\"), to deal\n"
|
|
"in the Software without restriction, including without limitation the rights\n"
|
|
"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
|
|
"copies of the Software, and to permit persons to whom the Software is\n"
|
|
"furnished to do so, subject to the following conditions:\n"
|
|
"\n"
|
|
"The above copyright notice and this permission notice shall be included in\n"
|
|
"all copies or substantial portions of the Software.\n"
|
|
"\n"
|
|
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
|
|
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
|
|
"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
|
|
"AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
|
|
"LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
|
|
"OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"
|
|
"THE SOFTWARE.\n"
|
|
"--]]\n"
|
|
"local ffi = require \"ffi\"\n"
|
|
"local bit = require \"bit\"\n"
|
|
"local reflect = {}\n"
|
|
"\n"
|
|
"-- Relevant minimal definitions from lj_ctype.h\n"
|
|
"ffi.cdef [[\n"
|
|
" typedef struct CType {\n"
|
|
" uint32_t info;\n"
|
|
" uint32_t size;\n"
|
|
" uint16_t sib;\n"
|
|
" uint16_t next;\n"
|
|
" uint32_t name;\n"
|
|
" } CType;\n"
|
|
"\n"
|
|
" typedef struct CTState {\n"
|
|
" CType *tab;\n"
|
|
" uint32_t top;\n"
|
|
" uint32_t sizetab;\n"
|
|
" void *L;\n"
|
|
" void *g;\n"
|
|
" void *finalizer;\n"
|
|
" void *miscmap;\n"
|
|
" } CTState;\n"
|
|
"]]\n"
|
|
"\n"
|
|
"local function gc_str(gcref) -- Convert a GCref (to a GCstr) into a string\n"
|
|
" if gcref ~= 0 then\n"
|
|
" local ts = ffi.cast(\"uint32_t*\", gcref)\n"
|
|
" return ffi.string(ts + 4, ts[3])\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function memptr(gcobj)\n"
|
|
" return tonumber(tostring(gcobj):match\"%x*$\", 16)\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Acquire a pointer to this Lua universe's CTState\n"
|
|
"local CTState do\n"
|
|
" local co = coroutine.create(function()end) -- Any live coroutine will do.\n"
|
|
" local uint32_ptr = ffi.typeof(\"uint32_t*\")\n"
|
|
" local G = ffi.cast(uint32_ptr, ffi.cast(uint32_ptr, memptr(co))[2])\n"
|
|
" -- In global_State, `MRef ctype_state` is immediately before `GCRef gcroot[GCROOT_MAX]`.\n"
|
|
" -- We first find (an entry in) gcroot by looking for a metamethod name string.\n"
|
|
" local anchor = ffi.cast(\"uint32_t\", ffi.cast(\"const char*\", \"__index\"))\n"
|
|
" local i = 0\n"
|
|
" while math.abs(tonumber(G[i] - anchor)) > 64 do\n"
|
|
" i = i + 1\n"
|
|
" end\n"
|
|
" -- We then work backwards looking for something resembling ctype_state.\n"
|
|
" repeat\n"
|
|
" i = i - 1\n"
|
|
" CTState = ffi.cast(\"CTState*\", G[i])\n"
|
|
" until ffi.cast(uint32_ptr, CTState.g) == G\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Acquire the CTState's miscmap table as a Lua variable\n"
|
|
"local miscmap do\n"
|
|
" local t = {}; t[0] = t\n"
|
|
" local tvalue = ffi.cast(\"uint32_t*\", memptr(t))[2]\n"
|
|
" ffi.cast(\"uint32_t*\", tvalue)[ffi.abi\"le\" and 0 or 1] = ffi.cast(\"uint32_t\", ffi.cast(\"uintptr_t\", CTState.miscmap))\n"
|
|
" miscmap = t[0]\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Information for unpacking a `struct CType`.\n"
|
|
"-- One table per CT_* constant, containing:\n"
|
|
"-- * A name for that CT_\n"
|
|
"-- * Roles of the cid and size fields.\n"
|
|
"-- * Whether the sib field is meaningful.\n"
|
|
"-- * Zero or more applicable boolean flags.\n"
|
|
"local CTs = {[0] =\n"
|
|
" {\"int\",\n"
|
|
" \"\", \"size\", false,\n"
|
|
" {0x08000000, \"bool\"},\n"
|
|
" {0x04000000, \"float\", \"subwhat\"},\n"
|
|
" {0x02000000, \"const\"},\n"
|
|
" {0x01000000, \"volatile\"},\n"
|
|
" {0x00800000, \"unsigned\"},\n"
|
|
" {0x00400000, \"long\"},\n"
|
|
" },\n"
|
|
" {\"struct\",\n"
|
|
" \"\", \"size\", true,\n"
|
|
" {0x02000000, \"const\"},\n"
|
|
" {0x01000000, \"volatile\"},\n"
|
|
" {0x00800000, \"union\", \"subwhat\"},\n"
|
|
" {0x00100000, \"vla\"},\n"
|
|
" },\n"
|
|
" {\"ptr\",\n"
|
|
" \"element_type\", \"size\", false,\n"
|
|
" {0x02000000, \"const\"},\n"
|
|
" {0x01000000, \"volatile\"},\n"
|
|
" {0x00800000, \"ref\", \"subwhat\"},\n"
|
|
" },\n"
|
|
" {\"array\",\n"
|
|
" \"element_type\", \"size\", false,\n"
|
|
" {0x08000000, \"vector\"},\n"
|
|
" {0x04000000, \"complex\"},\n"
|
|
" {0x02000000, \"const\"},\n"
|
|
" {0x01000000, \"volatile\"},\n"
|
|
" {0x00100000, \"vla\"},\n"
|
|
" },\n"
|
|
" {\"void\",\n"
|
|
" \"\", \"size\", false,\n"
|
|
" {0x02000000, \"const\"},\n"
|
|
" {0x01000000, \"volatile\"},\n"
|
|
" },\n"
|
|
" {\"enum\",\n"
|
|
" \"type\", \"size\", true,\n"
|
|
" },\n"
|
|
" {\"func\",\n"
|
|
" \"return_type\", \"nargs\", true,\n"
|
|
" {0x00800000, \"vararg\"},\n"
|
|
" {0x00400000, \"sse_reg_params\"},\n"
|
|
" },\n"
|
|
" {\"typedef\", -- Not seen\n"
|
|
" \"element_type\", \"\", false,\n"
|
|
" },\n"
|
|
" {\"attrib\", -- Only seen internally\n"
|
|
" \"type\", \"value\", true,\n"
|
|
" },\n"
|
|
" {\"field\",\n"
|
|
" \"type\", \"offset\", true,\n"
|
|
" },\n"
|
|
" {\"bitfield\",\n"
|
|
" \"\", \"offset\", true,\n"
|
|
" {0x08000000, \"bool\"},\n"
|
|
" {0x02000000, \"const\"},\n"
|
|
" {0x01000000, \"volatile\"},\n"
|
|
" {0x00800000, \"unsigned\"},\n"
|
|
" },\n"
|
|
" {\"constant\",\n"
|
|
" \"type\", \"value\", true,\n"
|
|
" {0x02000000, \"const\"},\n"
|
|
" },\n"
|
|
" {\"extern\", -- Not seen\n"
|
|
" \"CID\", \"\", true,\n"
|
|
" },\n"
|
|
" {\"kw\", -- Not seen\n"
|
|
" \"TOK\", \"size\",\n"
|
|
" },\n"
|
|
"}\n"
|
|
"\n"
|
|
"-- Set of CType::cid roles which are a CTypeID.\n"
|
|
"local type_keys = {\n"
|
|
" element_type = true,\n"
|
|
" return_type = true,\n"
|
|
" value_type = true,\n"
|
|
" type = true,\n"
|
|
"}\n"
|
|
"\n"
|
|
"-- Create a metatable for each CT.\n"
|
|
"local metatables = {\n"
|
|
"}\n"
|
|
"for _, CT in ipairs(CTs) do\n"
|
|
" local what = CT[1]\n"
|
|
" local mt = {__index = {}}\n"
|
|
" metatables[what] = mt\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Logic for merging an attribute CType onto the annotated CType.\n"
|
|
"local CTAs = {[0] =\n"
|
|
" function(a, refct) error(\"TODO: CTA_NONE\") end,\n"
|
|
" function(a, refct) error(\"TODO: CTA_QUAL\") end,\n"
|
|
" function(a, refct)\n"
|
|
" a = 2^a.value\n"
|
|
" refct.alignment = a\n"
|
|
" refct.attributes.align = a\n"
|
|
" end,\n"
|
|
" function(a, refct)\n"
|
|
" refct.transparent = true\n"
|
|
" refct.attributes.subtype = refct.typeid\n"
|
|
" end,\n"
|
|
" function(a, refct) refct.sym_name = a.name end,\n"
|
|
" function(a, refct) error(\"TODO: CTA_BAD\") end,\n"
|
|
"}\n"
|
|
"\n"
|
|
"-- C function calling conventions (CTCC_* constants in lj_refct.h)\n"
|
|
"local CTCCs = {[0] =\n"
|
|
" \"cdecl\",\n"
|
|
" \"thiscall\",\n"
|
|
" \"fastcall\",\n"
|
|
" \"stdcall\",\n"
|
|
"}\n"
|
|
"\n"
|
|
"local function refct_from_id(id) -- refct = refct_from_id(CTypeID)\n"
|
|
" local ctype = CTState.tab[id]\n"
|
|
" local CT_code = bit.rshift(ctype.info, 28)\n"
|
|
" local CT = CTs[CT_code]\n"
|
|
" local what = CT[1]\n"
|
|
" local refct = setmetatable({\n"
|
|
" what = what,\n"
|
|
" typeid = id,\n"
|
|
" name = gc_str(ctype.name),\n"
|
|
" }, metatables[what])\n"
|
|
"\n"
|
|
" -- Interpret (most of) the CType::info field\n"
|
|
" for i = 5, #CT do\n"
|
|
" if bit.band(ctype.info, CT[i][1]) ~= 0 then\n"
|
|
" if CT[i][3] == \"subwhat\" then\n"
|
|
" refct.what = CT[i][2]\n"
|
|
" else\n"
|
|
" refct[CT[i][2]] = true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" if CT_code <= 5 then\n"
|
|
" refct.alignment = bit.lshift(1, bit.band(bit.rshift(ctype.info, 16), 15))\n"
|
|
" elseif what == \"func\" then\n"
|
|
" refct.convention = CTCCs[bit.band(bit.rshift(ctype.info, 16), 3)]\n"
|
|
" end\n"
|
|
"\n"
|
|
" if CT[2] ~= \"\" then -- Interpret the CType::cid field\n"
|
|
" local k = CT[2]\n"
|
|
" local cid = bit.band(ctype.info, 0xffff)\n"
|
|
" if type_keys[k] then\n"
|
|
" if cid == 0 then\n"
|
|
" cid = nil\n"
|
|
" else\n"
|
|
" cid = refct_from_id(cid)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" refct[k] = cid\n"
|
|
" end\n"
|
|
"\n"
|
|
" if CT[3] ~= \"\" then -- Interpret the CType::size field\n"
|
|
" local k = CT[3]\n"
|
|
" refct[k] = ctype.size\n"
|
|
" if k == \"size\" and bit.bnot(refct[k]) == 0 then\n"
|
|
" refct[k] = \"none\"\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" if what == \"attrib\" then\n"
|
|
" -- Merge leading attributes onto the type being decorated.\n"
|
|
" local CTA = CTAs[bit.band(bit.rshift(ctype.info, 16), 0xff)]\n"
|
|
" if refct.type then\n"
|
|
" local ct = refct.type\n"
|
|
" ct.attributes = {}\n"
|
|
" CTA(refct, ct)\n"
|
|
" ct.typeid = refct.typeid\n"
|
|
" refct = ct\n"
|
|
" else\n"
|
|
" refct.CTA = CTA\n"
|
|
" end\n"
|
|
" elseif what == \"bitfield\" then\n"
|
|
" -- Decode extra bitfield fields, and make it look like a normal field.\n"
|
|
" refct.offset = refct.offset + bit.band(ctype.info, 127) / 8\n"
|
|
" refct.size = bit.band(bit.rshift(ctype.info, 8), 127) / 8\n"
|
|
" refct.type = {\n"
|
|
" what = \"int\",\n"
|
|
" bool = refct.bool,\n"
|
|
" const = refct.const,\n"
|
|
" volatile = refct.volatile,\n"
|
|
" unsigned = refct.unsigned,\n"
|
|
" size = bit.band(bit.rshift(ctype.info, 16), 127),\n"
|
|
" }\n"
|
|
" refct.bool, refct.const, refct.volatile, refct.unsigned = nil\n"
|
|
" end\n"
|
|
"\n"
|
|
" if CT[4] then -- Merge sibling attributes onto this type.\n"
|
|
" while ctype.sib ~= 0 do\n"
|
|
" local entry = CTState.tab[ctype.sib]\n"
|
|
" if CTs[bit.rshift(entry.info, 28)][1] ~= \"attrib\" then break end\n"
|
|
" if bit.band(entry.info, 0xffff) ~= 0 then break end\n"
|
|
" local sib = refct_from_id(ctype.sib)\n"
|
|
" sib:CTA(refct)\n"
|
|
" ctype = entry\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" return refct\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function sib_iter(s, refct)\n"
|
|
" repeat\n"
|
|
" local ctype = CTState.tab[refct.typeid]\n"
|
|
" if ctype.sib == 0 then return end\n"
|
|
" refct = refct_from_id(ctype.sib)\n"
|
|
" until refct.what ~= \"attrib\" -- Pure attribs are skipped.\n"
|
|
" return refct\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function siblings(refct)\n"
|
|
" -- Follow to the end of the attrib chain, if any.\n"
|
|
" while refct.attributes do\n"
|
|
" refct = refct_from_id(refct.attributes.subtype or CTState.tab[refct.typeid].sib)\n"
|
|
" end\n"
|
|
"\n"
|
|
" return sib_iter, nil, refct\n"
|
|
"end\n"
|
|
"\n"
|
|
"metatables.struct.__index.members = siblings\n"
|
|
"metatables.func.__index.arguments = siblings\n"
|
|
"metatables.enum.__index.values = siblings\n"
|
|
"\n"
|
|
"local function find_sibling(refct, name)\n"
|
|
" local num = tonumber(name)\n"
|
|
" if num then\n"
|
|
" for sib in siblings(refct) do\n"
|
|
" if num == 1 then\n"
|
|
" return sib\n"
|
|
" end\n"
|
|
" num = num - 1\n"
|
|
" end\n"
|
|
" else\n"
|
|
" for sib in siblings(refct) do\n"
|
|
" if sib.name == name then\n"
|
|
" return sib\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"metatables.struct.__index.member = find_sibling\n"
|
|
"metatables.func.__index.argument = find_sibling\n"
|
|
"metatables.enum.__index.value = find_sibling\n"
|
|
"\n"
|
|
"function reflect.typeof(x) -- refct = reflect.typeof(ct)\n"
|
|
" return refct_from_id(tonumber(ffi.typeof(x)))\n"
|
|
"end\n"
|
|
"\n"
|
|
"function reflect.getmetatable(x) -- mt = reflect.getmetatable(ct)\n"
|
|
" return miscmap[-tonumber(ffi.typeof(x))]\n"
|
|
"end\n"
|
|
"\n"
|
|
"return reflect\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.plugins.ffi.reflect\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.platform\n"
|
|
"package.preload[\"debugger.platform\"] = function(...)\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2011-2012 Sierra Wireless and others.\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Sierra Wireless - initial API and implementation\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Platform/OS specific features and path handling.\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"local url = require \"debugger.url\"\n"
|
|
"local util = require \"debugger.util\"\n"
|
|
"\n"
|
|
"local M = { }\n"
|
|
"\n"
|
|
"-- Execution plaform (could be win or unix)\n"
|
|
"-- Used to manage file path difference between the 2 platform\n"
|
|
"local platform = nil\n"
|
|
"\n"
|
|
"-- keep all computed URIs in cache (as they are quite long to compute)\n"
|
|
"local uri_cache = { }\n"
|
|
"\n"
|
|
"-- parse a normalized path and return a table of each segment\n"
|
|
"-- you could precise the path seperator.\n"
|
|
"local function split(path,sep)\n"
|
|
" local t = {}\n"
|
|
" for w in path:gmatch(\"[^\"..(sep or \"/\")..\"]+\")do\n"
|
|
" table.insert(t, w)\n"
|
|
" end\n"
|
|
" return t\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- return a table: key=searchpathname,value=true (add by guanyu)\n"
|
|
"local function convert_to_search_paths(searchpaths)\n"
|
|
" local t = {}\n"
|
|
" for w in searchpaths:gmatch(\"[^;]+\") do\n"
|
|
" t[w] = true\n"
|
|
" end\n"
|
|
" return t;\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- return a relative uri to M.base_dir for a given source, for android device (added by guanyu)\n"
|
|
"local function convert_to_android_file_uri(source)\n"
|
|
" if source:sub(1,6) == \"assets\" then -- replace assets\n"
|
|
" source = source:sub(8)\n"
|
|
" elseif M.search_paths then -- replace /mnt/sdcard/packagename/ with empty string\n"
|
|
" for k, v in pairs(M.search_paths) do\n"
|
|
" if #source > #k and M.search_paths[source:sub(1,#k)] then\n"
|
|
" source = source:sub(#k+1)\n"
|
|
" break\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return source\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- return a relative uri to M.base_dir for a given source, , for ios simulator/device? (added by guanyu\n"
|
|
"local function convert_to_ios_file_uri(source)\n"
|
|
" if M.search_paths then\n"
|
|
" for k, v in pairs(M.search_paths) do\n"
|
|
" if #source > #k and M.search_paths[source:sub(1,#k)] then\n"
|
|
" source = source:sub(#k+1)\n"
|
|
" break\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return source\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function convert_to_relative_file_uri(source)\n"
|
|
" if M.search_paths then\n"
|
|
" for k, v in pairs(M.search_paths) do\n"
|
|
" if #source > #k and M.search_paths[source:sub(1,#k)] then\n"
|
|
" source = source:sub(#k+1)\n"
|
|
" break\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return source\n"
|
|
"end\n"
|
|
"\n"
|
|
"--- Returns a RFC2396 compliant URI for given source, or false if the mapping failed\n"
|
|
"local function get_abs_file_uri (source)\n"
|
|
" local uri\n"
|
|
" if source:sub(1,1) == \"@\" then -- real source file\n"
|
|
" local sourcepath = source:sub(2)\n"
|
|
" if M.base_dir then\n"
|
|
" sourcepath = convert_to_relative_file_uri(sourcepath)\n"
|
|
" end\n"
|
|
" local normalizedpath = M.normalize(sourcepath)\n"
|
|
"-- if not M.is_path_absolute(normalizedpath) then\n"
|
|
"-- normalizedpath = M.normalize(M.base_dir .. \"/\" .. normalizedpath)--M.base_dir .. \"/src/\" .. normalizedpath\n"
|
|
"-- end\n"
|
|
" return M.to_file_uri(normalizedpath)\n"
|
|
" elseif source:sub(#source-3,-1):lower() == \".lua\" then -- loadstring relative path source file (added by guanyu)\n"
|
|
" -- android\n"
|
|
" if platform == \"android\" then\n"
|
|
" source = convert_to_android_file_uri(source)\n"
|
|
" -- ios ???\n"
|
|
" elseif platform == \"ios\" then\n"
|
|
" source = convert_to_ios_file_uri(source)\n"
|
|
" end\n"
|
|
" local normalizedpath = M.normalize(source)\n"
|
|
"-- if not M.is_path_absolute(normalizedpath) then\n"
|
|
"-- normalizedpath = M.normalize(M.base_dir .. \"/\" .. normalizedpath)--M.base_dir .. \"/src/\" .. normalizedpath\n"
|
|
"-- end\n"
|
|
" return M.to_file_uri(normalizedpath)\n"
|
|
" else -- dynamic code, stripped bytecode, tail return, ...\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"--FIXME: as result is cached, changes in package.path that modify the module name are missed\n"
|
|
"-- (mostly affect main module when Lua interpreter is launched with an absolute path)\n"
|
|
"local function get_module_uri (source)\n"
|
|
" if source:sub(1,1) == \"@\" then -- real source file\n"
|
|
" local uri\n"
|
|
" local sourcepath = source:sub(2)\n"
|
|
" local normalizedpath = M.normalize(sourcepath)\n"
|
|
" local luapathtable = split (package.path, \";\")\n"
|
|
" local is_source_absolute = M.is_path_absolute(sourcepath)\n"
|
|
" -- workarround : Add always the ?.lua entry to support\n"
|
|
" -- the case where file was loaded by : \"lua myfile.lua\"\n"
|
|
" table.insert(luapathtable,\"?.lua\")\n"
|
|
" for i,var in ipairs(luapathtable) do\n"
|
|
" -- avoid relative patterns matching absolute ones (e.g. ?.lua matches anything)\n"
|
|
" if M.is_path_absolute(var) == is_source_absolute then\n"
|
|
" local escaped = string.gsub(M.normalize(var),\"[%^%$%(%)%%%.%[%]%*%+%-%?]\",function(c) return \"%\"..c end)\n"
|
|
" local pattern = string.gsub(escaped,\"%%%?\",\"(.+)\")\n"
|
|
" local modulename = string.match(normalizedpath,pattern)\n"
|
|
" if modulename then\n"
|
|
" modulename = string.gsub(modulename,\"/\",\".\");\n"
|
|
" -- if we find more than 1 possible modulename return the shorter\n"
|
|
" if not uri or string.len(uri)>string.len(modulename) then\n"
|
|
" uri = modulename\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" if uri then return \"module:///\"..uri end\n"
|
|
" end\n"
|
|
" return false\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.get_uri (source)\n"
|
|
" -- search in cache\n"
|
|
" local uri = uri_cache[source]\n"
|
|
" if uri ~= nil then return uri end\n"
|
|
"\n"
|
|
" -- not found, create uri\n"
|
|
" if util.features.uri == \"module\" then\n"
|
|
" uri = get_module_uri(source)\n"
|
|
" if not uri then uri = get_abs_file_uri (source) end\n"
|
|
" else\n"
|
|
" uri = get_abs_file_uri (source)\n"
|
|
" end\n"
|
|
"\n"
|
|
" uri_cache[source] = uri\n"
|
|
" return uri\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- get path file from uri\n"
|
|
"function M.get_path (uri)\n"
|
|
" local parsed_path = assert(url.parse(uri))\n"
|
|
" if parsed_path.scheme == \"file\" then\n"
|
|
" return M.to_path(parsed_path)\n"
|
|
" else\n"
|
|
" -- search in cache\n"
|
|
" -- we should surely calculate it instead of find in cache\n"
|
|
" for k,v in pairs(uri_cache)do\n"
|
|
" if v == uri then\n"
|
|
" assert(k:sub(1,1) == \"@\")\n"
|
|
" return k:sub(2)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.normalize(path)\n"
|
|
" local parts = { }\n"
|
|
" for w in path:gmatch(\"[^/]+\") do\n"
|
|
" if w == \"..\" and #parts ~=0 then table.remove(parts)\n"
|
|
" elseif w ~= \".\" then table.insert(parts, w)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return (path:sub(1,1) == \"/\" and \"/\" or \"\") .. table.concat(parts, \"/\")\n"
|
|
"end\n"
|
|
"\n"
|
|
"function M.init(executionplatform,workingdirectory,searchpaths)\n"
|
|
" --------------------------\n"
|
|
" -- define current platform\n"
|
|
" --------------------------\n"
|
|
" -- check parameter\n"
|
|
" if executionplatform and executionplatform ~= \"unix\" and executionplatform ~=\"win\" and executionplatform ~=\"android\" and executionplatform ~=\"ios\"then\n"
|
|
" error(\"Unable to initialize platform module : execution platform should be 'unix' or 'win'.\")\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- use parameter as current platform\n"
|
|
" if executionplatform then\n"
|
|
" platform = executionplatform\n"
|
|
" else\n"
|
|
" --if not define try to guess it.\n"
|
|
" local function iswindows()\n"
|
|
" local p = io.popen(\"echo %os%\")\n"
|
|
" if p then\n"
|
|
" local result =p:read(\"*l\")\n"
|
|
" p:close()\n"
|
|
" return result == \"Windows_NT\"\n"
|
|
" end\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" local status, iswin = pcall(iswindows)\n"
|
|
" if status and iswin then\n"
|
|
" platform = \"win\"\n"
|
|
" else\n"
|
|
" platform = \"unix\"\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" --------------------------\n"
|
|
" -- platform dependent function\n"
|
|
" --------------------------\n"
|
|
" if platform == \"unix\" or platform == \"android\" or platform == \"ios\" then\n"
|
|
" -- The Path separator character\n"
|
|
" M.path_sep = \"/\"\n"
|
|
"\n"
|
|
" -- TODO the way to get the absolute path can be wrong if the program loads new source files by relative path after a cd.\n"
|
|
" -- currently, the directory is registered on start, this allows program to load any source file and then change working dir,\n"
|
|
" -- which is the most common use case.\n"
|
|
" M.base_dir = workingdirectory or os.getenv(\"PWD\")\n"
|
|
"\n"
|
|
" -- convert parsed URL table to file path for the current OS (see url.parse from luasocket)\n"
|
|
" M.to_file_uri = function (path)\n"
|
|
" if string.find(path,\"/\") ~= 1 then\n"
|
|
" path=\"/\"..path\n"
|
|
" end\n"
|
|
" return url.build{scheme=\"file\",authority=\"\", path=path}\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- return true is the path is absolute\n"
|
|
" -- the path must be normalized\n"
|
|
" M.is_path_absolute = function (path) return path:sub(1,1) == \"/\" end\n"
|
|
"\n"
|
|
" -- convert absolute normalized path file to uri\n"
|
|
" M.to_path = function (parsed_url) return url.unescape(parsed_url.path) end\n"
|
|
" else\n"
|
|
" -- Implementations for Windows, see UNIX versions for documentation.\n"
|
|
" M.path_sep = \"\\\\\"\n"
|
|
" M.is_path_absolute = function (path) return path:match(\"^%a:/\") end\n"
|
|
" M.to_file_uri = function (path) return url.build{scheme=\"file\",authority=\"\", path=\"/\"..path} end\n"
|
|
" M.to_path = function (parsed_url) return url.unescape(parsed_url.path):gsub(\"^/\", \"\") end\n"
|
|
"\n"
|
|
" local unixnormalize = M.normalize\n"
|
|
" M.normalize = function(path) return unixnormalize(path:gsub(\"\\\\\",\"/\"):lower()) end\n"
|
|
"\n"
|
|
" -- determine base dir\n"
|
|
" local function getworkingdirectory()\n"
|
|
" local p = io.popen(\"echo %cd%\")\n"
|
|
" if p then\n"
|
|
" local res = p:read(\"*l\")\n"
|
|
" p:close()\n"
|
|
" return M.normalize(res)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" M.base_dir = workingdirectory or getworkingdirectory()\n"
|
|
"\n"
|
|
" end\n"
|
|
" --fix bug: can not break when project path contains blank\n"
|
|
" --replace blank with %20 (add by guanyu)\n"
|
|
" M.base_dir = M.base_dir:gsub(\" \",\"%%20\")\n"
|
|
" -- the js/lua script searchpath if has more then one, split them with a semicolon \";\" (added by guanyu)\n"
|
|
" if searchpaths then M.search_paths = convert_to_search_paths(searchpaths) end\n"
|
|
" if not M.base_dir then error(\"Unable to determine the working directory.\") end\n"
|
|
"end\n"
|
|
"\n"
|
|
"return M\n"
|
|
"\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.platform\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.util\n"
|
|
"package.preload[\"debugger.util\"] = function(...)\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2011-2012 Sierra Wireless and others.\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Sierra Wireless - initial API and implementation\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Utility functions.\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"local M = { }\n"
|
|
"\n"
|
|
"-- log system\n"
|
|
"local LEVELS = { ERROR = 0, WARNING = 1, INFO = 2, DETAIL = 3, DEBUG = 4 }\n"
|
|
"local LOG_LEVEL = LEVELS.WARNING\n"
|
|
"\n"
|
|
"-- Debugger features handling. Any feature can be get like any regular table, setting features result in\n"
|
|
"-- error for unknown or read-only features.\n"
|
|
"M.features = setmetatable({ }, {\n"
|
|
" -- functions that format/validate data. If function is not provided, the feature cannot be modified.\n"
|
|
" validators = {\n"
|
|
" multiple_sessions = tonumber,\n"
|
|
" encoding = tostring,\n"
|
|
" max_children = tonumber,\n"
|
|
" max_data = tonumber,\n"
|
|
" max_depth = tonumber,\n"
|
|
" show_hidden = tonumber,\n"
|
|
" uri = tostring,\n"
|
|
" log_level = function(level_name)\n"
|
|
" -- set numerical index in internal var\n"
|
|
" LOG_LEVEL = assert(LEVELS[level_name], \"No such level\")\n"
|
|
" return level_name -- the displayed level is still the name\n"
|
|
" end,\n"
|
|
" },\n"
|
|
" __index = {\n"
|
|
" multiple_sessions = 0,\n"
|
|
" encoding =\"UTF-8\",\n"
|
|
" max_children = 32,\n"
|
|
" max_data = 0xFFFF,\n"
|
|
" max_depth = 1,\n"
|
|
" show_hidden = 1,\n"
|
|
" uri = \"file\",\n"
|
|
" log_level = \"WARNING\",\n"
|
|
" -- read only features\n"
|
|
" language_supports_threads = 0,\n"
|
|
" language_name = \"Lua\",\n"
|
|
" language_version = _VERSION,\n"
|
|
" protocol_version = 1,\n"
|
|
" supports_async = 1,\n"
|
|
" data_encoding = \"base64\",\n"
|
|
" breakpoint_languages = \"Lua\",\n"
|
|
" breakpoint_types = \"line conditional\",\n"
|
|
" },\n"
|
|
" __newindex = function(self, k, v)\n"
|
|
" local mt = getmetatable(self)\n"
|
|
" local values, validator = mt.__index, mt.validators[k]\n"
|
|
" if values[k] == nil then error(\"No such feature \" .. tostring(k)) end\n"
|
|
" if not validator then error(\"The feature \" .. tostring(k) .. \" is read-only\") end\n"
|
|
" v = assert(validator(v))\n"
|
|
" values[k] = v\n"
|
|
" end,\n"
|
|
"})\n"
|
|
"\n"
|
|
"-- Wraps debug function and an attached thread\n"
|
|
"-- also handle stack & coroutine management differencies between Lua versions\n"
|
|
"local getinfo, getlocal, setlocal = debug.getinfo, debug.getlocal, debug.setlocal\n"
|
|
"\n"
|
|
"-- Foreign thread is used to debug paused thread\n"
|
|
"local ForeignThreadMT = {\n"
|
|
" getinfo = function(self, level, what) return getinfo(self[1], level, what) end,\n"
|
|
" getlocal = function(self, level, idx) return getlocal(self[1], level, idx) end,\n"
|
|
" setlocal = function(self, level, idx, val) return setlocal(self[1], level, idx, val) end,\n"
|
|
"}\n"
|
|
"ForeignThreadMT.__index = ForeignThreadMT\n"
|
|
"function M.ForeignThread(coro) return setmetatable({ coro }, ForeignThreadMT) end\n"
|
|
"\n"
|
|
"-- Current thread is used to debug the thread that caused the hook\n"
|
|
"-- intended to be used *ONLY* in debug loop (executed in a new thread)\n"
|
|
"local CurrentThreadMT = {\n"
|
|
" getinfo = function(self, level, what) return getinfo(self[1], level + 2, what) end,\n"
|
|
" getlocal = function(self, level, idx) return getlocal(self[1], level + 2, idx) end,\n"
|
|
" setlocal = function(self, level, idx, val) return setlocal(self[1], level + 2, idx, val) end,\n"
|
|
"}\n"
|
|
"CurrentThreadMT.__index = CurrentThreadMT\n"
|
|
"function M.CurrentThread(coro) return setmetatable({ coro }, CurrentThreadMT) end\n"
|
|
"\n"
|
|
"\n"
|
|
"-- Some version dependant functions\n"
|
|
"if _VERSION == \"Lua 5.1\" then\n"
|
|
" local loadstring, getfenv, setfenv, debug_getinfo, MainThread =\n"
|
|
" loadstring, getfenv, setfenv, debug.getinfo, nil\n"
|
|
"\n"
|
|
" -- in 5.1 \"t\" flag does not exist and trigger an error so remove it from what\n"
|
|
" CurrentThreadMT.getinfo = function(self, level, what) return getinfo(self[1], level + 2, what:gsub(\"t\", \"\", 1)) end\n"
|
|
" ForeignThreadMT.getinfo = function(self, level, what) return getinfo(self[1], level, what:gsub(\"t\", \"\", 1)) end\n"
|
|
"\n"
|
|
" -- when we're forced to start debug loop on top of program stack (when on main coroutine)\n"
|
|
" -- this requires some hackery to get right stack level\n"
|
|
"\n"
|
|
" -- Fallback method to inspect running thread (only for main thread in 5.1 or for conditional breakpoints)\n"
|
|
" --- Gets a script stack level with additional debugger logic added\n"
|
|
" -- @param l (number) stack level to get for debugged script (0 based)\n"
|
|
" -- @return real Lua stack level suitable to be passed through deubg functions\n"
|
|
" local function get_script_level(l)\n"
|
|
" local hook = debug.gethook()\n"
|
|
" for i=2, math.huge do\n"
|
|
" if assert(debug.getinfo(i, \"f\")).func == hook then\n"
|
|
" return i + l -- the script to level is just below, but because of the extra call to this function, the level is ok for callee\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" if rawget(_G, \"jit\") then\n"
|
|
" MainThread = {\n"
|
|
" [1] = \"main\", -- as the raw thread object is used as table keys, provide a replacement.\n"
|
|
" -- LuaJIT completely eliminates tail calls from stack, so get_script_level retunrs wrong result in this case\n"
|
|
" getinfo = function(self, level, what) return getinfo(get_script_level(level) - 1, what:gsub(\"t\", \"\", 1)) end,\n"
|
|
" getlocal = function(self, level, idx) return getlocal(get_script_level(level) - 1, idx) end,\n"
|
|
" setlocal = function(self, level, idx, val) return setlocal(get_script_level(level) - 1, idx, val) end,\n"
|
|
" }\n"
|
|
" else\n"
|
|
" MainThread = {\n"
|
|
" [1] = \"main\",\n"
|
|
" getinfo = function(self, level, what) return getinfo(get_script_level(level) , what:gsub(\"t\", \"\", 1)) end,\n"
|
|
" getlocal = function(self, level, idx) return getlocal(get_script_level(level), idx) end,\n"
|
|
" setlocal = function(self, level, idx, val) return setlocal(get_script_level(level), idx, val) end,\n"
|
|
" }\n"
|
|
" end\n"
|
|
"\n"
|
|
"\n"
|
|
"\n"
|
|
" -- If the VM is vanilla Lua 5.1 or LuaJIT 2 without 5.2 compatibility, there is no way to get a reference to\n"
|
|
" -- the main coroutine, so fall back to direct mode: the debugger loop is started on the top of main thread\n"
|
|
" -- and the actual level is recomputed each time\n"
|
|
" local oldCurrentThread = M.CurrentThread\n"
|
|
" M.CurrentThread = function(coro) return coro and oldCurrentThread(coro) or MainThread end\n"
|
|
"\n"
|
|
" -- load a piece of code alog with its environment\n"
|
|
" function M.loadin(code, env)\n"
|
|
" local f,err = loadstring(code)\n"
|
|
" if not f then\n"
|
|
" return nil, err\n"
|
|
" else\n"
|
|
" return f and setfenv(f, env)\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- table that maps [gs]et environment to index\n"
|
|
" M.eval_env = setmetatable({ }, {\n"
|
|
" __index = function(self, func) return getfenv(func) end,\n"
|
|
" __newindex = function(self, func, env) return setfenv(func, env) end,\n"
|
|
" })\n"
|
|
"elseif _VERSION == \"Lua 5.2\" then\n"
|
|
" local load, debug_getinfo = load, debug.getinfo\n"
|
|
" function M.getinfo(coro, level, what)\n"
|
|
" if coro then return debug_getinfo(coro, level, what)\n"
|
|
" else return debug_getinfo(level + 1, what) end\n"
|
|
" end\n"
|
|
"\n"
|
|
" function M.loadin(code, env) return load(code, nil, nil, env) end\n"
|
|
"\n"
|
|
" -- no eval_env for 5.2 as functions does not have environments anymore\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- ----------------------------------------------------------------------------\n"
|
|
"-- Bare minimal log system.\n"
|
|
"-- ----------------------------------------------------------------------------\n"
|
|
"function M.log(level, msg, ...)\n"
|
|
" if (LEVELS[level] or -1) > LOG_LEVEL then return end\n"
|
|
" if select(\"#\", ...) > 0 then msg = msg:format(...) end\n"
|
|
" io.base.stderr:write(string.format(\"DEBUGGER\\t%s\\t%s\\n\", level, msg))\n"
|
|
"end\n"
|
|
"\n"
|
|
"return M\n"
|
|
"\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.util\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Module debugger.url\n"
|
|
"package.preload[\"debugger.url\"] = function(...)\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- URI parsing, composition and relative URL resolution\n"
|
|
"-- LuaSocket toolkit.\n"
|
|
"-- Author: Diego Nehab\n"
|
|
"-- RCS ID: $Id: url.lua,v 1.38 2006/04/03 04:45:42 diego Exp $\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- Declare module\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"local string = require(\"string\")\n"
|
|
"local base = _G\n"
|
|
"local table = require(\"table\")\n"
|
|
"\n"
|
|
"local _ENV = { }\n"
|
|
"if setfenv then setfenv(1, _ENV) end\n"
|
|
"\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- Module version\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"_VERSION = \"URL 1.0.1\"\n"
|
|
"\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- Encodes a string into its escaped hexadecimal representation\n"
|
|
"-- Input\n"
|
|
"-- s: binary string to be encoded\n"
|
|
"-- Returns\n"
|
|
"-- escaped representation of string binary\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"function escape(s)\n"
|
|
" return string.gsub(s, \"([^A-Za-z0-9_])\", function(c)\n"
|
|
" return string.format(\"%%%02x\", string.byte(c))\n"
|
|
" end)\n"
|
|
"end\n"
|
|
"\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- Protects a path segment, to prevent it from interfering with the\n"
|
|
"-- url parsing.\n"
|
|
"-- Input\n"
|
|
"-- s: binary string to be encoded\n"
|
|
"-- Returns\n"
|
|
"-- escaped representation of string binary\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"local function make_set(t)\n"
|
|
" local s = {}\n"
|
|
" for i,v in base.ipairs(t) do\n"
|
|
" s[t[i]] = 1\n"
|
|
" end\n"
|
|
" return s\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- these are allowed withing a path segment, along with alphanum\n"
|
|
"-- other characters must be escaped\n"
|
|
"local segment_set = make_set {\n"
|
|
" \"-\", \"_\", \".\", \"!\", \"~\", \"*\", \"'\", \"(\",\n"
|
|
" \")\", \":\", \"@\", \"&\", \"=\", \"+\", \"$\", \",\",\n"
|
|
"}\n"
|
|
"\n"
|
|
"local function protect_segment(s)\n"
|
|
" return string.gsub(s, \"([^A-Za-z0-9_])\", function (c)\n"
|
|
" if segment_set[c] then return c\n"
|
|
" else return string.format(\"%%%02x\", string.byte(c)) end\n"
|
|
" end)\n"
|
|
"end\n"
|
|
"\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- Encodes a string into its escaped hexadecimal representation\n"
|
|
"-- Input\n"
|
|
"-- s: binary string to be encoded\n"
|
|
"-- Returns\n"
|
|
"-- escaped representation of string binary\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"function unescape(s)\n"
|
|
" return string.gsub(s, \"%%(%x%x)\", function(hex)\n"
|
|
" return string.char(base.tonumber(hex, 16))\n"
|
|
" end)\n"
|
|
"end\n"
|
|
"\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- Builds a path from a base path and a relative path\n"
|
|
"-- Input\n"
|
|
"-- base_path\n"
|
|
"-- relative_path\n"
|
|
"-- Returns\n"
|
|
"-- corresponding absolute path\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"local function absolute_path(base_path, relative_path)\n"
|
|
" if string.sub(relative_path, 1, 1) == \"/\" then return relative_path end\n"
|
|
" local path = string.gsub(base_path, \"[^/]*$\", \"\")\n"
|
|
" path = path .. relative_path\n"
|
|
" path = string.gsub(path, \"([^/]*%./)\", function (s)\n"
|
|
" if s ~= \"./\" then return s else return \"\" end\n"
|
|
" end)\n"
|
|
" path = string.gsub(path, \"/%.$\", \"/\")\n"
|
|
" local reduced\n"
|
|
" while reduced ~= path do\n"
|
|
" reduced = path\n"
|
|
" path = string.gsub(reduced, \"([^/]*/%.%./)\", function (s)\n"
|
|
" if s ~= \"../../\" then return \"\" else return s end\n"
|
|
" end)\n"
|
|
" end\n"
|
|
" path = string.gsub(reduced, \"([^/]*/%.%.)$\", function (s)\n"
|
|
" if s ~= \"../..\" then return \"\" else return s end\n"
|
|
" end)\n"
|
|
" return path\n"
|
|
"end\n"
|
|
"\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- Parses a url and returns a table with all its parts according to RFC 2396\n"
|
|
"-- The following grammar describes the names given to the URL parts\n"
|
|
"-- <url> ::= <scheme>://<authority>/<path>;<params>?<query>#<fragment>\n"
|
|
"-- <authority> ::= <userinfo>@<host>:<port>\n"
|
|
"-- <userinfo> ::= <user>[:<password>]\n"
|
|
"-- <path> :: = {<segment>/}<segment>\n"
|
|
"-- Input\n"
|
|
"-- url: uniform resource locator of request\n"
|
|
"-- default: table with default values for each field\n"
|
|
"-- Returns\n"
|
|
"-- table with the following fields, where RFC naming conventions have\n"
|
|
"-- been preserved:\n"
|
|
"-- scheme, authority, userinfo, user, password, host, port,\n"
|
|
"-- path, params, query, fragment\n"
|
|
"-- Obs:\n"
|
|
"-- the leading '/' in {/<path>} is considered part of <path>\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"function parse(url, default)\n"
|
|
" -- initialize default parameters\n"
|
|
" local parsed = {}\n"
|
|
" for i,v in base.pairs(default or parsed) do parsed[i] = v end\n"
|
|
" -- empty url is parsed to nil\n"
|
|
" if not url or url == \"\" then return nil, \"invalid url\" end\n"
|
|
" -- remove whitespace\n"
|
|
" -- url = string.gsub(url, \"%s\", \"\")\n"
|
|
" -- get fragment\n"
|
|
" url = string.gsub(url, \"#(.*)$\", function(f)\n"
|
|
" parsed.fragment = f\n"
|
|
" return \"\"\n"
|
|
" end)\n"
|
|
" -- get scheme\n"
|
|
" url = string.gsub(url, \"^([%w][%w%+%-%.]*)%:\",\n"
|
|
" function(s) parsed.scheme = s; return \"\" end)\n"
|
|
" -- get authority\n"
|
|
" url = string.gsub(url, \"^//([^/]*)\", function(n)\n"
|
|
" parsed.authority = n\n"
|
|
" return \"\"\n"
|
|
" end)\n"
|
|
" -- get query stringing\n"
|
|
" url = string.gsub(url, \"%?(.*)\", function(q)\n"
|
|
" parsed.query = q\n"
|
|
" return \"\"\n"
|
|
" end)\n"
|
|
" -- get params\n"
|
|
" url = string.gsub(url, \"%;(.*)\", function(p)\n"
|
|
" parsed.params = p\n"
|
|
" return \"\"\n"
|
|
" end)\n"
|
|
" -- path is whatever was left\n"
|
|
" if url ~= \"\" then parsed.path = url end\n"
|
|
" local authority = parsed.authority\n"
|
|
" if not authority then return parsed end\n"
|
|
" authority = string.gsub(authority,\"^([^@]*)@\",\n"
|
|
" function(u) parsed.userinfo = u; return \"\" end)\n"
|
|
" authority = string.gsub(authority, \":([^:]*)$\",\n"
|
|
" function(p) parsed.port = p; return \"\" end)\n"
|
|
" if authority ~= \"\" then parsed.host = authority end\n"
|
|
" local userinfo = parsed.userinfo\n"
|
|
" if not userinfo then return parsed end\n"
|
|
" userinfo = string.gsub(userinfo, \":([^:]*)$\",\n"
|
|
" function(p) parsed.password = p; return \"\" end)\n"
|
|
" parsed.user = userinfo\n"
|
|
" return parsed\n"
|
|
"end\n"
|
|
"\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- Rebuilds a parsed URL from its components.\n"
|
|
"-- Components are protected if any reserved or unallowed characters are found\n"
|
|
"-- Input\n"
|
|
"-- parsed: parsed URL, as returned by parse\n"
|
|
"-- Returns\n"
|
|
"-- a stringing with the corresponding URL\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"function build(parsed)\n"
|
|
" local ppath = parse_path(parsed.path or \"\")\n"
|
|
" local url = build_path(ppath)\n"
|
|
" if parsed.params then url = url .. \";\" .. parsed.params end\n"
|
|
" if parsed.query then url = url .. \"?\" .. parsed.query end\n"
|
|
" local authority = parsed.authority\n"
|
|
" if parsed.host then\n"
|
|
" authority = parsed.host\n"
|
|
" if parsed.port then authority = authority .. \":\" .. parsed.port end\n"
|
|
" local userinfo = parsed.userinfo\n"
|
|
" if parsed.user then\n"
|
|
" userinfo = parsed.user\n"
|
|
" if parsed.password then\n"
|
|
" userinfo = userinfo .. \":\" .. parsed.password\n"
|
|
" end\n"
|
|
" end\n"
|
|
" if userinfo then authority = userinfo .. \"@\" .. authority end\n"
|
|
" end\n"
|
|
" if authority then url = \"//\" .. authority .. url end\n"
|
|
" if parsed.scheme then url = parsed.scheme .. \":\" .. url end\n"
|
|
" if parsed.fragment then url = url .. \"#\" .. parsed.fragment end\n"
|
|
" -- url = string.gsub(url, \"%s\", \"\")\n"
|
|
" return url\n"
|
|
"end\n"
|
|
"\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- Builds a absolute URL from a base and a relative URL according to RFC 2396\n"
|
|
"-- Input\n"
|
|
"-- base_url\n"
|
|
"-- relative_url\n"
|
|
"-- Returns\n"
|
|
"-- corresponding absolute url\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"function absolute(base_url, relative_url)\n"
|
|
" if base.type(base_url) == \"table\" then\n"
|
|
" base_parsed = base_url\n"
|
|
" base_url = build(base_parsed)\n"
|
|
" else\n"
|
|
" base_parsed = parse(base_url)\n"
|
|
" end\n"
|
|
" local relative_parsed = parse(relative_url)\n"
|
|
" if not base_parsed then return relative_url\n"
|
|
" elseif not relative_parsed then return base_url\n"
|
|
" elseif relative_parsed.scheme then return relative_url\n"
|
|
" else\n"
|
|
" relative_parsed.scheme = base_parsed.scheme\n"
|
|
" if not relative_parsed.authority then\n"
|
|
" relative_parsed.authority = base_parsed.authority\n"
|
|
" if not relative_parsed.path then\n"
|
|
" relative_parsed.path = base_parsed.path\n"
|
|
" if not relative_parsed.params then\n"
|
|
" relative_parsed.params = base_parsed.params\n"
|
|
" if not relative_parsed.query then\n"
|
|
" relative_parsed.query = base_parsed.query\n"
|
|
" end\n"
|
|
" end\n"
|
|
" else\n"
|
|
" relative_parsed.path = absolute_path(base_parsed.path or \"\",\n"
|
|
" relative_parsed.path)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return build(relative_parsed)\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- Breaks a path into its segments, unescaping the segments\n"
|
|
"-- Input\n"
|
|
"-- path\n"
|
|
"-- Returns\n"
|
|
"-- segment: a table with one entry per segment\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"function parse_path(path)\n"
|
|
" local parsed = {}\n"
|
|
" path = path or \"\"\n"
|
|
" --path = string.gsub(path, \"%s\", \"\")\n"
|
|
" string.gsub(path, \"([^/]+)\", function (s) table.insert(parsed, s) end)\n"
|
|
" for i = 1, #parsed do\n"
|
|
" parsed[i] = unescape(parsed[i])\n"
|
|
" end\n"
|
|
" if string.sub(path, 1, 1) == \"/\" then parsed.is_absolute = 1 end\n"
|
|
" if string.sub(path, -1, -1) == \"/\" then parsed.is_directory = 1 end\n"
|
|
" return parsed\n"
|
|
"end\n"
|
|
"\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"-- Builds a path component from its segments, escaping protected characters.\n"
|
|
"-- Input\n"
|
|
"-- parsed: path segments\n"
|
|
"-- unsafe: if true, segments are not protected before path is built\n"
|
|
"-- Returns\n"
|
|
"-- path: corresponding path stringing\n"
|
|
"-----------------------------------------------------------------------------\n"
|
|
"function build_path(parsed, unsafe)\n"
|
|
" local path = \"\"\n"
|
|
" local n = #parsed\n"
|
|
" if unsafe then\n"
|
|
" for i = 1, n-1 do\n"
|
|
" path = path .. parsed[i]\n"
|
|
" path = path .. \"/\"\n"
|
|
" end\n"
|
|
" if n > 0 then\n"
|
|
" path = path .. parsed[n]\n"
|
|
" if parsed.is_directory then path = path .. \"/\" end\n"
|
|
" end\n"
|
|
" else\n"
|
|
" for i = 1, n-1 do\n"
|
|
" path = path .. protect_segment(parsed[i])\n"
|
|
" path = path .. \"/\"\n"
|
|
" end\n"
|
|
" if n > 0 then\n"
|
|
" path = path .. protect_segment(parsed[n])\n"
|
|
" if parsed.is_directory then path = path .. \"/\" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" if parsed.is_absolute then path = \"/\" .. path end\n"
|
|
" return path\n"
|
|
"end\n"
|
|
"\n"
|
|
"return _ENV\n"
|
|
"\n"
|
|
"end\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- End of moduledebugger.url\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"-- Main content\n"
|
|
"--------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Copyright (c) 2011-2012 Sierra Wireless and others.\n"
|
|
"-- All rights reserved. This program and the accompanying materials\n"
|
|
"-- are made available under the terms of the Eclipse Public License v1.0\n"
|
|
"-- which accompanies this distribution, and is available at\n"
|
|
"-- http://www.eclipse.org/legal/epl-v10.html\n"
|
|
"--\n"
|
|
"-- Contributors:\n"
|
|
"-- Sierra Wireless - initial API and implementation\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"local DBGP_CLIENT_VERSION = \"1.1.0\"\n"
|
|
"\n"
|
|
"local debug = require \"debug\"\n"
|
|
"\n"
|
|
"-- To avoid cyclic dependency, internal state of the debugger that must be accessed\n"
|
|
"-- elsewhere (in commands most likely) will be stored in a fake module \"debugger.core\"\n"
|
|
"local core = { }\n"
|
|
"package.loaded[\"debugger.core\"] = core\n"
|
|
"\n"
|
|
"local util = require \"debugger.util\"\n"
|
|
"local platform = require \"debugger.platform\"\n"
|
|
"local dbgp = require \"debugger.dbgp\"\n"
|
|
"local commands = require \"debugger.commands\"\n"
|
|
"local context = require \"debugger.context\"\n"
|
|
"local url = require \"debugger.url\"\n"
|
|
"\n"
|
|
"local log = util.log\n"
|
|
"\n"
|
|
"\n"
|
|
"-- TODO complete the stdlib access\n"
|
|
"local corunning, cocreate, cowrap, coyield, coresume, costatus = coroutine.running, coroutine.create, coroutine.wrap, coroutine.yield, coroutine.resume, coroutine.status\n"
|
|
"\n"
|
|
"\n"
|
|
"-- register the URI of the debugger, to not jump into with redefined function or coroutine bootstrap stuff\n"
|
|
"local debugger_uri = nil -- set in init function\n"
|
|
"local transportmodule_uri = nil -- set in init function\n"
|
|
"\n"
|
|
"-- will contain the session object, and possibly a list of all sessions if a multi-threaded model is adopted\n"
|
|
"-- this is only used for async commands.\n"
|
|
"local active_session = nil\n"
|
|
"\n"
|
|
"-- tracks all active coroutines and associate an id to them, the table from_id is the id=>coro mapping, the table from_coro is the reverse\n"
|
|
"core.active_coroutines = { n = 0, from_id = setmetatable({ }, { __mode = \"v\" }), from_coro = setmetatable({ }, { __mode = \"k\" }) }\n"
|
|
"\n"
|
|
"core.prev_break_line = nil -- set in line_hook function\n"
|
|
"\n"
|
|
"-- \"BEGIN VERSION DEPENDENT CODE\"\n"
|
|
"local setbpenv -- set environment of a breakpoint (compiled function)\n"
|
|
"if _VERSION == \"Lua 5.1\" then\n"
|
|
" local setfenv = setfenv\n"
|
|
" setbpenv = setfenv\n"
|
|
"elseif _VERSION == \"Lua 5.2\" then\n"
|
|
" local setupvalue = debug.setupvalue\n"
|
|
" -- _ENV is the first upvalue\n"
|
|
" setbpenv = function(f, t) return setupvalue(f, 1, t) end\n"
|
|
"else error(_VERSION .. \"is not supported.\") end\n"
|
|
"-- \"END VERSION DEPENDENT CODE\"\n"
|
|
"\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Output redirection handling\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Override standard output functions & constants to redirect data written to these files to IDE too.\n"
|
|
"-- This works only for output done in Lua, output written by C extensions is still go to system output file.\n"
|
|
"\n"
|
|
"-- references to native values\n"
|
|
"io.base = { output = io.output, stdin = io.stdin, stdout = io.stdout, stderr = io.stderr }\n"
|
|
"\n"
|
|
"-- comment out by guanyu_yan, not redirect io.\n"
|
|
"-- function print(...)\n"
|
|
"-- local buf = {...}\n"
|
|
"-- for i=1, select(\"#\", ...) do\n"
|
|
"-- buf[i] = tostring(buf[i])\n"
|
|
"-- end\n"
|
|
"-- io.stdout:write(table.concat(buf, \"\\t\") .. \"\\n\")\n"
|
|
"-- end\n"
|
|
"\n"
|
|
"-- Actually change standard output file but still return the \"fake\" stdout\n"
|
|
"function io.output(output)\n"
|
|
" io.base.output(output)\n"
|
|
" return io.stdout\n"
|
|
"end\n"
|
|
"\n"
|
|
"local dummy = function() end\n"
|
|
"\n"
|
|
"-- metatable for redirecting output (not printed at all in actual output)\n"
|
|
"core.redirect_output = {\n"
|
|
" write = function(self, ...)\n"
|
|
" local buf = {...}\n"
|
|
" for i=1, select(\"#\", ...) do buf[i] = tostring(buf[i]) end\n"
|
|
" buf = table.concat(buf):gsub(\"\\n\", \"\\r\\n\")\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"stream\", attr = { type=self.mode }, util.b64(buf) } )\n"
|
|
" end,\n"
|
|
" flush = dummy,\n"
|
|
" close = dummy,\n"
|
|
" setvbuf = dummy,\n"
|
|
" seek = dummy\n"
|
|
"}\n"
|
|
"core.redirect_output.__index = core.redirect_output\n"
|
|
"\n"
|
|
"-- metatable for cloning output (outputs to actual system and send to IDE)\n"
|
|
"core.copy_output = {\n"
|
|
" write = function(self, ...)\n"
|
|
" core.redirect_output.write(self, ...)\n"
|
|
" io.base[self.mode]:write(...)\n"
|
|
" end,\n"
|
|
" flush = function(self, ...) return self.out:flush(...) end,\n"
|
|
" close = function(self, ...) return self.out:close(...) end,\n"
|
|
" setvbuf = function(self, ...) return self.out:setvbuf(...) end,\n"
|
|
" seek = function(self, ...) return self.out:seek(...) end,\n"
|
|
"}\n"
|
|
"core.copy_output.__index = core.copy_output\n"
|
|
"\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Breakpoint registry\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Registry of current stack levels of all running threads\n"
|
|
"local stack_levels = setmetatable( { }, { __mode = \"k\" } )\n"
|
|
"\n"
|
|
"-- File/line mapping for breakpoints (BP). For a given file/line, a list of BP is associated (DBGp specification section 7.6.1\n"
|
|
"-- require that multiple BP at same place must be handled)\n"
|
|
"-- A BP is a table with all additional properties (type, condition, ...) the id is the string representation of the table.\n"
|
|
"core.breakpoints = {\n"
|
|
" -- functions to call to match hit conditions\n"
|
|
" hit_conditions = {\n"
|
|
" [\">=\"] = function(value, target) return value >= target end,\n"
|
|
" [\"==\"] = function(value, target) return value == target end,\n"
|
|
" [\"%\"] = function(value, target) return (value % target) == 0 end,\n"
|
|
" }\n"
|
|
"}\n"
|
|
"\n"
|
|
"-- tracks events such as step_into or step_over\n"
|
|
"core.events = { }\n"
|
|
"\n"
|
|
"do\n"
|
|
" local file_mapping = { }\n"
|
|
" local id_mapping = { }\n"
|
|
" local waiting_sessions = { } -- sessions that wait for an event (over, into, out)\n"
|
|
" local step_into = nil -- session that registered a step_into event, if any\n"
|
|
" local sequence = 0 -- used to generate breakpoint IDs\n"
|
|
" local line_mapping = {} -- use to record the line of bp\n"
|
|
"\n"
|
|
" local function insert_line_mapping(line)\n"
|
|
" if line_mapping[line] == nil then\n"
|
|
" line_mapping[line] = 1\n"
|
|
" else\n"
|
|
" line_mapping[line] = line_mapping[line] + 1\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" local function remove_line_mapping(line)\n"
|
|
" if not line_mapping[line] and line_mapping[line] > 1 then\n"
|
|
" line_mapping[line] = line_mapping[line] - 1\n"
|
|
" else\n"
|
|
" line_mapping[line] = nil\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" function core.breakpoints.guess(line)\n"
|
|
" return line_mapping[line]\n"
|
|
" end\n"
|
|
"\n"
|
|
" function core.breakpoints.update(oldline,newbp)\n"
|
|
" local file = newbp.filename\n"
|
|
" local line = oldline\n"
|
|
" local filereg = file_mapping[file]\n"
|
|
" if not filereg then return nil end\n"
|
|
" local linereg = filereg[line]\n"
|
|
" if not linereg then return nil end\n"
|
|
" remove_line_mapping(oldline)\n"
|
|
" for i=1, #linereg do\n"
|
|
" if linereg[i].id == newbp.id then\n"
|
|
" table.remove(linereg, i)\n"
|
|
" break\n"
|
|
" end\n"
|
|
" end\n"
|
|
" linereg = {}\n"
|
|
" insert_line_mapping(newbp.lineno)\n"
|
|
" table.insert(linereg, newbp)\n"
|
|
" filereg[newbp.lineno] = linereg\n"
|
|
" end\n"
|
|
"\n"
|
|
" --- Inserts a new breakpoint into registry\n"
|
|
" -- @param bp (table) breakpoint data\n"
|
|
" -- @param uri (string, optional) Absolute file URI, for line breakpoints\n"
|
|
" -- @param line (number, optional) Line where breakpoint stops, for line breakpoints\n"
|
|
" -- @return breakpoint identifier\n"
|
|
" function core.breakpoints.insert(bp)\n"
|
|
" local bpid = sequence\n"
|
|
" sequence = bpid + 1\n"
|
|
" bp.id = bpid\n"
|
|
" -- re-encode the URI to avoid any mismatch (with authority for example)\n"
|
|
" local uri = url.parse(bp.filename)\n"
|
|
" bp.filename = url.build{ scheme=uri.scheme, authority=\"\", path=platform.normalize(uri.path)}\n"
|
|
"\n"
|
|
" local filereg = file_mapping[bp.filename]\n"
|
|
" if not filereg then\n"
|
|
" filereg = { }\n"
|
|
" file_mapping[bp.filename] = filereg\n"
|
|
" end\n"
|
|
"\n"
|
|
" local linereg = filereg[bp.lineno]\n"
|
|
" if not linereg then\n"
|
|
" linereg = {}\n"
|
|
" filereg[bp.lineno] = linereg\n"
|
|
" end\n"
|
|
" insert_line_mapping(bp.lineno)\n"
|
|
"\n"
|
|
" table.insert(linereg, bp)\n"
|
|
"\n"
|
|
" id_mapping[bpid] = bp\n"
|
|
" return bpid\n"
|
|
" end\n"
|
|
"\n"
|
|
" --- If breakpoint(s) exists for given file/line, uptates breakpoint counters\n"
|
|
" -- and returns whether a breakpoint has matched (boolean)\n"
|
|
" function core.breakpoints.at(file, line)\n"
|
|
" local temp = file:sub(#\"file://\"+1)\n"
|
|
" for k, _ in pairs(file_mapping) do\n"
|
|
" i,j = k:find(temp)\n"
|
|
" if(j == #k) then\n"
|
|
" file = k\n"
|
|
" break;\n"
|
|
" end\n"
|
|
" end\n"
|
|
" local bps = file_mapping[file] and file_mapping[file][line]\n"
|
|
" if not bps then return nil end\n"
|
|
"\n"
|
|
" local do_break = false\n"
|
|
" for _, bp in pairs(bps) do\n"
|
|
" if bp.state == \"enabled\" then\n"
|
|
" local match = true\n"
|
|
" if bp.condition then\n"
|
|
" -- TODO: this is not the optimal solution because Context can be instantiated twice if the breakpoint matches\n"
|
|
" local cxt = context.Context:new(active_session.coro, 0)\n"
|
|
" setbpenv(bp.condition, cxt)\n"
|
|
" local success, result = pcall(bp.condition)\n"
|
|
" if not success then log(\"ERROR\", \"Condition evaluation failed for breakpoint at %s:%d: %s\", file, line, result) end\n"
|
|
" -- debugger always stops if an error occurs\n"
|
|
" match = (not success) or result\n"
|
|
" end\n"
|
|
" if match then\n"
|
|
" bp.hit_count = bp.hit_count + 1\n"
|
|
" if core.breakpoints.hit_conditions[bp.hit_condition](bp.hit_count, bp.hit_value) then\n"
|
|
" if bp.temporary then\n"
|
|
" core.breakpoints.remove(bp.id)\n"
|
|
" end\n"
|
|
" do_break = true\n"
|
|
" -- there is no break to handle multiple breakpoints: all hit counts must be updated\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return do_break\n"
|
|
" end\n"
|
|
"\n"
|
|
" function core.breakpoints.get(id)\n"
|
|
" if id then return id_mapping[id]\n"
|
|
" else return id_mapping end\n"
|
|
" end\n"
|
|
"\n"
|
|
" function core.breakpoints.remove(id)\n"
|
|
" local bp = id_mapping[id]\n"
|
|
" if bp then\n"
|
|
" id_mapping[id] = nil\n"
|
|
" local linereg = file_mapping[bp.filename][bp.lineno]\n"
|
|
" for i=1, #linereg do\n"
|
|
" if linereg[i] == bp then\n"
|
|
" table.remove(linereg, i)\n"
|
|
" break\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" remove_line_mapping(bp.lineno)\n"
|
|
"\n"
|
|
" -- cleanup file_mapping\n"
|
|
" if not next(linereg) then file_mapping[bp.filename][bp.lineno] = nil end\n"
|
|
" if not next(file_mapping[bp.filename]) then file_mapping[bp.filename] = nil end\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" --- Returns an XML data structure that describes given breakpoint\n"
|
|
" -- @param id (number) breakpoint ID\n"
|
|
" -- @return Table describing a <breakpooint> tag or nil followed by an error message\n"
|
|
" function core.breakpoints.get_xml(id)\n"
|
|
" local bp = id_mapping[id]\n"
|
|
" if not bp then return nil, \"No such breakpoint: \"..tostring(id) end\n"
|
|
"\n"
|
|
" local response = { tag = \"breakpoint\", attr = { } }\n"
|
|
" for k,v in pairs(bp) do response.attr[k] = v end\n"
|
|
" if bp.expression then\n"
|
|
" response[1] = { tag = \"expression\", bp.expression }\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- internal use only\n"
|
|
" response.attr.expression = nil\n"
|
|
" response.attr.condition = nil\n"
|
|
" response.attr.temporary = nil -- TODO: the specification is not clear whether this should be provided, see other implementations\n"
|
|
" return response\n"
|
|
" end\n"
|
|
"\n"
|
|
" --- Register an event to be triggered.\n"
|
|
" -- @param event event name to register (must be \"over\", \"out\" or \"into\")\n"
|
|
" function core.events.register(event)\n"
|
|
" local thread = active_session.coro[1]\n"
|
|
" log(\"DEBUG\", \"Registered %s event for %s (%d)\", event, tostring(thread), stack_levels[thread])\n"
|
|
" if event == \"into\" then\n"
|
|
" step_into = true\n"
|
|
" else\n"
|
|
" waiting_sessions[thread] = { event, stack_levels[thread] }\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" --- Returns if an event (step into, over, out) is triggered.\n"
|
|
" -- Does *not* discard events (even if they match) as event must be discarded manually if a breakpoint match before anyway.\n"
|
|
" -- @return true if an event has matched, false otherwise\n"
|
|
" function core.events.does_match(line)\n"
|
|
" if step_into and core.prev_break_line ~= line then return true end\n"
|
|
"\n"
|
|
" local thread = active_session.coro[1]\n"
|
|
" local event = waiting_sessions[thread]\n"
|
|
" if event then\n"
|
|
" local event_type, target_level = unpack(event)\n"
|
|
" local current_level = stack_levels[thread]\n"
|
|
"\n"
|
|
" if (event_type == \"over\" and current_level <= target_level and core.prev_break_line ~= line) or -- step over\n"
|
|
" (event_type == \"out\" and current_level < target_level) then -- step out\n"
|
|
" log(\"DEBUG\", \"Event %s matched!\", event_type)\n"
|
|
" return true\n"
|
|
" end\n"
|
|
" end\n"
|
|
" return false\n"
|
|
" end\n"
|
|
"\n"
|
|
" --- Discards event for current thread (if any)\n"
|
|
" function core.events.discard()\n"
|
|
" waiting_sessions[active_session.coro[1]] = nil\n"
|
|
" step_into = nil\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"-- Debugger main loop\n"
|
|
"-------------------------------------------------------------------------------\n"
|
|
"\n"
|
|
"--- Send the XML response to the previous continuation command and clear the previous context\n"
|
|
"function core.previous_context_response(self, reason)\n"
|
|
" self.previous_context.status = self.state\n"
|
|
" self.previous_context.reason = reason or \"ok\"\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = self.previous_context } )\n"
|
|
" self.previous_context = nil\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function cleanup()\n"
|
|
" coroutine.resume, coroutine.wrap = coresume, cowrap\n"
|
|
" for _, coro in pairs(core.active_coroutines.from_id) do\n"
|
|
" debug.sethook(coro)\n"
|
|
" end\n"
|
|
" -- to remove hook on the main coroutine, it must be the current one (otherwise, this is a no-op) and this function\n"
|
|
" -- have to be called adain later on the main thread to finish cleaup\n"
|
|
" debug.sethook()\n"
|
|
" core.active_coroutines.from_id, core.active_coroutines.from_coro = { }, { }\n"
|
|
"end\n"
|
|
"\n"
|
|
"--- This function handles the debugger commands while the execution is paused. This does not use coroutines because there is no\n"
|
|
"-- way to get main coro in Lua 5.1 (only in 5.2)\n"
|
|
"local function debugger_loop(self, async_packet)\n"
|
|
" self.skt:settimeout(nil) -- set socket blocking\n"
|
|
"\n"
|
|
" -- in async mode, the debugger does not wait for another command before continuing and does not modify previous_context\n"
|
|
" local async_mode = async_packet ~= nil\n"
|
|
"\n"
|
|
" if self.previous_context and not async_mode then\n"
|
|
" self.state = \"break\"\n"
|
|
" core.previous_context_response(self)\n"
|
|
" end\n"
|
|
" self.stack = context.ContextManager(self.coro) -- will be used to mutualize context allocation for each loop\n"
|
|
"\n"
|
|
" while true do\n"
|
|
" -- reads packet\n"
|
|
" local packet = async_packet or dbgp.read_packet(self.skt)\n"
|
|
" if not packet then\n"
|
|
" log(\"WARNING\", \"lost debugger connection\")\n"
|
|
" cleanup()\n"
|
|
" break\n"
|
|
" end\n"
|
|
"\n"
|
|
" async_packet = nil\n"
|
|
" log(\"DEBUG\", packet)\n"
|
|
" local cmd, args, data = dbgp.cmd_parse(packet)\n"
|
|
"\n"
|
|
" -- FIXME: command such as continuations sent in async mode could lead both engine and IDE in inconsistent state :\n"
|
|
" -- make a blacklist/whitelist of forbidden or allowed commands in async ?\n"
|
|
" -- invoke function\n"
|
|
" local func = commands[cmd]\n"
|
|
" if func then\n"
|
|
" local ok, cont = xpcall(function() return func(self, args, data) end, debug.traceback)\n"
|
|
" if not ok then -- internal exception\n"
|
|
" local code, msg, attr\n"
|
|
" if type(cont) == \"table\" and getmetatable(cont) == dbgp.DBGP_ERR_METATABLE then\n"
|
|
" code, msg, attr = cont.code, cont.message, cont.attr\n"
|
|
" else\n"
|
|
" code, msg, attr = 998, tostring(cont), { }\n"
|
|
" end\n"
|
|
" log(\"ERROR\", \"Command %s caused: (%d) %s\", cmd, code, tostring(msg))\n"
|
|
" attr.command, attr.transaction_id = cmd, args.i\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = attr, dbgp.make_error(code, msg) } )\n"
|
|
" elseif cont then\n"
|
|
" self.previous_context = { command = cmd, transaction_id = args.i }\n"
|
|
" break\n"
|
|
" elseif cont == nil and async_mode then\n"
|
|
" break\n"
|
|
" elseif cont == false then -- In case of commands that fully resumes debugger loop, the mode is sync\n"
|
|
" async_mode = false\n"
|
|
" end\n"
|
|
" else\n"
|
|
" log(\"Got unknown command: \"..cmd)\n"
|
|
" dbgp.send_xml(self.skt, { tag = \"response\", attr = { command = cmd, transaction_id = args.i, }, dbgp.make_error(4) } )\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" self.stack = nil -- free allocated contexts\n"
|
|
" self.state = \"running\"\n"
|
|
" self.skt:settimeout(0) -- reset socket to async\n"
|
|
"end\n"
|
|
"\n"
|
|
"-- Stack handling can be pretty complex sometimes, especially with LuaJIT (as tail-call optimization are\n"
|
|
"-- more aggressive as stock Lua). So all debugger stuff is done in another coroutine, which leave the program\n"
|
|
"-- stack in a clean state and allow faster and clearer stack operations (no need to remove all debugger calls\n"
|
|
"-- from stack for each operation).\n"
|
|
"-- However, this does not always work with stock Lua 5.1 as the main coroutine cannot be referenced\n"
|
|
"-- (coroutine.running() return nil). For this particular case, the debugger loop is started on the top of\n"
|
|
"-- program stack and every stack operation is relative the the hook level (see MainThread in util.lua).\n"
|
|
"local function line_hook(line)\n"
|
|
" local do_break, packet = nil, nil\n"
|
|
"\n"
|
|
" if (core.prev_break_line ~= line) then\n"
|
|
" if core.breakpoints.guess(line) ~= nil then\n"
|
|
" local info = active_session.coro:getinfo(0, \"S\")\n"
|
|
" local uri = platform.get_uri(info.source)\n"
|
|
" if uri and uri ~= debugger_uri and uri ~= transportmodule_uri then -- the debugger does not break if the source is not known\n"
|
|
" do_break = core.breakpoints.at(uri, line) --or core.events.does_match()\n"
|
|
" end\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" if not do_break then\n"
|
|
" do_break = core.events.does_match(line)\n"
|
|
" end\n"
|
|
" if do_break then\n"
|
|
" core.prev_break_line = line\n"
|
|
" core.events.discard()\n"
|
|
" end\n"
|
|
" -- check for async commands\n"
|
|
" if not do_break then\n"
|
|
" packet = dbgp.read_packet(active_session.skt)\n"
|
|
" if packet then do_break = true end\n"
|
|
" end\n"
|
|
" if do_break then\n"
|
|
" core.prev_break_line = line\n"
|
|
" local success, err = pcall(debugger_loop, active_session, packet)\n"
|
|
" if not success then log(\"ERROR\", \"Error while debug loop: \"..err) end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local line_hook_coro = cocreate(function(line)\n"
|
|
" while true do\n"
|
|
" line_hook(line)\n"
|
|
" line = coyield()\n"
|
|
" end\n"
|
|
"end)\n"
|
|
"\n"
|
|
"local function debugger_hook(event, line)\n"
|
|
" local thread = corunning() or \"main\"\n"
|
|
" if event == \"call\" then\n"
|
|
" stack_levels[thread] = stack_levels[thread] + 1\n"
|
|
" elseif event == \"tail call\" then\n"
|
|
" -- tail calls has no effects on stack handling: it is only used only for step commands but a such even does not\n"
|
|
" -- interfere with any of them\n"
|
|
" elseif event == \"return\" or event == \"tail return\" then\n"
|
|
" stack_levels[thread] = stack_levels[thread] - 1\n"
|
|
" else -- line event: check for breakpoint\n"
|
|
" active_session.coro = util.CurrentThread(corunning())\n"
|
|
" if active_session.coro[1] == \"main\" then\n"
|
|
" line_hook(line)\n"
|
|
" else\n"
|
|
" -- run the debugger loop in another thread on the other cases (simplifies stack handling)\n"
|
|
" assert(coresume(line_hook_coro, line))\n"
|
|
" end\n"
|
|
" active_session.coro = nil\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"if rawget(_G, \"jit\") then\n"
|
|
" debugger_hook = function(event, line)\n"
|
|
" local thread = corunning() or \"main\"\n"
|
|
" if event == \"call\" then\n"
|
|
" if debug.getinfo(2, \"S\").what == \"C\" then return end\n"
|
|
" stack_levels[thread] = stack_levels[thread] + 1\n"
|
|
" elseif event == \"return\" or event == \"tail return\" then\n"
|
|
" -- Return hooks are not called for tail calls in JIT (but unlike 5.2 there is no way to know whether a call is tail or not).\n"
|
|
" -- So the only reliable way to know stack depth is to walk it.\n"
|
|
" local depth = 2\n"
|
|
" -- TODO: find the fastest way to call getinfo ('what' parameter)\n"
|
|
" while debug.getinfo(depth, \"f\") do depth = depth + 1 end\n"
|
|
" stack_levels[thread] = depth - 2\n"
|
|
" elseif event == \"line\" then\n"
|
|
" active_session.coro = util.CurrentThread(corunning())\n"
|
|
" if active_session.coro[1] == \"main\" then\n"
|
|
" line_hook(line)\n"
|
|
" else\n"
|
|
" -- run the debugger loop in another thread on the other cases (simplifies stack handling)\n"
|
|
" assert(coresume(line_hook_coro, line))\n"
|
|
" end\n"
|
|
" active_session.coro = nil\n"
|
|
" end\n"
|
|
" end\n"
|
|
"end\n"
|
|
"\n"
|
|
"local function init(host, port, idekey, transport, executionplatform, workingdirectory, searchpaths)\n"
|
|
" -- get connection data\n"
|
|
" local host = host or os.getenv \"DBGP_IDEHOST\" or \"127.0.0.1\"\n"
|
|
" local port = port or os.getenv \"DBGP_IDEPORT\" or \"10000\"\n"
|
|
" local idekey = idekey or os.getenv(\"DBGP_IDEKEY\") or \"luaidekey\"\n"
|
|
"\n"
|
|
" -- init plaform module\n"
|
|
" local executionplatform = executionplatform or os.getenv(\"DBGP_PLATFORM\") or nil\n"
|
|
" local workingdirectory = workingdirectory or os.getenv(\"DBGP_WORKINGDIR\") or nil\n"
|
|
" platform.init(executionplatform,workingdirectory,searchpaths)\n"
|
|
"\n"
|
|
" -- get transport layer\n"
|
|
" local transportpath = transport or os.getenv(\"DBGP_TRANSPORT\") or \"debugger.transport.luasocket\"\n"
|
|
" local transport = require(transportpath)\n"
|
|
"\n"
|
|
" -- install base64 functions into util\n"
|
|
" util.b64, util.rawb64, util.unb64 = transport.b64, transport.rawb64, transport.unb64\n"
|
|
"\n"
|
|
" local skt = assert(transport.create())\n"
|
|
" skt:settimeout(nil)\n"
|
|
"\n"
|
|
" -- try to connect several times: if IDE launches both process and server at same time, first connect attempts may fail\n"
|
|
" local ok, err\n"
|
|
" print(string.format(\"Debugger v%s\", DBGP_CLIENT_VERSION))\n"
|
|
" print(string.format(\"Debugger: Trying to connect to %s:%s ... \", host, port))\n"
|
|
" ok, err = skt:connect(host, port)\n"
|
|
" for i=1, 4 do\n"
|
|
" if ok then\n"
|
|
" print(\"Debugger: Connection succeed.\")\n"
|
|
" break\n"
|
|
" else\n"
|
|
" -- wait\n"
|
|
" transport.sleep(0.5)\n"
|
|
" -- then retry.\n"
|
|
" print(string.format(\"Debugger: Retrying to connect to %s:%s ... \", host, port))\n"
|
|
" ok, err = skt:connect(host, port)\n"
|
|
" end\n"
|
|
" end\n"
|
|
" if err then error(string.format(\"Cannot connect to %s:%d : %s\", host, port, err)) end\n"
|
|
"\n"
|
|
" -- get the debugger and transport layer URI\n"
|
|
" debugger_uri = platform.get_uri(debug.getinfo(1).source)\n"
|
|
" transportmodule_uri = platform.get_uri(debug.getinfo(transport.create).source)\n"
|
|
"\n"
|
|
" -- get the root script path (the highest possible stack index)\n"
|
|
" local source\n"
|
|
" for i=2, math.huge do\n"
|
|
" local info = debug.getinfo(i)\n"
|
|
" if not info then break end\n"
|
|
" source = platform.get_uri(info.source) or source\n"
|
|
" end\n"
|
|
" if not source then source = \"unknown:/\" end -- when loaded before actual script (with a command line switch)\n"
|
|
"\n"
|
|
" -- generate some kind of thread identifier\n"
|
|
" local thread = corunning() or \"main\"\n"
|
|
" stack_levels[thread] = 1 -- the return event will set the counter to 0\n"
|
|
" local sessionid = tostring(os.time()) .. \"_\" .. tostring(thread)\n"
|
|
"\n"
|
|
" dbgp.send_xml(skt, { tag = \"init\", attr = {\n"
|
|
" appid = \"Lua DBGp\",\n"
|
|
" idekey = idekey,\n"
|
|
" session = sessionid,\n"
|
|
" thread = tostring(thread),\n"
|
|
" parent = \"\",\n"
|
|
" language = \"Lua\",\n"
|
|
" protocol_version = \"1.0\",\n"
|
|
" fileuri = source\n"
|
|
" } })\n"
|
|
"\n"
|
|
" --FIXME util.CurrentThread(corunning) => util.CurrentThread(corunning()) WHAT DOES IT FIXES ??\n"
|
|
" local sess = { skt = skt, state = \"starting\", id = sessionid, coro = util.CurrentThread(corunning) }\n"
|
|
" active_session = sess\n"
|
|
" debugger_loop(sess)\n"
|
|
"\n"
|
|
" -- set debug hooks\n"
|
|
" debug.sethook(debugger_hook, \"rlc\")\n"
|
|
"\n"
|
|
" -- install coroutine collecting functions.\n"
|
|
" -- TODO: maintain a list of *all* coroutines can be overkill (for example, the ones created by copcall), make a extension point to\n"
|
|
" -- customize debugged coroutines\n"
|
|
" -- coroutines are referenced during their first resume (so we are sure that they always have a stack frame)\n"
|
|
" local function resume_handler(coro, ...)\n"
|
|
" if costatus(coro) == \"dead\" then\n"
|
|
" local coro_id = core.active_coroutines.from_coro[coro]\n"
|
|
" core.active_coroutines.from_id[coro_id] = nil\n"
|
|
" core.active_coroutines.from_coro[coro] = nil\n"
|
|
" stack_levels[coro] = nil\n"
|
|
" end\n"
|
|
" return ...\n"
|
|
" end\n"
|
|
"\n"
|
|
" function coroutine.resume(coro, ...)\n"
|
|
" if not stack_levels[coro] then\n"
|
|
" -- first time referenced\n"
|
|
" stack_levels[coro] = 0\n"
|
|
" core.active_coroutines.n = core.active_coroutines.n + 1\n"
|
|
" core.active_coroutines.from_id[core.active_coroutines.n] = coro\n"
|
|
" core.active_coroutines.from_coro[coro] = core.active_coroutines.n\n"
|
|
" debug.sethook(coro, debugger_hook, \"rlc\")\n"
|
|
" end\n"
|
|
" return resume_handler(coro, coresume(coro, ...))\n"
|
|
" end\n"
|
|
"\n"
|
|
" -- coroutine.wrap uses directly C API for coroutines and does not trigger our overridden coroutine.resume\n"
|
|
" -- so this is an implementation of wrap in pure Lua\n"
|
|
" local function wrap_handler(status, ...)\n"
|
|
" if not status then error((...)) end\n"
|
|
" return ...\n"
|
|
" end\n"
|
|
"\n"
|
|
" function coroutine.wrap(f)\n"
|
|
" local coro = coroutine.create(f)\n"
|
|
" return function(...)\n"
|
|
" return wrap_handler(coroutine.resume(coro, ...))\n"
|
|
" end\n"
|
|
" end\n"
|
|
"\n"
|
|
" return sess\n"
|
|
"end\n"
|
|
"\n"
|
|
"return init\n"
|
|
"\n";
|
|
|
|
|
|
|
|
int luaopen_lua_m_debugger(lua_State *L) {
|
|
luaL_loadstring(L, lua_m_debugger);
|
|
return 1;
|
|
}
|
|
|
|
|
|
static luaL_Reg lua_debugger_modules[] = {
|
|
{"debugger", luaopen_lua_m_debugger},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
void luaopen_lua_debugger(lua_State* L)
|
|
{
|
|
luaL_Reg* lib = lua_debugger_modules;
|
|
lua_getglobal(L, "package");
|
|
lua_getfield(L, -1, "preload");
|
|
for (; lib->func; lib++)
|
|
{
|
|
lib->func(L);
|
|
lua_setfield(L, -2, lib->name);
|
|
}
|
|
lua_pop(L, 2);
|
|
}
|