Search code examples
xcoderealmrx-swiftlldb

How to hide the third-party calls in the call stack in Xcode


When I work with Realm or RxSwift and there's an exception, I get these giant call stacks that I don't care about, and have to find the one or two lines that are my code. I'd like to only show symbols on the call stack that are from within the target running, not linked libraries. How can I hide these?

enter image description here


Solution

  • I don't know of a way to filter what Xcode displays, but you may find lldb's python scripting helpful. There are at least three possible ways to filter the stack:

    1. Frame name
    2. Library
    3. Source path

    First, to filter the stack by name, a regex can be used to check the function name. The regex can either match the frames you want to remove, or what you want to keep. From the given stack trace, I can't tell which ones you want to keep so I'll demonstrate removing Realm frames:

    (lldb) script
    import re
    for frame in lldb.thread:
        if not re.search("[Rr]ealm|RLM", frame.name):
            print(f"{frame.idx}: {frame.name}")
    

    Matching by function name isn't always ideal, it could be a long or complicated regex. If the libraries you want to filter happen to be dynamic libraries or frameworks, then the easiest method is to filter by "module":

    (lldb) script
    for frame in lldb.thread:
        if frame.module.file.basename != "Realm":
            print(f"{frame.idx}: {frame.name}")
    

    The third option is to filter by the source path associated with each frame. I don't know this would work for Realm (it depends on the debug info), but I have done this with RxSwift. In this example, frames that have RxSwift/ in their source path will not be printed:

    (lldb) script
    for frame in lldb.thread:
        if "RxSwift/" not in frame.line_entry.file.fullpath:
            print(f"{frame.idx}: {frame.name}")
    

    Reusable Command

    These methods can be combined to make a stack trace command that's suited for your project. To make that command, put your custom for loop into a file, and load it from your ~/.lldbinit:

    command script import path/to/mystack.py
    

    When writing an lldb command, a couple changes need to be made. Here's an example that combines two of the above methods:

    import lldb
    import re
    
    REALM_PATTERN = re.compile("[Rr]ealm|RLM")
    
    @lldb.command()
    def mystack(debugger, command, ctx, result, _):
        for frame in ctx.thread:
            if not is_rx(frame) and not is_realm(frame):
                print(f"{frame.idx}: {frame.name}", file=result)
    
    def is_realm(frame):
       return REALM_PATTERN.search(frame.name)
    
    def is_rx(frame):
       return "RxSwift/" in frame.line_entry.file.fullpath