hb_debugger.erl - External Debugger Integration
Overview
Purpose: Bootstrap external debuggers for HyperBEAM development
Module: hb_debugger
Supported: VS Code, Emacs, LSP-compatible editors
Boot Time: ~10 seconds
This module provides interfaces for attaching external graphical debuggers to HyperBEAM. It supports Erlang Language Server (erlang-ls) integration and enables setting breakpoints, profiling functions, and interactive debugging.
Debugging Setup
The simplest way to use external debugging:
- VS Code: Use included
launch.jsonconfiguration - Emacs: Configure with erlang-ls LSP client
- Manual: Start with
rebar3 debuggingfor console access
The debugger spawns HyperBEAM, attaches the debugger, and executes specified functions with breakpoints.
Dependencies
- Erlang/OTP:
debugger,int(interpreter),file - HyperBEAM:
hb_util,dev_profile
Public Functions Overview
%% Debugger Startup
-spec start() -> Seconds.
-spec start_and_break(Module, Function) -> ok.
-spec start_and_break(Module, Function, Args) -> ok.
-spec start_and_break(Module, Function, Args, DebuggerScope) -> ok.
%% Profiling
-spec profile_and_stop(Fun) -> no_return().
%% Utilities
-spec await_breakpoint() -> Breakpoint.Public Functions
1. start/0
-spec start() -> Seconds
when
Seconds :: integer().Description: Start the debugger server and wait for an external debugger node to connect. Returns the number of seconds waited.
Test Code:-module(hb_debugger_start_test).
-include_lib("eunit/include/eunit.hrl").
% NOTE: start/0 blocks waiting for external debugger - cannot unit test
% Just verify function is exported
start_exported_test() ->
code:ensure_loaded(hb_debugger),
?assert(erlang:function_exported(hb_debugger, start, 0)).% In your code
hb_debugger:start(),
% Debugger now attached, continue execution
my_function().2. start_and_break/2, start_and_break/3, start_and_break/4
-spec start_and_break(Module, Function) -> ok
when
Module :: atom(),
Function :: atom().
-spec start_and_break(Module, Function, Args) -> ok
when
Module :: atom(),
Function :: atom(),
Args :: [term()].
-spec start_and_break(Module, Function, Args, DebuggerScope) -> ok
when
Module :: atom(),
Function :: atom(),
Args :: [term()],
DebuggerScope :: [atom() | binary()] | binary().Description: Bootstrap function that starts debugger, waits for attachment, sets breakpoint on Module:Function/Arity, then calls the function. Optionally interprets additional modules matching DebuggerScope prefixes.
-module(hb_debugger_break_test).
-include_lib("eunit/include/eunit.hrl").
% NOTE: start_and_break/* blocks waiting for external debugger - cannot unit test
% Just verify functions are exported
start_and_break_exported_test() ->
code:ensure_loaded(hb_debugger),
?assert(erlang:function_exported(hb_debugger, start_and_break, 2)),
?assert(erlang:function_exported(hb_debugger, start_and_break, 3)),
?assert(erlang:function_exported(hb_debugger, start_and_break, 4)).% Simple breakpoint
hb_debugger:start_and_break(my_module, my_function).
% With arguments
hb_debugger:start_and_break(my_module, my_function, [arg1, arg2]).
% With debugger scope (interprets matching modules)
hb_debugger:start_and_break(
my_module,
my_function,
[arg1],
[hb_, dev_, my_] % Comma-separated list or binary
).3. profile_and_stop/1
-spec profile_and_stop(Fun) -> no_return()
when
Fun :: fun().Description: Profile a function with eflame and stop the node. Redirects output to profiling-output file and exits after profiling completes.
-module(hb_debugger_profile_test).
-include_lib("eunit/include/eunit.hrl").
% NOTE: profile_and_stop/1 halts the node - cannot unit test
% Just verify function is exported
profile_and_stop_exported_test() ->
code:ensure_loaded(hb_debugger),
?assert(erlang:function_exported(hb_debugger, profile_and_stop, 1)).% Profile a specific function
Fun = fun() ->
my_expensive_operation(),
another_operation()
end,
hb_debugger:profile_and_stop(Fun).
% Node will exit after profiling
% Check 'profiling-output' file for results4. await_breakpoint/0
-spec await_breakpoint() -> Breakpoint
when
Breakpoint :: term().Description: Wait for a breakpoint to be set by the debugger. If no debugger is connected, starts one first. Returns when breakpoint is detected.
Test Code:-module(hb_debugger_await_test).
-include_lib("eunit/include/eunit.hrl").
% NOTE: await_breakpoint/0 blocks waiting for breakpoint - cannot unit test
% Just verify function is exported
await_breakpoint_exported_test() ->
code:ensure_loaded(hb_debugger),
?assert(erlang:function_exported(hb_debugger, await_breakpoint, 0)).% In your development code
hb_debugger:await_breakpoint(),
% Continue after breakpoint is set
my_function_to_debug().Common Patterns
%% Simple debugging session
% 1. Start debugger and break on function
hb_debugger:start_and_break(my_module, my_function).
%% Debug with arguments
% 2. Test specific inputs
Args = [<<"test">>, 123, #{key => value}],
hb_debugger:start_and_break(my_module, my_function, Args).
%% Debug with full scope
% 3. Interpret multiple module prefixes
hb_debugger:start_and_break(
my_module,
my_function,
Args,
<<"hb_,dev_,my_">> % All matching modules available to debugger
).
%% Profile performance
% 4. Find bottlenecks
ProfileFun = fun() ->
Results = expensive_computation(),
process_results(Results)
end,
hb_debugger:profile_and_stop(ProfileFun).
%% Wait for manual breakpoint
% 5. Interactive debugging
hb_debugger:start(),
hb_debugger:await_breakpoint(),
my_code_to_debug().
%% Conditional debugging
% 6. Debug only when condition met
case should_debug() of
true ->
hb_debugger:start_and_break(my_module, my_function),
debug_enabled;
false ->
my_module:my_function(),
normal_execution
end.VS Code Integration
Launch Configuration
Example launch.json for VS Code:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug HyperBEAM",
"type": "erlang",
"request": "launch",
"projectnode": "hyperbeam@localhost",
"cookie": "hyperbeam_cookie",
"module": "hb_debugger",
"function": "start_and_break",
"args": [
"my_module",
"my_function",
[],
"hb_,dev_"
],
"cwd": "${workspaceFolder}",
"verbose": true
}
]
}Steps to Debug
- Open project in VS Code with erlang-ls extension
- Set breakpoints in code
- Press F5 or select "Debug HyperBEAM" configuration
- Debugger attaches and breaks at specified function
- Use VS Code debug controls (step, continue, inspect)
Module Interpretation
Debugger Scope
The DebuggerScope parameter controls which modules are interpreted (loaded into debugger):
% Binary with comma-separated prefixes
<<"hb_,dev_,my_module">>
% List of atoms
[hb_, dev_, my_module]
% List of binaries
[<<"hb_">>, <<"dev_">>, <<"my_">>]- Finds all HyperBEAM modules
- Filters by prefix match
- Attempts to interpret each module
- Skips modules that fail to load
- Returns list of successfully interpreted modules
Interpretation Failures
Some modules may fail to interpret due to:
- NIF dependencies
- Parse transform issues
- External dependencies
- Already loaded in a conflicting way
These failures are logged but don't prevent debugging:
interpret(Module) ->
case int:interpretable(Module) of
true -> int:i(Module) == ok;
Error ->
io:format("Could not interpret: ~p~n", [Error]),
false
end.Profiling with eflame
Profile Output
The profile_and_stop/1 function generates flame graph data:
Output Location: profiling-output file in current directory
- Function call hierarchy
- Time spent in each function
- Call count statistics
- Flame graph SVG (if eflame configured)
Profiling Options
% Profiling is configured via dev_profile device
Opts = #{
<<"return-mode">> => <<"open">>, % Open results after profiling
<<"engine">> => <<"eflame">> % Use eflame profiler
}.Distributed Erlang
Node Connection
Debugger relies on Distributed Erlang:
Node Name: Printed on startup
io:format("Node is: ~p~n", [node()]).
% Output: hyperbeam@localhostCookie: Printed on startup
io:format("Cookie is: ~p~n", [erlang:get_cookie()]).
% Output: hyperbeam_cookie% From debugger node
net_kernel:connect_node('hyperbeam@localhost').Connection Detection
Waits for any node to connect:
await_debugger(N) ->
case nodes() ++ nodes(hidden) of
[] ->
timer:sleep(1000),
await_debugger(N + 1);
[Node | _] ->
io:format("Peer: ~p~n", [Node]),
N
end.References
- Erlang Debugger - OTP
debuggerapplication - Erlang Interpreter -
intmodule documentation - erlang-ls - Erlang Language Server for VS Code/Emacs
- eflame - Flame graph profiler for Erlang
- Distributed Erlang - Node connectivity guide
Notes
- Boot Time: Approximately 10 seconds for debugger startup
- LSP Support: Best experience with erlang-ls extension
- Interpretation: Some modules may fail to interpret (non-critical)
- Timeout: 250ms timeout per module interpretation attempt
- Profiling:
profile_and_stop/1exits the node after completion - Output Redirect: Profiling redirects group leader to file
- Breakpoint Wait: Infinite loop until breakpoint is set
- Node Connection: Requires distributed Erlang with matching cookie
- Scope Performance: Large scope increases boot time
- Production Warning: Remove debugger calls before production deployment