【问题标题】:How to log all methods used in iOS app如何记录iOS应用程序中使用的所有方法
【发布时间】:2011-11-08 09:19:28
【问题描述】:

我正在为客户开发一款 iPad 应用。已经完成了大量的工作,我正在尝试拼凑整个设计的运行方式。

我想做的一件事是记录应用运行时调用了哪些方法。我见过一个自定义 DTrace 脚本,它旨在记录启动时的所有方法,但是当我在 Instruments 中运行它时,我没有得到任何结果。

记录方法的最佳方式是什么?

【问题讨论】:

  • 我在回答here 中描述的是 DTrace 脚本吗?如果是这样,那只有在模拟器上运行时才有效。此外,它只跟踪在-applicationDidFinishLaunching: 中调用的方法,因此如果您使用较新的-application:didFinishLaunchingWithOptions:,它不会显示任何内容。您可以编辑脚本以反映这种较新的方法,或者简单地删除条件以使其记录所有内容。
  • 嗨布拉德。是的,这是您博客中的代码。我进行了更改,但没有成功,但这仅仅是因为我缺乏 DTrace 经验。
  • 也许脚本的处理方式发生了一些变化。我去看看。

标签: iphone ios ipad instruments


【解决方案1】:

受 tc 对类似问题 here 的回答的启发,我整理了一个调试断点操作,该操作将在您的应用程序中每次触发 objc_msgSend() 时注销类和方法名称。这与我在 this answer 中描述的 DTrace 脚本类似。

要启用此断点操作,请创建一个新的符号断点(在 Xcode 4 中,转到断点导航器并使用窗口左下角的加号创建一个新的符号断点)。将符号设为objc_msgSend,将其设置为在评估操作后自动继续,并使用以下命令将操作设置为调试器命令:

printf "[%s %s]\n", (char *)object_getClassName(*(long*)($esp+4)),*(long *)($esp+8)

您的断点应如下所示:

当针对您的应用程序运行时,这应该会注销这样的消息:

[UIApplication sharedApplication]
[UIApplication _isClassic]
[NSCFString getCString:maxLength:encoding:]
[UIApplication class]
[SLSMoleculeAppDelegate isSubclassOfClass:]
[SLSMoleculeAppDelegate initialize]

如果您想知道我在哪里提取了内存地址,请阅读 Objective-C 运行时内部的this Phrack article。上面的内存地址仅适用于模拟器,因此您可能需要调整它以在 iOS 设备上运行应用程序。 Collin 建议在 his answer 中进行以下修改以在设备上运行它:

printf "[%s %s]\n", (char *)object_getClassName($r0),$r1

另外,我想您会发现,注销应用程序中调用的每个方法都会让您不知所措。您也许可以使用一些条件来过滤它,但我不知道这是否有助于您了解代码的执行方式。

【讨论】:

  • 好主意,但正如您所说的,运行时有点压倒性。
  • 在 xcode 4 中尝试此操作并得到此“错误:'printf' 不是有效命令。”
  • 这个有效 expr -- (void)printf("[%s, %s]\n",(char ) object_getClassName(*(long)($esp +4)), (char *) *(long *)($esp+8) ) 并且它开始记录一个永无止境的列表,当应用程序启动时?
  • 在断点设置的情况下,有什么办法可以避免系统调用?
  • 这在 simulator (Xcode 8) 上对我有用:expr -- (void)printf("[%s %s]\n",(char *) object_getClassName(*(long*)($rdi)), (char *)($rsi))
【解决方案2】:

如果您使用的是 LLDB,则需要使用以下调试器命令。这些在 Xcode 4.6 中进行了测试。

设备:

expr -- (void) printf("[%s %s]\n", (char *)object_getClassName($r0),$r1)

模拟器:

expr -- (void) printf("[%s %s]\n", (char *)object_getClassName(*(long*)($esp+4)), *(long *)($esp+8))

【讨论】:

  • 这在 XCode 6.1 中失败并显示以下消息:The process has been returned to the state before expression evaluation. error: Execution was interrupted, reason: Attempted to dereference an invalid pointer. 知道如何修改语句以使其正常工作吗?
  • 在 ARM64 上,这需要是 expr -- (void) printf("[%s %s]\n", (char *)object_getClassName($x0),$x1)
  • @bcattle 第一个参数必须是一个对象。这对我有用:expr -- (void)printf("[%s %s]\n", (char *)object_getClassName(@($x0)),$x1)
【解决方案3】:

要在设备上跟踪 Xcode 6 下的应用代码,我必须使用以下调试器表达式。

expr -- (void) printf("[%s %s]\n", (char *)object_getClassName($arg1),$arg2)

【讨论】:

    【解决方案4】:

    Brad Larson 的方法可以通过使用调试器命令在设备上运行:

    printf "[%s %s]\n", (char *)object_getClassName($r0),$r1
    

    更多信息可以在此处的技术说明中找到:technotes

    【讨论】:

      【解决方案5】:

      以后的xcode版本你需要这样调用

      expr -- (void)printf("[%s, %s]\n",(char *) object_getClassName(*(long*)($esp+4)), (char *) *(long *)($esp+8) )
      

      【讨论】:

      • 它只是一遍又一遍地抛出“错误:使用未声明的标识符'$esp'错误:1错误解析表达式”
      • 那是因为 $esp 是一个 x86 寄存器,只存在于模拟器中 --> 该命令只能在模拟器中使用。
      • 这在 XCode 6.1 中失败并显示以下消息:The process has been returned to the state before expression evaluation. error: Execution was interrupted, reason: Attempted to dereference an invalid pointer. 知道如何修改语句以使其正常工作吗?
      【解决方案6】:

      如果您想将输出限制为仅发送到一个类的消息,您可以添加这样的条件

      (int)strcmp((char*)object_getClassName($r0), "NSString")==0

      【讨论】:

      • 在模拟器中:(int)strcmp((char*)object_getClassName((long)($esp+4)), "NSString")==0
      • 你收到这条消息了吗? "警告:运行函数时遇到断点,跳过命令和条件以防止递归。"
      【解决方案7】:

      如果您想在 64 位的模拟器中记录方法,请改用以下命令:

      expr -- (void) printf("[%s %s]\n", (char *)object_getClassName($rdi), (char *) $rsi)
      

      或者如果这不起作用,请以这种方式记录:

      主要思想是使用$rdi作为对象(self),使用$rsi作为选择器。

      【讨论】:

      • 你不会收到类似这样的消息“警告:运行函数时命中断点,跳过命令和条件以防止递归。”
      【解决方案8】:

      一位开发人员教我在每个方法中添加相同的两个日志语句。一个作为第一行,另一个作为最后一行。我认为他有一个脚本可以自动为他的项目执行此操作,但结果是:

      NSLog(@"<<< Entering %s >>>", __PRETTY_FUNCTION__);
      NSLog(@"<<< Leaving %s >>>", __PRETTY_FUNCTION__);
      

      在控制台上,这将输出如下内容:

       <<< Entering -[MainListTableViewController viewDidLoad] >>>
      

      非常有助于跟踪正在发生的事情。

      【讨论】:

      • 我会使用Marcus Zarra's DLog statement 来避免在运行发布版本时出现大量日志语句。或者,更好的是,制作一个使用不同 #ifdef 的版本,以便您可以独立于 Debug 或 Release 构建打开和关闭方法日志记录。
      • 这假设方法内部没有返回,触发该方法的提前退出。在这种情况下,看起来程序正在输入方法并且永远不会离开。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多