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: likeframe=6plus 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 therequirechain 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?».