neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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: