Search code examples
iosdebugginglldbapmmach

How can I parse the ID of a parent method that calls from another thread by using Thread.callStackSymbols?


I am trying to implement a small analytics solution to trace calls throughout a code base in an automated manner.

Unfortunately, I have no idea about low-level OS stuff, and I do not even know how to google these things. Therefore, the following points are unclear to me:

  1. What do the columns in a stacktrace refer to?
    1. What is a memory load address?
    2. What is a return address?

I did figure out that the 0x1234 column at index 2 in a stacktrace is a unique identifier for the calling method, and column at index 3 is a unique identifier for its parent method. This way I can track child and parent methods inside the same Thread. (Sometimes this 0x1234 seems to be re-used, but this is alright because at that point I have already tracked the trace.)

  1. How do I track the parent method from within the child method, when it got called from another thread?
  2. How do I see in the stacktrace that a child method completed via a closure?

When debugging in Xcode, I see this information in the Xcode side panel, so it must be somehwere!

enter image description here

Other than that, I am lost. Please help?

I have also attached the code for this example here:

import Foundation

struct Dog {
    func bark() {
        print("CallStackSymbols for '\(#function)':")
        print("\t\(Thread.callStackSymbols.joined(separator: "\n\t"))")
        
        DispatchQueue.global(qos: .userInitiated).async {
            barkInBackground()
        }
    }
    
    func barkInBackground() {
        print("CallStackSymbols for '\(#function)':")
        print("\t\(Thread.callStackSymbols.joined(separator: "\n\t"))")
    }
}

let dog = Dog()
dog.bark()
sleep(1000)

Solution

  • After a lot of research, I found the answers to my questions myself.

    --

    I will omit the answers to the following questions because in light of new information, they have gotten irrelevant, and I do not feel competent enough to answer them satisfyingly:

    1. What do the columns in a stacktrace refer to?
      1. What is a memory load address?
      2. What is a return address?

    --

    Reframing the Question #1: "How to get a "parent" thread for NSThread in iOS?"

    You can't. There is no such thing as parent thread. A thread is an independent entity, even if a thread can communicate with other threads but there is no hierarchy involved. (Source)

    --

    Reframing the Question #2: "How to track the logical trace of methods across multiple threads?

    You have to pass an id into the closure of DispatchQueue, when a new task is given to a background queue (which might create a new thread behind the scenes).

    (Clean method swizzling is not possible here because you would have to CHANGE the original implementation instead of ADDING something to it.)

    Here is an example code that works (=> customers who want their trace to encompass multiple threads, have to use a custom wrapper (e.g. extension method) because iOS does not expose any interface to do this in an automated manner.)

    let traceId = UUID().uuidString
    DispatchQueue.global(qos: .userInitiated).async {
        Tracker.trackMethod(self, parentId: traceId)
        barkInBackground()
    }