Lua debugger: output improvements, eval command.

This commit is contained in:
antirez 2015-11-11 10:15:26 +01:00
parent 1f8d614423
commit cf4700bda4
2 changed files with 186 additions and 54 deletions

View File

@ -526,11 +526,9 @@ sds sdsCatColorizedLdbReply(sds o, char *s, size_t len) {
if (strstr(s,"<reply>")) color = "cyan";
if (strstr(s,"<error>")) color = "red";
if (strstr(s,"<value>")) color = "magenta";
if (isdigit(s[0])) {
char *p = s+1;
while(isdigit(*p)) p++;
if (*p == '*') color = "yellow"; /* Current line. */
else if (*p == '#') color = "bold"; /* Break point. */
if (len > 4 && isdigit(s[3])) {
if (s[1] == '>') color = "yellow"; /* Current line. */
else if (s[2] == '#') color = "bold"; /* Break point. */
}
return sdscatcolor(o,s,len,color);
}
@ -1030,6 +1028,28 @@ static int issueCommand(int argc, char **argv) {
return issueCommandRepeat(argc, argv, config.repeat);
}
/* Split the user provided command into multiple SDS arguments.
* This function normally uses sdssplitargs() from sds.c which is able
* to understand "quoted strings", escapes and so forth. However when
* we are in Lua debugging mode and the "eval" command is used, we want
* the remaining Lua script (after "e " or "eval ") to be passed verbatim
* as a single big argument. */
static sds *cliSplitArgs(char *line, int *argc) {
if (config.eval_ldb && (strstr(line,"eval ") == line ||
strstr(line,"e ") == line))
{
sds *argv = zmalloc(sizeof(sds)*2);
*argc = 2;
int len = strlen(line);
int elen = line[1] == ' ' ? 2 : 5; /* "e " or "eval "? */
argv[0] = sdsnewlen(line,elen-1);
argv[1] = sdsnewlen(line+elen,len-elen);
return argv;
} else {
return sdssplitargs(line,argc);
}
}
static void repl(void) {
sds historyfile = NULL;
int history = 0;
@ -1053,7 +1073,7 @@ static void repl(void) {
cliRefreshPrompt();
while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
if (line[0] != '\0') {
argv = sdssplitargs(line,&argc);
argv = cliSplitArgs(line,&argc);
if (history) linenoiseHistoryAdd(line);
if (historyfile) linenoiseHistorySave(historyfile);

View File

@ -51,9 +51,11 @@ void ldbEnable(client *c);
void evalGenericCommandWithDebugging(client *c, int evalsha);
void luaLdbLineHook(lua_State *lua, lua_Debug *ar);
void ldbLog(sds entry);
void ldbLogRedisReply(char *reply, size_t maxlen);
/* Debugger shared state is stored inside this global structure. */
#define LDB_BREAKPOINTS_MAX 64
#define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */
#define LDB_REPLY_MAX_LOG_LEN 60 /* Max chars when logging a reply. */
struct ldbState {
int fd; /* Socket of the debugging client. */
int active; /* Are we debugging EVAL right now? */
@ -107,13 +109,11 @@ void sha1hex(char *digest, char *script, size_t len) {
* Basically we take the arguments, execute the Redis command in the context
* of a non connected client, then take the generated reply and convert it
* into a suitable Lua type. With this trick the scripting feature does not
* need the introduction of a full Redis internals API. Basically the script
* need the introduction of a full Redis internals API. The script
* is like a normal client that bypasses all the slow I/O paths.
*
* Note: in this function we do not do any sanity check as the reply is
* generated by Redis directly. This allows us to go faster.
* The reply string can be altered during the parsing as it is discarded
* after the conversion is completed.
*
* Errors are returned as a table with a single 'err' field set to the
* error string.
@ -123,21 +123,11 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) {
char *p = reply;
switch(*p) {
case ':':
p = redisProtocolToLuaType_Int(lua,reply);
break;
case '$':
p = redisProtocolToLuaType_Bulk(lua,reply);
break;
case '+':
p = redisProtocolToLuaType_Status(lua,reply);
break;
case '-':
p = redisProtocolToLuaType_Error(lua,reply);
break;
case '*':
p = redisProtocolToLuaType_MultiBulk(lua,reply);
break;
case ':': p = redisProtocolToLuaType_Int(lua,reply); break;
case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break;
case '+': p = redisProtocolToLuaType_Status(lua,reply); break;
case '-': p = redisProtocolToLuaType_Error(lua,reply); break;
case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply); break;
}
return p;
}
@ -579,12 +569,8 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
redisProtocolToLuaType(lua,reply);
/* If the debugger is active, log the reply from Redis. */
if (ldb.active && ldb.step) {
sds replycopy = sdsnew("<reply> ");
replycopy = sdscat(replycopy,reply); /* It's always null terminated. */
if (sdslen(replycopy) > 70) sdsrange(replycopy,0,69);
ldbLog(replycopy);
}
if (ldb.active && ldb.step)
ldbLogRedisReply(reply,LDB_REPLY_MAX_LOG_LEN);
/* Sort the output array if needed, assuming it is a non-null multi bulk
* reply as expected. */
@ -1687,6 +1673,25 @@ protoerr:
return NULL;
}
/* Log the specified line in the Lua debugger output. */
void ldbLogSourceLine(int lnum) {
char *line = ldbGetSourceLine(lnum);
char *prefix;
int bp = ldbIsBreakpoint(lnum);
int current = ldb.currentline == lnum;
if (current && bp)
prefix = "->#";
else if (current)
prefix = "-> ";
else if (bp)
prefix = " #";
else
prefix = " ";
sds thisline = sdscatprintf(sdsempty(),"%s%-3d %s", prefix, lnum, line);
ldbLog(thisline);
}
/* Implement the "list" command of the Lua debugger. If around is 0
* the whole file is listed, otherwise only a small portion of the file
* around the specified line is shown. When a line number is specified
@ -1697,22 +1702,16 @@ void ldbList(int around, int context) {
for (j = 1; j <= ldb.lines; j++) {
if (around != 0 && abs(around-j) > context) continue;
char *line = ldbGetSourceLine(j);
int mark;
if (ldb.currentline == j)
mark = '*';
else
mark = ldbIsBreakpoint(j) ? '#' : ':';
sds thisline = sdscatprintf(sdsempty(),"%d%c %s", j, mark, line);
ldbLog(thisline);
ldbLogSourceLine(j);
}
}
/* Produce a debugger log entry representing the value of the Lua object
* currently on the top of the stack. */
void ldbLogStackValue(lua_State *lua) {
* currently on the top of the stack. As a side effect the element is
* popped. */
void ldbLogStackValue(lua_State *lua, char *prefix) {
int t = lua_type(lua,-1);
sds s = sdsnew("<value> ");
sds s = sdsnew(prefix);
switch(t) {
case LUA_TSTRING:
@ -1751,6 +1750,91 @@ void ldbLogStackValue(lua_State *lua) {
lua_pop(lua,1);
}
char *ldbRedisProtocolToHuman_Int(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Status(sds *o, char *reply);
char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply);
/* Get Redis protocol from 'reply' and appends it in human readable form to
* the passed SDS string 'o'.
*
* Note that the SDS string is passed by reference (pointer of pointer to
* char*) so that we can return a modified pointer, as for SDS semantics. */
char *ldbRedisProtocolToHuman(sds *o, char *reply) {
char *p = reply;
switch(*p) {
case ':': p = ldbRedisProtocolToHuman_Int(o,reply); break;
case '$': p = ldbRedisProtocolToHuman_Bulk(o,reply); break;
case '+': p = ldbRedisProtocolToHuman_Status(o,reply); break;
case '-': p = ldbRedisProtocolToHuman_Status(o,reply); break;
case '*': p = ldbRedisProtocolToHuman_MultiBulk(o,reply); break;
}
return p;
}
char *ldbRedisProtocolToHuman_Int(sds *o, char *reply) {
char *p = strchr(reply+1,'\r');
*o = sdscatlen(*o,reply+1,p-reply-1);
return p+2;
}
char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply) {
char *p = strchr(reply+1,'\r');
long long bulklen;
string2ll(reply+1,p-reply-1,&bulklen);
if (bulklen == -1) {
*o = sdscatlen(*o,"NULL",4);
return p+2;
} else {
*o = sdscatrepr(*o,p+2,bulklen);
return p+2+bulklen+2;
}
}
char *ldbRedisProtocolToHuman_Status(sds *o, char *reply) {
char *p = strchr(reply+1,'\r');
*o = sdscatrepr(*o,reply,p-reply);
return p+2;
}
char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply) {
char *p = strchr(reply+1,'\r');
long long mbulklen;
int j = 0;
string2ll(reply+1,p-reply-1,&mbulklen);
p += 2;
if (mbulklen == -1) {
*o = sdscatlen(*o,"NULL",4);
return p;
}
*o = sdscatlen(*o,"[",1);
for (j = 0; j < mbulklen; j++) {
p = ldbRedisProtocolToHuman(o,p);
if (j != mbulklen-1) *o = sdscatlen(*o,",",1);
}
*o = sdscatlen(*o,"]",1);
return p;
}
/* Log a Redis reply as debugger output, in an human readable format.
* If the resulting string is longer than 'len' plus a few more chars
* used as prefix, it gets truncated. */
void ldbLogRedisReply(char *reply, size_t maxlen) {
sds log = sdsnew("<reply> ");
maxlen += sdslen(log);
ldbRedisProtocolToHuman(&log,reply);
/* Trip and add ... if the length was reached, to hint the user it's not
* the whole reply. */
if (sdslen(log) > maxlen) {
sdsrange(log,0,maxlen-1);
log = sdscatlen(log," ...",4);
}
ldbLog(log);
}
/* Implements the "print" command of the Lua debugger. It scans for Lua
* var "varname" starting from the current stack frame up to the top stack
* frame. The first matching variable is printed. */
@ -1765,7 +1849,7 @@ void ldbPrint(lua_State *lua, char *varname) {
while((name = lua_getlocal(lua,&ar,i)) != NULL) {
i++;
if (strcmp(varname,name) == 0) {
ldbLogStackValue(lua);
ldbLogStackValue(lua,"<value> ");
return;
} else {
lua_pop(lua,1); /* Discard the var name on the stack. */
@ -1784,10 +1868,8 @@ void ldbBreak(sds *argv, int argc) {
} else {
ldbLog(sdscatfmt(sdsempty(),"%i breakpoints set:",ldb.bpcount));
int j;
for (j = 0; j < ldb.bpcount; j++) {
ldbLog(sdscatfmt(sdsempty(),"%i# %s", ldb.bp[j],
ldbGetSourceLine(ldb.bp[j])));
}
for (j = 0; j < ldb.bpcount; j++)
ldbLogSourceLine(ldb.bp[j]);
}
} else {
int j;
@ -1819,6 +1901,28 @@ void ldbBreak(sds *argv, int argc) {
}
}
/* Implements the Lua debugger "eval" command. It just compiles the user
* passed fragment of code and executes it, showing the result left on
* the stack. */
void ldbEval(lua_State *lua, sds *argv, int argc) {
/* Glue the script together if it is composed of multiple arguments. */
sds code = sdsjoinsds(argv+1,argc-1," ",1);
if (luaL_loadbuffer(lua,code,sdslen(code),"@ldb_eval")) {
ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1)));
lua_pop(lua,1);
sdsfree(code);
return;
}
sdsfree(code);
if (lua_pcall(lua,0,1,0)) {
ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1)));
lua_pop(lua,1);
return;
}
ldbLogStackValue(lua,"<retval> ");
}
/* Read debugging commands from client. */
void ldbRepl(lua_State *lua) {
sds *argv;
@ -1845,8 +1949,9 @@ void ldbRepl(lua_State *lua) {
ldb.cbuf = sdsempty();
/* Execute the command. */
if (!strcasecmp(argv[0],"help")) {
if (!strcasecmp(argv[0],"h") || !strcasecmp(argv[0],"help")) {
ldbLog(sdsnew("Redis Lua debugger help:"));
ldbLog(sdsnew("[h]elp Show this help."));
ldbLog(sdsnew("[s]tep Run current line and stop again."));
ldbLog(sdsnew("[n]ext Alias for step."));
ldbLog(sdsnew("[c]continue Run till next breakpoint."));
@ -1857,6 +1962,7 @@ ldbLog(sdsnew("[b]eark Show all breakpoints."));
ldbLog(sdsnew("[b]eark <line> Add a breakpoint to the specified line."));
ldbLog(sdsnew("[b]eark -<line> Remove breakpoint from the specified line."));
ldbLog(sdsnew("[b]eark 0 Remove all breakpoints."));
ldbLog(sdsnew("[e]eval <code> Execute some Lua code in a new callframe."));
ldbSendLogs();
} else if (!strcasecmp(argv[0],"s") || !strcasecmp(argv[0],"step") ||
!strcasecmp(argv[0],"n") || !strcasecmp(argv[0],"next")) {
@ -1864,9 +1970,12 @@ ldbLog(sdsnew("[b]eark 0 Remove all breakpoints."));
break;
} else if (!strcasecmp(argv[0],"c") || !strcasecmp(argv[0],"continue")){
break;
} else if (!strcasecmp(argv[0],"b") || !strcasecmp(argv[0],"break")){
} else if (!strcasecmp(argv[0],"b") || !strcasecmp(argv[0],"break")) {
ldbBreak(argv,argc);
ldbSendLogs();
} else if (!strcasecmp(argv[0],"e") || !strcasecmp(argv[0],"eval")) {
ldbEval(lua,argv,argc);
ldbSendLogs();
} else if (argc == 2 &&
(!strcasecmp(argv[0],"p") || !strcasecmp(argv[0],"print")))
{
@ -1879,7 +1988,7 @@ ldbLog(sdsnew("[b]eark 0 Remove all breakpoints."));
ldbList(around,ctx);
ldbSendLogs();
} else {
ldbLog(sdsnew("Unknown Redis Lua debugger command or "
ldbLog(sdsnew("<error> Unknown Redis Lua debugger command or "
"wrong number of arguments."));
ldbSendLogs();
}
@ -1899,12 +2008,15 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
lua_getinfo(lua,"Sl",ar);
if(strstr(ar->short_src,"user_script") == NULL) return;
if (ldb.step || ldbIsBreakpoint(ar->currentline)) {
int bp = ldbIsBreakpoint(ar->currentline);
if (ldb.step || bp) {
char *reason = bp ? "break point" : "step over";
ldb.currentline = ar->currentline;
ldb.step = 0;
int mark = ldbIsBreakpoint(ldb.currentline) ? '#' : '*';
ldbLog(sdscatprintf(sdsempty(),"%d%c %s", (int)ar->currentline,
mark, ldbGetSourceLine(ar->currentline)));
ldbLog(sdscatprintf(sdsempty(),
"* Stopped at %d, stop reason = %s",
ldb.currentline, reason));
ldbLogSourceLine(ldb.currentline);
ldbSendLogs();
ldbRepl(lua);
}