> On Sep 27, 2016, at 1:09 PM, Daniel Austin Noland via lldb-dev
> <[email protected]> wrote:
>
> I have been giving study to the private and public classes for
> break/watchpoints and I have some ideas for how to improve them. I am
> looking for comments from the community on this now to avoid wasted work and
> invite suggestion for improvements and any objections you may have.
>
> Some problems with things as they stand:
>
> 1. The Watchpoint and SBWatchpoint classes share very little implementation
> with the Breakpoint and SBBreakpoint classes. Both Breakpoint and Watchpoint
> inherit from Stoppoint, but that class provides very little functionality and
> not much of an interface. This is both a waste and a potential hazard (in the
> sense that we run the risk of allowing breakpoints and watchpoints to evolve
> in parallel thus complicating the API and making things more difficult to
> merge later).
> 2. The SBWatchpoint class lacks feature parity with SBBreakpoint (e.g. no
> callback implementation in SBWatchpoint).
It would be great to add any needed features internally (lldb_private::*) and
in the public API (lldb::*).
> 3. The Breakpoint class has grown an ecosystem of support classes which are
> unnecessarily specific to it. For example, I don't see a huge reason to
> maintain a totally distinct implementation for BreakpointList and
> WatchpointList.
Internally we can use virtual functions and have the list be a list of some
shared base class, that is fine. I don't believe we export the lists across the
public API so that shouldn't be a problem.
> 4. All of these classes are "old school" (not necessarily bad, but
> potentially a problem). For example:
> a. lots of const char* running around.
Feel free to use llvm::StringRef internally (lldb_private namespace), but not
in the public API. Anything in the "lldb" namespace can't change as we maintain
backward compatibility. The main gist in the public API:
- no inheritance
- fixed ivars that never change that are either one of: std::shared_ptr,
std::unique_ptr, or raw pointer.
- no virtual functions
- can't remove functions, you can add, but not remove or change. If you need to
change, just add an overloaded version with different args.
> b. DISALLOW_COPY_AND_ASSIGN(BreakpointEventData) to make ctors and such
> private rather than using ctor() = delete (which provides better compiler
> errors)
Change the DISALLOW_COPY_AND_ASSIGN macro. Done.
> c. use of Error& args in function signatures as opposed to
> Expected<ReturnType>.
Not sure I would tackle this. It would need to happen everywhere and would need
to not lose any lldb_private::Error functionality.
> d. callback implementation uses function pointers (an ever confusing topic,
> especially for new programmers) where I think we could use templated methods
> (or just a parent Callback class) to significant effect.
Internally fine, but the public API needs a simple function pointer. I don't
see the need to require templates.
> 5. Confusing or simply wrong documentation (e.g.
> Breakpoint::ResolveBreakpointInModules has arguments marked as @param[in]
> when the description of those arguments clearly indicates that they will be
> the output of the function).
Feel free to fix.
> 6. We simply don't have a Finishpoint class (triggers callback at the
> conclusion of the function or scope)
>
> My plan:
>
> I would like to refactor this in a few phases.
>
> Phase 1: Low hanging fruit
> --------------------------
>
> * Kick as much functionality as is reasonable from Breakpoint up to
> Stoppoint. At a minimum I would expand the Stoppoint API to include features
> which should logically be available to Breakpoint, Watchpoint, a hypothetical
> Finishpoint, and any future *point class. I would also try to refactor the
> Breakpoint support classes (e.g. BreakpointID) to derive from a new class
> (i.e. StoppointID) and kick functionality up the chain there as well. This
> would include methods like GetDescription, SetOptions, GetOptions, AddName,
> and so on.
fine
>
> * Review and clean the source docs
fine
>
> * Expand test coverage where that seems easy (perhaps there are tests hiding
> somewhere I didn't see?)
fine
>
> I would try to break private APIs minimally if at all. SB api will not be
> broken.
No worries, improve the internals as much as you want as long as the SB APIs do
not change.
>
> This should go a long ways toward resolving problems 1, 2, 3, and 5.
>
> Phase 2: Restructure / Modernize the Private API / Implementation
> -----------------------------------------------------------------
>
> * Change Error& out parameters to Expected<ReturnType>.
I am fine trying this out on a few classes like Watchpoint internally to see
how we like it.
> * Get rid of all the const char* vars / args in favor of a better string type
> (which?)
StringRef is fine as long as the ownership is never assumed to transfer, so
these are perfect for arguments, but not for storing a reference to a string
that is passed in as an argument inside an instance variable of a class. So
"const char *" in the public API, and llvm::StringRef internally is fine.
> * Prefer explicitly deleted copy ctor / assignments over multiline macro
> DISALLOW_COPY_AND_ASSIGN
Just change the macro. Do that quickly as a separate change list if you want as
we will OK that right away.
> * Try to reduce the (perhaps excessive) use of friendship between the support
> classes. For instance, is it necessary to give BreakpointLocation access to
> private data members from BreakpointLocationList, Process, BreakpointSite,
> and StopInfoBreakpoint? Wouldn't it be better to expand the functionality of
> those other classes?
Feel free to give us patches, we can review these on a case by case basis.
>
> A more significant change could be a rewrite of the callback functionality.
>
> There are some problems with the way callbacks are implemented in terms of
> maintainability and extensibility. I think that using templates and simple
> inheritance we could shift to using callback objects instead of function
> pointers. I have a crude code sketch of this plan in the works now, and I
> will submit that if there is interest in this idea. Essentially, the idea is
> to let the user define their own Breakpoint or Watchpoint (or whatever) child
> class which overrides a pure virtual run method from a parent
> StoppointCallback templated class. The template parameter of
> StoppointCallback would allow us to define a std::unique_ptr<UserData> member
> where the user can hang any data they want for the callback. That saves us
> from void pointers (which I find very awkward).
>
> template <class UserData>
> class StoppointCallback {
> private:
> std::unique_ptr<UserData> m_data;
> // ...
> };
I would switch to use std::function internally so we can use lambdas. I don't
see the need to templatize anything since you can do almost anything with
function objects and lambdas, both which work in place of a std::function. No
need to invent anything. We didn't have C++11 and C++14 back when this was
first coded, so we didn't use it back then. Public APIs will need to use
standard function callbacks. So "std::*" or "llvm::*" in the public API.
> I also think that such a decision requires significant thought before we pull
> that trigger. It does constitute a significant addition to the SB api, tho I
> don't think it would break the SB api per se since we can always use
> overloading and template specialization to keep the old functionality. On
> the other hand, ABI compatibility may be a problem. I don't know that much
> about SWIG or the needs of LLDB from a ABI stability perspective, so please
> speak up if I'm suggesting something crazy.
No templates in the public API please.
>
> That said, it is somewhat difficult to keep the API consistent between
> Breakpoint and Watchpoint using function pointers; the signature associated
> with each will differ (e.g. Watchpoint should provide old and new values of
> the subject variable, while that does not apply to Breakpoint at all). I
> welcome any suggestions which allow us to avoid logic duplication. My goto
> just happens to be templates here.
Not sure why they are better. Again, use std::function internally will buy us
labmdas, function pointers, and function object support, and in the public API
we must keep it simple and use function pointers.
>
> Doing even a subset of phase 2 would go a long ways toward fixing problem 4.
>
> Phase 3: Expanded Feature Set
> -----------------------------
>
> I would love to see these features:
>
> * Watch variables from a function in every scope of that function OR in only
> select invocations. Perhaps this already exists, but I can only get it to
> watch from a particular scope.
You would run out of watchpoints so quickly these would probably not be very
useful. Most things we use have 2 - 4 watchpoints at most. If you watched every
instance it would quickly run out. What should happen when it runs out of
resources? Just stop?
> * Better (read functional) remote GDB server break / watch. As has been
> disused in other threads, this is not really easy but is very desirable. For
> instance, I would very much like to see lldb play nice with valgrind's vgdb
> server as it offers some amazing break / watch functionality. As of today I
> need to do a great many hacks to make that work properly.
If something does support infinite watchpoints, we should be able to take
advantage of that and your case above could just work.
> * Finishpoints. This just seems obvious. (problem 6)
no problems with adding this once we have other support.
> * GDB style tracepoints. This may be very difficult but it seems very
> desirable.
I don't know these, but I know Jim does.
Watchpoints are in need of some serious work, so I am happy to see someone
willing to dive in. I think you have a pretty good idea of where we stand now,
let me know what you think of my comments.
Greg Clayton
_______________________________________________
lldb-dev mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-dev