IDE and DAP integration#
This chapter covers editor-driven debugging: Perl::LanguageServer
in VS Code and its counterparts in Neovim / Emacs / IntelliJ,
remote-over-SSH debugging, and container-mode debugging via
kubectl or docker.
Readers who reach for an IDE rather than a terminal prompt will find here the minimum viable setup, the config keys that matter, and the known fragility points (Coro, path mapping, attach vs launch).
The landscape#
Two wire protocols, both with Perl backends:
Protocol |
Perl backend |
Status |
|---|---|---|
DAP |
|
Active. |
DBGp |
|
Stagnant since 2018. |
For new setups, use DAP via Perl::LanguageServer. DBGp survives
only where an existing client (vim vdebug, pugdebug) is
already in use.
Perl::LanguageServer bundles an LSP (code intelligence,
diagnostics, go-to-definition) and a DAP (debugger) adapter in
one process. The LSP half works on any Perl file in the editor;
the DAP half runs only during a debug session.
Minimal VS Code setup#
CPAN side — install into the Perl the debuggee will run under:
cpanm Perl::LanguageServer
Editor side — install the richterger.perl extension:
Ctrl-P → ext install richterger.perl
.vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "perl",
"request": "launch",
"name": "Debug current file",
"program": "${workspaceFolder}/${relativeFile}",
"stopOnEntry": true,
"args": [],
"cwd": "${workspaceFolder}",
"env": {},
"reloadModules": true
}
]
}
Open a .pl or .pm, set breakpoints in the gutter, press F5.
The Debug Console accepts any Perl expression, evaluated in the
current stack frame.
.vscode/settings.json for @INC and interpreter selection:
{
"perl.perlInc": [
"${workspaceFolder}/lib",
"${workspaceFolder}/local/lib/perl5"
],
"perl.perlCmd": "/usr/bin/perl"
}
Capabilities#
Perl::LanguageServer supports the DAP features that matter:
Launch, pause, step in / over / out, return.
Conditional and unconditional breakpoints, added at any time.
Variable inspection across stack frames.
Set variable — change a value mid-session from the editor.
Watch expressions.
Evaluate-in-context at the Debug Console.
Module hot-reload via
reloadModules: true.
Not supported:
Attach to a running process.
request: "attach"is not implemented; only"launch"(the LS starts the debuggee itself).
Remote debugging over SSH#
Edit locally; debug on a remote host. settings.json:
{
"perl.sshCmd": "ssh",
"perl.sshAddr": "deploy@host.example",
"perl.sshUser": "deploy",
"perl.sshWorkspaceRoot": "/srv/app",
"perl.pathMap": [
["/srv/app", "${workspaceFolder}"]
]
}
Perl::LanguageServer runs on the remote host; the editor stays
local. pathMap translates filenames the debuggee reports
(/srv/app/lib/X.pm) to local editor paths
(${workspaceFolder}/lib/X.pm).
Breakpoints that silently fail to bind are almost always a
pathMap mismatch.
Container-mode debugging#
launch.json accepts container keys that invoke docker,
podman, docker-compose, or kubectl:
Key |
Values |
|---|---|
|
|
|
|
|
Image (for |
|
Extra args passed to the container runtime. |
|
|
Attach to an already-running Docker container:
{
"type": "perl",
"request": "launch",
"name": "Debug in container",
"program": "/app/bin/worker.pl",
"containerCmd": "docker",
"containerMode": "exec",
"containerName": "my-running-worker",
"pathMap": [["/app", "${workspaceFolder}"]]
}
Kubernetes pod:
{
"type": "perl",
"request": "launch",
"name": "Debug in pod",
"program": "/app/bin/worker.pl",
"containerCmd": "kubectl",
"containerMode": "exec",
"containerName": "my-pod-abc123",
"containerArgs": ["-n", "staging", "--container", "app"],
"pathMap": [["/app", "${workspaceFolder}"]]
}
Constraints:
Perl::LanguageServermust be installed inside the image. Bake it into the Dockerfile or build a dev-image variant.pathMapis the single biggest failure mode. Breakpoints that never bind mean the debuggee reports a path that does not translate.
Other editors#
Neovim — use
nvim-dapwithPerl::LanguageServeras the adapter. The adapter spec is a short Lua snippet that points atperl -MPerl::LanguageServer -e '...'.Emacs —
dap-modewith the same adapter.IntelliJ —
Devel::Camelcadedb+ the JetBrains Perl plugin. Not DAP; proprietary protocol. Viable but outside thePerl::LanguageServerecosystem.Sublime Text — DAP clients exist via plugins; use
Perl::LanguageServeras the adapter.
Special-mode launches#
launch.json keys for non-default invocation:
Key |
Effect |
|---|---|
|
Injects |
|
Re-execs the debuggee as that user (needs passwordless sudo). |
|
Enables module hot-reload during the session. |
|
Break at the first statement. |
Gotchas#
BEGIN-time errors surface as DAP “output” events, not as stopped-at-breakpoint events. Use
stopOnEntry: trueto get a stop at all if the script dies inBEGIN.Application
$SIG{__DIE__}can swallow exceptions before the debugger’s handler runs. Wrap the app’s handler in anif ($^S)guard (see exceptions).Watch expressions re-evaluate on every step. Avoid expensive or side-effect expressions.
No source maps. Perl has no concept equivalent to a JavaScript source map. Source filters (
Smart::Comments) can shift reported line numbers by one; breakpoints in filtered files may land on the wrong line.Future::AsyncAwaitstepping. Works for simple cases; stepping through complexawaitchains is unreliable. Drop toperl -dfor deep async investigation, or use logging.Windows native —
Perl::LanguageServer’s stdin handling is broken on Windows. WSL works.Coro.
Perl::LanguageServerrelies on Coro internally; the most-cited fragility point is Coro interacting badly with unusual build configurations. If the server refuses to start, check Coro’s install log first.
Find out more#
interactive-debugger —
perl -ddirectly, when the IDE stack is overkill or unavailable.