14.3. Getting Started

A small postdissector is enough to exercise everything the debugger does. Save the following as printer.lua:

local printer      = Proto("printer", "Demo Postdissector")
local sessions     = {}  -- frame number -> {src, dst}
local last_seen    = {}  -- Address -> frame number
local opcode_names = { [0x10] = "ack", [0x11] = "syn-ack" }
local handshake    = { syn = true, fin = false }
local ip_src_F     = Field.new("ip.src")
local tcp_flags_F  = Field.new("tcp.flags")

function printer.dissector(tvb, pinfo, tree)
  local offset = 0

  sessions[pinfo.number] = { src = pinfo.src, dst = pinfo.dst }
  last_seen[tostring(pinfo.src)] = pinfo.number

  -- Reference each file-scope local at least once so Lua keeps
  -- them as upvalues of this closure (otherwise the watch
  -- examples below can't reach them).
  local _captured = { printer, opcode_names, handshake,
                      ip_src_F, tcp_flags_F }

  print(pinfo.number, tostring(pinfo.src), tostring(pinfo.dst))
end

register_postdissector(printer)

Every example later in this chapter resolves against names this script defines, so you can paste any of them into the Watch panel without setting up extra state. What’s in scope while printer.dissector is paused:

The local _captured = { …​ } line in the dissector body is demo-only: Lua captures file-scope locals as upvalues only if the closure references them, so each name has to be mentioned at least once for it to show up under Upvalues. A real postdissector’s protocol logic does the same work organically. opcode_names and handshake are otherwise unused — they exist solely to seed hex-keyed and boolean-keyed tables for the path-watch examples below.

Field.new(…​) is called at file scope on purpose: extractors can only be constructed at script load, before any dissector or tap callback runs. Inside the dissector callback the script references ip_src_F / tcp_flags_F (and so do the watch examples below), but never calls Field.new again.

Drop it into your personal plugins directory (the exact path on your install is also shown under HelpAbout WiresharkFoldersPersonal Lua Plugins). The canonical locations are:

Then:

  1. Pick AnalyzeReload Lua Plugins (Ctrl+Shift+L) so Wireshark picks up the new file. The same shortcut also works from the Reload Lua Plugins toolbar action inside the Lua Debugger dialog once it’s open.
  2. Open ToolsLua Debugger and tick the Enabled checkbox in the dialog header.
  3. Expand the Files section and double-click printer.lua to open it in the editor.
  4. Click in the gutter on a line inside printer.dissector to toggle a breakpoint (a red circle appears).
  5. Load any capture file. Dissection starts, the breakpoint hits, and the debugger pauses — the paused line gets a yellow stripe (and a yellow right-pointing triangle in the gutter), the Enabled checkbox dot turns yellow, the window title changes to Lua Debugger — Paused, and the main window grays out behind a "Lua debugger paused" banner.
  6. Inspect the paused state:

    • Variables shows Locals (tvb, pinfo, tree), Upvalues, and Globals for the current frame.
    • Stack Trace shows the postdissector call on top of whatever frame drove the dissection.
    • Try Add Watch on pinfo.src (via the Variables context menu or the button in the Watch section header, Ctrl+Shift+W).
  7. Use Step Over (F10), Step Into (F11), or Continue (F5) to advance. The Watch tree updates on every pause; values that changed since the previous pause are drawn in a bold accent color and briefly flash.