Rust spoils me, I rarely need to reach for a debugger thanks to structured logging via tracing
and errors with spantraces thanks to eyre
. When I do find myself wanting to really dig my grubby little paws into some code, configuring the debugger targets can feel intimidating.
Experience has taught me that I can conquer intimidating things though, and sometimes that is best done with a little help. While hacking on a pgx
bug, I realized I was using a newly installed system, and needed to reconfigure my debugger settings.
This made a good opportunity for me to share with you! This short article will cover how to configure Visual Studio Code with CodeLLDB so you can visually debug your pgx
extensions. This includes being able to step into the pgx
or postgres
sources. While the instructions may be Arch Linux specific, they should be able to be adapted to a different Linux.
Brief setup
Before we start, set the following sysctl
to ensure you can actually do a debug as a non-root user:
Now, the base packages we need:
If you haven't already, setup yay
or another AUR helper:
Next, install the Microsoft branded Visual Studio Code (visual-studio-code-bin
) from the AUR. (The Open Source build, code
doesn't have the extensions we need.)
Don't be brash, read the
PKGBUILD
s whichyay
shows you, ensure you are comfortable with them. Do this every time, no matter what.
Let's install some extensions, rust-analyzer
, CodeLLDB, C/C++, and Hex Editor:
We'll then pull the PostgreSQL sources locally and build them into a result
directory:
Now let's clone the pgx
sources locally and install from there:
# Checkout a specific revision if desired.
Finally, we'll create an extension to explore with and open up code
:
Update the default PostgreSQL version feature. Edit the Cargo.toml
's [features]
block to have:
# debuggable/Cargo.toml
# ...
[]
= ["pg14"]
# ...
Our current code
window has the debuggable
extension open, we're going to make a 'Workspace' with pgx
in the same window.
Hit Ctrl+Shift+P (That is -- hold down the Control key while then holding the Shift key, then also the P key, releasing them afterward) then enter into the command pallette add folder to workspace
and ensure the picked selection is indeed the option: Workspaces: Add folder to workspace...
.
In this file chooser, select the ~/git/tcdi/pgx
folder we cloned earlier.
Do the same with ~/git/postgres/postgres
.
At this point, consider saving the workspace via the "File" menu.
Debugging the SQL generation process
While it's (hopefully) unlikely you'll encounter bugs with the SQL generation process of pgx
, let's cover that first.
pgx
's SQL generation occurs via cargo-pgx
's schema
subcommand, usually invoked via the cargo pgx schema
call. The cargo-pgx
binary will call cargo
to build the library, dlopen
a postmaster
'mock', inspect the symbols which the various pgx
macros generator, then invoke the symbols to gather metadata. Finally, it feeds that metadata into the pgx_utils::sql_entity_graph::PgxSql
generator.
Macro expansion
As far as I am aware, there isn't a good way to hook up lldb
to a running proc macro.
The best info I could find was in 'Testing Proc Macros' and "Debugging tips".
If you happen to know of a way, please email me! I'll give you credit in this section.
Instead, you will likely rely on cargo expand
:
Now you can use cargo expand
on the entire extension (with no args), or select specific symbols to expand:
$ cargo expand hello_debuggable
Checking debuggable v0.0.0
Finished dev target in 0.22s
Note that pgx
macros will generate other symbols to expand, too. Typically #[pg_extern]
macros produce a $FUNCNAME_wrapper
function which is what PostgreSQL calls:
$ cargo expand hello_debuggable_wrapper
Checking debuggable v0.0.0
Finished dev target in 0.22s
unsafe extern "C"
For SQL generation, most macros will also produce like a __pgx_internals_TYPE_FUNCNAME
:
$ cargo expand __pgx_internals_fn_hello_debuggable
Checking debuggable v0.0.0
Finished dev target in 0.22s
pub extern "C"
cargo-pgx
's SQL generation
To debug the pgx_utils::sql_entity_graph::PgxSql
or cargo-pgx
side of SQL generation, you can create a debug configuration in debuggable/.vscode/launch.json
with this:
// debuggable/.vscode/launch.json
Navigate over to pgx/pgx_utils/src/sql_entity_graph/pg_extern/mod.rs
and Ctrl+F tracing::trace!(sql = %ext_sql);
,, it should be in the to_sql
function, click the red dot that appears when you hover to the left of the line number there, and set a breakpoint.
In the "Run & Debug" pane (selected in screenshot above on the left), hit the "Play" button beside "SQL generation" at the top. In a terminal you should see cargo pgx schema
get run and begin to process the SQL, it will hit the breakpoint during this process and allow you to inspect and work with it:
At the top middle of the screenshot above, you can see the 'Continue', 'Step over', 'Step into' and related buttons. To the left side we can see 'Variables', the 'Watch' pane, as well as the call stack which caused us to arrive at a particular position.
Debugging pgx
extensions
Debugging running extension code is a little bit different. Let's recall something about how PostgreSQL works:
The PostgreSQL server can handle multiple concurrent connections from clients. To achieve this it starts (“forks”) a new process for each connection. From that point on, the client and the new server process communicate without intervention by the original postgres process. Thus, the supervisor server process is always running, waiting for client connections, whereas client and associated server processes come and go. (All of this is of course invisible to the user. We only mention it here for completeness.)
So PostgreSQL starts a supervisor which then forks a new process each connection. This means, if we're debugging extension, we want to attach to that fork.
CodeLLDB will let us attach to a running process this way, so we'll add a new job to the launch.json
:
// debuggable/.vscode/launch.json
Then we'll change up the #[pg_extern]
function in the code to make it more interesting:
Set a breakpoint on the highlighted line, then in a terminal run cargo pgx run
.
Once you reach a psql
prompt:
)
debuggable=#
Select the 'Attach' option from the debugger dropdown. (Where we hit 'SQL Generation' before in the 'Run & Debug' pane.) You should see a postgres
process with your extensio name.
Attaching won't immediately break on anything, but when you start running commands from psql
you can trigger breakpoints within the extension:
CREATE EXTENSION debuggable;
SELECT hello_debuggable ;
-- Breakpoint hit!!!
On the breakpoint, you'll see that familiar screen again:
Debugging pgx
and PostgreSQL code
Having now learnt to debug cargo-pgx
and our own extension, how do we debug if code slips inside pgx
, or even PostgreSQL?
Let's modify our hello_debuggable
to call something over the Server Programming Interface (SPI):
Do a cargo pgx run
again, and attach to the process like before. Then in psql
:
DROP EXTENSION IF EXISTS debuggable CASCADE; CREATE EXTENSION debuggable;
SELECT hello_debuggable ;
Break! Now we're going to 'Step into' this Spi::get_one
statement.
Now, navigate over to postgres/src/backend/executor/spi.c
and set a breakpoint on the int SPI_connect(void)
function:
int
Now hit the 'Continue' on the debugger and we should break inside PostgreSQL:
Fantastic, now we can debug almost every part of our pgx
extension. That's all for this article today!
I hope you manage to find your bugs! If not, let us know in Discord