-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Fix interpreter SetIP breakpoint handling #131784
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -845,11 +845,16 @@ static void InterpBreakpoint(const int32_t *ip, const InterpMethodContextFrame * | |
| EX_END_CATCH | ||
| pThread->SetFilterContext(NULL); | ||
|
|
||
| // The debugger may have moved execution via SetIP. If so, drop the bypass | ||
| // (it was set up for the original IP) and resume at the new context via | ||
| // ResumeAfterCatchException. | ||
| // The debugger may have moved execution via SetIP. Preserve a bypass | ||
| // created for the destination before resuming at the new context. | ||
| if ((GetIP(&ctx) != (PCODE)ip) || (GetSP(&ctx) != (DWORD64)pFrame)) | ||
| { | ||
| if (GetIP(&ctx) == (PCODE)savedBypassAddress) | ||
| { | ||
| pThreadContext->m_bypassAddress = savedBypassAddress; | ||
| pThreadContext->m_bypassOpcode = savedBypassOpcode; | ||
| } | ||
|
|
||
| ThrowResumeAfterCatchException(GetSP(&ctx), GetIP(&ctx)); | ||
| } | ||
|
|
||
|
|
@@ -1500,10 +1505,17 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr | |
| INTOP_CASE(INTOP_BREAKPOINT) | ||
| { | ||
| pFrame->ip = ip; | ||
| LOG((LF_CORDB, LL_INFO10000, "InterpExecMethod: Hit breakpoint at IP %p\n", ip)); | ||
| InterpBreakpoint(ip, pFrame, stack, pInterpreterFrame); | ||
|
|
||
| int32_t bypassOpcode = 0; | ||
| if (pThreadContext->HasBypass(ip, &bypassOpcode)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not clear to me why we want to execute the bypass byte code. Do we do that for JITted code too? I would expect that in native code, if I set a breakpoint at say a division instruction, we would not execute that instruction until we resume after breakpoint. Why would the interpreter be different?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This bypass dispatch was intended for a case where I thought this would be safe, as I don't expect we would have more than one bypass set at given ip and so if there is a bypass already set when we hit
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes we do have the same patch-skip concept for JITted code, implemented at the native instruction level.
Correct. We do not execute the patched instruction when stopping at the breakpoint. The bypass is installed when the debugger resumes execution and executes the saved original instruction once. With SetIP, if the destination contains a breakpoint patch, the interpreter needs the same behavior; otherwise, it immediately reports that destination breakpoint instead of executing the requested instruction. |
||
| { | ||
| LOG((LF_CORDB, LL_INFO10000, "InterpExecMethod: Pre-callback bypass at IP %p with opcode 0x%x\n", ip, bypassOpcode)); | ||
| pThreadContext->ClearBypass(); | ||
| INTOP_DISPATCH(bypassOpcode); | ||
| } | ||
|
|
||
| LOG((LF_CORDB, LL_INFO10000, "InterpExecMethod: Hit breakpoint at IP %p\n", ip)); | ||
| InterpBreakpoint(ip, pFrame, stack, pInterpreterFrame); | ||
|
|
||
| // After debugger callback, check if bypass was set on the thread context | ||
| if (pThreadContext->HasBypass(ip, &bypassOpcode)) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In which scenario does this happen ? Say the debugger wants to continue execution at a different ip. Then it would just set the ip to the new location and it just starts executing from there. What would be the point of setting a bypass. If the target instruction is a breakpoint, sounds like it should just execute it normally.
Maybe the debugger still wants to execute the original instruction that was trapped, before dispatching to the new location ? In that case, isn't
savedBypassAddresspointing to the original breakpoint location ? Or issavedBypassAddressalways pointing to the new target with the opcode from the other trapping instruction, suggesting that the debugger has enough information to always set the bypass to the right values. In which case, why are we doing this equality check, should we just set the bypass unconditionally, unifying with the code below ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for the case when
SetIPmoves execution to a place that already has a breakpoint. After debugger resumes we would hit the breakpoint again.I'm not sure we can because we need to exit through
ThrowResumeAfterCatchExceptionto move to the new IP, thus skipping the regular breakpoint bypass path.I'm not sure that's what debugger want, I would expect that if execution is changed via
SetIP, we just move to the new location.We only need the bypass in case we go to breakpoint with
SetIPother case will execute normally.