Lua debugger: initial REPL.

This commit is contained in:
antirez 2015-11-09 11:08:57 +01:00
parent c494db89b5
commit d3d1fa9437
2 changed files with 164 additions and 20 deletions

View File

@ -375,8 +375,15 @@ static int cliSelect(void) {
* even if there is already a connected socket. */ * even if there is already a connected socket. */
static int cliConnect(int force) { static int cliConnect(int force) {
if (context == NULL || force) { if (context == NULL || force) {
if (context != NULL) if (context != NULL) {
redisFree(context); redisFree(context);
/* Disconnection from the server signals end of EVAL
* debugging session. */
if (config.eval_ldb) {
config.eval_ldb = 0;
cliRefreshPrompt();
}
}
if (config.hostsocket == NULL) { if (config.hostsocket == NULL) {
context = redisConnect(config.hostip,config.hostport); context = redisConnect(config.hostip,config.hostport);
@ -637,7 +644,8 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
size_t *argvlen; size_t *argvlen;
int j, output_raw; int j, output_raw;
if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) { if (!config.eval_ldb && /* In debugging mode, let's pass "help" to Redis. */
(!strcasecmp(command,"help") || !strcasecmp(command,"?"))) {
cliOutputHelp(--argc, ++argv); cliOutputHelp(--argc, ++argv);
return REDIS_OK; return REDIS_OK;
} }

View File

@ -62,7 +62,9 @@ struct ldbState {
int bp[LDB_BREAKPOINTS_MAX]; /* An array of breakpoints line numbers. */ int bp[LDB_BREAKPOINTS_MAX]; /* An array of breakpoints line numbers. */
int bpcount; /* Number of valid entries inside bp. */ int bpcount; /* Number of valid entries inside bp. */
int step; /* Stop at next line ragardless of breakpoints. */ int step; /* Stop at next line ragardless of breakpoints. */
robj *src; /* Lua script source code. */ sds *src; /* Lua script source code split by line. */
int lines; /* Number of lines in 'src'. */
sds cbuf; /* Debugger client command buffer. */
} ldb; } ldb;
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
@ -1424,10 +1426,10 @@ void ldbInit(void) {
ldb.fd = -1; ldb.fd = -1;
ldb.active = 0; ldb.active = 0;
ldb.logs = listCreate(); ldb.logs = listCreate();
ldb.traces = listCreate();
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree); listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
listSetFreeMethod(ldb.traces, (void (*)(void*))sdsfree);
ldb.src = NULL; ldb.src = NULL;
ldb.lines = 0;
ldb.cbuf = sdsempty();
} }
/* Remove all the pending messages in the specified list. */ /* Remove all the pending messages in the specified list. */
@ -1442,10 +1444,11 @@ void ldbFlushLog(list *log) {
void ldbEnable(client *c) { void ldbEnable(client *c) {
c->flags |= CLIENT_LUA_DEBUG; c->flags |= CLIENT_LUA_DEBUG;
ldbFlushLog(ldb.logs); ldbFlushLog(ldb.logs);
ldbFlushLog(ldb.traces);
ldb.fd = c->fd; ldb.fd = c->fd;
ldb.step = 0; ldb.step = 1;
ldb.bpcount = 0; ldb.bpcount = 0;
sdsfree(ldb.cbuf);
ldb.cbuf = sdsempty();
} }
void ldbDisable(client *c) { void ldbDisable(client *c) {
@ -1453,14 +1456,25 @@ void ldbDisable(client *c) {
} }
/* Append a log entry to the specified LDB log. */ /* Append a log entry to the specified LDB log. */
void ldbLog(list *log, sds entry) { void ldbLog(sds entry) {
listAddNodeTail(log,entry); listAddNodeTail(ldb.logs,entry);
} }
/* Send ldb.logs and ldb.traces to the debugging client as a multi-bulk /* Send ldb.logs to the debugging client as a multi-bulk reply
* reply consisting of simple strings. Log entries which include newlines * consisting of simple strings. Log entries which include newlines have them
* have them replaced with spaces. The entries sent are also consumed. */ * replaced with spaces. The entries sent are also consumed. */
void ldbWriteLogs(void) { void ldbSendLogs(void) {
sds proto = sdsempty();
proto = sdscatfmt(proto,"*%i\r\n", (int)listLength(ldb.logs));
while(listLength(ldb.logs)) {
listNode *ln = listFirst(ldb.logs);
proto = sdscatlen(proto,"+",1);
sdsmapchars(ln->value,"\r\n"," ",2);
proto = sdscatsds(proto,ln->value);
proto = sdscatlen(proto,"\r\n",2);
listDelNode(ldb.logs,ln);
}
write(ldb.fd,proto,sdslen(proto));
} }
/* Start a debugging session before calling EVAL implementation. /* Start a debugging session before calling EVAL implementation.
@ -1495,9 +1509,12 @@ int ldbStartSession(client *c) {
/* Setup our debugging session. */ /* Setup our debugging session. */
anetBlock(NULL,ldb.fd); anetBlock(NULL,ldb.fd);
anetSendTimeout(NULL,ldb.fd,5000);
ldb.active = 1; ldb.active = 1;
ldb.src = c->argv[1]; /* First argument of EVAL is the script itself. */ /* First argument of EVAL is the script itself. We split it into different
incrRefCount(ldb.src); * lines since this is the way the debugger accesses the source code. */
sds srcstring = c->argv[1]->ptr;
ldb.src = sdssplitlen(srcstring,sdslen(srcstring),"\n",1,&ldb.lines);
return 1; return 1;
} }
@ -1513,8 +1530,16 @@ void ldbEndSession(client *c) {
/* Otherwise let's restore client's state. */ /* Otherwise let's restore client's state. */
anetNonBlock(NULL,ldb.fd); anetNonBlock(NULL,ldb.fd);
anetSendTimeout(NULL,ldb.fd,0);
/* Close the client connectin after sending the final EVAL reply
* in order to signal the end of the debugging session. */
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
/* Cleanup. */
sdsfreesplitres(ldb.src,ldb.lines);
ldb.lines = 0;
ldb.active = 0; ldb.active = 0;
decrRefCount(ldb.src);
} }
/* Wrapper for EVAL / EVALSHA that enables debugging, and makes sure /* Wrapper for EVAL / EVALSHA that enables debugging, and makes sure
@ -1528,14 +1553,125 @@ void evalGenericCommandWithDebugging(client *c, int evalsha) {
} }
} }
/* Return a pointer to ldb.src source code line, considering line to be
* one-based, and returning a special string for out of range lines. */
char *ldbGetSourceLine(int line) {
int idx = line-1;
if (idx < 0 || idx >= ldb.lines) return "<out of range source code line>";
return ldb.src[line];
}
/* Expect a valid multi-bulk command in the debugging client query buffer.
* On success the command is parsed and returned as an array of SDS strings,
* otherwise NULL is returned and there is to read more buffer. */
sds *ldbReplParseCommand(int *argcp) {
sds *argv = NULL;
int argc = 0;
if (sdslen(ldb.cbuf) == 0) return NULL;
/* Working on a copy is simpler in this case. We can modify it freely
* for the sake of simpler parsing. */
sds copy = sdsdup(ldb.cbuf);
char *p = copy;
/* This Redis protocol parser is a joke... just the simplest thing that
* works in this context. It is also very forgiving regarding broken
* protocol. */
/* Seek and parse *<count>\r\n. */
p = strchr(p,'*'); if (!p) goto protoerr;
char *plen = p+1; /* Multi bulk len pointer. */
p = strstr(p,"\r\n"); if (!p) goto protoerr;
*p = '\0'; p += 2;
*argcp = atoi(plen);
if (*argcp <= 0 || *argcp > 1024) goto protoerr;
/* Parse each argument. */
argv = zmalloc(sizeof(sds)*(*argcp));
argc = 0;
while(argc < *argcp) {
if (*p != '$') goto protoerr;
plen = p+1; /* Bulk string len pointer. */
p = strstr(p,"\r\n"); if (!p) goto protoerr;
*p = '\0'; p += 2;
int slen = atoi(plen); /* Length of this arg. */
if (slen <= 0 || slen > 1024) goto protoerr;
argv[argc++] = sdsnewlen(p,slen);
p += slen; /* Skip the already parsed argument. */
if (p[0] != '\r' || p[1] != '\n') goto protoerr;
p += 2; /* Skip \r\n. */
}
sdsfree(copy);
return argv;
protoerr:
sdsfreesplitres(argv,argc);
sdsfree(copy);
return NULL;
}
/* Read debugging commands from client. */
void ldbRepl(void) {
sds *argv;
int argc;
/* We continue processing commands until a command that should return
* to the Lua interpreter is found. */
while(1) {
while((argv = ldbReplParseCommand(&argc)) == NULL) {
char buf[1024];
int nread = read(ldb.fd,buf,sizeof(buf));
if (nread <= 0) {
/* Make sure the script runs without user input since the
* client is no longer connected. */
ldb.step = 0;
ldb.bpcount = 0;
return;
}
ldb.cbuf = sdscatlen(ldb.cbuf,buf,nread);
}
/* Flush the old buffer. */
sdsfree(ldb.cbuf);
ldb.cbuf = sdsempty();
/* Execute the command. */
if (!strcasecmp(argv[0],"help")) {
ldbLog(sdsnew("Redis Lua debugger help:"));
ldbLog(sdsnew("[s]tep : run current line and stop again."));
ldbLog(sdsnew("[c]continue: run till next breakpoint."));
ldbSendLogs();
} else if (!strcasecmp(argv[0],"s") || !strcasecmp(argv[0],"step")) {
ldb.step = 1;
break;
} else if (!strcasecmp(argv[0],"c") || !strcasecmp(argv[0],"continue")){
break;
} else {
ldbLog(sdsnew("Unknown Redis Lua debugger command."));
ldbSendLogs();
}
/* Free the command vector. */
sdsfreesplitres(argv,argc);
}
/* Free the current command argv if we break inside the while loop. */
sdsfreesplitres(argv,argc);
}
/* This is the core of our Lua debugger, called each time Lua is about /* This is the core of our Lua debugger, called each time Lua is about
* to start executing a new line. */ * to start executing a new line. */
void luaLdbLineHook(lua_State *lua, lua_Debug *ar) { void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
lua_getstack(lua,0,ar); lua_getstack(lua,0,ar);
lua_getinfo(lua,"Sl",ar); lua_getinfo(lua,"Sl",ar);
if(strstr(ar->short_src,"user_script") != NULL) if(strstr(ar->short_src,"user_script") == NULL) return;
printf("%s %d\n", ar->short_src, (int) ar->currentline);
if (ldb.step) {
ldb.step = 0;
ldbLog(sdscatprintf(sdsempty(),"%d: %s", (int)ar->currentline,
ldbGetSourceLine(ar->currentline-1)));
ldbSendLogs();
ldbRepl();
}
} }