Custom "synthetic" stack frames

Hello!

An idea popped in to my head, that it would really nice if for our custom task/threadpool implementation, I could get the backtrace of the task enqueuement site displayed in the debugger, with some fancy python scripting.

Doubly nice, if it would be a “compatible” mechanism that Xcode uses for the dispatch_async tasks, where when stopping in a breakpoint inside the task, it will show also the “Enqeued from” backtrace, so that my custom stuff would be shown as well. I imagine if there happens to be a “standard” way to do this, that it would work in other IDEs as well.

Now, I haven’t tried it yet, but I imagine the python side would be doable, my task object would need to save the result of backtrace() when enqueuing, and then the python script would go calling around backtrace_symbols() for resolving it for display. Perhaps some mechanism to find the right frame to search for the backtrace.

But with all my browsing python/c++ references, and poking around in Xcode’s lldb, I haven’t stumbled upon any relevant functionality to display the information in a “standard” way, so it would show up in the IDEs, which makes me fear that its done bespoke in Xcode, even if the functionality would be massively useful in many places (such as embedded interpreters, message queues, etc.)

Or I didn’t know what to look for, I was somehow imagining to to be similar to the variable formatters with “synthetic children”, but I suppose I may have a simplistic view of it.

Hi, interesting ideas. The existing “extended backtrace” feature that you’re seeing in Xcode is provided by the SystemRuntimeMacOSX plugin (in the C++ sources), see SystemRuntimeMacOSX::GetExtendedBacktraceThread(). There’s a dylib called libBacktraceRecording.dylib that is inserted into processes where this is recorded, and it collects a backtrace every time something is enqueued with libdispatch. Then when that enqueued item is executing, lldb can ask libBacktraceRecording for the backtrace that was recorded previously. (I prototyped this entirely in lldb, but stopping the process each time something was enqueued with libdispatch was far too slow)

Under the covers, libBacktraceRecording is giving lldb an array of pc values for the stack. For this extended backtrace, lldb creates a HistoryThread that records the pc’s and hands out stackframes with those pc values when queried.

I wrote all of this a decade ago, so I won’t remember the finer details of how it all works without looking at source, but that’s where it all is.

Today we don’t have any scripting hooks to provide that same kind of extended-backtrace functionality, sourced from the script-bridge, I don’t think. @mib has been doing a lot of work close to this recently, but I think this might be the first time this particular use case has been considered.

The “enqueuing backtrace” is provided as the result of calling Thread::FetchThreadExtendedInfo, which returns a JSON dictionary out of which lldb constructs the history threads.

FetchThreadExtendedInfo is a virtual method, and for instance the gdb-remote threads implement the querying of the SystemRuntime as Jason described.

FetchThreadExtendedInfo isn’t currently hooked into Python. so there’s no way to plug your own provider into extant threads in lldb. Adding a hook for that wouldn’t be hard, though you’d have to figure out what to do in cases where both the underlying thread implementation and the hook provider have extended info.

However, note that ScriptedThreads ARE able to provide extended info. So another way to do this that will work with the current lldb would be to wrap the process you are presenting in a scripted process and scripted threads. The trick here is to make a pass-through ScriptedProcess that makes ScriptedThreads for each real thread and forwards all the normal thread requests to the underlying thread, but handles the extended info request specially.

On the python side, you provide extended info by implementing get_extended_info on your scripted thread. There’s an example of doing this in the scripted_process.py file in the examples directory.

The ScriptedProcess/ScriptedThread interfaces are still fairly new, but they are the way that you can freely “represent” the underlying program however would suit some particular use case. For instance, if you had a green thread implementation, you could have the ScriptedProcess reflect both the executing threads and the green threads (or only the green threads hiding the others, etc…)