Tweaks to the Remote GDB interface to make it usable
This commit is contained in:
parent
4141e69003
commit
e00d927a02
|
@ -0,0 +1,111 @@
|
|||
#
|
||||
# This is very much a work in progress to show how we can use macros to make the
|
||||
# GDB interface a lot more useable. For example the next / step commands only
|
||||
# work if the stepper doesn't leave the current scope. Beyond that you have a
|
||||
# single hardware breakpoint which can be used as an hb or a wa. You have to
|
||||
# remember to delete the previous one, so the br macro does this for you.
|
||||
#
|
||||
set confirm off
|
||||
set print null-stop
|
||||
define br
|
||||
d
|
||||
hb $arg0
|
||||
end
|
||||
set pagination off
|
||||
|
||||
define prTV
|
||||
if $arg0
|
||||
set $type = $arg0.tt
|
||||
set $val = $arg0.value
|
||||
|
||||
if $type == 0
|
||||
# NIL
|
||||
printf "Nil\n"
|
||||
end
|
||||
if $type == 1
|
||||
# Boolean
|
||||
printf "Boolean: %u\n", $val->n
|
||||
end
|
||||
if $type == 2
|
||||
# ROTable
|
||||
printf "ROTable: %p\n", $val->p
|
||||
end
|
||||
if $type == 3
|
||||
# Light Function
|
||||
printf "Light Func: %p\n",p $arg $val->p
|
||||
end
|
||||
if $type == 4
|
||||
# Light User Data
|
||||
printf "Light Udata: %p\n", $val->pend
|
||||
end
|
||||
if $type == 5
|
||||
# Number
|
||||
printf "Boolean: %u\n", $val->n
|
||||
end
|
||||
if $type == 6
|
||||
# TString
|
||||
set $o = &($val.gc->ts.tsv)
|
||||
printf "Common header: next = %p, marked = 0x%01x\n", $o->next, $o->marked
|
||||
printf "String: hash = 0x%08x, len = %u : %s\n", $o->hash, $o->len, (char *)($ts+1)
|
||||
end
|
||||
if $type == 7
|
||||
# Table
|
||||
__printComHdr $arg0
|
||||
set $ts = (TString *)$val->p
|
||||
end
|
||||
if $type == 8
|
||||
# Function
|
||||
set $o = &($val->gc.cl.c)
|
||||
printf "Common header: next = %p, marked = 0x%01x\n", $o->next, $o->marked
|
||||
if $o->isC == 0
|
||||
set $o = &($val->gc.cl.l)
|
||||
printf "LClosure: nupvalues = %u, gclist = %p, env = %p, p = %p\n", $o->nupvalues, $o->gclist, $o->env, $o->p
|
||||
else
|
||||
printf "CClosure: nupvalues = %u, gclist = %p, env = %p, f = %p\np", $o->nupvalues, $o->gclist, $o->env, $o->f
|
||||
end
|
||||
end
|
||||
if $type == 9
|
||||
# UserData
|
||||
end
|
||||
if $type == 10
|
||||
# Thread
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
define prL
|
||||
if L > 0
|
||||
printf " stack: %u\n", L->top-L->base
|
||||
printf " hooking: %u, %u, %u, %u, %p\n", L->hookmask, L->allowhook, L->basehookcount, L->hookcount, L->hook
|
||||
end
|
||||
end
|
||||
|
||||
define dumpstrt
|
||||
set $st = $arg0
|
||||
set $i = 0
|
||||
while $i< $st->size
|
||||
set $o = &(((TString *)($st->hash[$i]))->tsv)
|
||||
while $o
|
||||
if $o->next
|
||||
printf "Slot: %5i %p %p %08x %02x %4u", $i, $o, $o->next, $o->hash, $o->marked, $o->len
|
||||
else
|
||||
printf "Slot: %5i %p %08x %02x %4u", $i, $o, $o->hash, $o->marked, $o->len
|
||||
end
|
||||
if $o->marked & 0x80
|
||||
printf "* %s\n", *(char **)($o+1)
|
||||
else
|
||||
printf " %s\n", (char *)($o+1)
|
||||
end
|
||||
set $o = &(((TString *)($o->next))->tsv)
|
||||
end
|
||||
set $i = $i + 1
|
||||
end
|
||||
end
|
||||
|
||||
define dumpRAMstrt
|
||||
dumpstrt &(L->l_G->strt)
|
||||
end
|
||||
|
||||
set history save on
|
||||
set history size 1000
|
||||
|
|
@ -12,11 +12,19 @@
|
|||
// This adds the asserts in LUA. It also adds some useful extras to the
|
||||
// node module. This is all silent in normal operation and so can be enabled
|
||||
// without any harm (except for the code size increase and slight slowdown)
|
||||
// Either here for a global change or in the DEFINES variable in the relevant
|
||||
// Makefile for ony one subdirectory. If you want to use the remote GDB to
|
||||
// handle breaks and failed assetions then enable DEVELOPMENT_USE GDB
|
||||
//#define DEVELOPMENT_TOOLS
|
||||
|
||||
//#define DEVELOPMENT_USE_GDB
|
||||
#ifdef DEVELOPMENT_TOOLS
|
||||
#if defined(LUA_CROSS_COMPILER) || !defined(DEVELOPMENT_USE_GDB)
|
||||
extern void luaL_assertfail(const char *file, int line, const char *message);
|
||||
#define lua_assert(x) ((x) ? (void) 0 : luaL_assertfail(__FILE__, __LINE__, #x))
|
||||
#else
|
||||
extern void luaL_dbgbreak(void);
|
||||
#define lua_assert(x) ((x) ? (void) 0 : luaL_dbgbreak())
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// This enables lots of debug output and changes the serial bit rate. This
|
||||
|
|
|
@ -44,6 +44,101 @@
|
|||
#define LUA_USECCLOSURES 0
|
||||
#define LUA_USELIGHTFUNCTIONS 1
|
||||
|
||||
//#define DEBUG_ALLOCATOR
|
||||
#ifdef DEBUG_ALLOCATOR
|
||||
/*
|
||||
** {======================================================================
|
||||
** Diagnosticd version for realloc. This is enabled only if the
|
||||
** DEBUG_ALLOCATOR is defined. It is a cutdown version of the allocator
|
||||
** used in the Lua Test Suite -- a compromise between the ability catch
|
||||
** most alloc/free errors and overruns and working within the RAM limits
|
||||
** of the ESP8266 architecture. ONLY FOR HEAVY HACKERS
|
||||
** =======================================================================
|
||||
*/
|
||||
#define this_realloc debug_realloc
|
||||
#define ASSERT(s) if (!(s)) {asm ("break 0,0" ::);}
|
||||
#define MARK 0x55 /* 01010101 (a nice pattern) */
|
||||
#define MARK4 0x55555555
|
||||
#define MARKSIZE 4 /* size of marks after each block */
|
||||
#define fillmem(mem,size) memset(mem, -MARK, size)
|
||||
|
||||
typedef struct Memcontrol { /* memory-allocator control variables */
|
||||
uint32_t numblocks;
|
||||
uint32_t total;
|
||||
uint32_t maxmem;
|
||||
uint32_t memlimit;
|
||||
} Memcontrol;
|
||||
static Memcontrol mc = {0,0,0,32768};
|
||||
|
||||
typedef union MemHeader {
|
||||
L_Umaxalign a; /* ensures maximum alignment for Header */
|
||||
struct {
|
||||
size_t size;
|
||||
int mark;
|
||||
} d;
|
||||
} MemHeader;
|
||||
|
||||
static void freeblock (MemHeader *block) {
|
||||
if (block) {
|
||||
size_t size = block->d.size;
|
||||
fillmem(block, sizeof(MemHeader) + size + MARKSIZE); /* erase block */
|
||||
c_free(block); /* actually free block */
|
||||
mc.numblocks--; /* update counts */
|
||||
mc.total -= size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void *debug_realloc (void *b, size_t oldsize, size_t size) {
|
||||
MemHeader *block = cast(MemHeader *, b);
|
||||
int i;
|
||||
if (block == NULL) {
|
||||
oldsize = 0;
|
||||
} else {
|
||||
block--; /* go to real header */
|
||||
ASSERT(block->d.mark == MARK4);
|
||||
ASSERT(oldsize == block->d.size);
|
||||
for (i = 0; i < MARKSIZE; i++) /* check marks after block */
|
||||
ASSERT (cast(char *, b)[oldsize + i] == MARK);
|
||||
}
|
||||
if (size == 0) {
|
||||
freeblock(block);
|
||||
return NULL;
|
||||
} else if (size > oldsize && mc.total+size-oldsize > mc.memlimit)
|
||||
return NULL; /* fake a memory allocation error */
|
||||
else {
|
||||
MemHeader *newblock;
|
||||
int i;
|
||||
size_t commonsize = (oldsize < size) ? oldsize : size;
|
||||
size_t realsize = sizeof(MemHeader) + size + MARKSIZE;
|
||||
newblock = cast(MemHeader *, c_malloc(realsize)); /* alloc a new block */
|
||||
if (newblock == NULL)
|
||||
return NULL; /* really out of memory? */
|
||||
if (block) {
|
||||
memcpy(newblock + 1, block + 1, commonsize); /* copy old contents */
|
||||
freeblock(block); /* erase (and check) old copy */
|
||||
}
|
||||
/* initialize new part of the block with something weird */
|
||||
fillmem(cast(char *, newblock + 1) + commonsize, size - commonsize);
|
||||
/* initialize marks after block */
|
||||
newblock->d.mark = MARK4;
|
||||
newblock->d.size = size;
|
||||
for (i = 0; i < MARKSIZE; i++)
|
||||
cast(char *, newblock + 1)[size + i] = MARK;
|
||||
mc.total += size;
|
||||
if (mc.total > mc.maxmem)
|
||||
mc.maxmem = mc.total;
|
||||
mc.numblocks++;
|
||||
return (newblock + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* }====================================================================== */
|
||||
#else
|
||||
#define this_realloc(p,os,s) c_realloc(p,s)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** {======================================================
|
||||
** Error-report functions
|
||||
|
@ -786,8 +881,12 @@ static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
|
|||
void *nptr;
|
||||
|
||||
if (nsize == 0) {
|
||||
#ifdef DEBUG_ALLOCATOR
|
||||
return (void *)this_realloc(ptr, osize, nsize);
|
||||
#else
|
||||
c_free(ptr);
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
if (L != NULL && (mode & EGC_ALWAYS)) /* always collect memory if requested */
|
||||
luaC_fullgc(L);
|
||||
|
@ -798,18 +897,45 @@ static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
|
|||
if(G(L)->memlimit > 0 && (mode & EGC_ON_MEM_LIMIT) && l_check_memlimit(L, nsize - osize))
|
||||
return NULL;
|
||||
}
|
||||
nptr = (void *)c_realloc(ptr, nsize);
|
||||
nptr = (void *)this_realloc(ptr, osize, nsize);
|
||||
if (nptr == NULL && L != NULL && (mode & EGC_ON_ALLOC_FAILURE)) {
|
||||
luaC_fullgc(L); /* emergency full collection. */
|
||||
nptr = (void *)c_realloc(ptr, nsize); /* try allocation again */
|
||||
nptr = (void *)this_realloc(ptr, osize, nsize); /* try allocation again */
|
||||
}
|
||||
return nptr;
|
||||
}
|
||||
|
||||
LUALIB_API void luaL_assertfail(const char *file, int line, const char *message) {
|
||||
dbg_printf("ASSERT@%s(%d): %s\n", file, line, message);
|
||||
#if defined(LUA_CROSS_COMPILER)
|
||||
exit(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef DEVELOPMENT_USE_GDB
|
||||
/*
|
||||
* This is a simple stub used by lua_assert() if DEVELOPMENT_USE_GDB is defined.
|
||||
* Instead of crashing out with an assert error, this hook starts the GDB remote
|
||||
* stub if not already running and then issues a break. The rationale here is
|
||||
* that when testing the developer migght be using screen/PuTTY to work ineractively
|
||||
* with the Lua Interpreter via UART0. However if an assert triggers, then there
|
||||
* is the option to exit the interactive session and start the Xtensa remote GDB
|
||||
* which will then sync up with the remote GDB client to allow forensics of the error.
|
||||
*/
|
||||
extern void gdbstub_init(void);
|
||||
|
||||
LUALIB_API void luaL_dbgbreak(void) {
|
||||
static int repeat_entry = 0;
|
||||
if (repeat_entry == 0) {
|
||||
dbg_printf("Start up the gdb stub if not already started\n");
|
||||
gdbstub_init();
|
||||
repeat_entry = 1;
|
||||
}
|
||||
asm("break 0,0" ::);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static int panic (lua_State *L) {
|
||||
(void)L; /* to avoid warnings */
|
||||
#if defined(LUA_USE_STDIO)
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
* This module, when enabled with the LUA_USE_MODULES_GDBSTUB define causes
|
||||
* the gdbstub code to be included and enabled to handle all fatal exceptions.
|
||||
* This allows you to use the lx106 gdb to catch the exception and then poke
|
||||
* around. You can't continue from an exception (at least not easily).
|
||||
* around. You can continue from a break, but attempting to continue from an
|
||||
* exception usually fails.
|
||||
*
|
||||
* This should not be included in production builds as any exception will
|
||||
* put the nodemcu into a state where it is waiting for serial input and it has
|
||||
|
@ -41,8 +42,8 @@ static int lgdbstub_open(lua_State *L) {
|
|||
static const LUA_REG_TYPE gdbstub_map[] = {
|
||||
{ LSTRKEY( "brk" ), LFUNCVAL( lgdbstub_break ) },
|
||||
{ LSTRKEY( "gdboutput" ), LFUNCVAL( lgdbstub_gdboutput) },
|
||||
|
||||
{ LSTRKEY( "open" ), LFUNCVAL( lgdbstub_open) },
|
||||
{ LNILKEY, LNILVAL }
|
||||
};
|
||||
|
||||
NODEMCU_MODULE(GDBSTUB, "gdbstub", gdbstub_map, lgdbstub_open);
|
||||
NODEMCU_MODULE(GDBSTUB, "gdbstub", gdbstub_map, NULL);
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
|
||||
static exception_handler_fn load_store_handler;
|
||||
|
||||
|
||||
void load_non_32_wide_handler (struct exception_frame *ef, uint32_t cause)
|
||||
{
|
||||
/* If this is not EXCCAUSE_LOAD_STORE_ERROR you're doing it wrong! */
|
||||
|
@ -73,13 +72,15 @@ void load_non_32_wide_handler (struct exception_frame *ef, uint32_t cause)
|
|||
{
|
||||
die:
|
||||
/* Turns out we couldn't fix this, so try and chain to the handler
|
||||
* that was set by the SDK. If none then trigger a system break instead
|
||||
* and hang if the break doesn't get handled. This is effectively
|
||||
* what would happen if the default handler was installed. */
|
||||
* that was set. (This is typically a remote GDB break). If none
|
||||
* then trigger a system break instead and hang if the break doesn't
|
||||
* get handled. This is effectively what would happen if the default
|
||||
* handler was installed. */
|
||||
if (load_store_handler) {
|
||||
load_store_handler(ef, cause);
|
||||
return;
|
||||
}
|
||||
asm ("break 1, 1");
|
||||
asm ("break 1, 1");
|
||||
while (1) {}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue