Debugging
lwIP-CE provides two complementary tools for diagnosing problems: a live event callback that fires as the stack runs, and a traceback buffer that captures the most recent WARN and ERROR events so you can read them after the fact. Neither requires a debugger or a serial console — both work on real hardware.
Unified event callback
All modules (USB driver, TLS, allocator, socket layer, WebSocket) route their
events through a single callback registered with lwip_set_event_cb():
void lwip_set_event_cb(lwip_event_fn event_fn);
Pass NULL to disable (callback is disabled by default). The callback signature is:
typedef void (*lwip_event_fn)(const struct lwip_event *ev);
struct lwip_event has two fixed fields and a union:
struct lwip_event {
uint8_t module; /* lwip_debug_module_t — which component fired */
uint8_t kind; /* lwip_event_kind_t — what kind of event */
union { ... } data;
};
Module values (ev->module):
Constant |
Component |
|---|---|
|
Socket/connection layer, netif, dispatch |
|
USB Ethernet driver |
|
Custom allocator |
|
TLS handshake, record layer, crypto |
|
WebSocket framing layer |
Call lwip_debug_module_name(ev->module) for a human-readable label (e.g.
"tls").
Event kinds (ev->kind):
Kind |
Meaning and |
|---|---|
|
Normal progress milestone. |
|
Trace point. Self-throttled: only fires when the |
|
The stack noticed a problem but chose to proceed (for example, an
unsupported certificate link type that was tolerated). |
|
Hard failure. |
|
A meaningful state transition. |
|
AppVar or Flash read/write. |
|
Network bytes received or sent. |
|
USB or battery power state change. |
State change values for LWIP_EV_STATE_CHG:
Constant |
Meaning |
|---|---|
|
Ethernet link state changed. |
|
DHCP address assigned or lost. |
|
Connection attempt in progress. |
|
Connection is up and ready for I/O. |
|
Connection closed cleanly. |
|
Connection failed (error or timeout). |
|
TLS handshake started. |
|
TLS handshake succeeded. |
|
TLS handshake failed. |
Call lwip_debug_state_change_name(ev->data.state.change_event) for a
human-readable label (e.g. "conn_established").
Decoding a code event location:
ERROR, WARN, and DEBUG events encode {file_id, line} into data.code.loc:
uint8_t file_id = LWIP_EVENT_CODE_FILE(ev->data.code.loc);
uint32_t line = LWIP_EVENT_CODE_LINE(ev->data.code.loc);
uint16_t extra = ev->data.code.extra; /* 0 for bare ERROR()/WARN() */
/* Human-readable file name: */
const char *filename = lwip_debug_file_name(file_id); /* e.g. "handshake.c" */
Minimal event callback example:
#include <lwip.h>
#include <ti/screen.h>
#include <stdio.h>
static void on_event(const struct lwip_event *ev)
{
char buf[80];
switch (ev->kind) {
case LWIP_EV_INFO:
snprintf(buf, sizeof(buf), "[info] %s\n", ev->data.msg);
break;
case LWIP_EV_WARN:
snprintf(buf, sizeof(buf), "[warn] %s:%lu extra=%u\n",
lwip_debug_file_name(LWIP_EVENT_CODE_FILE(ev->data.code.loc)),
LWIP_EVENT_CODE_LINE(ev->data.code.loc),
ev->data.code.extra);
break;
case LWIP_EV_ERROR:
snprintf(buf, sizeof(buf), "[error] %s:%lu extra=%u\n",
lwip_debug_file_name(LWIP_EVENT_CODE_FILE(ev->data.code.loc)),
LWIP_EVENT_CODE_LINE(ev->data.code.loc),
ev->data.code.extra);
break;
case LWIP_EV_STATE_CHG:
snprintf(buf, sizeof(buf), "[state] %s\n",
lwip_debug_state_change_name(ev->data.state.change_event));
break;
default:
return;
}
os_PutStrFull(buf);
}
int main(void)
{
if (!lwip_start()) return 1;
lwip_set_event_cb(on_event);
/* ... */
}
Traceback
The event callback fires in real time. If a failure happens mid-handshake, you may not have a screen up yet, or the failure may be buried inside a chain of callbacks. The traceback buffer captures the most recent WARN and ERROR events (and socket-layer errors) automatically, in newest-first order. Read it any time after a failure:
const struct lwip_traceback_entry *lwip_get_traceback(uint8_t *count);
count is filled with the number of entries. The returned pointer is owned by
lwIP and is valid until the next call to lwip_get_traceback() or until a new
entry is pushed into the buffer. Copy what you need before calling anything else.
struct lwip_traceback_entry fields:
Field |
Type |
Meaning |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
Source line number. Zero for socket-wrapper entries. |
|
|
Optional extra code from |
|
|
|
|
|
|
|
|
Raw |
|
|
|
|
|
|
Traceback walkthrough example:
uint8_t count;
const struct lwip_traceback_entry *tb = lwip_get_traceback(&count);
for (uint8_t i = 0; i < count; i++) {
const struct lwip_traceback_entry *e = &tb[i];
if (e->file) {
/* Code-level WARN or ERROR */
printf("[%s] %s:%lu",
lwip_debug_module_name(e->module),
lwip_debug_file_name(e->file),
e->line);
if (e->extra)
printf(" extra=0x%04x", e->extra);
printf("\n");
} else {
/* Socket-wrapper error entry */
printf("[socket] component=%u op=%u raw=%d mapped=%u status=%u\n",
e->component, e->operation,
e->raw_error, e->mapped_error, e->status);
}
}
Note
DEBUG events do not occupy traceback slots — they are callback-only.
Only WARN, ERROR, and socket-wrapper errors are captured.