This is an archive of the discontinued LLVM Phabricator instance.

[lldb] Reset breakpoint hit count before new runs
ClosedPublic

Authored by fdeazeve on Sep 14 2022, 6:40 AM.

Details

Summary

A common debugging pattern is to set a breakpoint that only stops after
a number of hits is recorded. The current implementation never resets
the hit count of breakpoints; as such, if a user re-runs their
program, the debugger will never stop on such a breakpoint again.

This behavior is arguably undesirable, as it renders such breakpoints
ineffective on all but the first run. This commit changes the
implementation of the Will{Launch, Attach} methods so that they reset
the _target's_ breakpoint hitcounts.

Diff Detail

Event Timeline

fdeazeve created this revision.Sep 14 2022, 6:40 AM
Herald added a project: Restricted Project. · View Herald TranscriptSep 14 2022, 6:40 AM
fdeazeve requested review of this revision.Sep 14 2022, 6:40 AM
Herald added a project: Restricted Project. · View Herald TranscriptSep 14 2022, 6:40 AM
fdeazeve added inline comments.Sep 14 2022, 6:43 AM
lldb/source/Breakpoint/Breakpoint.cpp
332

By the way, I don' think this counter is ever incremented anywhere. I'll removing it in a separate patch and see what happens

fdeazeve added inline comments.Sep 14 2022, 6:59 AM
lldb/source/Breakpoint/Breakpoint.cpp
332

Ah, nvm, Breakpoint is a friend of BreakpointLocation, and the latter increments this counter

JDevlieghere added inline comments.Sep 14 2022, 8:42 AM
lldb/source/Target/Process.cpp
2763–2766

Can this be a private member so we don't have to pass in *this?

fdeazeve added inline comments.Sep 14 2022, 8:51 AM
lldb/source/Target/Process.cpp
2763–2766

No strong preferences on my part, but I had made it a free function because it can be implemented in terms of the public behaviour, i.e. it doesn't rely on implementation details.

jingham requested changes to this revision.Sep 14 2022, 10:20 AM

Resetting the hit counts on rerun is a more useful behavior, so this is all fine. But the Target is the one that does all the breakpoint management, so I think the resetting should go through the Target, not the Process. And we generally delegate "do on all breakpoints" operations to the BreakpointList, so it would be more consistent with the current structure to go Process::WillLaunch -> Target::ResetHitCounts -> BreakpointList::ResetHitCounts.

lldb/source/Target/Process.cpp
2763–2766

Resetting all the hit counts in a BreakpointList seems to me a job of the BreakpointList. Also, while DoWillLaunch/Attach is where this action belongs, which is how the Process gets involved, the Target is the one that manages the breakpoints in general. So I think it would be better to have the Target do ResetHitCounts, and these Process calls should just dial up it's Target.

This revision now requires changes to proceed.Sep 14 2022, 10:20 AM
fdeazeve updated this revision to Diff 460165.Sep 14 2022, 11:30 AM

Addressed review comments

Resetting the hit counts on rerun is a more useful behavior, so this is all fine. But the Target is the one that does all the breakpoint management, so I think the resetting should go through the Target, not the Process. And we generally delegate "do on all breakpoints" operations to the BreakpointList, so it would be more consistent with the current structure to go Process::WillLaunch -> Target::ResetHitCounts -> BreakpointList::ResetHitCounts.

This makes sense! I've fixed this in the latest revision

fdeazeve added inline comments.Sep 14 2022, 11:33 AM
lldb/source/Breakpoint/Breakpoint.cpp
334

@jingham actually, should I apply the same idea to BreakpointLocationList?

fdeazeve updated this revision to Diff 460176.Sep 14 2022, 12:06 PM

Also applied Jim's comment to the LocationList class, as this involves a single
lock, instead of one lock per location.
If this is wrong, please let me know and I'll revert.

mib added a comment.EditedSep 14 2022, 7:09 PM

Resetting the hit counts on rerun is a more useful behavior, so this is all fine. But the Target is the one that does all the breakpoint management, so I think the resetting should go through the Target, not the Process. And we generally delegate "do on all breakpoints" operations to the BreakpointList, so it would be more consistent with the current structure to go Process::WillLaunch -> Target::ResetHitCounts -> BreakpointList::ResetHitCounts.

@fdeazeve Pretty cool! But I agree with Jim, breakpoints are handled by the target not the process. As a side-note, in the current implementation, I don't think it's necessary to add the DoWillAttachToProcessWithID method and override it in each process plugin. I think you could have just reset the hit_counter in the top-level Process::{Attach,Launch} class.

I don't think it's necessary to add the DoWillAttachToProcessWithID method and override it in each process plugin. I think you could have just reset the hit_counter in the top-level Process::{Attach,Launch} class.

@jingham Thoughts on this?

The more we gather up the "things you have to do before an operation starts" or after it ends, etc. into WillDoOperation methods, the less ad hoc code you have in the callers that you have to remember to copy over if you are introducing a different way of doing the same operation. It also expresses when the operation needs to be done, which if you just had a call out in Process::Attach or whatever you would not know. Either way will work, but I think packaging these "do before" and "do after" tasks into methods makes everything clearer and easier to maintain.

