Runtime support for std::is_debugger_present()

Is anybody working on std::is_debugger_present() for libc++ yet?

For libstdc++ we’re talking about adding a volatile bool flag inside the library that debuggers could set to say a debugger is present. This would be more reliable than detecting whether the process is being ptraced (which can give false positives when a non-debugger is tracing, e.g. strace), and checking the executable name of a tracer to see if it contains something like “db” (which can give false negatives, e.g. AFAIK the totalview executable doesn’t have “db” in its name).

It would be nice if GDB and LLDB (and others) knew how to set this flag to announce when a debugger is attached/detached, so a public API in libstdc++ makes sense, something like __gnu_cxx::attach_debugger(bool).

Sidebar: freestanding support

A public API would also allow freestanding programs to call the function manually when being remotely debugged. This potentially solve the problem described in P2546R5 with is_debugger_present being hard to implement for freestanding.

Would libc++ want to do something like that too? If yes, it seems like it would make sense for libc++ and libstdc++ to support the same protocol, so debuggers don’t need to support two different ways to announce themselves. So maybe a new void __cxa_attach_debugger(bool) function in the Itanium ABI? Or should we just do our own things for now, and then consider harmonizing later?

1 Like

There is a PR here WIP - [libc++][debugging] P2546R5: Debugging support & P2810R4: `is_debugger_present` `is_replaceable` by H-G-Hristov · Pull Request #81447 · llvm/llvm-project · GitHub

I like the idea of a clean protocol for debuggers to advertise themselves

1 Like

CC @H-G-Hristov

Also see the comment WIP - [libc++][debugging] P2546R5: Debugging support & P2810R4: `is_debugger_present` `is_replaceable` by H-G-Hristov · Pull Request #81447 · llvm/llvm-project · GitHub and the following discussion

1 Like

In the PR @cor3ntin refers to I mention using a bool* as a hook instead of directly setting a bool: WIP - [libc++][debugging] P2546R5: Debugging support & P2810R4: `is_debugger_present` `is_replaceable` by H-G-Hristov · Pull Request #81447 · llvm/llvm-project · GitHub – I’m all for doing whatever works sooner rather than later though. :slight_smile:

Whatever the mechanism is, it ought to be something which the user can tell a debugger to NOT enable. Sometimes you really don’t want the program to change its behavior.

So maybe a new void __cxa_attach_debugger(bool) function in the Itanium ABI?

I assume the debugger would be able to find the function via debug symbols, so it wouldn’t need to be exported from the binary/DSO (in case the library is statically linked in to the program)?

What if there’s multiple copies of the function (e.g. different DSOs each have a distinct copy of the library)? Maybe a debugger should call every function having the given name, that it can find.

Is there a good reason to use a function call to announce debugger attach/detach, instead of having the debugger set the value of a symbol (or, as above, multiple symbols) having a given name? Since the call would happen effectively as an interrupt, doing anything other than setting a global from it seems like a bad idea – so maybe better to have the interface be for the debugger to just set the value of a variable of a given name instead.

Also, while this functionality is currently C++ only, it’s not really C++ specific at all. If we’re going to create a new ABI for debuggers to automatically announce their presence to a program, maybe it should be done in a manner which could also work for future versions of C or other programming languages. Therefore I’d say that using the name prefix __cxa is unfortunate.

I’m not really keen on having an interface where a debugger automatically calls into a process. Yes, many debuggers have features where the user can make such a call, but that’s something the user is directly responsible for. But having the debugger automatically call some well-known name really seems like a potential security problem.

A variable that the debugger can (attempt to, and not mind if it fails) modify seems much safer.

2 Likes

On Darwin systems, there’s a bit set in the kinfo_proc to indicate that a process is being debugged, so it is straightforward to check if you’re being debugged with a KERN_PROC / KERN_PROC_PID sysctl to get the kinfo_proc for the process, and check the P_TRACED flag.

One possibility would be to have a global variable and the debugger could set that to true on attach, clear it to false when detaching/quitting. If the debugger crashed, it would be left set. Neither lldb nor gdb set variables in the debugee like this today.

Another possibility would be to have the debugger put a breakpoint on is_debugger_present() and when that breakpoint is hit, force a return true. The debuggers already insert a breakpoint (on all Unix systems?) in the dynamic loader, to be notified about newly loaded libraries. gdb/lldb call these “internal breakpoints”, and it’s a well known process to insert them on attach, remove them on detach/quit. I don’t know how well this model fits to Windows.

If there could be a is_debugger_present in multiple binaries, the number of breakpoints could start to become a problem. gdb, last I looked, starts to have performance problems when it has large numbers of breakpoints set. lldb has no such limitations, beyond the time to send all the breakpoint set/clear packets on attach/detach. There is a large performance cost to hitting a breakpoint and resuming - if this is_debugger_present call might be in a hot loop of code, it would be noticable.

On Darwin, from lldb’s point of view, I would prefer the sysctl approach as the lightest weight way of implementing this. After that, I’d prefer breakpoints, using a very familiar way of contacting the debugger. The global would be a new kind of thing to do when attaching to a process.

This is probably less of a concern for Windows since it already has a IsDebuggerPresent function that libc++ could hook into. If libc++ can’t use it directly, it may be able to use some lower level functionality in Windows to achieve the same goal.