Repository navigation
Code in block passed to macro has line number of macro invocation #39153
Description
Activity
- addedA-debuginfoArea: Debugging information in compiled programs (DWARF, PDB, etc.)Area: Debugging information in compiled programs (DWARF, PDB, etc.)
on Jan 18, 2017 @hsivonen: you can use
-Zdebug-macrosto turn this off.you can use
-Zdebug-macrosto turn this off.This helps thank you.
It surprises me that the other issue isn't a solved problem on the gdb/DWARF level, considering that C macros allow adjacent statements to be sourced from different places, too.
It surprises me that the other issue isn't a solved problem on the gdb/DWARF level, considering that C macros allow adjacent statements to be sourced from different places, too.
The rust compiler can emit DWARF saying where each line comes from and gdb should do the right thing when stepping. The keyword to look for in the DWARF standard is
DW_LNS_set_file. Also, FWIW, I think there is at least one other bug open about debuginfo and macros. #35238 talks about pretending that macros are the result of function inlining, but I don't understand why that would be necessary, or generally even desirable. I feel though that I am probably missing something.@tromey: We wanted to be able to step over macros like println!(), which are far more common in Rust than they are in C/C++.
I found two ways of achieving that:
- tag all code produced by a macro with the location of the macro expansion site (this is what is currently implemented). Unfortunately, this means that you cannot step through the macro guts even when that's desired. (Well, you can revert to the old behavior by compiling with -Zdebug-macros).
- tag all expanded code with true source locations, but then pretend that it ended up at the expansion site as a result of inlining (which macros are a form of, actually). This would be the preferred solution, since you'd be able to step over or into as needed; however, this turned out to be impossible to express using LLVM debug metadata.
but then pretend that it ended up at the expansion site as a result of inlining (which macros are a form of, actually)
This is the part I don't think is desirable. Inlining implies that there is a new function scope, which isn't necessarily the case in reality. It seems like this could make debugging weird unless you take extra effort to inject variables from the function into this new scope.
I tend to think this is another area where some Rust DWARF extension would be preferable; though maybe there are other halfway solutions, like a different heuristic for what locations to emit (e.g., if the macro invocation contains significant amounts of user code, emit locations for that code from the expansion, so that stepping "works").
Inlining implies that there is a new function scope, which isn't necessarily the case in reality.
Yeah, that's basically the reason we didn't pursue this route.
I hope my PR #39678 will sufficiently address the issue with
include!()and other macros like that.This is also a problem when profiling Rust code, at least with the
Valgrind/Callgrind/KCachegrind stack, since they (as a result of the dwarf
src loc info) wind up attributing all costs in the included file to the include
macro invokation itself, which is pretty much useless.@vadimcn Didn't you add a command line flag to control how macro source locations are handled?
@michaelwoerister yes, -Zdebug-macros does fix it. It would be nicer if this
was the default setting, though. At least from a profiling point of view, where
seeing inside the macro is important.@julian-seward1: You scenario is exactly why I added that flag. However, I fail to see why would we want to make it the default. The change was made for the sake of sane debugging experience. Very few people want to land in the middle of a macro definition in libstd (for which we don't even emit line numbers), when trying to step over a
println!()in their source.I, too, think that
-Zdebug-macrosbehavior should be the default. There are many non-macro cases where one wouldn't want to step into standard-library code. It seems to me that some kind of debugger-side ignore filter for the standard-library paths would be a better solution than the compiler obfuscating the location info.I, too, think that -Zdebug-macros behavior should be the default.
@hsivonen: I can understand that this may be annoying when debugging macro-heavy code (I am guessing you are referring to this?), but you've gotta agree that such code is pretty uncommon.
There are many non-macro cases where one wouldn't want to step into standard-library code.
But non-macro cases never were a problem - step-over works just fine for plain functions.
It seems to me that some kind of debugger-side ignore filter for the standard-library paths would be a better solution.
That would be debugger-dependent and everybody would have to take adjust their config to set up such exclusions. I am not even sure it would work. Debuggers typically allow one to exclude functions from being stepped in, however in this case, all code belongs to the function where the macro was expanded.
All things considered, I believe that the current default is correct.
That's not to say we should not try to improve. Can we identify more cases when we can be certain that stepping-in is the desired behavior, like this one? Perhaps an attribute can be added that allows to opt out of debug location override? I'll be happy to hear about any ideas that do not involve flipping the global default.
PS: Or, maybe, someone wants to design a better debug info support for macros and pitch it to the DWARF Standards Committee?
11 remaining items
Do we have any debuginfo attributes at all today? I'm worried about the ad-hoc nature of
#[collapse_debuginfo].As per #39153 (comment), I'm wondering if tackling this from the
#[track_caller]/Location::caller()side of things might result in a better interface for e.g. macro authors, and then debuginfo just follows from there.At the very least, I don't think it would be a good idea to consider stabilizing something here (whatever the timeline for that may be, but still) without being certain whether desired behaviors for
#[track_caller]and debuginfo align or not.
There's also the matter of always having full debugger awareness of macro expansion layers (like
rustcdiagnostics with-Z macro-backtrace), but I don't see it discussed since #39153 (comment) and I'm guessing there's practical reasons why it's not being pursued? (it's not super clear from the comments in this thread what exactly is wrong there, my best guess is existing debugger behavior isn't very friendly to such an approach?)
Since I mentioned
-Z macro-backtrace, we should probably also make that the default in pretty much any case where the macro author doesn't want to hide it from the macro user (whichever heuristic we may end up using for that) - it's a shame we have all that functionality implemented and keep it locked away behind a flag.Reacted by David WoodThere's also the matter of always having full debugger awareness of macro expansion layers ...
Technically this is possible, but hard to accomplish in practice. One would need to:
- Come up with a novel representation for macro expansions for both debug info formats (DWARF and PDB)
1.1 Convince DWARF Committee and Microsoft to accept it into their standards. - Modify Rust's private LLVM to emit this information.
2.1 Upstream it to mainline LLVM. - Modify all debuggers out there (well, at least GDB, LLDB and WinDbg) to interpret this information.
(2.1) and (3) might get pushback without (1.1)
The whole procedure is likely to take years.IMO, a more practical approach would be emitting debug information that is equivalent to an inlined lambda function that captures all variables in the surrounding scope. Might need to extend LLVM's DIBuilder API to support this scenario, but otherwise should be feasible to do.
Reacted by Eduard-Mihai Burtescu and Wesley Wiser- Come up with a novel representation for macro expansions for both debug info formats (DWARF and PDB)
- addedT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.
on Apr 5, 2023 - added a commit that references this issue
on Feb 12, 2024 - added a commit that references this issue
on Apr 26, 2024 - added 2 commits that reference this issue
on Jan 27, 2026
It appears that (at least on Linux) if you pass a block of code to a macro, the line numbers for code in the block become the line of the start of the macro invocation rather than the actual lines within the block. This makes debugging hard.
Consider:
Line reported for
one(),two()andthree()is the line thatcall_macro_defined_somewhere!({is on. Expected line number forone()to be the line number forcall_macro_defined_somewhere!({plus one, etc.