jingham accepted this revision.Sep 16 2022, 10:27 AM

This version looks good to me.

This revision is now accepted and ready to land.Sep 16 2022, 10:27 AM
This revision was automatically updated to reflect the committed changes.
labath added a subscriber: labath.Sep 21 2022, 6:26 AM

I am afraid that this patch misses one method of initiating a debug session -- connecting to a running debug server (process connect, SBTarget::ConnectRemote). The breakpoint hit counts don't get reset in case of a reconnect. This isn't a particularly common use case (and the only reason I've noticed it is that for PlatformQemuUser, all "launches" are actually "connects" under the hood), but I've verified that this problem can be reproduced by issuing connect commands manually (on the regular host platform). I'm pretty sure that was not intentional.

Fixing this by adding another callout to ResetBreakpointHitCounts would be easy enough, but I'm also thinking if there isn't a better place from which to call this function, one that would capture all three scenarios in a single statement. I think that one such place could be Target::CreateProcess. This function is called by all three code paths, and it's a very good indicator that we will be starting a new debug session.

What do you think?

I am afraid that this patch misses one method of initiating a debug session -- connecting to a running debug server (process connect, SBTarget::ConnectRemote). The breakpoint hit counts don't get reset in case of a reconnect. This isn't a particularly common use case (and the only reason I've noticed it is that for PlatformQemuUser, all "launches" are actually "connects" under the hood), but I've verified that this problem can be reproduced by issuing connect commands manually (on the regular host platform). I'm pretty sure that was not intentional.

Fixing this by adding another callout to ResetBreakpointHitCounts would be easy enough, but I'm also thinking if there isn't a better place from which to call this function, one that would capture all three scenarios in a single statement. I think that one such place could be Target::CreateProcess. This function is called by all three code paths, and it's a very good indicator that we will be starting a new debug session.

What do you think?

My understanding is that there is an obligation of calling the WillXX methods before calling XX, so by placing the reset code in the WillXX functions we can rely on that guarantee. Right now, the same is implicit for "one must call Target::CreateProcess before launching or attaching". We could instead define a WillConnect and have the usual contract for that.

The code is fairly new to me, so I'm not confident enough to make a judgement call here. Thoughts?

I am afraid that this patch misses one method of initiating a debug session -- connecting to a running debug server (process connect, SBTarget::ConnectRemote). The breakpoint hit counts don't get reset in case of a reconnect. This isn't a particularly common use case (and the only reason I've noticed it is that for PlatformQemuUser, all "launches" are actually "connects" under the hood), but I've verified that this problem can be reproduced by issuing connect commands manually (on the regular host platform). I'm pretty sure that was not intentional.

Fixing this by adding another callout to ResetBreakpointHitCounts would be easy enough, but I'm also thinking if there isn't a better place from which to call this function, one that would capture all three scenarios in a single statement. I think that one such place could be Target::CreateProcess. This function is called by all three code paths, and it's a very good indicator that we will be starting a new debug session.

What do you think?

My understanding is that there is an obligation of calling the WillXX methods before calling XX, so by placing the reset code in the WillXX functions we can rely on that guarantee. Right now, the same is implicit for "one must call Target::CreateProcess before launching or attaching". We could instead define a WillConnect and have the usual contract for that.

I wouldn't really call it a "usual" contract, but yes, I'm sure this could be fixed by adding a WillConnect method. It might be also sufficient to just call WillAttach from at some appropriate place, since a "connect" operation looks very similar to an "attach", and a lot of the initialization operations are the same. I think we're already something like this somewhere (maybe for DidAttach?). However, that still leaves us with three (or two) places that need to be kept in sync.

The code is fairly new to me, so I'm not confident enough to make a judgement call here. Thoughts?

I don't see any advantage in doing this particular action "just before" a launch, as opposed to doing in on process creation, so I would definitely do it that way.

I also find it weird to be going through the Process class just to call another Target method, when all of the launch/attach/connect operations already go through the Target class, and so it should be perfectly capable of calling that method on its own.

If we use Target::CreateProcess to handle this task, how do we ensure that that will always get called any time we make a new process? That function doesn't do all that much special, it's only a couple lines long so it could easily get inlined somewhere.

If we use Target::CreateProcess to handle this task, how do we ensure that that will always get called any time we make a new process?

I'm not really sure how to answer that.

Clearly something like that can happen, but I think the overall risk is low. The most likely scenario for that happening would be if someone adds a new, fourth, method of initiating a process (in addition to launching, attaching and connecting), but I just don't know what would that be. But if one is doing that, I think it's at least as likely that he will forget to create the DoWillInitiate method and call the breakpoint reset function from there.

I think the only way to ensure that can't happen is to store the actual hit counts within the Process class itself, but I don't think anyone has an apetite to design something like that.

Anyone trying to create a function by bypassing a function called CreateProcess should think really hard before proceeding. And the more stuff we put into that function, the harder it will be for someone to bypass it.