1. Overview
  2. Debugging workflow
  3. Debugging workflow: CI
  4. Basic interface
  5. Visualizing control flow
  6. Call stacks
  7. Explaining dataflow
  8. Multiprocess
  9. Search box
  10. Source files
  11. Condition and print expressions
  12. Toolbox
  13. Alerts
  14. Application logs
  15. Callees
  16. View operators
  17. Notebook
  18. Instruction execution
  19. Javascript
  20. Browser UI integration
  21. Screenshots
  22. Additional views
  23. GDB
  24. System debug info
  25. Compiler issues
  26. The Pernosco vision
  27. Related work

Compiler issues

Pernosco depends on the quality of DWARF debuginfo generated by compilers. We have measured this recently and also have quite a lot of anecdotal data. GCC and Clang both produce good debuginfo for C and C++ code in debug builds. For optimized builds the situation is not so good for GCC and significantly worse for Clang. Clang often just gives up on tracking variable locations inside optimized code. Rust (which uses LLVM, the same backend as Clang) is the same, but worse. Because non-optimized builds are so slow, especially for Rust, it is common for projects to build everything with at least some level of optimization; typically at least inlining is enabled. Thus, from the point of view of debugging, projects moving from GCC to Clang is unfortunate. Fortunately there is some interest in improving the quality of debuginfo for Clang optimized code, and some improvements are happening — we would love to see more! In the meantime, Chromium is using a special Clang flag that makes "optimistic" assumptions for the sake of more complete debuginfo.

Pernosco has different demands on debuginfo than traditional debuggers do. Pernosco does not use the CFI stack unwinding information at all. On the other hand, Pernosco can make better use of some existing features than traditional debuggers. For example, the DW_OP_(GNU_)entry_value feature indicates that at some points within a function, a variable has the same value as some expression evaluated on entering the function; GDB computes the value by (conceptually) unwinding the stack to the function entry point and then evaluating the expression, but this only works for a subset of registers. Pernosco simply evaluates the expressions at the time the function was entered, which works for any expression. (In practice GCC does not currently generate uses of DW_OP_(GNU_)entry_value that GDB can't use.)

A missing DWARF feature that Pernosco could use very effectively would be to explicitly identify code ranges where a local variable value is not available, but is known to be unchanged from some point earlier in the code where it was available. This is very often the case after a variable's live range ended and its register was reused for another variable. If we could be sure the variable was unchanged from when it was available, Pernosco could look backwards in time to get the value. We need an explicit DWARF feature to indicate it is safe to use the last available value, because a variable's value can change during a code range where it is unavailable in DWARF (e.g. because an optimization pass lost track of it). Unfortunately this feature would be nigh impossible for traditional debuggers to use, so we may have to wait for the ecosystem to shift in Pernosco's direction.

Pernosco would benefit if DWARF could identify the results of inlined functions. This information is not currently available, which makes Pernosco's "callees" view less effective.

A missing feature that would benefit Pernosco and also traditional debuggers is explicitly identifying code ranges where a variable has not been initialized. Typically in non-optimized builds compilers set the live range of a variable to the entire lexical scope it's in, and in optimized builds the live range often starts as soon as the variable is declared. Pernosco would benefit from knowing where a variable has been declared but not yet initialized, to avoid polluting the display with uninitialized variable values. (There was actually a patch submitted for this twelve years ago but it did not land.)

Fixing these issues is difficult because the DWARF spec and associated process are not in a good state. For example, the DWARF spec omits describing obsolete DWARF features, so writing DWARF-consuming tools that work with a range of older compilers and binaries requires consulting multiple DWARF specifications and reconciling their contents. Another issue is that vendor extensions are supposed to use an "experimental feature" part of the tag/attribute namespace. Compilers and debuggers ship using the values in that namespace, and later the features are standardized with different tag/attribute values. DWARF consumers have to support both values, and the standardized flavor may not match the original vendor extension. (This is reminiscient of vendor-prefixing in Web standards, and some lessons from Web standards, such as the importance of a shared test suite, could be usefully applied to DWARF.)

As well as processing DWARF, debuggers need to demangle symbols for C++, Rust and other languages. C++ mangling is mostly standardized, but unfortunately it is extremely complicated, the spec is difficult to read, and GCC and Clang are diverging in how they interpret the spec. Rust recently standardized mangling with a somewhat better spec.