|
| 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 | + |
| 28 | + |
| 29 | +This will open a dialog for you to enter the PID: |
| 30 | + |
| 31 | + |
| 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 | + |
| 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 | + |
| 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 | + |
| 88 | + |
| 89 | +and after |
| 90 | + |
| 91 | + |
| 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 | + |
| 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 | + |
| 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