P2546R5 `<debugging>`
WG21-P2546R5 <debugging>
WG21-P2810R4 is_debugger_present() Is Replaceable
Feature-test macro:
#define __cpp_lib_debugging 202403L
We need to teach the IDE about this new extensionless header.
Note: We're focused on implementing the remaining library-only features in C++23. Until that's done, we will NOT be accepting PRs for C++26 features.
This is some far future perhaps, but I want to leave a comment here on how I'd like this to be implemented.
There are DebugBreak and IsDebuggerPresent functions. But I want them to be implemented not via function calls.
For DebugBreak it is pretty much obvious. For debugging convenience, it is better to avoid having a Windows DLL as a top stack frame, ideally we'd want to inline the break into the call site. That's why __debugbreak() intrinsic is the best option.
For IsDebuggerPresent the reason is performance. We'd want to avoid impacting non-debugger path. Instead of calling IsDebuggerPresent we can get BeingDebugged field from PEB, which we can get from TEB, which is __readfsdword and __readgsqword.
This is some far future perhaps, but I want to leave a comment here on how I'd like this to be implemented.
There are
DebugBreakandIsDebuggerPresentfunctions. But I want them to be implemented not via function calls.For
DebugBreakit is pretty much obvious. For debugging convenience, it is better to avoid having a Windows DLL as a top stack frame, ideally we'd want to inline the break into the call site. That's why__debugbreak()intrinsic is the best option.For
IsDebuggerPresentthe reason is performance. We'd want to avoid impacting non-debugger path. Instead of callingIsDebuggerPresentwe can getBeingDebuggedfiend fromTEB, which we can get fromTEB, which is __readfsdword and __readgsqword.
Have to use x18 Instead of these intrinsics under arm64, correct?
Have to use
x18Instead of these intrinsics under arm64, correct?
For obtaining TEB apparently yes. I have no much idea about Arm64 though.
Have to use
x18Instead of these intrinsics under arm64, correct?For obtaining TEB apparently yes. I have no much idea about Arm64 though.
I see. I'd think the implementation is easier to maintain when it takes fewer dependencies especially on the architecture specifics such as the processor ISA. Likely, it is the OS job to provide a good abstraction over that.
The offsets of the fields you're advising to use are not guaranteed to be preserved, and there is a fair warning about that in the documentation you've linked:
[This structure may be altered in future versions of Windows. Applications should use the alternate functions listed in this topic.]
Relying on these offsets (== bypassing the abstraction the OS provides) can make binaries non-portable b/w different releases of the OS, or even b/w updates.
The performance argument that was invoked needs data. Modern CPUs are good at caching, speculative execution and branch prediction, and without compelling performance difference, it might be hard to employ the performance argument. Any context switch for the thread is very likely to take more time that can be saved by breaking the API boundary that simple as IsDebuggerPresent is.
I don't think it is actually possible to alter most of TEB / PEB fields.
A lot of software already made assumptions.
TEB is implicitly used by the compiler for exceptions or thread-local storage.
Sure PEB and BeingDebugged are different things, but they may be used too (like, in debugger-protection techniques).
I still agree that the abstraction is useful,
I also agree that using taken branches and fetching cached data is very cheap. Still, this is not free. Comparison with context switching doesn't seem relevant, as I'm discussing the case when debugger is not present, and so the switching to the debugger does not happen.
It is a judgement call. Taking into account that future OSes/platforms bring a lot more likely risks, I'd take this unlikely one.
(Ideally I'd want IsDebuggerPresent to be defined in Windows SDK to use TEB directly)
We've agreed that the abstractions and the SDK exist for a good reason, and to me it appears that we've agreed that the performance argument requires data. "Not free" doesn't justify ignoring the warning of no structure layout guarantees. C++ idioms like pimpl use indirection that is not free (well, even using the compiler isn't) yet provides other benefits.
Here, for the benefits that are not backed up with the data, it doesn't look prudent to risk ABI compatibility by using the fields of the internal structures.
Just stumbled on this issue as I was looking into implementing this myself.
Using the SDK's IsDebuggerPresent (in Release) is actually just 2 CALLs (thunk + real function) and then 2 MOVs (one to get the TEB ptr, one for the BeingDebugged field, which is pretty much what you would get by doing it manually but with the overhead of 2jumps.
While I'd usually agree of things being inlined where possible, it seems it wouldn't bring much benefit here.