Tracing: watching execution flow#

This chapter covers traces that record what a program did without stopping it: the debugger’s t command and trace options, the Devel::Trace module family, and the regex-engine tracer. Use tracing when the bug is “which code path actually ran?” rather than “what value does X have at line N?”.

Readers coming in know how to set breakpoints and inspect state. A trace is the complement — “follow every step and tell me about it” rather than “stop at one interesting place”.

t: trace inside the debugger#

t toggles per-statement tracing. Every breakable line prints a location marker before executing:

  DB<1> t
Trace = on

  DB<2> c
main::(app.pl:5):          my $key = 'welcome';
main::(app.pl:6):          my %data = (
main::handle_request(app.pl:42):   my $user = load_user($req);
main::load_user(app.pl:87):        my ($req) = @_;
main::load_user(app.pl:88):        return unless $req;
...

Restricting the depth:

  DB<3> t 2           # toggle with maximum sub-depth 2
  DB<4> t 0 my_work() # trace one expression only

The frame option controls what the trace prints at sub calls and returns:

Bit

Flag

Effect

1

enter

Print on sub entry.

2

exit

Print on sub exit.

4

ctx+args

Include call context and arguments.

8

tied/ref

Mark tied-variable and reference sub calls.

16

return

Print the return value.

Set frame via o:

  DB<5> o frame=15       # enter + exit + ctx/args + tied/ref

Common combinations:

  • frame=2: just entry/exit traces, one line per call — the call-tree view.

  • frame=6: add caller context and argument dump — full trace.

  • frame=30: like frame=6 plus return values.

Trace output goes to the debugger’s output stream. Redirect it to a file so it does not drown the interactive session:

  DB<6> o LineInfo=trace.out

Read the file in another terminal:

tail -f trace.out

Non-interactive tracing: NonStop + AutoTrace#

For “record a full execution trace with no interaction”, combine NonStop=1 (no prompts) and AutoTrace=1 (tracing on from start):

PERLDB_OPTS="NonStop=1 AutoTrace=1 frame=2 LineInfo=trace.out" \
  perl -d script.pl

The program runs straight through; trace.out captures every breakable line and every sub entry. Ideal for CI-mode failure capture, headless servers, or any environment where a TTY prompt is unavailable.

Add inhibit_exit=0 to let the program fall off the end without the “Debugged program terminated” prompt.

Devel::Trace: every line, without the debugger#

Devel::Trace traces every line of every file to STDERR. No command-line interaction. No breakpoints. Simple and loud:

perl -d:Trace script.pl 2> trace.out
>> app.pl:4: my $key = 'welcome';
>> app.pl:5: my %data = (
>> app.pl:11: print "$data{$key}\n";

Run it once, grep the output for the puzzle. Output size is proportional to program run length — use on a minimal reproducer, not the whole test suite.

Variants:

  • Devel::DumpTrace — same plus per-line variable values.

  • Devel::TraceUse — “which modules were loaded, in what order, by whom”:

    perl -d:TraceUse script.pl
    
  • Devel::TraceINC — narrower: just the require chain with timing.

  • Devel::Trace::Subs — call-flow and stack-trace generator for “which subs actually ran”.

Tracing a specific region#

The $DB::trace variable toggles tracing from Perl code:

$DB::trace = 1;
interesting_region();
$DB::trace = 0;

Assigning to $DB::trace when not under -d autovivifies the variable harmlessly — the code is safe to commit and runs as a no-op outside the debugger.

Same pattern for single-stepping a region:

$DB::single = 1;        # break here when next statement runs under -d

Regex tracing: use re 'debug'#

The regex engine has its own tracer. Enable lexically inside a region, or on the command line:

{
    use re 'debug';
    $str =~ /(\w+)\s+(\w+)/;
}
perl -Mre=debug -e '"hello world" =~ /(\w+)\s+(\w+)/'

Compile-phase output shows what the optimiser did:

  • Compiling REx 'PATTERN' — the source pattern.

  • size N — internal node-table size.

  • anchored 'SUB' at K / floating 'SUB' at K..LIMIT — which substring the engine will look for as a pre-filter.

  • stclass TYPE — the starting-character class.

  • minlen N — no match possible on input shorter than N.

  • Node listing — the compiled program.

Runtime output shows each step:

  POS <PRE-MATCH> <POST-MATCH>   |  NODE_NUM: OPNAME

Indentation reflects backtracking depth; failed... and failed, try continuation... mark backtracks; Match successful! / Match failed. close the trace.

If no runtime output appears at all, the optimiser decided the match without entering the engine — either minlen pre-filtered or the pattern was folded to a constant test.

pperl’s regex engine is a native reimplementation, not a port of perl5’s. The trace is functionally equivalent but the exact node names and field formats are not byte-identical to upstream. Parsing the trace programmatically (few tools do) will need adjustment.

Partial switches narrow the output:

use re 'debugcolor';                  # ANSI colour
use re debug => 'EXECUTE';            # runtime only, no compile output

Devel::TraceUse for module-load bugs#

A program that runs slowly at startup, or a “why is this module loaded at all?”, is almost always a module-graph question. Devel::TraceUse prints the full use/require tree:

perl -d:TraceUse script.pl
Modules used from script.pl:
   1. strict 1.12, script.pl line 2 [main]
   2. warnings 1.58, script.pl line 2 [main]
   3. DBI 1.643, script.pl line 5 [main]
      4. Carp 1.52, DBI line 13 [DBI]
      5. DynaLoader 1.47, DBI line 14 [DBI]
   ...

Indentation shows the dependency chain. Cross-reference with the line number in the parent file to see where each load happens.

Combining traces with the debugger#

Trace to a file, step interactively:

PERLDB_OPTS="frame=6 LineInfo=trace.out" perl -d app.pl

The debugger prompt appears normally; trace.out accumulates the call-tree as you step and continue. Useful when a call chain is too deep to follow by stepping but you still want to stop at specific points to inspect.

Find out more#

  • breakpoints — stopping at a specific point rather than recording every step.

  • inspecting-state — the commands that answer “what is this value?”.