Skip to content

Commit 2f89059

Browse files
authored
Merge pull request #268 from r-devel/docs/lldb
Docs : documentation for LLDB debugging
2 parents 0657ca7 + e2c8187 commit 2f89059

11 files changed

+186
-0
lines changed

.markdownlint-cli2.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ config:
1313
ol-prefix: false
1414
# MD041 - first-line-heading disable due to splitting of sections across multiple files
1515
first-line-h1: false
16+
# MD007 - list indentation
17+
list_indentation:
18+
indent: 4 # use 4 spaces for nested lists
1619

1720
globs:
1821

394 KB
Loading

docs/assets/lldb_after_u.png

390 KB
Loading

docs/assets/lldb_before_u.png

390 KB
Loading

docs/assets/lldb_debug_console.png

381 KB
Loading
383 KB
Loading
68.6 KB
Loading
268 KB
Loading

docs/assets/lldb_watch.png

408 KB
Loading
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
This tutorial introduces debugging C code with LLDB (the Low-Level Debugger)
2+
via VS Code's Run and Debug functionality. It is also possible to use lldb
3+
via the command line using bash terminals in the R Dev Container.
4+
5+
#### 1. Open an R Terminal Running R Built from Source
6+
7+
If necessary, run `which_r` to switch to a version of R you have built
8+
following the [Building R](building_r.md) tutorial.[^1]
9+
10+
Then open an R terminal by clicking on `R: (not attached)` in the status bar,
11+
or running `R: Create R terminal` from the VS Code command palette.
12+
13+
#### 2. Attach LLDB to the Running R Process
14+
15+
Find the process ID (PID) of your R session, by calling `Sys.getpid()` in R:
16+
17+
```r
18+
Sys.getpid()
19+
```
20+
21+
The PID is also shown after the R version number in the status bar (you may
22+
need to deselect some of the statuses shown to see the status for R).
23+
24+
Open the "Run and Debug" sidebar and click the green arrow next to the
25+
drop-down box at the top.
26+
27+
![alt text](../assets/lldb_start_debugging.png)
28+
29+
This will open a dialog for you to enter the PID:
30+
31+
![alt text](../assets/lldb_select_process.png)
32+
33+
When you enter the PID the corresponding process is shown underneath -
34+
click on it to select that process for LLDB to attach to.
35+
36+
#### 3. Set a Breakpoint in C Code
37+
38+
For example, to debug the `rlogis` C function, open
39+
`$TOP_SRCDIR/src/nmath/rlogis.c` and set a breakpoint by clicking to the left
40+
of the line number corresponding to the first line in the body of the function:
41+
42+
![alt text](../assets/lldb_add_breakpoint.png)
43+
44+
#### 4. Trigger the Debugger
45+
46+
In the R terminal, run the `rlogis()` command, which calls the `rlogis` C
47+
function:
48+
49+
```r
50+
rlogis(1)
51+
```
52+
53+
This will trigger the LLDB debugger and pause at the line where the
54+
breakpoint was added:
55+
56+
![alt text](../assets/lldb_debugging_rlogis.png)
57+
58+
#### 5. Using the Debugger Toolbar
59+
60+
After pausing at a breakpoint, use the LLDB
61+
toolbar buttons and commands to control execution:
62+
63+
- **Continue/Pause** (▷ in blue, or F5): Resume running until the next
64+
breakpoint or the end of the call from R. This changes to a pause button
65+
(⏸ in blue) when the code is running, allowing you to pause execution.
66+
- **Step Over** (↷ in blue, or F10): Run the current line of code and stop
67+
at the next line.
68+
- **Step Into** (↓ in blue, or F11): Run the current line of code and step
69+
into the next function called to start debugging the code in that function.
70+
- **Step Out** (↑ in blue, or Shift+F11): Run the remainder of the current
71+
function and stop at the point where the function was called. This will step
72+
out through several internal C functions in the call stack - use
73+
**Continue** instead to finish and return to R.
74+
- **Restart** (⟲ in green, or Cmd/Ctrl+Shift+F5): Start again from the
75+
beginning.
76+
- **Disconnect** (🔌 in red, or Shift+F5): Detach the debugger but keep R
77+
running.
78+
- **Stop** (access from more controls): Teminate the debugging session and
79+
the R process (closes the R terminal).
80+
81+
#### 6. Inspect Variables and Expressions
82+
83+
The Variables sub-panel of the Run and Debug side panel shows the current value
84+
of variables in the current environment. This is particularly helpful for
85+
local variables defined in the function, e.g. before `u` is defined:
86+
87+
![alt text](../assets/lldb_before_u.png)
88+
89+
and after
90+
91+
![alt text](../assets/lldb_after_u.png)
92+
93+
In the Watch sub-panel we can define expressions to watch as we step through
94+
the code. For example, we might watch `u / (1 - u)` and `scale == 0`:
95+
96+
![alt text](../assets/lldb_watch.png)
97+
98+
Note these expressions can only use simple operations, for example, we can't
99+
watch `log (u / (1 - u))` as this uses the `log` function.
100+
101+
The watch panel can also be used to dereference pointers (e.g. `*ptr`) or
102+
access elements of an array (e.g. `array[5]`).
103+
104+
#### 7. Using the Debug Console
105+
106+
Using the Debug Console, we can interact with LLDB via the command line,
107+
enabling more advanced debugging.
108+
109+
![alt text](../assets/lldb_debug_console.png)
110+
111+
##### Stepping through the code
112+
113+
These commands are equivalent to the debug toolbar buttons
114+
115+
- `c` continue
116+
- `n` next (Step Over)
117+
- `s` step into
118+
119+
##### Navigating the call stack
120+
121+
These commands are equivalent to selecting function names in the Call Stack
122+
sub-panel of the Run and Debug panel.
123+
124+
- `up` shows the next level up in the call stack (also, `down`)
125+
126+
##### Setting breakpoints
127+
128+
- `break set -n FUNC_NAME` to set a breakpoint at the start of the function,
129+
e.g.
130+
- `break set -n C_plotXY` to set a breakpoint in `C_plotXY`
131+
- `break set -n Rf_logis` to set a breakpoint in `rlogis`[^2]
132+
- `break set -l 31` sets a breakpoint at line 31 of the current file
133+
- `breakpoint delete` deletes **all** breakpoints
134+
135+
##### Values, expressions and calls
136+
137+
- `print x` prints x, where `x` is a simple expression with the same
138+
restrictions as for the Watch sub-panel
139+
- `expr x` evaluates C expression `x`. For example
140+
- Modify the value of objects: `expr u = 0.2`
141+
- Call functions, defining the return type:
142+
`expr (double) log(u / (1 - u))`
143+
- `call FUNC_NAME()` runs a C function in the debugger. We can use this to run C
144+
functions defined in R[^2], e.g.
145+
146+
```c
147+
call Rf_rgamma(10.0 / 2.0, 2.0)
148+
```
149+
150+
```text
151+
(double) $2 = 10.161318250826971
152+
```
153+
154+
```c
155+
call Rf_PrintValue(x)
156+
```
157+
158+
Note the second call asks R to print the value of x, so the output will
159+
appear in the **R terminal**, not the debug console. This can be useful
160+
for printing `SEXP` objects.
161+
<!--
162+
Using `expr` to compile and run a C expression is a feature of LLDB
163+
vs GDB
164+
-->
165+
166+
<!--
167+
Not so helpful lldb commands:
168+
- `q` or `quit`: does not seem to have any effect in VS Code
169+
-->
170+
171+
[^1]: Debugging C code requires R to have been built with `CFLAGS="-g -O0"`.
172+
This will not be the case for binary installs (such as the version of R
173+
pre-installed in the container) or R built following the Building R tutorial
174+
for R Dev Container ≤ v0.3.
175+
[^2]:
176+
C function names in R are internally prefixed (usually with `Rf_` or `R_`) to
177+
avoid name collisions. If the name in the function definition does not have
178+
a prefix, this means a header file maps the unprefixed name to a
179+
prefixed name. Search the R sources for `#define FUNC_NAME` to find the
180+
mapping, e.g. `#define rlogis Rf_logis` in
181+
`$TOP_SRCDIR/src/include/Rmath.h0.in` tells us we must use `Rf_logis` as the
182+
function name in lldb commands like `break set -n` and `call`.

0 commit comments

Comments
 (0)