Skip to content

Commit c068831

Browse files
LifeLexalejandro-perez-facultyngoldbaum
authored
docs: added docs on debugging using breakpoints (#4943)
* docs: added docs on debugging using breakpoints * docs: added missing code type md * Update guide/src/debugging.md Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com> * docs: refactor debugging section to address comments * docs: install mdbook-tabs and update docs --------- Co-authored-by: Alejandro Perez Gancedo <alejandro.perez@faculty.ai> Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com>
1 parent bdc372f commit c068831

File tree

7 files changed

+409
-7
lines changed

7 files changed

+409
-7
lines changed

.github/workflows/gh-pages.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
- name: Setup mdBook
3131
uses: taiki-e/install-action@v2
3232
with:
33-
tool: mdbook,lychee
33+
tool: mdbook,mdbook-tabs,lychee
3434

3535
- name: Prepare tag
3636
id: prepare_tag

.netlify/build.sh

+8
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ if [ "${INSTALLED_MDBOOK_LINKCHECK_VERSION}" != "mdbook v${MDBOOK_LINKCHECK_VERS
8989
cargo install mdbook-linkcheck@${MDBOOK_LINKCHECK_VERSION} --force
9090
fi
9191

92+
# Install latest mdbook-tabs. Netlify will cache the cargo bin dir, so this will
93+
# only build mdbook-tabs if needed.
94+
MDBOOK_TABS_VERSION=$(cargo search mdbook-tabs --limit 1 | head -1 | tr -s ' ' | cut -d ' ' -f 3 | tr -d '"')
95+
INSTALLED_MDBOOK_TABS_VERSION=$(mdbook-tabs --version || echo "none")
96+
if [ "${INSTALLED_MDBOOK_TABS_VERSION}" != "mdbook-tabs v${MDBOOK_TABS_VERSION}" ]; then
97+
cargo install mdbook-tabs@${MDBOOK_TABS_VERSION} --force
98+
fi
99+
92100
pip install nox
93101
nox -s build-guide
94102
mv target/guide/ netlify_build/main/

Contributing.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ https://doc.rust-lang.org/rustdoc/documentation-tests.html for a guide on doctes
6060

6161
You can preview the user guide by building it locally with `mdbook`.
6262

63-
First, install [`mdbook`][mdbook] and [`nox`][nox]. Then, run
63+
First, install [`mdbook`][mdbook], the [`mdbook-tabs`][mdbook-tabs] plugin and [`nox`][nox]. Then, run
6464

6565
```shell
6666
nox -s build-guide -- --open
@@ -235,5 +235,6 @@ In the meanwhile, some of our maintainers have personal GitHub sponsorship pages
235235
- [messense](https://github.com/sponsors/messense)
236236

237237
[mdbook]: https://rust-lang.github.io/mdBook/cli/index.html
238+
[mdbook-tabs]: https://mdbook-plugins.rustforweb.org/tabs.html
238239
[lychee]: https://github.com/lycheeverse/lychee
239240
[nox]: https://github.com/theacodes/nox

guide/book.toml

+4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ author = "PyO3 Project and Contributors"
66
[preprocessor.pyo3_version]
77
command = "python3 guide/pyo3_version.py"
88

9+
[preprocessor.tabs]
10+
911
[output.html]
1012
git-repository-url = "https://github.com/PyO3/pyo3/tree/main/guide"
1113
edit-url-template = "https://github.com/PyO3/pyo3/edit/main/guide/{path}"
1214
playground.runnable = false
15+
additional-css = ["theme/tabs.css"]
16+
additional-js = ["theme/tabs.js"]

guide/src/debugging.md

+294-5
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,303 @@ Run Valgrind with `valgrind --suppressions=valgrind-python.supp ./my-command --w
3434

3535
The best start to investigate a crash such as an segmentation fault is a backtrace. You can set `RUST_BACKTRACE=1` as an environment variable to get the stack trace on a `panic!`. Alternatively you can use a debugger such as `gdb` to explore the issue. Rust provides a wrapper, `rust-gdb`, which has pretty-printers for inspecting Rust variables. Since PyO3 uses `cdylib` for Python shared objects, it does not receive the pretty-print debug hooks in `rust-gdb` ([rust-lang/rust#96365](https://github.com/rust-lang/rust/issues/96365)). The mentioned issue contains a workaround for enabling pretty-printers in this case.
3636

37-
* Link against a debug build of python as described in the previous chapter
38-
* Run `rust-gdb <my-binary>`
39-
* Set a breakpoint (`b`) on `rust_panic` if you are investigating a `panic!`
40-
* Enter `r` to run
41-
* After the crash occurred, enter `bt` or `bt full` to print the stacktrace
37+
* Link against a debug build of python as described in the previous chapter
38+
* Run `rust-gdb <my-binary>`
39+
* Set a breakpoint (`b`) on `rust_panic` if you are investigating a `panic!`
40+
* Enter `r` to run
41+
* After the crash occurred, enter `bt` or `bt full` to print the stacktrace
4242

4343
Often it is helpful to run a small piece of Python code to exercise a section of Rust.
4444

4545
```console
4646
rust-gdb --args python -c "import my_package; my_package.sum_to_string(1, 2)"
4747
```
48+
49+
## Setting breakpoints in your Rust code
50+
51+
One of the preferred ways by developers to debug their code is by setting breakpoints. This can be achieved in PyO3 by using a debugger like `rust-gdb` or `rust-lldb` with your Python interpreter.
52+
53+
For more information about how to use both `lldb` and `gdb` you can read the [gdb to lldb command map](https://lldb.llvm.org/use/map.html) from the lldb documentation.
54+
55+
### Common setup
56+
57+
1. Compile your extension with debug symbols:
58+
59+
```bash
60+
# Debug is the default for maturin, but you can explicitly ensure debug symbols with:
61+
RUSTFLAGS="-g" maturin develop
62+
63+
# For setuptools-rust users:
64+
pip install -e .
65+
```
66+
67+
> **Note**: When using debuggers, make sure that `python` resolves to an actual Python binary or symlink and not a shim script. Some tools like pyenv use shim scripts which can interfere with debugging.
68+
69+
### Debugger specific setup
70+
71+
Depeding on your OS and your preferences you can use two different debuggers, `rust-gdb` or `rust-lldb`.
72+
73+
{{#tabs }}
74+
{{#tab name="Using rust-gdb" }}
75+
76+
1. Launch rust-gdb with the Python interpreter:
77+
78+
```bash
79+
rust-gdb --args python
80+
```
81+
82+
2. Once in gdb, set a breakpoint in your Rust code:
83+
84+
```bash
85+
(gdb) break your_module.rs:42
86+
```
87+
88+
3. Run your Python script that imports and uses your Rust extension:
89+
90+
```bash
91+
# Option 1: Run an inline Python command
92+
(gdb) run -c "import your_module; your_module.your_function()"
93+
94+
# Option 2: Run a Python script
95+
(gdb) run your_script.py
96+
97+
# Option 3: Run pytest tests
98+
(gdb) run -m pytest tests/test_something.py::TestName
99+
```
100+
101+
{{#endtab }}
102+
{{#tab name="Using rust-lldb (for macOS users)" }}
103+
104+
1. Start rust-lldb with Python:
105+
106+
```bash
107+
rust-lldb -- python
108+
```
109+
110+
2. Set breakpoints in your Rust code:
111+
112+
```bash
113+
(lldb) breakpoint set --file your_module.rs --line 42
114+
```
115+
116+
3. Run your Python script:
117+
118+
```bash
119+
# Option 1: Run an inline Python command
120+
(lldb) run -c "import your_module; your_module.your_function()"
121+
122+
# Option 2: Run a Python script
123+
(lldb) run your_script.py
124+
125+
# Option 3: Run pytest tests
126+
(lldb) run -m pytest tests/test_something.py::TestName
127+
```
128+
129+
{{#endtab }}
130+
{{#endtabs }}
131+
132+
### Using VS Code
133+
134+
VS Code with the Rust and Python extensions provides an integrated debugging experience:
135+
136+
1. First, install the necessary VS Code extensions:
137+
* [Rust Analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
138+
* [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)
139+
* [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
140+
141+
2. Create a `.vscode/launch.json` file with a configuration that uses the LLDB Debug Launcher:
142+
143+
```json
144+
{
145+
"version": "0.2.0",
146+
"configurations": [
147+
{
148+
"name": "Debug PyO3",
149+
"type": "lldb",
150+
"request": "attach",
151+
"program": "${workspaceFolder}/.venv/bin/python",
152+
"pid": "${command:pickProcess}",
153+
"sourceLanguages": [
154+
"rust"
155+
]
156+
},
157+
{
158+
"name": "Launch Python with PyO3",
159+
"type": "lldb",
160+
"request": "launch",
161+
"program": "${workspaceFolder}/.venv/bin/python",
162+
"args": ["${file}"],
163+
"cwd": "${workspaceFolder}",
164+
"sourceLanguages": ["rust"]
165+
},
166+
{
167+
"name": "Debug PyO3 with Args",
168+
"type": "lldb",
169+
"request": "launch",
170+
"program": "${workspaceFolder}/.venv/bin/python",
171+
"args": ["path/to/your/script.py", "arg1", "arg2"],
172+
"cwd": "${workspaceFolder}",
173+
"sourceLanguages": ["rust"]
174+
},
175+
{
176+
"name": "Debug PyO3 Tests",
177+
"type": "lldb",
178+
"request": "launch",
179+
"program": "${workspaceFolder}/.venv/bin/python",
180+
"args": ["-m", "pytest", "tests/your_test.py::test_function", "-v"],
181+
"cwd": "${workspaceFolder}",
182+
"sourceLanguages": ["rust"]
183+
}
184+
]
185+
}
186+
```
187+
188+
This configuration supports multiple debugging scenarios:
189+
* Attaching to a running Python process
190+
* Launching the currently open Python file
191+
* Running a specific script with command-line arguments
192+
* Running pytest tests
193+
194+
3. Set breakpoints in your Rust code by clicking in the gutter next to line numbers.
195+
196+
4. Start debugging:
197+
* For attaching to a running Python process: First start the process, then select the "Debug PyO3" configuration and click Start Debugging (F5). You'll be prompted to select the Python process to attach to.
198+
* For launching a Python script: Open your Python script, select the "Launch Python with PyO3" configuration and click Start Debugging (F5).
199+
* For running with arguments: Select "Debug PyO3 with Args" (remember to edit the configuration with your actual script path and arguments).
200+
* For running tests: Select "Debug PyO3 Tests" (edit the test path as needed).
201+
202+
5. When debugging PyO3 code:
203+
* You can inspect Rust variables and data structures
204+
* Use the debug console to evaluate expressions
205+
* Step through Rust code line by line using the step controls
206+
* Set conditional breakpoints for more complex debugging scenarios
207+
208+
### Advanced Debugging Configurations
209+
210+
For advanced debugging scenarios, you might want to add environment variables or enable specific Rust debug flags:
211+
212+
```json
213+
{
214+
"name": "Debug PyO3 with Environment",
215+
"type": "lldb",
216+
"request": "launch",
217+
"program": "${workspaceFolder}/.venv/bin/python",
218+
"args": ["${file}"],
219+
"env": {
220+
"RUST_BACKTRACE": "1",
221+
"PYTHONPATH": "${workspaceFolder}"
222+
},
223+
"sourceLanguages": ["rust"]
224+
}
225+
```
226+
227+
### Debugging from Jupyter Notebooks
228+
229+
For Jupyter Notebooks run from VS Code, you can use the following helper functions to automate the launch configuration:
230+
231+
```python
232+
from pathlib import Path
233+
import os
234+
import json
235+
import sys
236+
237+
238+
def update_launch_json(vscode_config_file_path=None):
239+
"""Update VSCode launch.json with the correct Jupyter kernel PID.
240+
241+
Args:
242+
vscode_config_file_path (str, optional): Path to the .vscode/launch.json file.
243+
If not provided, will use the current working directory.
244+
"""
245+
pid = get_jupyter_kernel_pid()
246+
if not pid:
247+
print("Could not determine Jupyter kernel PID.")
248+
return
249+
250+
# Determine launch.json path
251+
if vscode_config_file_path:
252+
launch_json_path = vscode_config_file_path
253+
else:
254+
launch_json_path = os.path.join(Path(os.getcwd()), ".vscode", "launch.json")
255+
256+
# Get Python interpreter path
257+
python_path = sys.executable
258+
259+
# Default debugger config
260+
debug_config = {
261+
"version": "0.2.0",
262+
"configurations": [
263+
{
264+
"name": "Debug PyO3 (Jupyter)",
265+
"type": "lldb",
266+
"request": "attach",
267+
"program": python_path,
268+
"pid": pid,
269+
"sourceLanguages": ["rust"],
270+
},
271+
{
272+
"name": "Launch Python with PyO3",
273+
"type": "lldb",
274+
"request": "launch",
275+
"program": python_path,
276+
"args": ["${file}"],
277+
"cwd": "${workspaceFolder}",
278+
"sourceLanguages": ["rust"]
279+
}
280+
],
281+
}
282+
283+
# Create .vscode directory if it doesn't exist
284+
try:
285+
os.makedirs(os.path.dirname(launch_json_path), exist_ok=True)
286+
287+
# If launch.json already exists, try to update it instead of overwriting
288+
if os.path.exists(launch_json_path):
289+
try:
290+
with open(launch_json_path, "r") as f:
291+
existing_config = json.load(f)
292+
293+
# Check if our configuration already exists
294+
config_exists = False
295+
for config in existing_config.get("configurations", []):
296+
if config.get("name") == "Debug PyO3 (Jupyter)":
297+
config["pid"] = pid
298+
config["program"] = python_path
299+
config_exists = True
300+
301+
if not config_exists:
302+
existing_config.setdefault("configurations", []).append(debug_config["configurations"][0])
303+
304+
debug_config = existing_config
305+
except Exception:
306+
# If reading fails, we'll just overwrite with our new configuration
307+
pass
308+
309+
with open(launch_json_path, "w") as f:
310+
json.dump(debug_config, f, indent=4)
311+
print(f"Updated launch.json with PID: {pid} at {launch_json_path}")
312+
except Exception as e:
313+
print(f"Error updating launch.json: {e}")
314+
315+
316+
def get_jupyter_kernel_pid():
317+
"""Find the process ID (PID) of the running Jupyter kernel.
318+
319+
Returns:
320+
int: The process ID of the Jupyter kernel, or None if not found.
321+
"""
322+
# Check if we're running in a Jupyter environment
323+
if 'ipykernel' in sys.modules:
324+
pid = os.getpid()
325+
print(f"Jupyter kernel PID: {pid}")
326+
return pid
327+
else:
328+
print("Not running in a Jupyter environment.")
329+
return None
330+
```
331+
332+
To use these functions:
333+
334+
1. Run the cell containing these functions in your Jupyter notebook
335+
2. Run `update_launch_json()` in a cell
336+
3. In VS Code, select the "Debug PyO3 (Jupyter)" configuration and start debugging

guide/theme/tabs.css

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.mdbook-tabs {
2+
display: flex;
3+
}
4+
5+
.mdbook-tab {
6+
background-color: var(--table-alternate-bg);
7+
padding: 0.5rem 1rem;
8+
cursor: pointer;
9+
border: none;
10+
font-size: 1.6rem;
11+
line-height: 1.45em;
12+
}
13+
14+
.mdbook-tab.active {
15+
background-color: var(--table-header-bg);
16+
font-weight: bold;
17+
}
18+
19+
.mdbook-tab-content {
20+
padding: 1rem 0rem;
21+
}
22+
23+
.mdbook-tab-content table {
24+
margin: unset;
25+
}

0 commit comments

Comments
 (0)