It would be good if you would in you posts disambiguate between backend and frontend. Debugger is a rather loose term.
It be too much to say we have 2 interfaces. We have 2 places that define part of the interface.
And important to point out: this is the interface between ONE frontend and ONE backend (more below).
The new class mainly is watch value related. The backend used to do all the formatting (print as dec, or hex, or oct...). And now there is a way to just give the raw data.
- DebugThread /ThreadWorker, are part of FpDebug. The normal frontend does not access them at all.
- Threading is introduced in LazDebuggerFp and handled inside of it. And if used through the official intf, then its not visible to the outsider.
(Of course currently not all you needed is accessible via the proper interface)
- Some threading is also in FpDebug, but again not visible outside
The interface as far as it exists does not expose any threads. However it is meant to be called from the main thread only. I.e. it is not threadsafe, and can not be called from any random thread that the frontend or an extension may have created.
1 & 2)
The backend already does not know of those windows.-
- For the backend we need to have interfaces providing that data
- For the frontend we can have interfaces, to either replace a window, or hook into it (if/when appropriate). For watches I started an interface for Value providers. That likely will be extended, up to and including access to the canvas for custom paint.
3) As mentioned those issues are the lack of an interface. Your work around then of course encounter deep internal details....
4)
- RegisterStateChangeHandler / Init / DoStateChange
I have to do a bit of guessing here. You mean
- so the frontend extension can initialize itself?
- or the frontend can send a command for the backend to initialize?
You do have to register somehow for that.
Either
- As current, register event handlers
- register an interface and implement that interface in your extension, and have those methods called in your extension via that interface.
- or ?
But yes, the current state is indeed to be replaced. It has to many internal values. More dedicated notifications should be added.
To start with an example (from memory / exact implementation needs to be checked)
(Assuming DebugBoss was exposed via IdeIntf or similar (TODO))
To be able to display watches the frontend registers a IDbgWatchesMonitorIntf.
If you would replace the entire frontend (not just add/replace a window, but replace everything (the entire IdeDebugger), you would register your own. Currently there is only one monitor allowed, so if you just add a new Window the the existing IdeDebugger, then you have to interact with a future interface of the IdeDebugger (as the IdeDebugger already is the current frontend).
That currently does not yet exist. So you have to look at whatever DebugBoss.Watches offers.
So there will be yet another API to be done... DebuggerFrontEndIntf
That is imho correct. The Backend does not care that there are several Windows, and if they need or need not to know of each other. => E.g. what happens if I select an entry in the History window.
For the Backend there is only one frontend. It registers one Handler for watches.
The frontend then has to decide, if it needs to provide access for multiple parties (different windows) then that is something the frontend has to do.
Mind that currently IdeDebugger is the only frontend. And in most cases extra frontend like your CPU view will be additions to that. => but you could (not yet) replace it entirely.
By registering the IDbgWatchesMonitorIntf the IdeDebugger can also get new notifications (better than the current StateChange). But that hasn't yet been done.
Currently we have
"Frontend (IdeDebugger)" <> LazDebuggerIntf/DebuggerIntf (the interface) <> "Backend (E.g. LazDebuggerFp)"
We also need (todo)
"Extensions" <> LazIdeDebuggerIntf <> "Frontend (IdeDebugger)"
Then if you want to actually get data (evaluate a watch) you need to call (on the backend interface)
IDbgWatchesSupplierIntf.RequestData
- That is probably going to be handed through to the backend by the future IdeDebuggerIntf.
- You need to pass in a IDbgWatchValueIntf
IDbgWatchValueIntf carries all the info needed. The context (stack/thread), the expression, any options, and an interface to receive the result.
I opted for passing in an Interface (that the caller must implement). That allows easy extension in future.
Of course anyone implementing the interface (e.g. IDbgWatchValueIntf) will have to add new methods if it gets extended.
Therefore for most of the interfaces there is a template in form of a generic. So if the class implementing the interface inherits from that template, new methods will have default implementations.
Something like...
TWatchValue = class(specialize TDbgDataRequestTemplateBase<TBaseClass, IDbgWatchValueIntf>, IDbgWatchValueIntf)
So to get an expression evaluated as watch, you have a class implementing IDbgWatchValueIntf.
You call: IDbgWatchesSupplierIntf.RequestData(myClass);
The backend then can query the thread, stackframe, expression and more.
The backend then can call BeginUpdate, ResData, SetValidity, EndUpdate to write the result.
The EndUpdate or SetValidity acts as signal that the work has been done.
The handling of ResData calls is currently in IdeDebugger (frontend). That should maybe move into a public package. But that is part of breaking everything into interfaces, and tools packages.
You can get a memory dump with the above watches interface. But a dedicated API for that will be of interest too.
Though there are some benefits from getting the memory via watches.
If you want the memory for "MyFooInstance", then
- using a watch you can pass the variable name, and it will get the address.
- TODO: a flag could be specified if you want the variable or the data
objects are a pointer, so the variable would be the memory containing the pointer.
the data would be the memory pointed to.
- The backend does know the size of the data, and can return the exact amount of memory.
- The frontend can already override that size (at least extend it)
Of course if you are looking at other memory, things like the size of a variable do not matter. And then a simpler API would help, where you just give an address (still could be a variable) and the size you want.
There is no easier way to get the ID of the active thread.
I have to double check
ThreadsMonitor.CurrentThreads.EntryById[ ThreadsMonitor.CurrentThreads.CurrentThreadId ].ThreadTargetId
That is the old interface (hence the complicated way). So changes can and should be discussed.
Though the idea to have an internal ID (just between frontend and backend) is IMHO ok. It simplifies implementing many different backends, as they are free to specify what they expect.
You can then query info using that ID. (like the OS id for the thread)
There is no direct access to TDebuggerIntf, I had to get it via DebugBoss.Snapshots.Debugger (but that may not be necessary after what you wrote)
Register reading is only partially done, so I had to implement it completely by myself, as I need to fully work with SIMD XMM/YMM registers both in Windows and (importantly) in Linux
Yes there is a lot more interface needed.
For registers a similar interface like for watches should be created. Or well, If you have different ideas, I am happy to look at / discuss them.
- Registers should be returned in a generic form, leaving all formatting to the frontend
- This may include a copy of the raw content, if a frontend exists specific for the current architecture.
- Changing the content of a register has to be implement too.
And of course threads and stacks too.... And all the rest.
Of course that will mean to change all existing backends. So its gonna take time, once a design for the interface has been decided.
And about the registers. Here I don't understand a little bit, what do you mean tracking? I react to notifications from DebugBoss->OnState and there I re-read the thread context + subscribe to TThreadsNotification.OnChange to re-update the registers when the thread switches in the debugger window.
You calculate addresses for access via registers? I haven't looked deeply at it. Is that just for
[RIP + 123] style access?
or also [RAX*4+123] ?
If RAX too, then based on the current value (register from context for current thread) of RAX?
Or if you have
mov RAX, [foo]
mov RBX, [RAX + 123]
In the 2nd statement would you have the value "[foo]" loaded to RAX in the first statement?
Obviously only if there is on jump from other code to the 2nd statement.