dev_tools.txt (19083B)
1 *dev_tools.txt* Nvim 2 3 4 NVIM REFERENCE MANUAL 5 6 7 Tools and techniques for developing Nvim *dev-tools* 8 9 This is for developing or debugging Nvim itself. 10 11 Type |gO| to see the table of contents. 12 13 ============================================================================== 14 Quickstart guide to developing Nvim *dev-quickstart* 15 16 You can start hacking on Nvim in less than 5 minutes: 17 18 1. Ensure you have the build prerequisites from `BUILD.md`. 19 2. Clone the source repo and "cd" into it: > 20 git clone https://github.com/neovim/neovim 21 cd neovim 22 # (Optional) Build and run Nvim: 23 make 24 VIMRUNTIME=./runtime ./build/bin/nvim --luamod-dev 25 3. Run a single test. We will start with "example_spec.lua", which is a real 26 test that shows how tests are written: > 27 make functionaltest TEST_FILE=test/functional/example_spec.lua 28 4. Notice the `before_each` block in the test file. Because it calls 29 `clear()`, each `it()` test will start a new Nvim instance. 30 5. Tests will do stuff in the Nvim instance and make assertions using `eq()`. 31 Tests that want to check the UI can also use `screen:expect()`. 32 6. Now make a code change in Nvim itself, then you can see the effects. The 33 example test does `feed('iline1…')`, so let's make a change to the 34 insert-mode code, which lives in `src/nvim/edit.c`. In the 35 `insert_handle_key` function, just after the `normalchar` label, add this 36 code: > 37 s->c = 'x'; 38 7. Then run the "example_spec.lua" test again, and it should fail with 39 something like this: > 40 test/functional/example_spec.lua:31: Row 1 did not match. 41 Expected: 42 |*line1 | 43 |*line^2 | 44 |{0:~ }| 45 |{0:~ }| 46 | | 47 Actual: 48 |*xine1 | 49 |*xine^2 | 50 |{0:~ }| 51 |{0:~ }| 52 | | 53 54 You now understand how to modify the codebase, write tests, and run tests. See 55 |dev-arch| for details about the internal architecture. 56 57 ============================================================================== 58 Logs *dev-tools-logs* 59 60 Low-level log messages sink to `$NVIM_LOG_FILE`. 61 62 UI events are logged at DEBUG level. > 63 64 rm -rf build/ 65 make CMAKE_BUILD_TYPE=Debug 66 67 Use `LOG_CALLSTACK()` (Linux only) to log the current stacktrace. To log to an 68 alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`. Requires 69 `-no-pie` ([ref](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860394#15)): > 70 71 rm -rf build/ 72 make CMAKE_BUILD_TYPE=Debug CMAKE_EXTRA_FLAGS="-DCMAKE_C_FLAGS=-no-pie" 73 74 Many log messages have a shared prefix, such as "UI" or "RPC". Use the shell to 75 filter the log, e.g. at DEBUG level you might want to exclude UI messages: > 76 77 tail -F ~/.local/state/nvim/nvim.log | cat -v | stdbuf -o0 grep -v UI | stdbuf -o0 tee -a log 78 79 80 ============================================================================== 81 Reproducible build 82 83 To make a reproducible build of Nvim, set cmake variable `LUA_GEN_PRG` to 84 a LuaJIT binary built with `LUAJIT_SECURITY_PRN=0`. See commit 85 cb757f2663e6950e655c6306d713338dfa66b18d. 86 87 88 ============================================================================== 89 Debug TUI *dev-tools-tui* 90 91 TUI INSPECT 92 93 Use the Ghostty https://ghostty.org/ inspector tool to observe and query the 94 output and events from any terminal application such as Nvim. 95 96 From the Ghostty inspector you can click the "Terminal IO" tab to get a trace. 97 98 TERMINFO LOGGING 99 100 At 'verbose' level 3, Nvim logs its internal terminfo state, so you can see 101 exactly what terminfo values it is using on the current system. > 102 103 nvim -V3log 104 105 TUI DEBUGGING WITH GDB LLDB 106 107 Launching the Nvim TUI involves two processes, one for main editor state and one 108 for rendering the TUI. Both of these processes use the nvim binary, so somewhat 109 confusingly setting a breakpoint in either will generally succeed but may not be 110 hit depending on which process the breakpoints were set in. 111 112 To debug the main process, you can debug the nvim binary with the `--headless` 113 flag which does not launch the TUI and will allow you to set breakpoints in code 114 not related to TUI rendering like so: > 115 116 lldb -- ./build/bin/nvim --headless --listen ~/.cache/nvim/debug-server.pipe 117 118 While in lldb, enter `run`. You can then attach to the headless process in a 119 new terminal window to interact with the editor like so: > 120 121 ./build/bin/nvim --remote-ui --server ~/.cache/nvim/debug-server.pipe 122 123 Conversely for debugging TUI rendering, you can start a headless process and 124 debug the remote-ui process multiple times without losing editor state. 125 126 For details on using nvim-dap and automatically debugging the child (main) 127 process, see [here](https://zignar.net/2023/02/17/debugging-neovim-with-neovim-and-nvim-dap/) 128 129 TUI REDRAW 130 131 For debugging Nvim TUI redraw behavior it is sometimes useful to slow down its 132 redraws. Set the 'writedelay' and 'redrawdebug' options to see where and when 133 the UI is painted. > 134 135 :set writedelay=50 rdb=compositor 136 137 Note: Nvim uses an internal screenbuffer to only send minimal updates even if a large 138 region is repainted internally. To also highlight excess internal redraws, use > 139 140 :set writedelay=50 rdb=compositor,nodelta 141 142 TUI TRACE 143 144 From the Ghostty inspector you can click the "Terminal IO" tab to get a trace. 145 146 Alternatively, the ancient `script` command is the "state of the art". The 147 libvterm `vterm-dump` utility formats the result for human-readability. 148 149 Record a Nvim terminal session and format it with `vterm-dump`: >sh 150 151 script foo 152 ./build/bin/nvim -u NONE 153 # Exit the script session with CTRL-d 154 155 # Use `vterm-dump` utility to format the result. 156 ./.deps/usr/bin/vterm-dump foo > bar 157 158 Then you can compare `bar` with another session, to debug TUI behavior. 159 160 TERMINAL REFERENCE 161 162 - `man terminfo` 163 - https://github.com/vim/vim/blob/0124320c97b0fbbb44613f42fc1c34fee6181fc8/src/libvterm/doc/seqs.txt 164 - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html 165 166 ============================================================================== 167 Debug Performance *dev-tools-perf* 168 169 PROFILING (EASY) 170 171 For debugging performance bottlenecks in any code, there is a simple (and very 172 effective) approach: 173 174 1. Run the slow code in a loop. 175 2. Break execution ~5 times and save the stacktrace. 176 3. The cause of the bottleneck will (almost always) appear in most of the stacktraces. 177 178 179 PROFILING (FANCY) 180 181 For more advanced profiling, consider `perf` + `flamegraph`. 182 183 184 USDT PROFILING (POWERFUL) 185 186 Or you can use USDT probes via `NVIM_PROBE` ([#12036](https://github.com/neovim/neovim/pull/12036)). 187 188 > USDT is basically a way to define stable probe points in userland binaries. 189 > The benefit of bcc is the ability to define logic to go along with the probe 190 > points. 191 192 Tools: 193 - bpftrace provides an awk-like language to the kernel bytecode, BPF. 194 - BCC provides a subset of C. Provides more complex logic than bpftrace, but takes a bit more effort. 195 196 Example using bpftrace to track slow vim functions, and print out any files 197 that were opened during the trace. At the end, it prints a histogram of 198 function timing: > 199 200 #!/usr/bin/env bpftrace 201 202 BEGIN { 203 @depth = -1; 204 } 205 206 tracepoint:sched:sched_process_fork /@pidmap[args->parent_pid]/ { 207 @pidmap[args->child_pid] = 1; 208 } 209 210 tracepoint:sched:sched_process_exit /@pidmap[args->pid]/ { 211 delete(@pidmap[args->pid]); 212 } 213 214 usdt:build/bin/nvim:neovim:eval__call_func__entry { 215 @pidmap[pid] = 1; 216 @depth++; 217 @funcentry[@depth] = nsecs; 218 } 219 220 usdt:build/bin/nvim:neovim:eval__call_func__return { 221 $func = str(arg0); 222 $msecs = (nsecs - @funcentry[@depth]) / 1000000; 223 224 @time_histo = hist($msecs); 225 226 if ($msecs >= 1000) { 227 printf("%u ms for %s\n", $msecs, $func); 228 print(@files); 229 } 230 231 clear(@files); 232 delete(@funcentry[@depth]); 233 @depth--; 234 } 235 236 tracepoint:syscalls:sys_enter_open, 237 tracepoint:syscalls:sys_enter_openat { 238 if (@pidmap[pid] == 1 && @depth >= 0) { 239 @files[str(args->filename)] = count(); 240 } 241 } 242 243 END { 244 clear(@depth); 245 } 246 247 $ sudo bpftrace funcslower.bt 248 1527 ms for Slower 249 @files[/usr/lib/libstdc++.so.6]: 2 250 @files[/etc/fish/config.fish]: 2 251 <snip> 252 253 ^C 254 @time_histo: 255 [0] 71430 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| 256 [1] 346 | | 257 [2, 4) 208 | | 258 [4, 8) 91 | | 259 [8, 16) 22 | | 260 [16, 32) 85 | | 261 [32, 64) 7 | | 262 [64, 128) 0 | | 263 [128, 256) 0 | | 264 [256, 512) 6 | | 265 [512, 1K) 1 | | 266 [1K, 2K) 5 | | 267 < 268 269 ============================================================================== 270 Backtraces *dev-tools-backtrace* 271 272 LINUX 273 274 Core dumps are disabled by default on Ubuntu, CentOS and others. 275 To enable core dumps: 276 >bash 277 ulimit -c unlimited 278 < 279 On systemd-based systems getting a backtrace is as easy as: 280 >bash 281 coredumpctl -1 gdb 282 < 283 `coredumpctl` is an optional tool, so you may need to install it: 284 >bash 285 sudo apt install systemd-coredump 286 < 287 288 The full backtrace is most useful; please send us the `backtrace.txt` file 289 when reporting a bug related to a crash: 290 >bash 291 2>&1 coredumpctl -1 gdb | tee -a backtrace.txt 292 (gdb) thread apply all bt full 293 < 294 295 On systems without `coredumpctl`, you may find a `core` dump file appearing 296 in the current directory or in other locations. On Linux systems where 297 `apport` is installed (such as Ubuntu), the directory where core dump files 298 are saved can be `/var/lib/apport/coredump` or elsewhere, depending on the 299 system configuration (see `/proc/sys/kernel/core_pattern`). See also: 300 https://stackoverflow.com/a/18368068 301 302 To get a backtrace from the `./core` dump file: 303 >bash 304 gdb build/bin/nvim ./core 2>&1 | tee backtrace.txt 305 (gdb) thread apply all bt full 306 < 307 308 MACOS 309 310 If `nvim` crashes, you can see the backtrace in `Console.app` (under "Crash 311 Reports" or "User Diagnostic Reports" for older macOS versions). 312 >bash 313 open -a Console 314 < 315 You may also want to enable core dumps on macOS. To do this, first make sure 316 the `/cores/` directory exists and is writable: 317 >bash 318 sudo mkdir /cores 319 sudo chown root:admin /cores 320 sudo chmod 1775 /cores 321 < 322 Then set the core size limit to `unlimited`: 323 >bash 324 ulimit -c unlimited 325 < 326 Note that this is done per shell process. If you want to make this the default 327 for all shells, add the above line to your shell's init file (e.g. `~/.bashrc` 328 or similar). 329 330 You can then open the core file in `lldb`: 331 >bash 332 lldb -c /cores/core.12345 333 < 334 Apple's documentation archive has some other useful information 335 https://developer.apple.com/library/archive/technotes/tn2124/_index.html#//apple_ref/doc/uid/DTS10003391-CH1-SECCOREDUMPS, 336 but note that some of the things on this page are out of date (such as enabling 337 core dumps with `/etc/launchd.conf`). 338 339 340 WINDOWS 341 342 If the Windows version of Nvim crashes in a reproducible manner, you can take 343 some steps to provide a useful bug report. 344 345 You must obtain the debugger symbols (PDB) file for the Nvim executable: nvim.pdb. 346 The PDB should be available from the same place that you obtained the 347 executable (TODO: not currently provided by Nvim CI releases). Be sure to use 348 the PDB that matches the EXE (same build). 349 350 If you built the executable yourself with the Microsoft Visual C++ compiler, 351 then the PDB was built with the EXE. 352 353 If you have Visual Studio, use that instead of the VC Toolkit and WinDbg. 354 355 For other compilers, always use the corresponding debugger: gdb or lldb. 356 357 Debugging Nvim crashes with Visual Studio 2005 ~ 358 359 First launch nvim.exe and then launch Visual Studio. (If you don't have 360 Visual Studio, get it from https://visualstudio.microsoft.com/downloads/). 361 362 On the Tools menu, click Attach to Process. Choose the Nvim process. 363 364 In Nvim, reproduce the crash. A dialog will appear in Visual Studio, telling 365 you about the unhandled exception in the Nvim process. Click Break to break 366 into the process. 367 368 Visual Studio will pop up another dialog, telling you that no symbols are 369 loaded and that the source code cannot be displayed. Click OK. 370 371 Several windows will open. Right-click in the Call Stack window. Choose Load 372 Symbols. The Find Symbols dialog will open, looking for (g)vim.pdb. Navigate 373 to the directory where you have the PDB file and click Open. 374 375 At this point, you should have a full call stack with vim function names and 376 line numbers. Double-click one of the lines and the Find Source dialog will 377 navigate to the directory where the Nvim source is (if you have it.) 378 379 If you don't know how to debug this any further, follow the instructions 380 at ":help bug-report". Paste the call stack into the bug report. 381 382 From Visual Studio you can also try saving a minidump via the Debug menu and 383 send it with the bug report. A minidump is a small file (<100KB), which 384 contains information about the state of your process. 385 386 ============================================================================== 387 Gdb *dev-tools-gdb* 388 389 USING GDB TO STEP THROUGH FUNCTIONAL TESTS 390 391 Use `TEST_TAG` to run tests matching busted tags (of the form `#foo` e.g. 392 `it("test #foo ...", ...)`): 393 >bash 394 GDB=1 TEST_TAG=foo make functionaltest 395 < 396 Then, in another terminal: 397 >bash 398 gdb build/bin/nvim 399 (gdb) target remote localhost:7777 400 401 -- See `nvim_argv` in https://github.com/neovim/neovim/blob/master/test/functional/testnvim.lua. 402 403 USING LLDB TO STEP THROUGH UNIT TESTS 404 405 > 406 lldb .deps/usr/bin/luajit -- .deps/usr/bin/busted --lpath="./build/?.lua" test/unit/ 407 < 408 USING GDB 409 410 To attach to a running `nvim` process with a pid of 1234 (Tip: the pid of a 411 running Nvim instance can be obtained by calling |getpid()|), for instance: 412 >bash 413 gdb -tui -p 1234 build/bin/nvim 414 < 415 The `gdb` interactive prompt will appear. At any time you can: 416 417 - `break foo` to set a breakpoint on the `foo()` function 418 - `n` to step over the next statement 419 - `<Enter>` to repeat the last command 420 - `s` to step into the next statement 421 - `c` to continue 422 - `finish` to step out of the current function 423 - `p zub` to print the value of `zub` 424 - `bt` to see a backtrace (callstack) from the current location 425 - `CTRL-x CTRL-a` or `tui enable` to show a TUI view of the source file in the 426 current debugging context. This can be extremely useful as it avoids the 427 need for a gdb "frontend". 428 - `<up>` and `<down>` to scroll the source file view 429 430 GDB REVERSE DEBUGGING 431 432 - `set record full insn-number-max unlimited` 433 - `continue` for a bit (at least until `main()` is executed 434 - `record` 435 - provoke the bug, then use `revert-next`, `reverse-step`, etc. to rewind the 436 debugger 437 438 USING GDBSERVER 439 440 You may want to connect multiple `gdb` clients to the same running `nvim` 441 process, or you may want to connect to a remote `nvim` process with a local 442 `gdb`. Using `gdbserver`, you can attach to a single process and control it 443 from multiple `gdb` clients. 444 445 Open a terminal and start `gdbserver` attached to `nvim` like this: 446 >bash 447 gdbserver :6666 build/bin/nvim 2> gdbserver.log 448 < 449 `gdbserver` is now listening on port 6666. You then need to attach to this 450 debugging session in another terminal: 451 >bash 452 gdb build/bin/nvim 453 < 454 Once you've entered `gdb`, you need to attach to the remote session: 455 > 456 (gdb) target remote localhost:6666 457 < 458 In case gdbserver puts the TUI as a background process, the TUI can become 459 unable to read input from pty (and receives SIGTTIN signal) and/or output data 460 (SIGTTOU signal). To force the TUI as the foreground process, you can add 461 >c 462 signal (SIGTTOU, SIG_IGN); 463 if (!tcsetpgrp(data->input.in_fd, getpid())) { 464 perror("tcsetpgrp failed"); 465 } 466 < 467 to `tui.c:terminfo_start`. 468 469 USING GDBSERVER IN TMUX 470 471 Consider using a custom makefile 472 https://github.com/neovim/neovim/blob/master/BUILD.md#custom-makefile to 473 quickly start debugging sessions using the `gdbserver` method mentioned above. 474 This example `local.mk` will create the debugging session when you type 475 `make debug`. 476 >make 477 .PHONY: dbg-start dbg-attach debug build 478 479 build: 480 @$(MAKE) nvim 481 482 dbg-start: build 483 @tmux new-window -n 'dbg-neovim' 'gdbserver :6666 ./build/bin/nvim -D' 484 485 dbg-attach: 486 @tmux new-window -n 'dbg-cgdb' 'cgdb -x gdb_start.sh ./build/bin/nvim' 487 488 debug: dbg-start dbg-attach 489 < 490 Here `gdb_start.sh` includes `gdb` commands to be called when the debugger 491 starts. It needs to attach to the server started by the `dbg-start` rule. For 492 example: 493 > 494 (gdb) target remote localhost:6666 495 (gdb) br main 496 < 497 ============================================================================== 498 Debugging crashes or memory leaks *dev-tools-asan* 499 500 BUILD WITH ASAN 501 502 Building Nvim with Clang sanitizers (Address Sanitizer: ASan, Undefined 503 Behavior Sanitizer: UBSan, Memory Sanitizer: MSan, Thread Sanitizer: TSan) is 504 a good way to catch undefined behavior, leaks and other errors as soon as they 505 happen. It's significantly faster than Valgrind. 506 507 Requires clang 3.4 or later, and `llvm-symbolizer` must be in `$PATH`: > 508 509 clang --version 510 511 Build Nvim with sanitizer instrumentation (choose one): > 512 513 CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_ASAN_UBSAN=ON" 514 CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_MSAN=ON" 515 CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_TSAN=ON" 516 517 Create a directory to store logs: > 518 519 mkdir -p "$HOME/logs" 520 521 Configure the sanitizer(s) via these environment variables: > 522 523 # Change to detect_leaks=1 to detect memory leaks (slower, noisier). 524 export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan" 525 # Show backtraces in the logs. 526 export MSAN_OPTIONS="log_path=${HOME}/logs/msan" 527 export TSAN_OPTIONS="log_path=${HOME}/logs/tsan" 528 529 Logs will be written to `${HOME}/logs/*san.PID` then. 530 531 For more information: https://github.com/google/sanitizers/wiki/SanitizerCommonFlags 532 533 534 535 vim:tw=78:ts=8:sw=4:et:ft=help:norl: