I've written a DWARF expression parser and stack unwinder and its not true that those three use-cases are enough to be robust [0]. Based on my own observations, you need to implement:
- undefined
- same value
- offset(N)
- val_offset(N)
- register(N)
I've never come across expression(E), val_expression(E), or architectural. But you definitely do need the above in the common cases.
More generally, I'm kinda confused by that quote, I think the author may be conflating DWARF expressions and stack unwinding a bit?
What I think they might have been getting at is that with DWARF expression evaluation [1] in particular (i.e. not just stack unwinding), I've never even seen +80% of the opcodes I've implemented in the wild across several programming languages and compilers. I also left about 1/4 of them unimplemented and I've never had an issue. The intention of the DWARF spec writers was to provide folks with a stack machine that was Turing-complete, but in practice, yeah, nobody really takes advantage of it because there's not really a need.
I've found two examples of DWARF expressions in the wild: 1. A handwritten expression in libpthread <= 2.19, which was removed in 2014 [1], and 2. the expression covering the PLT section of most binaries. [2]
> More generally, I'm kinda confused by that quote, I think the author may be conflating DWARF expressions and stack unwinding a bit?
I suspect they only cared about / looked at the portion of DWARF information needed for stack unwinding. I'd read the quote with that implicit qualifier after "almost all dwarf programs".
I find this surprising! Was this for off the shelf applications or some custom binaries?
We see DWARF expressions such as `DW_CFA_def_cfa_expression` on the regular. See the "Test Plan" section and commit messages of the PR that introduced support for this particular opcode, as well as the simple executable that shows this opcode [0] and its printed unwind table [1]
Yep! I've never seen anything besides DW_CFA_def_cfa, DW_CFA_offset, and DW_CFA_nop in my CIE definition instructions before. Not to say you shouldn't support them or that they don't exist - they just haven't come up for me yet on the compilers I've been digging in to. I've been examining programs I wrote myself, compiled on Linux using a variety of tools (gcc, g++, clang, clang++, rust, go, zig, and jai are the compilers I've been supporting). I've been writing a debugger from scratch for the past few months, so the set of programs I've been testing on is quite small compared to you I'm sure.
I've never encountered a DW_CFA_def_cfa_expression before in the wild, which compilers(s) are you seeing that in out of curiosity? I see Parca supports many more languages/compilers than I do. I do support that opcode (and all the other CIE opcodes) in my debugger, I just have yet to find a compiler that generates it.
Side note, just looked through the Parca marketing materials, it looks awesome! Excited to try it out on some of my stuff and see what I can find.
That's very curious! We definitely see way more DWARF opcodes in the wild, including `DW_CFA_remember_state`, `DW_CFA_restore_state`, `DW_CFA_advance_*`, among others.
Statistically speaking, a profiler has more chances of bumping into program counters (PCs) that fall in ranges defined by a larger set of DWARF opcodes. We run at 100Hz, which can quickly yield a large number of different program counters, while I assume that most debugging sessions won't hit that many different PCs.
I'm using Fedora 36 which ships `gcc (GCC) 12.2.1 20220819 (Red Hat 12.2.1-2)`
> I've been writing a debugger from scratch for the past few months
Sounds fun! Are you planning on writing about this? I am sure there would be many people interested in reading about this topic!
Either way feel free to reach out at javier @ my company's domain if you want to discuss the DWARF opcodes thing further!
Good point about profilers' stack unwinders hitting much more code than a traditional debugger does, I'm sure that's mostly to blame. I've only been debugging a few thousand lines of code with my debugger so far, so we can't really call it a serious tool yet! I also am using Fedora 36 with the gcc version that it ships with. :shrug:
I'm not sure if I plan to write about it, it doesn't feel like I'm uncovering anything that anyone can't go look up pretty easily. I've been building it mostly because I'm frustrated with all the existing tools out there, save for RemedyBG (which is awesome, but Windows-only, and I'm only on Windows every so often). However, I'm still quite a ways off from being a reasonable daily driver!
> The intention of the DWARF spec writers was to provide folks with a stack machine that was Turing-complete, but in practice, yeah, nobody really takes advantage of it because there's not really a need.
There definitely is a need for better DWARF information, be it with new opcodes or not. It's just that generating them has not been a priority for compilers.
- undefined - same value - offset(N) - val_offset(N) - register(N)
I've never come across expression(E), val_expression(E), or architectural. But you definitely do need the above in the common cases.
More generally, I'm kinda confused by that quote, I think the author may be conflating DWARF expressions and stack unwinding a bit?
What I think they might have been getting at is that with DWARF expression evaluation [1] in particular (i.e. not just stack unwinding), I've never even seen +80% of the opcodes I've implemented in the wild across several programming languages and compilers. I also left about 1/4 of them unimplemented and I've never had an issue. The intention of the DWARF spec writers was to provide folks with a stack machine that was Turing-complete, but in practice, yeah, nobody really takes advantage of it because there's not really a need.
[0] https://dwarfstd.org/doc/DWARF5.pdf (section 6.4) [1] https://dwarfstd.org/doc/DWARF5.pdf (section 7.7)