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?”.