-
Notifications
You must be signed in to change notification settings - Fork 13.4k
"Just My Code" support for LLDB #61152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@llvm/issue-subscribers-lldb |
@yuanfang-chen worked on this, perhaps he can help answer some of the questions. |
It sounds to me like there are two kinds of problems this is trying to solve. One is "I have various sorts of function dispatchers - std::function dispatch, objc_msgSend dispatch, swift async dispatch, etc" that I want "step in" to step in through. The broader one is "I want debugging to always stop in some subset of my functions, but I want to automate which of those functions that is".
lldb's approach to the first problem has been to detect "trampoline functions" and figure out how to predict their results and go to the "interesting code". That's what the thread plans returned by ShouldStopHere are for. For instance, we should be able to figure out how all the C++ function dispatches work and just automatically take you to the actual function. We shouldn't need any user-generated markup for that. We do the same thing for objc_msgSend, async dispatch in swift, etc.
I'm less impressed by the "we got to the target of an STL dispatch" part of that demo because the debugger really should be able to recognize all the standard STL function dispatches, figure out where they are going and go there.
For hand-crafted dispatchers that lldb can't be expected to know about, the "lldb" solution would be to make an affordance for library authors to add to the ShouldStopHere detection mechanism, which would use the knowledge of their dispatch machinery to predict the target, and then we can run to there.
I like this approach since it targets the choke point - the dispatcher - rather than trying to mark all of user code.
Breaking everywhere in user code is easy, we don't need anything fancy to do that. If specifying "which user code functions we really want to stop in" involves some kind of config files, it would be just as easy to consult those in the "set breakpoints almost everywhere in user code" action. Setting a lot of breakpoints isn't super expensive, the expensive part is hitting them, which both solutions will involve. lldb already has a way to implement custom logic for breakpoint setting, so we wouldn't even have to do this in lldb proper.
I think I'm sounding more negative that I mean to. I do think that it should be the debugger's job to grok the common trampolines on the system, and we want to do that regardless of this markup. And doing that is lots more efficient that having every interesting user function on all threads hit breakpoints and continue while we're just trying to get from a trampoline to its target. So I don't think I would implement the feature this way, but OTOH I don't think it would be particularly hard to adopt, so if people are excited that, go for it...
Jim
… On Mar 3, 2023, at 4:20 AM, David Spickett ***@***.***> wrote:
This issue is to gather information for potentially supporting the "Just My Code" feature in lldb.
This is a Microsoft Debugger feature announced here: https://devblogs.microsoft.com/cppblog/announcing-jmc-stepping-in-visual-studio/
The clang code generation for it was implemented for ELF in:
https://discourse.llvm.org/t/rfc-just-my-code-stepping-for-non-msvc-debuggers/60279
https://reviews.llvm.org/D119910
And I presume was/is used with another downstream debugger.
This inserts calls to a __CheckForDebuggerJustMyCode function at the start of every compiled function. There is a default implementation provided to prevent a linker error if the C runtime (I assume) doesn't have one. The default implementation is just:
000000000000004c <__CheckForDebuggerJustMyCode>:
4c: d65f03c0 ret
The signature for this function is:
void __CheckForDebuggerJustMyCode(const char* flag);
There is a global variable inserted that contains the value 1 by default. 1 meaning that it is your code. Code will pass the address of that variable to that function. This means (I think) that a debugger could change that global byte to a 0 to mark a file as not your code at runtime.
The only real implementation I've seen is from MSVC. The basic idea is:
if the flag is 1
then go to some location that the debugger has placed a breakpoint on
else return
I am not sure if the break is placed exactly there, or how it is placed. You could arrange for a global symbol
that the debugger could break on. This symbol being present would also tell the debugger if jmc is present in the process.
The idea is that when you shouldn't stop, the code just runs through the function and returns. When you should, it will
end up at the break location and you go into the debugger.
After you've hit the break location you step out once, into the user function, then show the user
that you've stopped. So that they never see this intermediate function in the callstack.
What I am unsure about is does this feature only work/work best when all code is compiled with jwc?
Microsoft's debugger has configuration files for what paths you want to ignore. A change to those files does not
require a rebuild, this is why I think it is modifying these global vars each time that config updates.
https://learn.microsoft.com/en-us/visualstudio/debugger/just-my-code?view=vs-2022#BKMK_CPP_Customize_call_stack_behavior
If you decide at runtime that some of the jmc compatible code isn't yours, set those global vars to 0 and the jmc
function returns without breaking. Which suggests that you can only add to "your code" if the code in question is compiled with jmc. If the code is not jmc compatible it can never be added, unless you ignore jmc completely and fall back to filtering the steps normally.
Which seems to be what the pre-jmc version of the feature did in Microsoft's debugger.
(GDB can also do this https://sourceware.org/gdb/download/onlinedocs/gdb/Skipping-Over-Functions-and-Files.html)
Which has the disadvantage of going into the debugger every time, which is what the jmc function aims to avoid
For someone who is almost never debugging system code though, they won't hit that fallback.
Based on that conjecture the work would be:
An implementation of __CheckForDebuggerJustMyCode somewhere (compiler-rt?). Containing some unique symbol for the debugger to find.
Some flag to link that in with the compiled code and/or documentation to do that manually.
Teaching lldb how to detect the presence of jmc and find the place to break in __CheckForDebuggerJustMyCode.
Adding a setting to globally ignore jmc.
Adding the logic to do step ins using the jmc break.
Adding setting(s) to remove from "your code".
Adding a fallback to existing filtering if the code you want to add is not jmc compatible.
(not sure that we have those filters either)
—
Reply to this email directly, view it on GitHub <#61152>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ADUPVW4XC3WZOJWDNWUNBU3W2HOYFANCNFSM6AAAAAAVORMEZA>.
You are receiving this because you are subscribed to this thread.
|
Hi @DavidSpickett, Thanks for looking into JMC support in LLDB. Indeed, the ELF port of the JMC instrumentation is to support a downstream debugger. I'm actually not part of the debugger side of the work and I'm not a debugger expert but here is my understanding. First I want to mention that before JMC came into existence, MSVC's initial tackle on the problem is to annotate well-known STL libraries (https://devblogs.microsoft.com/cppblog/announcing-jmc-stepping-in-visual-studio/) which seems to be a workflow burden to some developers so later they developed JMC as a universal/general solution. The MSVC implementation of The debugger would, at startup or before launching the debugged program, set per-function boolean flags according to user configuration files, then before each As you could see, the breakpoint is predicated on the per-function flag and the current thread number, so there should be no unnecessary stop at that breakpoint. And JMC-style step-in is simply setting that breakpoint and doing a @jimingham, thanks a lot for shedding light on the LLDB's capabilities in this regard. It is definitely worthwhile to consider an alternative solution that skips compiler instrumentation. The solution sounds good to me except that it requires work from the user to teach LLDB the no-so-well-known/user dispatchers which contradicts the reason JMC was developed which is to reduce user intervention. That being said, my personal preference is to first implement JMC in LLDB similar to the MSVC solution (which I believe requires less LLDB change) and gradually improve upon that with LLDB-specific capabilities. |
Agreed.
My interest is mostly because: So I want to see how it works at least. I was also surprised at how it is implemented and don't appreciate the trade-offs yet.
I was on the right track then. Thanks for explaining!
JMC and what Jim is talking about is in the same areas, lldb source wise. So I am leaning toward the known solution first but can always change gears with what I've learned so far. |
Hey @DavidSpickett Thanks for opening this issue. I might end up having a relevant use case. I maintain the project Xeus-cpp which is a Jupyter Kernel to run C++ code based on clang-repl. As a part of the ongoing efforts for implementing a debugger for Xeus-cpp, we would be attaching lldb on top of JIT compiled code generated through clang-repl. Before integrating the same with Jupyter, we tried to get something working with Vs-code itself leveraging the debugger there. We got stuff working debugg.1.mp4But are struggling to replicate the same if we have multiple inputs/code blocks in our repl. In that case stepping into a function (probably in We've opened a dedicated issue here We are hopeful that the JMC framework might help our use case. Would be great to hear from you ! |
Assuming you JIT with this just my code enabled, then each function would have the check inserted into it. However that check relies on a translation unit gobal variable. So you can't say function X is my code but function Y is not, if X and Y were in the same source file. If the bits you want to skip are all pre-compiled or part of a single JIT unit (I don't know the technical term for this), then it could work. However I have not personally looked at this feature in a long time and have not implemented anything in LLDB or have plans to. I'm available to review PRs as usual of course. In the meantime there are some settings you could look into, first: https://lldb.llvm.org/use/map.html#ignore-a-function-when-doing-a-source-level-single-step-in Though this has a surprising side effect, if the place you want to stop is deeper in the callstack, it won't stop there if something earlier gets skipped. Think about skipping a C++ std lib function that takes a user defined callback. Skipping the std lib function actually bypasses the user's code in the callback. Which is surprising but just how this feature works, GDB acts similarly. Give it a go in case I'm wrong though :) Alternatively, if the code in between doesn't have debug info:
Hardly a principled solution but in theory you don't need to debug the glue between the JIT'd blocks. These settings sound like what you want. Keep stepping further in, until we get to a function with debug info. |
This issue is to gather information for potentially supporting the "Just My Code" feature in LLDB.
This is a Microsoft Debugger feature announced here: https://devblogs.microsoft.com/cppblog/announcing-jmc-stepping-in-visual-studio/
The Clang code generation for it was implemented for ELF in:
https://discourse.llvm.org/t/rfc-just-my-code-stepping-for-non-msvc-debuggers/60279
https://reviews.llvm.org/D119910
And I presume was/is used with another downstream debugger.
This inserts calls to a
__CheckForDebuggerJustMyCode
function at the start of every compiled function. There is a default implementation provided to prevent a linker error if the C runtime (I assume) doesn't have one. The default implementation is just:000000000000004c <__CheckForDebuggerJustMyCode>: 4c: d65f03c0 ret
The signature for this function is:
There is a global variable inserted that contains the value 1 by default. 1 meaning that it is your code. Code will pass the address of that variable to that function. This means (I think) that a debugger could change that global byte to a 0 to mark a file as not your code at runtime.
The only real implementation I've seen is from MSVC. The basic idea is:
I am not sure if the break is placed exactly there, or how it is placed. You could arrange for a global symbol
that the debugger could break on. This symbol being present would also tell the debugger if jmc is present in the process.
The idea is that when you shouldn't stop, the code just runs through the function and returns. When you should, it will
end up at the break location and you go into the debugger.
After you've hit the break location you step out once, into the user function, then show the user
that you've stopped. So that they never see this intermediate function in the callstack.
What I am unsure about is does this feature only work/work best when all code is compiled with jwc?
Microsoft's debugger has configuration files for what paths you want to ignore. A change to those files does not
require a rebuild, this is why I think it is modifying these global vars each time that config updates.
https://learn.microsoft.com/en-us/visualstudio/debugger/just-my-code?view=vs-2022#BKMK_CPP_Customize_call_stack_behavior
If you decide at runtime that some of the jmc compatible code isn't yours, set those global vars to 0 and the jmc
function returns without breaking. Which suggests that you can only add to "your code" if the code in question is compiled with jmc. If the code is not jmc compatible it can never be added, unless you ignore jmc completely and fall back to filtering the steps normally.
Which seems to be what the pre-jmc version of the feature did in Microsoft's debugger.
(GDB can also do this https://sourceware.org/gdb/download/onlinedocs/gdb/Skipping-Over-Functions-and-Files.html)
Which has the disadvantage of going into the debugger every time, which is what the jmc function aims to avoid.
For someone who is almost never debugging system code though, they won't hit that fallback.
Based on that conjecture the work would be:
__CheckForDebuggerJustMyCode
somewhere (compiler-rt?). Containing some unique symbol for the debugger to find.__CheckForDebuggerJustMyCode
.(not sure that we have those filters either)
The text was updated successfully, but these errors were encountered: