You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1254 lines
46 KiB
1254 lines
46 KiB
/*
|
|
** See LICENSE Notice in lua.h
|
|
** Copyright (C) 2015-2016 Dibyendu Majumdar
|
|
*
|
|
* Standalone Lua/Ravi interpreter that is meant to be used as
|
|
* a debugger in the Visual Studio Code IDE.
|
|
* See https://marketplace.visualstudio.com/items?itemName=ravilang.ravi-debug
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <inttypes.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <direct.h>
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
#define chdir _chdir
|
|
/* When debugging
|
|
#include <windows.h>
|
|
*/
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <lauxlib.h>
|
|
#include <lua.h>
|
|
#include <lualib.h>
|
|
|
|
#include "protocol.h"
|
|
|
|
/* debugger state */
|
|
enum {
|
|
DEBUGGER_BIRTH = 1, /* Initial state */
|
|
DEBUGGER_INITIALIZED = 2, /* Have processed VSCode initialize request */
|
|
DEBUGGER_PROGRAM_LAUNCHED =
|
|
3, /* Have processed VSCode launch request - Lua program running */
|
|
DEBUGGER_PROGRAM_STEPPING = 4, /* In stepping mode */
|
|
DEBUGGER_PROGRAM_RUNNING = 5, /* In continue mode - will stop at breakpoint */
|
|
DEBUGGER_PROGRAM_STOPPED = 6, /* Have hit a break point */
|
|
DEBUGGER_PROGRAM_TERMINATED = 7 /* Program completed */
|
|
};
|
|
|
|
/* sub states of DEBUGGER_PROGRAM_STEPPING */
|
|
enum {
|
|
DEBUGGER_STEPPING_IN = 1, /* Step into */
|
|
DEBUGGER_STEPPING_OVER = 2, /* Step over */
|
|
DEBUGGER_STEPPING_OUT = 3 /* Step out of the current function */
|
|
};
|
|
|
|
/* Following values must fit into 2 bits and 0 is not a valid value */
|
|
enum {
|
|
VAR_TYPE_LOCALS = 1,
|
|
VAR_TYPE_VARARGS = 2,
|
|
VAR_TYPE_UPVALUES = 3,
|
|
VAR_TYPE_GLOBALS = 4,
|
|
VAR_LUA_GLOBALS = 5
|
|
};
|
|
|
|
/* Lua globals names - we use this to split globals into two lists */
|
|
static const char *lua_globals[] = {
|
|
"ipairs", "error", "utf8", "rawset", "tostring",
|
|
"select", "tonumber", "_VERSION", "loadfile", "xpcall",
|
|
"string", "rawlen", "ravitype", "print", "rawequal",
|
|
"setmetatable", "require", "getmetatable", "next", "package",
|
|
"coroutine", "io", "_G", "math", "collectgarbage",
|
|
"os", "table", "ravi", "dofile", "pcall",
|
|
"load", "module", "rawget", "debug", "assert",
|
|
"type", "pairs", "bit32", NULL};
|
|
|
|
typedef struct {
|
|
int depth;
|
|
int64_t sourceReference;
|
|
} SourceOnStack;
|
|
|
|
enum {
|
|
MAX_PROG_ARGS = 5
|
|
};
|
|
|
|
typedef struct {
|
|
char progpath[256];
|
|
int argc;
|
|
char argv[MAX_PROG_ARGS][256];
|
|
} Program;
|
|
|
|
/*
|
|
* These statics are temporary - eventually they will be moved to
|
|
* the Lua global state; but right now while things are
|
|
* evolving this is easier to work with.
|
|
*/
|
|
static FILE *my_logger = NULL;
|
|
static int thread_event_sent =
|
|
0; /* Set to 1 once we have sent a thread event to VSCode */
|
|
static int debugger_state =
|
|
DEBUGGER_BIRTH; /* Debugger's state is tracked in this variable */
|
|
static Breakpoint breakpoints[MAX_TOTAL_BREAKPOINTS];
|
|
static ProtocolMessage req, res;
|
|
static ProtocolMessage output_response;
|
|
static char workingdir[1024];
|
|
/* Following is for tracking the dynamically generated sources */
|
|
static SourceOnStack sourceOnStack[MAX_STACK_FRAMES];
|
|
static int sourceOnStackCount = 0;
|
|
/* Following three are for the three differet stepping modes */
|
|
static int stepping_mode = DEBUGGER_STEPPING_IN; /* default */
|
|
static int stepping_stacklevel =
|
|
-1; /* This tracks the stack level from where a step over or step out was
|
|
requested */
|
|
static lua_State *stepping_lua_State =
|
|
NULL; /* Tracks the Lua State that requested a step over or step out */
|
|
static membuff_t readbuf;
|
|
/* Since 1.8 VSCode has a bug in the way output events are handled so we
|
|
try to buffer until newline */
|
|
static char output_buffer[8 * 1024];
|
|
|
|
/*
|
|
* Generate response to InitializeRequest
|
|
* Send InitializedEvent
|
|
*/
|
|
static void handle_initialize_request(ProtocolMessage *req,
|
|
ProtocolMessage *res, FILE *out) {
|
|
if (debugger_state >= DEBUGGER_INITIALIZED) {
|
|
vscode_send_error_response(req, res, VSCODE_INITIALIZE_RESPONSE,
|
|
"already initialized", out, my_logger);
|
|
return;
|
|
}
|
|
/* Send InitializeResponse */
|
|
vscode_make_success_response(req, res, VSCODE_INITIALIZE_RESPONSE);
|
|
res->u.Response.u.InitializeResponse.body.supportsConfigurationDoneRequest =
|
|
1;
|
|
vscode_send(res, out, my_logger);
|
|
|
|
/* Send InitializedEvent */
|
|
/* From VSCode Adapter comment: InitializedEvent must not be sent before
|
|
* InitializeRequest has returned its result */
|
|
vscode_make_initialized_event(res);
|
|
vscode_send(res, out, my_logger);
|
|
|
|
/* Send notification */
|
|
/* This is just for display by the front end */
|
|
vscode_send_output_event(res, "console", "Debugger initialized\n", out,
|
|
my_logger);
|
|
debugger_state = DEBUGGER_INITIALIZED;
|
|
}
|
|
|
|
/*
|
|
* Generate response to ThreadRequest
|
|
* We treat all Lua Threads as part of the same OS thread
|
|
* so we always returned a fixed thread identifier
|
|
*/
|
|
static void handle_thread_request(ProtocolMessage *req, ProtocolMessage *res,
|
|
FILE *out) {
|
|
vscode_make_success_response(req, res, VSCODE_THREAD_RESPONSE);
|
|
res->u.Response.u.ThreadResponse.threads[0].id = 1;
|
|
strncpy(res->u.Response.u.ThreadResponse.threads[0].name, "RaviThread",
|
|
sizeof res->u.Response.u.ThreadResponse.threads[0].name);
|
|
vscode_send(res, out, my_logger);
|
|
}
|
|
|
|
/* Takes an intptr_t value and make sure that it will
|
|
* fit into a double without loss - which is what is used to represent a
|
|
* number in JSON and JavaScript.
|
|
*/
|
|
static inline intptr_t ensure_value_fits_in_mantissa(intptr_t sourceReference) {
|
|
/* pointer values are less than 53 bits anyway so nothing to do, but we assert
|
|
* just in case */
|
|
assert(sourceReference <= 9007199254740991);
|
|
return sourceReference;
|
|
}
|
|
|
|
static void get_path_and_name(char *path, size_t pathlen, char *name,
|
|
size_t namelen, const char *input_name,
|
|
const char *workingdir) {
|
|
/* If the source does not include a path then
|
|
we prefix the source with the path to the
|
|
working directrory so that it can be reliably
|
|
found, else we assume that either the path to the
|
|
source is absolue or it is relative to the working
|
|
directory */
|
|
const char *last_path_delim = strrchr(input_name, '/');
|
|
if (last_path_delim) {
|
|
/* source includes a path */
|
|
ravi_string_copy(name, last_path_delim + 1, namelen);
|
|
ravi_string_copy(path, input_name, pathlen);
|
|
} else {
|
|
/* prepend the working directory to the name */
|
|
ravi_string_copy(name, input_name, namelen);
|
|
if (workingdir[0]) {
|
|
size_t n = strlen(workingdir);
|
|
if (workingdir[n - 1] == '/')
|
|
snprintf(path, pathlen, "%s%s", workingdir, input_name);
|
|
else
|
|
snprintf(path, pathlen, "%s/%s", workingdir, input_name);
|
|
} else {
|
|
/* no working directory */
|
|
ravi_string_copy(path, input_name, pathlen);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Handle StackTraceRequest
|
|
* FIXME - does not handle the paging concept
|
|
* truncates the stack trace to MAX_STACK_FRAMES
|
|
*/
|
|
static void handle_stack_trace_request(ProtocolMessage *req,
|
|
ProtocolMessage *res, lua_State *L,
|
|
FILE *out) {
|
|
lua_Debug entry;
|
|
int depth = 0; /* TODO we ignore startFrame in the request */
|
|
vscode_make_success_response(req, res, VSCODE_STACK_TRACE_RESPONSE);
|
|
sourceOnStackCount =
|
|
0; /* for tracking functions that have a dynamic source */
|
|
while (lua_getstack(L, depth, &entry) &&
|
|
depth < req->u.Request.u.StackTraceRequest.levels &&
|
|
depth < MAX_STACK_FRAMES) {
|
|
int status = lua_getinfo(L, "Sln", &entry);
|
|
assert(status);
|
|
const char *src = entry.source;
|
|
if (*src == '@') {
|
|
/* Source is a file */
|
|
src++;
|
|
char path[1024];
|
|
char name[256];
|
|
get_path_and_name(path, sizeof path, name, sizeof name, src, workingdir);
|
|
ravi_string_copy(
|
|
res->u.Response.u.StackTraceResponse.stackFrames[depth].source.path,
|
|
path, sizeof res->u.Response.u.StackTraceResponse.stackFrames[depth]
|
|
.source.path);
|
|
ravi_string_copy(
|
|
res->u.Response.u.StackTraceResponse.stackFrames[depth].source.name,
|
|
name, sizeof res->u.Response.u.StackTraceResponse.stackFrames[depth]
|
|
.source.name);
|
|
} else if (memcmp(src, "=[C]", 4) == 0) {
|
|
/* C Function so source is not available */
|
|
res->u.Response.u.StackTraceResponse.stackFrames[depth]
|
|
.source.sourceReference = -1;
|
|
ravi_string_copy(
|
|
res->u.Response.u.StackTraceResponse.stackFrames[depth].source.name,
|
|
"<C function>",
|
|
sizeof res->u.Response.u.StackTraceResponse.stackFrames[depth]
|
|
.source.name);
|
|
} else {
|
|
/* Source is a string - send a reference to the stack frame */
|
|
/* Currently (as ov VSCode 1.1 the sourceReference must be unique within
|
|
* a debug session. A cheap way of making this unique is to use the
|
|
* pointer to the source itself. However as we are passing this to
|
|
* a JavaScript number (double) - we have to be careful how we encode
|
|
* the reference. Following ensures that we encode the pointer value in
|
|
* the mantissa bits.
|
|
*/
|
|
int64_t sourceReference = (intptr_t)src;
|
|
sourceReference = ensure_value_fits_in_mantissa(sourceReference);
|
|
/* Record the depth -> source mapping so that we can later on
|
|
determine the correct depth from the source reference */
|
|
/* following not range checked as already checked */
|
|
sourceOnStack[sourceOnStackCount].depth = depth;
|
|
sourceOnStack[sourceOnStackCount++].sourceReference = sourceReference;
|
|
res->u.Response.u.StackTraceResponse.stackFrames[depth]
|
|
.source.sourceReference = sourceReference;
|
|
/* name must be uniquely associated with the source else
|
|
* VSCode gets confused (see
|
|
* https://github.com/Microsoft/vscode/issues/6360) */
|
|
snprintf(
|
|
res->u.Response.u.StackTraceResponse.stackFrames[depth].source.name,
|
|
sizeof res->u.Response.u.StackTraceResponse.stackFrames[depth]
|
|
.source.name,
|
|
"<dynamic function %p>", src);
|
|
}
|
|
res->u.Response.u.StackTraceResponse.stackFrames[depth].id = depth;
|
|
res->u.Response.u.StackTraceResponse.stackFrames[depth].line =
|
|
entry.currentline;
|
|
const char *funcname = entry.name ? entry.name : "?";
|
|
ravi_string_copy(
|
|
res->u.Response.u.StackTraceResponse.stackFrames[depth].name, funcname,
|
|
sizeof res->u.Response.u.StackTraceResponse.stackFrames[depth].name);
|
|
depth++;
|
|
}
|
|
res->u.Response.u.StackTraceResponse.totalFrames = depth;
|
|
vscode_send(res, out, my_logger);
|
|
}
|
|
|
|
/*
|
|
* Handle the request to provide the source given a source reference
|
|
* Previously in the stack request response we have encoded a mapping between
|
|
* each source reference to the stack depth so we can now lookup in the
|
|
* mapping and obtain the appropriate source from the Lua VM
|
|
*/
|
|
static void handle_source_request(ProtocolMessage *req, ProtocolMessage *res,
|
|
lua_State *L, FILE *out) {
|
|
lua_Debug entry;
|
|
int64_t sourceReference = req->u.Request.u.SourceRequest.sourceReference;
|
|
int depth = -1;
|
|
/* search for the source reference */
|
|
for (int i = 0; i < sourceOnStackCount; i++) {
|
|
if (sourceOnStack[i].sourceReference == sourceReference) {
|
|
depth = sourceOnStack[i].depth;
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
fprintf(my_logger,
|
|
"SEARCHED FOR sourceReference=%" PRId64 ", found at stack depth =
|
|
%d\n",
|
|
sourceReference, depth);
|
|
*/
|
|
vscode_make_success_response(req, res, VSCODE_SOURCE_RESPONSE);
|
|
if (lua_getstack(L, depth, &entry)) {
|
|
int status = lua_getinfo(L, "Sln", &entry);
|
|
if (status) {
|
|
const char *src = entry.source;
|
|
if (*src != '@' && *src != '=') {
|
|
/* Source is a string */
|
|
ravi_string_copy(res->u.Response.u.SourceResponse.content, src,
|
|
sizeof res->u.Response.u.SourceResponse.content);
|
|
} else
|
|
goto l_nosource;
|
|
} else
|
|
goto l_nosource;
|
|
} else {
|
|
l_nosource:
|
|
ravi_string_copy(res->u.Response.u.SourceResponse.content,
|
|
"Source not available",
|
|
sizeof res->u.Response.u.SourceResponse.content);
|
|
}
|
|
vscode_send(res, out, my_logger);
|
|
}
|
|
|
|
/*
|
|
* Handle SetBreakpointsRequest
|
|
* A list of all breakpoints is maintained globally - this
|
|
* is simply a linear array of breakpoints. We search and overwrite
|
|
* all breakpoints associated with the given source.
|
|
* Note that breakpoints against dynamic source is not handled
|
|
*/
|
|
static void handle_set_breakpoints_request(ProtocolMessage *req,
|
|
ProtocolMessage *res, FILE *out,
|
|
FILE *my_logger) {
|
|
vscode_make_success_response(req, res, VSCODE_SET_BREAKPOINTS_RESPONSE);
|
|
int j = 0, k = 0; /* j tracks the position in our global breakpoints,
|
|
k tracks the breakpoint position in the response,
|
|
i tracks the breakpoint position in the request */
|
|
for (int i = 0; i < MAX_BREAKPOINTS; i++) {
|
|
/* Make sure that the breakpoint is in a source file - disallow breakpoints
|
|
* in dynamic code */
|
|
if (req->u.Request.u.SetBreakpointsRequest.breakpoints[i].line > 0 &&
|
|
req->u.Request.u.SetBreakpointsRequest.source.sourceReference == 0) {
|
|
while (j < MAX_TOTAL_BREAKPOINTS) {
|
|
int y = j++;
|
|
/* as we overwrite any previously set breakpoints we do not need
|
|
to match the line number */
|
|
if (breakpoints[y].source.path[0] == 0 ||
|
|
strcmp(breakpoints[y].source.path,
|
|
req->u.Request.u.SetBreakpointsRequest.source.path) == 0) {
|
|
ravi_string_copy(breakpoints[y].source.path,
|
|
req->u.Request.u.SetBreakpointsRequest.source.path,
|
|
sizeof breakpoints[0].source.path);
|
|
breakpoints[y].line =
|
|
req->u.Request.u.SetBreakpointsRequest.breakpoints[i].line;
|
|
//fprintf(my_logger, "Saving breakpoint j=%d, k=%d, i=%d\n", y, k, i);
|
|
if (k < MAX_BREAKPOINTS) {
|
|
res->u.Response.u.SetBreakpointsResponse.breakpoints[k].line =
|
|
req->u.Request.u.SetBreakpointsRequest.breakpoints[i].line;
|
|
res->u.Response.u.SetBreakpointsResponse.breakpoints[k].verified =
|
|
false;
|
|
ravi_string_copy(
|
|
res->u.Response.u.SetBreakpointsResponse.breakpoints[k]
|
|
.source.path,
|
|
breakpoints[y].source.path,
|
|
sizeof res->u.Response.u.SetBreakpointsResponse.breakpoints[k]
|
|
.source.path);
|
|
k++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (j == MAX_TOTAL_BREAKPOINTS || k == MAX_BREAKPOINTS) break;
|
|
}
|
|
}
|
|
/* Clear any other breakpoints within the same source file */
|
|
for (; j < MAX_TOTAL_BREAKPOINTS; j++) {
|
|
if (strcmp(breakpoints[j].source.path,
|
|
req->u.Request.u.SetBreakpointsRequest.source.path) == 0) {
|
|
breakpoints[j].source.path[0] = 0;
|
|
breakpoints[j].line = 0;
|
|
}
|
|
}
|
|
vscode_send(res, out, my_logger);
|
|
}
|
|
|
|
/*
|
|
* Handle ScopesRequest
|
|
* Since VSCode requires us to send a numeric references we need
|
|
* to encode the stack depth and the scope within a numeric value.
|
|
* We do this by packing the values into an double value.
|
|
*/
|
|
static void handle_scopes_request(ProtocolMessage *req, ProtocolMessage *res,
|
|
lua_State *L, FILE *out) {
|
|
lua_Debug entry;
|
|
int depth = 0;
|
|
vscode_make_success_response(req, res, VSCODE_SCOPES_RESPONSE);
|
|
depth = req->u.Request.u.ScopesRequest.frameId;
|
|
if (lua_getstack(L, depth, &entry)) {
|
|
int status = lua_getinfo(L, "u", &entry);
|
|
assert(status);
|
|
int i = 0;
|
|
ravi_string_copy(res->u.Response.u.ScopesResponse.scopes[i].name,
|
|
"Locals",
|
|
sizeof res->u.Response.u.ScopesResponse.scopes[0].name);
|
|
PackedInteger varRef;
|
|
memset(&varRef, 0, sizeof(PackedInteger));
|
|
varRef.vartype = (unsigned int)VAR_TYPE_LOCALS;
|
|
varRef.depth = (unsigned int)depth;
|
|
res->u.Response.u.ScopesResponse.scopes[i].variablesReference =
|
|
vscode_pack(&varRef);
|
|
res->u.Response.u.ScopesResponse.scopes[i].expensive = 0;
|
|
i++;
|
|
if (entry.isvararg) {
|
|
ravi_string_copy(
|
|
res->u.Response.u.ScopesResponse.scopes[i].name, "Var Args",
|
|
sizeof res->u.Response.u.ScopesResponse.scopes[0].name);
|
|
memset(&varRef, 0, sizeof(PackedInteger));
|
|
varRef.vartype = (unsigned int)VAR_TYPE_VARARGS;
|
|
varRef.depth = (unsigned int)depth;
|
|
res->u.Response.u.ScopesResponse.scopes[i].variablesReference =
|
|
vscode_pack(&varRef);
|
|
res->u.Response.u.ScopesResponse.scopes[i].expensive = 0;
|
|
i++;
|
|
}
|
|
ravi_string_copy(res->u.Response.u.ScopesResponse.scopes[i].name,
|
|
"Globals",
|
|
sizeof res->u.Response.u.ScopesResponse.scopes[0].name);
|
|
memset(&varRef, 0, sizeof(PackedInteger));
|
|
varRef.vartype = (unsigned int)VAR_TYPE_GLOBALS;
|
|
varRef.depth = (unsigned int)depth;
|
|
res->u.Response.u.ScopesResponse.scopes[i].variablesReference =
|
|
vscode_pack(&varRef);
|
|
res->u.Response.u.ScopesResponse.scopes[i].expensive = 0;
|
|
i++;
|
|
ravi_string_copy(res->u.Response.u.ScopesResponse.scopes[i].name,
|
|
"Lua Globals",
|
|
sizeof res->u.Response.u.ScopesResponse.scopes[0].name);
|
|
memset(&varRef, 0, sizeof(PackedInteger));
|
|
varRef.vartype = (unsigned int)VAR_LUA_GLOBALS;
|
|
varRef.depth = (unsigned int)depth;
|
|
res->u.Response.u.ScopesResponse.scopes[i].variablesReference =
|
|
vscode_pack(&varRef);
|
|
res->u.Response.u.ScopesResponse.scopes[i].expensive = 0;
|
|
} else {
|
|
vscode_make_error_response(req, res, VSCODE_SCOPES_RESPONSE,
|
|
"Error retrieving stack frame");
|
|
}
|
|
vscode_send(res, out, my_logger);
|
|
}
|
|
|
|
// Count number of entries in a Lua table
|
|
// The standard luaL_len() only works for sequences
|
|
static int count_table_entries(lua_State *L, int stack_index) {
|
|
int count = 0;
|
|
int oldt = lua_gettop(L);
|
|
// Push another reference to the table on top of the stack (so we know
|
|
// where it is, and this function can work for negative, positive and
|
|
// pseudo indices
|
|
lua_pushvalue(L, stack_index);
|
|
// stack now contains: -1 => table
|
|
lua_pushnil(L); // push first key
|
|
// stack now contains: -1 => nil; -2 => table
|
|
while (lua_next(L, -2)) {
|
|
// stack now contains: -1 => value; -2 => key; -3 => table
|
|
count++;
|
|
lua_pop(L, 1); // pop value, but keep key
|
|
}
|
|
// pop the table
|
|
lua_pop(L, 1);
|
|
assert(lua_gettop(L) == oldt);
|
|
return count;
|
|
}
|
|
|
|
// Get information regarding a Lua table
|
|
static void get_table_info(lua_State *L, int stack_idx, char *buf, size_t len) {
|
|
int num = count_table_entries(L, stack_idx);
|
|
const char *typename = ravi_typename(L, stack_idx);
|
|
const void *ptr = lua_topointer(L, stack_idx);
|
|
snprintf(buf, len, "%s %p (%d items)", typename, ptr, num);
|
|
}
|
|
|
|
// Get information regarding a Lua userdata value
|
|
static void get_userdata(lua_State *L, int stack_idx, char *buf, size_t len) {
|
|
const char *udata = raviL_tolstring(L, stack_idx, NULL);
|
|
vscode_json_stringify(udata, buf, len);
|
|
lua_pop(L, 1); /* remove result from raviL_tolstring() */
|
|
}
|
|
|
|
// Get information regarding a Lua value
|
|
static int get_value(lua_State *L, int stack_idx, char *buf, size_t len) {
|
|
int rc = 0;
|
|
int l_type = lua_type(L, stack_idx);
|
|
*buf = 0;
|
|
switch (l_type) {
|
|
case LUA_TNIL: {
|
|
snprintf(buf, len, "nil");
|
|
break;
|
|
}
|
|
case LUA_TBOOLEAN: {
|
|
snprintf(buf, len, "%s",
|
|
(lua_toboolean(L, stack_idx) != 0) ? "true" : "false");
|
|
break;
|
|
}
|
|
case LUA_TLIGHTUSERDATA: {
|
|
get_userdata(L, stack_idx, buf, len);
|
|
break;
|
|
}
|
|
case LUA_TNUMBER: {
|
|
double num = lua_tonumber(L, stack_idx);
|
|
if ((long)num == num)
|
|
snprintf(buf, len, "%ld (0x%lx)", (long)num, (unsigned long)num);
|
|
else
|
|
snprintf(buf, len, "%g", num);
|
|
break;
|
|
}
|
|
case LUA_TSTRING: {
|
|
char tbuf[1024];
|
|
// We assume here that the value at stack index is safe to convert
|
|
// to a string
|
|
snprintf(tbuf, sizeof tbuf, "%s", lua_tostring(L, stack_idx));
|
|
vscode_json_stringify(tbuf, buf, len);
|
|
break;
|
|
}
|
|
case LUA_TTABLE: {
|
|
get_table_info(L, stack_idx, buf, len);
|
|
rc = 1;
|
|
break;
|
|
}
|
|
case LUA_TFUNCTION: {
|
|
snprintf(buf, len, "%s", ravi_typename(L, stack_idx));
|
|
break;
|
|
}
|
|
case LUA_TUSERDATA: {
|
|
get_userdata(L, stack_idx, buf, len);
|
|
break;
|
|
}
|
|
case LUA_TTHREAD: {
|
|
snprintf(buf, len, "thread %p", lua_topointer(L, stack_idx));
|
|
break;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int search_for_name(const char *name, const char **filter) {
|
|
if (filter == NULL) return 0;
|
|
for (int i = 0; filter[i] != NULL; i++)
|
|
if (strcmp(name, filter[i]) == 0) return 1;
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
static const char *var_type_as_string(int vtype) {
|
|
switch (vtype) {
|
|
case VAR_TYPE_LOCALS:
|
|
return "locals";
|
|
case VAR_TYPE_VARARGS:
|
|
return "varargs";
|
|
case VAR_TYPE_UPVALUES:
|
|
return "upvalues";
|
|
case VAR_TYPE_GLOBALS:
|
|
return "globals";
|
|
case VAR_LUA_GLOBALS:
|
|
return "Lua globals";
|
|
default:
|
|
return "invalid";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Get a table's values into the response
|
|
*/
|
|
static void get_table_values(ProtocolMessage *res, lua_State *L, int stack_idx,
|
|
const PackedInteger *parent, int depth,
|
|
int add_var_reference, const char **filter) {
|
|
// Push another reference to the table on top of the stack (so we know
|
|
// where it is, and this function can work for negative, positive and
|
|
// pseudo indices
|
|
lua_pushvalue(L, stack_idx);
|
|
// stack now contains: -1 => table
|
|
lua_pushnil(L); // push first key
|
|
/* stack now contains: -1 => nil (key); -2 => table */
|
|
int v = 1; /* v is the position of key in table from iterator point of view */
|
|
int j = 0; /* j is the index in response */
|
|
assert(depth <= 5);
|
|
int var = depth == 5 ? 0 : parent->x8[depth];
|
|
//fprintf(my_logger, "get_table_values() --> depth = %d var request = %d\n",
|
|
// depth, var);
|
|
|
|
while (lua_next(L, -2) && j < MAX_VARIABLES) {
|
|
// stack now contains: -1 => value; -2 => key; -3 => table
|
|
if (var != 0) {
|
|
/* drill down if we are on the requested variable */
|
|
if (v == var && lua_type(L, -1) == LUA_TTABLE && depth < 5) {
|
|
//fprintf(my_logger,
|
|
// "get_table_values() --> drilling down var request = %d\n", var);
|
|
get_table_values(res, L, -1, parent, depth + 1, true, filter);
|
|
}
|
|
lua_pop(L, 1);
|
|
} else if (j + 1 == MAX_VARIABLES) {
|
|
ravi_string_copy(
|
|
res->u.Response.u.VariablesResponse.variables[j].name, "...",
|
|
sizeof res->u.Response.u.VariablesResponse.variables[0].name);
|
|
ravi_string_copy(
|
|
res->u.Response.u.VariablesResponse.variables[j].value, "",
|
|
sizeof res->u.Response.u.VariablesResponse.variables[0].value);
|
|
lua_pop(L, 1); /* pop value */
|
|
} else {
|
|
// stack now contains: -1 => value; -2 => key; -3 => table
|
|
// copy the key so that lua_tostring does not modify the
|
|
// original
|
|
lua_pushvalue(L, -2);
|
|
// stack now contains: -1 => key; -2 => value; -3 => key
|
|
char key[sizeof res->u.Response.u.VariablesResponse.variables[0].name];
|
|
get_value(L, -1, key, sizeof key);
|
|
if (!search_for_name(key, filter)) {
|
|
ravi_string_copy(
|
|
res->u.Response.u.VariablesResponse.variables[j].name, key,
|
|
sizeof res->u.Response.u.VariablesResponse.variables[0].name);
|
|
ravi_string_copy(
|
|
res->u.Response.u.VariablesResponse.variables[j].type,
|
|
ravi_typename(L, -2),
|
|
sizeof res->u.Response.u.VariablesResponse.variables[0].type);
|
|
int is_table = get_value(
|
|
L, -2, res->u.Response.u.VariablesResponse.variables[j].value,
|
|
sizeof res->u.Response.u.VariablesResponse.variables[0].value);
|
|
if (is_table && add_var_reference && depth < 5) {
|
|
PackedInteger pi;
|
|
memset(&pi, 0, sizeof pi);
|
|
pi.vartype = parent->vartype; /* vartype */
|
|
pi.depth = parent->depth; /* depth */
|
|
/* Copy the indices reported by the previous call */
|
|
for (int i = 0; i < depth; i++) pi.x8[i] = parent->x8[i];
|
|
assert(depth >= 0 && depth < 5);
|
|
pi.x8[depth] = v; /* cannot be zero based !*/
|
|
//fprintf(my_logger,
|
|
// "Encoding table index [%s] as vartype=%s depth=%u %u %u %u "
|
|
// "%u %d\n",
|
|
// key, var_type_as_string(pi.vartype), pi.depth, pi.x8[0],
|
|
// pi.x8[1], pi.x8[2], pi.x8[3], pi.x8[4]);
|
|
res->u.Response.u.VariablesResponse.variables[j].variablesReference =
|
|
vscode_pack(&pi);
|
|
//fprintf(my_logger, "Key %s variable reference %lld\n", key,
|
|
// res->u.Response.u.VariablesResponse.variables[j]
|
|
// .variablesReference);
|
|
} else {
|
|
/* We do not support further drill down */
|
|
res->u.Response.u.VariablesResponse.variables[j].variablesReference =
|
|
0;
|
|
}
|
|
j++;
|
|
}
|
|
// pop value + copy of key, leaving original key
|
|
lua_pop(L, 2);
|
|
}
|
|
v++;
|
|
}
|
|
lua_pop(L, 1); /* pop the table */
|
|
}
|
|
|
|
/*
|
|
* The VSCode front-end sends a variables request when it wants to
|
|
* display variables. Unfortunately a limitation is that only a numeric
|
|
* reference field is available named 'variableReference' to identify the
|
|
* variable.
|
|
* We need to know various bits about the variable - such as its stack frame
|
|
* location, the variable's location. Therefore we have to encode various pieces
|
|
* of information in the numeric value.
|
|
*/
|
|
static void handle_variables_request(ProtocolMessage *req, ProtocolMessage *res,
|
|
lua_State *L, FILE *out) {
|
|
lua_Debug entry;
|
|
vscode_make_success_response(req, res, VSCODE_VARIABLES_RESPONSE);
|
|
int64_t varRef = req->u.Request.u.VariablesRequest.variablesReference;
|
|
PackedInteger pi;
|
|
memset(&pi, 0, sizeof pi);
|
|
vscode_unpack(varRef, &pi);
|
|
//fprintf(my_logger,
|
|
// "Unpacked variable reference to vartype=%s depth=%u %u %u %u %u %d\n",
|
|
// var_type_as_string(pi.vartype), pi.depth, pi.x8[0], pi.x8[1],
|
|
// pi.x8[2], pi.x8[3], pi.x8[4]);
|
|
|
|
/*
|
|
* The variable reference is encoded such that it contains:
|
|
* type (4 bits) - the scope type
|
|
* depth (8 bits) - the stack frame
|
|
* var (8 bits) - the index of the variable as provided to lua_getlocal()
|
|
* - These are negative for varargs values
|
|
*/
|
|
int type = pi.vartype;
|
|
int depth = pi.depth;
|
|
int var = pi.x8[0];
|
|
int isvararg = type == VAR_TYPE_VARARGS;
|
|
// fprintf(my_logger, "Var Request --> %d isvararg=%d\n", type, isvararg);
|
|
if (lua_getstack(L, depth, &entry)) {
|
|
if (type == VAR_TYPE_GLOBALS || type == VAR_LUA_GLOBALS) {
|
|
if (var == 0) {
|
|
/* top level */
|
|
lua_pushglobaltable(L);
|
|
int stack_idx = lua_gettop(L);
|
|
/* depth is set to 0 as there is no local scope variable */
|
|
get_table_values(res, L, stack_idx, &pi, 0, true,
|
|
type == VAR_TYPE_GLOBALS ? lua_globals : NULL);
|
|
lua_pop(L, 1);
|
|
} else {
|
|
/* The request is to expand an entry in the global table */
|
|
/* var is the position of key in global table */
|
|
int checktop = lua_gettop(L);
|
|
lua_pushglobaltable(L);
|
|
/* stack now contains: -1 => table */
|
|
lua_pushnil(L); /* push first key */
|
|
/* stack now contains: -1 => nil (key); -2 => table */
|
|
int v = 1; /* v is the position of key in table from iterator point of
|
|
view */
|
|
while (lua_next(L, -2)) {
|
|
if (v == var) {
|
|
/* We found the value we need to expand - we know already this is a
|
|
* table */
|
|
int current_top = lua_gettop(L);
|
|
int l_type = lua_type(L, current_top);
|
|
/* We know this should be a table but we check for safety */
|
|
assert(l_type == LUA_TTABLE);
|
|
if (l_type == LUA_TTABLE) {
|
|
/* depth set to 1 as 0 is taken by global reference */
|
|
get_table_values(res, L, current_top, &pi, 1, true, NULL);
|
|
}
|
|
/* TODO we can break the loop here */
|
|
}
|
|
v++;
|
|
lua_pop(L, 1); /* pop value */
|
|
}
|
|
lua_pop(L, 1); /* pop global table */
|
|
assert(lua_gettop(L) == checktop);
|
|
}
|
|
} else if (type == VAR_TYPE_LOCALS ||
|
|
type == VAR_TYPE_VARARGS) { /* locals */
|
|
if (var == 0) {
|
|
/*
|
|
* A top level request - i.e. from the scope
|
|
*/
|
|
for (int n = 1, v = 0; v < MAX_VARIABLES; n++) {
|
|
if (v + 1 == MAX_VARIABLES) {
|
|
/* Let the user know that we are not displaying all the variables */
|
|
ravi_string_copy(
|
|
res->u.Response.u.VariablesResponse.variables[v].name, "...",
|
|
sizeof res->u.Response.u.VariablesResponse.variables[0].name);
|
|
ravi_string_copy(
|
|
res->u.Response.u.VariablesResponse.variables[v].value, "",
|
|
sizeof res->u.Response.u.VariablesResponse.variables[0].value);
|
|
break;
|
|
}
|
|
const char *name = lua_getlocal(L, &entry, isvararg ? -n : n);
|
|
if (name) {
|
|
/* Temporary variables have names that start with (*temporary).
|
|
* Skip such variables
|
|
*/
|
|
if (*name != '(' || strcmp(name, "(*vararg)") == 0) {
|
|
if (*name == '(') {
|
|
char temp[80];
|
|
snprintf(temp, sizeof temp, "[%d]", n);
|
|
ravi_string_copy(
|
|
res->u.Response.u.VariablesResponse.variables[v].name, temp,
|
|
sizeof res->u.Response.u.VariablesResponse.variables[0]
|
|
.name);
|
|
} else {
|
|
ravi_string_copy(
|
|
res->u.Response.u.VariablesResponse.variables[v].name, name,
|
|
sizeof res->u.Response.u.VariablesResponse.variables[0]
|
|
.name);
|
|
}
|
|
int is_table = get_value(
|
|
L, lua_gettop(L),
|
|
res->u.Response.u.VariablesResponse.variables[v].value,
|
|
sizeof res->u.Response.u.VariablesResponse.variables[0]
|
|
.value);
|
|
ravi_string_copy(
|
|
res->u.Response.u.VariablesResponse.variables[v].type,
|
|
ravi_typename(L, -1),
|
|
sizeof res->u.Response.u.VariablesResponse.variables[0].type);
|
|
if (is_table) {
|
|
/* If the variable is a table then we pass pack a reference
|
|
that is used by the front end to drill down */
|
|
PackedInteger newref;
|
|
memset(&newref, 0, sizeof newref);
|
|
newref.vartype = type;
|
|
newref.depth = depth;
|
|
newref.x8[0] = n; /* local variable reference at depth 0 */
|
|
res->u.Response.u.VariablesResponse.variables[v]
|
|
.variablesReference = vscode_pack(&newref);
|
|
} else {
|
|
/* not a table */
|
|
res->u.Response.u.VariablesResponse.variables[v]
|
|
.variablesReference = 0;
|
|
}
|
|
v++;
|
|
}
|
|
lua_pop(L, 1); /* pop the value */
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
} else /* var != 0 */ {
|
|
/*
|
|
* drill down
|
|
*/
|
|
const char *name =
|
|
lua_getlocal(L, &entry, type == VAR_TYPE_VARARGS ? -var : var);
|
|
if (name) {
|
|
int stack_idx = lua_gettop(L);
|
|
int l_type = lua_type(L, stack_idx);
|
|
if (l_type == LUA_TTABLE) {
|
|
/* depth set to 1 as 0 is taken by local variable reference */
|
|
get_table_values(res, L, stack_idx, &pi, 1, true, NULL);
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
vscode_make_error_response(req, res, VSCODE_VARIABLES_RESPONSE,
|
|
"Error retrieving variables");
|
|
}
|
|
vscode_send(res, out, my_logger);
|
|
}
|
|
|
|
/* Sets values in the Lua global package table such as 'path' and 'cpath' */
|
|
static void set_package_var(lua_State *L, const char *key, const char *value) {
|
|
lua_getglobal(L, "package");
|
|
lua_pushstring(L, value);
|
|
lua_setfield(L, -2, key);
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
/* Parse the launch program into command and args */
|
|
static void parse_launch_program(Program *prog, const char *cmd) {
|
|
memset(prog, 0, sizeof *prog);
|
|
const char *cp = cmd;
|
|
int i = 0;
|
|
for (; i < sizeof prog->progpath - 1; i++) {
|
|
if (cmd[i] && cmd[i] != ' ') {
|
|
prog->progpath[i] = cmd[i];
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
fprintf(my_logger, "Program to be debugged = %s\n", prog->progpath);
|
|
for (int j = 0; j < MAX_PROG_ARGS; j++) {
|
|
while (cmd[i] && cmd[i] == ' ')
|
|
i++;
|
|
if (!cmd[i])
|
|
break;
|
|
int k = 0;
|
|
for (; k < sizeof prog->argv[0] - 1; k++) {
|
|
if (!cmd[i] || cmd[i] == ' ')
|
|
break;
|
|
prog->argv[j][k] = cmd[i];
|
|
i++;
|
|
}
|
|
prog->argv[j][k] = 0;
|
|
prog->argc++;
|
|
}
|
|
for (int j = 0; j < prog->argc; j++) {
|
|
fprintf(my_logger, "Arg [%d] = %s\n", j, prog->argv[j]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The VSCode front-end sends a Launch request when the user
|
|
* starts a debug session. This is where the actual Lua code
|
|
* execution begins
|
|
*/
|
|
static void handle_launch_request(ProtocolMessage *req, ProtocolMessage *res,
|
|
lua_State *L, FILE *out) {
|
|
if (debugger_state != DEBUGGER_INITIALIZED) {
|
|
vscode_send_error_response(req, res, VSCODE_LAUNCH_RESPONSE,
|
|
"not initialized or unexpected state", out,
|
|
my_logger);
|
|
return;
|
|
}
|
|
/* We allow the user to set LUA_PATH and LUA_CPATH */
|
|
if (req->u.Request.u.LaunchRequest.lua_path[0]) {
|
|
set_package_var(L, "path", req->u.Request.u.LaunchRequest.lua_path);
|
|
}
|
|
if (req->u.Request.u.LaunchRequest.lua_cpath[0]) {
|
|
set_package_var(L, "cpath", req->u.Request.u.LaunchRequest.lua_cpath);
|
|
}
|
|
/* We allow the user to change current directory */
|
|
if (req->u.Request.u.LaunchRequest.cwd[0]) {
|
|
if (chdir(req->u.Request.u.LaunchRequest.cwd) != 0) {
|
|
char temp[1024];
|
|
snprintf(temp, sizeof temp, "Unable to change directory to %s\n",
|
|
req->u.Request.u.LaunchRequest.cwd);
|
|
vscode_send_output_event(res, "console", temp, out, my_logger);
|
|
vscode_send_error_response(req, res, VSCODE_LAUNCH_RESPONSE,
|
|
"Launch failed", out, my_logger);
|
|
return;
|
|
} else {
|
|
/* Make a note of the working directory so that we can work out the
|
|
path name of any source files */
|
|
ravi_string_copy(workingdir, req->u.Request.u.LaunchRequest.cwd,
|
|
sizeof workingdir);
|
|
}
|
|
}
|
|
const char *progname = req->u.Request.u.LaunchRequest.program;
|
|
Program prog;
|
|
parse_launch_program(&prog, progname);
|
|
int status = luaL_loadfile(L, prog.progpath);
|
|
if (status != LUA_OK) {
|
|
char temp[1024];
|
|
snprintf(temp, sizeof temp, "Failed to launch %s due to error: %s\n",
|
|
prog.progpath, lua_tostring(L, -1));
|
|
vscode_send_output_event(res, "console", temp, out, my_logger);
|
|
vscode_send_error_response(req, res, VSCODE_LAUNCH_RESPONSE,
|
|
"Launch failed", out, my_logger);
|
|
lua_pop(L, 1);
|
|
return;
|
|
} else {
|
|
/* Lua program successfully compiled. Although we have not yet
|
|
launched, we tell VSCode that we have at this stage */
|
|
vscode_send_success_response(req, res, VSCODE_LAUNCH_RESPONSE, out,
|
|
my_logger);
|
|
}
|
|
if (req->u.Request.u.LaunchRequest.stopOnEntry)
|
|
debugger_state = DEBUGGER_PROGRAM_STEPPING;
|
|
else
|
|
debugger_state = DEBUGGER_PROGRAM_RUNNING;
|
|
/* create arg table if applicable */
|
|
if (prog.argc > 0) {
|
|
lua_createtable(L, 2, 1);
|
|
lua_pushstring(L, prog.progpath);
|
|
lua_rawseti(L, -2, 0); /* arg[0] will have program */
|
|
for (int i = 0; i < MAX_PROG_ARGS && i < prog.argc; i++) {
|
|
lua_pushstring(L, prog.argv[i]);
|
|
lua_rawseti(L, -2, i+1);
|
|
}
|
|
lua_setglobal(L, "arg");
|
|
}
|
|
/* Start the Lua code! */
|
|
/* From here on the debugger will get control inside the debugger() function
|
|
below which is setup as a Lua hook whenever Lua steps across a new line of
|
|
code. When control gets back here it means the program finished executing
|
|
*/
|
|
if (lua_pcall(L, 0, 0, 0)) {
|
|
char temp[1024];
|
|
snprintf(temp, sizeof temp, "Program terminated with error: %s\n",
|
|
lua_tostring(L, -1));
|
|
vscode_send_output_event(res, "console", temp, out, my_logger);
|
|
lua_pop(L, 1);
|
|
}
|
|
vscode_send_terminated_event(res, out, my_logger);
|
|
debugger_state = DEBUGGER_PROGRAM_TERMINATED;
|
|
}
|
|
|
|
static int compare_paths(const char *a, const char *b) {
|
|
/* skip any drive letters */
|
|
const char *p = strchr(a, ':');
|
|
if (p)
|
|
a = p + 1;
|
|
p = strchr(b, ':');
|
|
if (p)
|
|
b = p + 1;
|
|
/* case sensitive compare */
|
|
return strcmp(a, b);
|
|
}
|
|
|
|
/**
|
|
* Called via Lua Hook or from main()
|
|
* If called from main then debugger_state == DEBUGGER_BIRTH and ar == NULL
|
|
*/
|
|
static void debugger(lua_State *L, lua_Debug *ar, FILE *in, FILE *out) {
|
|
if (debugger_state == DEBUGGER_PROGRAM_TERMINATED) {
|
|
return;
|
|
}
|
|
|
|
/* If the program is running or stepping over/out
|
|
then check if we hit a breakpoint. */
|
|
if (debugger_state == DEBUGGER_PROGRAM_RUNNING ||
|
|
(debugger_state == DEBUGGER_PROGRAM_STEPPING &&
|
|
(stepping_mode == DEBUGGER_STEPPING_OUT ||
|
|
stepping_mode == DEBUGGER_STEPPING_OVER))) {
|
|
int initialized = 0;
|
|
for (int j = 0; j < MAX_BREAKPOINTS; j++) {
|
|
/* fast check - are we on a breakpoint line number */
|
|
if (!breakpoints[j].source.path[0] ||
|
|
ar->currentline != breakpoints[j].line)
|
|
continue;
|
|
/* potential match of breakpoint line - we need to check the source name
|
|
*/
|
|
if (!initialized) initialized = lua_getinfo(L, "S", ar);
|
|
if (!initialized) break;
|
|
if (ar->source[0] == '@') {
|
|
/* Only support breakpoints on source files */
|
|
if (compare_paths(breakpoints[j].source.path, ar->source + 1) == 0) {
|
|
/* hit breakpoint */
|
|
debugger_state = DEBUGGER_PROGRAM_STEPPING;
|
|
stepping_mode = DEBUGGER_STEPPING_IN;
|
|
stepping_stacklevel = -1;
|
|
stepping_lua_State = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (debugger_state == DEBUGGER_PROGRAM_RUNNING) return;
|
|
}
|
|
if (debugger_state == DEBUGGER_PROGRAM_STEPPING) {
|
|
/* First check stepping over/out conditions and continue
|
|
execution if not satisfied */
|
|
if (stepping_mode == DEBUGGER_STEPPING_OVER && L == stepping_lua_State &&
|
|
ar->stacklevel > stepping_stacklevel)
|
|
return; /* we are deeper into the stack, so continue execution */
|
|
else if (stepping_mode == DEBUGGER_STEPPING_OUT &&
|
|
L == stepping_lua_State && ar->stacklevel >= stepping_stacklevel)
|
|
return; /* we are still in current function or deeper so continue
|
|
execution */
|
|
/* OK so we are going to be stopping */
|
|
/* running within Lua at line change */
|
|
if (!thread_event_sent) {
|
|
/* thread started - only sent once in the debug session */
|
|
thread_event_sent = 1;
|
|
vscode_send_thread_event(&res, true, out, my_logger);
|
|
/* Inform VSCode we have stopped */
|
|
vscode_send_stopped_event(&res, "entry", out, my_logger);
|
|
} else {
|
|
/* Inform VSCode we have stopped */
|
|
vscode_send_stopped_event(&res, "step", out, my_logger);
|
|
}
|
|
debugger_state = DEBUGGER_PROGRAM_STOPPED;
|
|
}
|
|
/* Reset stepping mode */
|
|
stepping_mode = DEBUGGER_STEPPING_IN;
|
|
stepping_stacklevel = -1;
|
|
|
|
/* Wait for debugger command */
|
|
bool get_command = true;
|
|
int command = VSCODE_UNKNOWN_REQUEST;
|
|
// Sleep(60 * 1000);
|
|
while (get_command &&
|
|
(command = vscode_get_request(in, &readbuf, &req, my_logger)) !=
|
|
VSCODE_EOF) {
|
|
switch (command) {
|
|
case VSCODE_INITIALIZE_REQUEST: {
|
|
handle_initialize_request(&req, &res, out);
|
|
break;
|
|
}
|
|
case VSCODE_LAUNCH_REQUEST: {
|
|
handle_launch_request(&req, &res, L, out);
|
|
break;
|
|
}
|
|
case VSCODE_STACK_TRACE_REQUEST: {
|
|
handle_stack_trace_request(&req, &res, L, out);
|
|
break;
|
|
}
|
|
case VSCODE_SCOPES_REQUEST: {
|
|
handle_scopes_request(&req, &res, L, out);
|
|
break;
|
|
}
|
|
case VSCODE_VARIABLES_REQUEST: {
|
|
handle_variables_request(&req, &res, L, out);
|
|
break;
|
|
}
|
|
case VSCODE_SOURCE_REQUEST: {
|
|
handle_source_request(&req, &res, L, out);
|
|
break;
|
|
}
|
|
case VSCODE_DISCONNECT_REQUEST: {
|
|
vscode_send_terminated_event(&res, out, my_logger);
|
|
debugger_state = DEBUGGER_PROGRAM_TERMINATED;
|
|
vscode_send_success_response(&req, &res, VSCODE_DISCONNECT_RESPONSE,
|
|
out, my_logger);
|
|
exit(0);
|
|
}
|
|
case VSCODE_SET_EXCEPTION_BREAKPOINTS_REQUEST: {
|
|
vscode_send_success_response(&req, &res,
|
|
VSCODE_SET_EXCEPTION_BREAKPOINTS_RESPONSE,
|
|
out, my_logger);
|
|
break;
|
|
}
|
|
case VSCODE_SET_BREAKPOINTS_REQUEST: {
|
|
handle_set_breakpoints_request(&req, &res, out, my_logger);
|
|
break;
|
|
}
|
|
case VSCODE_CONFIGURATION_DONE_REQUEST: {
|
|
vscode_send_success_response(
|
|
&req, &res, VSCODE_CONFIGURATION_DONE_RESPONSE, out, my_logger);
|
|
break;
|
|
}
|
|
case VSCODE_THREAD_REQUEST: {
|
|
handle_thread_request(&req, &res, out);
|
|
break;
|
|
}
|
|
case VSCODE_STEPIN_REQUEST: {
|
|
vscode_send_success_response(&req, &res, VSCODE_STEPIN_RESPONSE, out,
|
|
my_logger);
|
|
debugger_state = DEBUGGER_PROGRAM_STEPPING;
|
|
get_command = false;
|
|
break;
|
|
}
|
|
case VSCODE_STEPOUT_REQUEST: {
|
|
vscode_send_success_response(&req, &res, VSCODE_STEPOUT_RESPONSE, out,
|
|
my_logger);
|
|
debugger_state = DEBUGGER_PROGRAM_STEPPING;
|
|
stepping_stacklevel = ar->stacklevel;
|
|
stepping_mode = DEBUGGER_STEPPING_OUT;
|
|
stepping_lua_State = L;
|
|
get_command = false;
|
|
break;
|
|
}
|
|
case VSCODE_NEXT_REQUEST: {
|
|
/* Step Over */
|
|
vscode_send_success_response(&req, &res, VSCODE_NEXT_RESPONSE, out,
|
|
my_logger);
|
|
debugger_state = DEBUGGER_PROGRAM_STEPPING;
|
|
stepping_stacklevel = ar->stacklevel;
|
|
stepping_mode = DEBUGGER_STEPPING_OVER;
|
|
stepping_lua_State = L;
|
|
get_command = false;
|
|
break;
|
|
}
|
|
case VSCODE_CONTINUE_REQUEST: {
|
|
debugger_state = DEBUGGER_PROGRAM_RUNNING;
|
|
vscode_send_success_response(&req, &res, VSCODE_CONTINUE_RESPONSE, out,
|
|
my_logger);
|
|
get_command = false;
|
|
break;
|
|
}
|
|
default: {
|
|
char msg[100];
|
|
snprintf(msg, sizeof msg, "%s not yet implemented",
|
|
req.u.Request.command);
|
|
fprintf(my_logger, "%s\n", msg);
|
|
vscode_send_error_response(&req, &res, command, msg, out, my_logger);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Lua Hook used by the debugger
|
|
* Setup to intercept at every line change
|
|
*/
|
|
void ravi_debughook(lua_State *L, lua_Debug *ar) {
|
|
int event = ar->event;
|
|
if (event == LUA_HOOKLINE) {
|
|
debugger(L, ar, stdin, stdout);
|
|
}
|
|
}
|
|
|
|
void ravi_debug_writestring(const char *s, size_t l) {
|
|
size_t already_used = strlen(output_buffer);
|
|
char *buf = output_buffer + already_used;
|
|
size_t buflen = sizeof output_buffer - already_used - 1;
|
|
int overflow = 0;
|
|
if (l >= buflen) {
|
|
l = buflen - 1;
|
|
overflow = 1;
|
|
}
|
|
ravi_string_copy(buf, s, l + 1);
|
|
if (overflow || *s == '\n') {
|
|
vscode_send_output_event(&output_response, "stdout", output_buffer, stdout,
|
|
my_logger);
|
|
output_buffer[0] = 0;
|
|
}
|
|
}
|
|
|
|
void ravi_debug_writeline(void) { ravi_debug_writestring("\n", 1); }
|
|
|
|
void ravi_debug_writestringerror(const char *fmt, const char *p) {
|
|
char temp[256];
|
|
snprintf(temp, sizeof temp, fmt, p);
|
|
vscode_send_output_event(&output_response, "stderr", temp, stdout, my_logger);
|
|
}
|
|
|
|
/*
|
|
** Create the 'arg' table, which stores all arguments from the
|
|
** command line ('argv'). It should be aligned so that, at index 0,
|
|
** it has 'argv[script]', which is the script name. The arguments
|
|
** to the script (everything after 'script') go to positive indices;
|
|
** other arguments (before the script name) go to negative indices.
|
|
** If there is no script name, assume interpreter's name as base.
|
|
*/
|
|
static void createargtable(lua_State *L, char **argv, int argc, int script) {
|
|
int i, narg;
|
|
if (script == argc) script = 0; /* no script name? */
|
|
narg = argc - (script + 1); /* number of positive indices */
|
|
lua_createtable(L, narg, script + 1);
|
|
for (i = 0; i < argc; i++) {
|
|
lua_pushstring(L, argv[i]);
|
|
lua_rawseti(L, -2, i - script);
|
|
}
|
|
lua_setglobal(L, "arg");
|
|
}
|
|
|
|
static inline bool is_bigendian() {
|
|
static const int i = 1;
|
|
return (*((char *)&i)) == 0;
|
|
}
|
|
|
|
/*
|
|
* Entry point for the debugger
|
|
* The debugger will use stdin/stdout to interact with VSCode
|
|
* The protocol used is described in protocol.h.
|
|
*/
|
|
int main(int argc, char **argv) {
|
|
if (is_bigendian()) {
|
|
fprintf(stderr, "Big endian architecture not supported\n");
|
|
exit(1);
|
|
}
|
|
membuff_init(&readbuf, 0);
|
|
/* For debugging purposes we log the interaction */
|
|
#ifdef _WIN32
|
|
my_logger = fopen("/temp/out1.txt", "w");
|
|
#else
|
|
my_logger = fopen("/tmp/out1.txt", "w");
|
|
#endif
|
|
if (my_logger == NULL) my_logger = stderr;
|
|
#ifdef _WIN32
|
|
/* The VSCode debug protocol requires binary IO */
|
|
_setmode(_fileno(stdout), _O_BINARY);
|
|
#endif
|
|
/* switch off buffering */
|
|
setbuf(my_logger, NULL);
|
|
setbuf(stdout, NULL);
|
|
lua_State *L = luaL_newstate(); /* create Lua state */
|
|
if (L == NULL) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
/* redirect Lua's stdout and stderr */
|
|
ravi_set_writefuncs(L, ravi_debug_writestring, ravi_debug_writeline,
|
|
ravi_debug_writestringerror);
|
|
luaL_checkversion(L); /* check that interpreter has correct version */
|
|
luaL_openlibs(L); /* open standard libraries */
|
|
createargtable(L, argv, argc, 0); /* Create a the args global in Lua */
|
|
lua_sethook(L, ravi_debughook, LUA_MASKCALL | LUA_MASKLINE | LUA_MASKRET, 0);
|
|
debugger_state = DEBUGGER_BIRTH;
|
|
ravi_set_debugger_data(
|
|
L, &debugger_state); /* This is useless data right now but it ensures that
|
|
the hook cannot be removed */
|
|
debugger(L, NULL, stdin, stdout);
|
|
lua_close(L);
|
|
fclose(my_logger);
|
|
membuff_free(&readbuf);
|
|
return 0;
|
|
}
|