如何使用 Thread.callStackSymbols 分析从另一个线程调用的父方法的 ID?

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

提问人:ramnik 提问时间:9/22/2023 更新时间:9/26/2023 访问量:33

问:

我正在尝试实现一个小型分析解决方案,以自动化方式跟踪整个代码库中的调用。

不幸的是,我对低级操作系统的东西一无所知,我什至不知道如何谷歌这些东西。因此,我不清楚以下几点:

  1. 堆栈跟踪中的列指的是什么?
    1. 什么是内存加载地址?
    2. 什么是退货地址?

我确实发现堆栈跟踪中索引 2 处的列是调用方法的唯一标识符,索引 3 处的列是其父方法的唯一标识符。这样,我就可以在同一线程中跟踪子方法和父方法。(有时这似乎被重复使用,但这没关系,因为那时我已经跟踪了跟踪。0x12340x1234

  1. 当父方法从另一个线程调用时,如何从子方法中跟踪父方法?
  2. 如何在堆栈跟踪中查看子方法通过闭包完成?

在 Xcode 中调试时,我在 Xcode 侧面板中看到此信息,所以它一定是某个!

enter image description 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)
iOS 调试 lldb apm mach

评论


答:

0赞 ramnik 9/22/2023 #1

经过大量研究,我自己找到了问题的答案。

--

我将省略以下问题的答案,因为根据新信息,它们已经变得无关紧要,而且我觉得没有足够的能力令人满意地回答它们:

  1. 堆栈跟踪中的列指的是什么?
    1. 什么是内存加载地址?
    2. 什么是退货地址?

--

重构问题 #1:“如何在 iOS 中获取 NSThread 的”父“线程?

你不能。没有父线程这样的东西。线程是一个 独立实体,即使线程可以与其他线程通信 线程,但不涉及层次结构。(来源)

--

重构问题 #2:“如何跨多个线程跟踪方法的逻辑跟踪?

当将新任务分配给后台队列时,您必须将 id 传递到 的闭包中(这可能会在后台创建一个新线程)。DispatchQueue

(这里不可能进行干净的方法切换,因为您必须更改原始实现而不是向其添加某些内容。

下面是一个有效的示例代码 (=> 希望其跟踪包含多个线程的客户必须使用自定义包装器(例如扩展方法),因为 iOS 不会公开任何接口来以自动方式执行此操作

let traceId = UUID().uuidString
DispatchQueue.global(qos: .userInitiated).async {
    Tracker.trackMethod(self, parentId: traceId)
    barkInBackground()
}
1赞 Jim Ingham 9/23/2023 #2

libdispatch 是 Darwin 系统的一部分,用于处理队列和运行块等。这也是调试器查询此历史“谁在何时调度了什么”信息的代理。但是,已安装的调度运行时库:/usr/lib/system/libdispatch.dylib 不会收集任何此类信息。这是因为这样做(甚至真的添加功能来这样做)会减慢执行速度,增加内存使用量,并且 libdispatch 不需要它来完成它的实际工作。Dispatch 是系统中非常关键的性能部分,因此库的正常版本无法承担超出必要范围的工作。

取而代之的是,有一个 libdispatch 的“内省”版本,当您启用“队列调试”功能时,Xcode 会加载该版本而不是普通版本。该版本的 libdispatch 确实收集了这些信息,并提供了一个 API 来访问它,lldb 使用它来生成这些“历史线程”。你可以看到你确实在使用变体库:如果在 Xcode 中停止时,你运行 lldb 命令:

(lldb) image list libdispatch.dylib
[  0] 6C6BE4E9-B201-3B02-8E69-33DF61FF3A44 0x0000000102ad8000 /usr/lib/system/introspection/libdispatch.dylib 

这是正在使用的 libdispatch 的内省版本。

但是,在 Darwin 系统上正常运行的应用程序不应加载此库,因此不会收集有关队列和调度的历史信息,也不会提供查询此信息的 API。因此,如果您想自己跟踪它,则必须按照所示手动进行。

评论

0赞 ramnik 9/23/2023
非常感谢!现在,我也知道苹果没有公开什么以及他们为什么要:D这样做。