【问题标题】:Objective C - how to programmatically stop execution for debugging, while allowing continuation?Objective C - 如何以编程方式停止执行以进行调试,同时允许继续?
【发布时间】:2012-08-03 23:23:29
【问题描述】:

我已经成功地让我的调试版本在以编程方式指定条件时停止执行,使用目标 C 中的标准 NSAssert(condition_which_should_evaluate_true, @"error message") 语句,并在断点导航器。

很好,但是大多数时候我在调试时,我也想在那之后继续正常的程序执行。通常在断言失败后继续程序有助于追踪混乱/错误的来源。至少就我记忆中的我在不同平台上编程而言。

在 Objective C 开发中是否有标准的方法来做到这一点?

【问题讨论】:

  • 如果要继续执行,只需将错误打印到屏幕上即可。
  • 为什么不只使用一个断言宏来调用您始终设置断点的例程?
  • @Hot Licks:不得不爬堆栈以找出调用的位置、访问内存等很烦人。下面的kill 方法没有这种不便。
  • 调试器向您显示堆栈——“调用的位置”只需点击一下即可。
  • @Hot Licks: 刚试了下,无论如何我都无法让它工作;它会记录但不会中断,可能是因为宏不是“代码”,而是断点设置器忽略的预处理器的替换指令。在任何情况下,如果您使用与静态函数等相同原理的东西,那么在调试时,当注意力和注意力非常重要时,这些额外的“点击”对我来说很快就会增加。此外,静态函数方法仅在打开断点时才有效。所以kill 方法提供了更大的 FWIW 灵活性。

标签: objective-c breakpoints assertions continuation


【解决方案1】:

有办法。这不是 Objective-C 的东西,而是 Unix 的东西。

kill(getpid(), SIGSTOP);

或者简单地说:

raise(SIGSTOP);

在斯威夫特中:

raise(SIGSTOP)

这将在__kill__pthread_kill 函数中中断调试器。然后,您需要上几个堆栈帧来查看调用 killraise 的帧。您可以使用调试器的 continue 命令恢复执行。

请注意,如果您没有在调试器下运行并执行此操作,则您的应用将挂起。看看 [技术问答 QA1631:检测调试器](http://developer.apple.com/library/mac/#qa/qa1361/_index.html。您可以使用该信息编写一个包装函数或宏,在调试器下运行时仅发送 SIGSTOPThis answer 可能会有所帮助。

此外,Foundation 框架提供了一个不同的断言宏以用于常规函数。我是NSCAssert

【讨论】:

  • 非常感谢。在试验了几个星期之后,我将您的 cmets 与我在“iOS 5 Programming Pushing the Limits”中获得的一些错误处理技巧结合起来,向我介绍了 Lumberjack 日志框架。我已将结果放在单独的答案中,以防其他人发现它有用。
  • 不幸的是,如果从非主线程中使用(至少在 OS X 10.10 上),这将不起作用。某处正在拦截 SIGSTOP 信号并重定向到主线程,因此调试器错误地在那里中断(libsystem_kernel.dylibmach_msg_trap + 10,队列 = 'com.apple.main-thread',停止原因 = 信号 SIGSTOP`)
  • 包含<signal.h> 以声明raise 函数。
【解决方案2】:

不知道为什么没有其他人给出明确直截了当的答案...已经五年了,但迟到总比没有好..


预处理器:

#ifdef DEBUG
#define manualBreakpoint() \
            NSLog(@"\n\n\
                    Breakpoint called on: \n\n\
                    File: %s \n\n\
                    Line number: %i", __FILE__, __LINE__);\
                                                          \
            raise(SIGSTOP)
#else
#define manualBreakpoint() ;
#endif

用法:

要使用它,只需输入以下内容:manualBreakpoint();

注意事项:

这将在调用该代码时停止应用程序并在堆栈跟踪中显示当前方法(以及记录应用程序已停止的文件名和行号)如果您处于调试模式,如果您处于应用商店的生产模式(又名 releasedistributionarchive)它什么也不做。

【讨论】:

    【解决方案3】:

    这是我的做法:

    首先,在断点选项卡中,如果引发任何异常,我将我的应用程序设置为中断:

    然后在代码中(我通常有包含这样的通用定义的通用头文件,我会在任何地方导入):

    static void ThrowException(NSString* reason)
    {
       @try 
       {
          @throw [NSException
                   exceptionWithName:@"DebugAssertionException"
                   reason:reason
                   userInfo:nil];
       }  
       @catch (NSException * e) 
       {
          NSLog(@"%@", e);
       }
    }
    
    #define MYAssert(test, fmt, ...) if (!(test)) { ThrowException([NSString stringWithFormat:@"%s !!! ASSERT !!! " fmt, __PRETTY_FUNCTION__, ##__VA_ARGS__]); }
    

    现在,您可以像使用 NSAssert 一样使用它,但您只需触发一个断点,而不是杀死您的应用程序:

    MYAssert(bEverythingOkay, @"Something went wrong!");
    
    // Or with arguments of course
    MYAssert(bEverythingOkay, @"Something went wrong (TestValue=%zd; Reason=%@)", myTestValue, [this getLastError]);
    

    【讨论】:

      【解决方案4】:

      如果你想使用控制台,一个简单的技巧是:

      if (!condition_which_should_evaluate_true) {
         ; // Put breakpoint here
      }
      

      然后将断点放在该行内。如果条件计算结果为 NO,则调用您的断点。

      【讨论】:

        【解决方案5】:

        使用 Rob 的 cmets,以及来自“ios5 Programming: Pushing the Limits”和 Lumberjack 框架的一些想法,这里有一个宏可以让调试器停止并允许在 DEBUG 构建中的断言期间继续,但除此之外像在RELEASE(或实际上任何非-DEBUG)构建期间一样做。

        #ifdef DEBUG
        
        #define MyAssert(condition, desc, ...) \
        if (!(condition)) { \
            NSLog((desc), ## __VA_ARGS__); \
            if (AmIBeingDebugged()) \
                kill (getpid(), SIGSTOP); \
            else { \
                NSLog(@"%@, %d: could not break into debugger.", THIS_FILE, __LINE__); \
            } \
        }
        
        #define MyCAssert(condition, desc, ...) \
        if (!(condition)) { \
            NSLog((desc), ## __VA_ARGS__); \
            if (AmIBeingDebugged()) \
                kill (getpid(), SIGSTOP); \
            else \
                NSLog(@"%@, %d: could not break into debugger.", THIS_FILE, __LINE__)); \
            } \
        }
        
        #else  //NOT in DEBUG
        
        #define MyAssert(condition, desc, ...) \
        if (!(condition)) { \
            DDLogError((desc), ## __VA_ARGS__); \   //use NSLog if not using Lumberjack
            NSAssert((condition), (desc), ## __VA_ARGS__); \
        }
        
        #define MyCAssert(condition, desc, ...) \
        if (!(condition)) { \
            DDLogError((desc), ## __VA_ARGS__); \   //use NSLog if not using Lumberjack
            NSCAssert((condition), (desc), ## __VA_ARGS__); \
        }
        
        #endif //end  DEBUG
        
        #endif
        

        这些宏需要函数 AmIBeingDebugged(),您可以通过 Rob 提供的链接从 Apple 获得该函数:技术问答 QA1631:检测调试器。 (您还需要在构建设置中定义DEBUG。)

        请注意,我在非DEBUG 构建中选择了 Lumberjack 的 DDLogError() 而不是 NSLog(),因为它会吐出发生致命断言的方法、文件和行号。

        【讨论】:

          【解决方案6】:

          听起来您想使用条件断点。如果您通过单击源代码的边缘设置断点,然后按住 ctrl 并单击蓝色的小断点,您可以编辑一些选项,包括使断点以变量的值为条件。

          这是blog post with some screenshots 和更多信息。

          This Stack Overflow question 也有一些不错的建议。


          如果你坚持以编程方式触发断点,那就写一个函数,在里面放一个断点:

          void MyConditionalBreak(BOOL condition, NSString *comment)
          {
              if (condition) {
                  NSLog(@"Stopped because %@", comment); // SET BREAKPOINT ON THIS LINE
              }
          }
          

          然后你可以用与 NSAssert 类似的方式调用这个函数。如果您在项目的预编译头文件 (Whatever.pch) 中声明该函数,它将在您的所有源文件中可用,而无需显式 #import 任何内容。

          【讨论】:

          • 条件断点有助于调试,毫无疑问,但正如我在标题中所说,我想以编程方式(即在代码中)停止执行,原因有三个:(1)指定断点是一种痛苦,当我正在进行编码流程(切换到鼠标、右键单击、单击文本字段等;或者执行一长串晦涩难懂的键盘快捷键); (2)我使用断言的方式意味着我通常希望它们与断点分开组织,据我所知,这不能在断点窗口中完成; (3) 最重要的是,断点不能像断言那样在项目之间移植。
          • @Merk 我添加了一个替代方法,即只需编写您自己的函数并在其中放置一个断点,一次,您就完成了。我认为它比信号/陷阱解决方案要容易一些,这将需要您处理堆栈帧。
          【解决方案7】:

          我绝不是该领域的专家,但我使用的代码可以通过键盘输入进入调试器。

          DCIntrospectDCIntrospect 来自 github 上的国内猫软件。

          看看它的主文件顶部DCIntrospect.m,看看它是怎么做的。

          它引用了一些来源,但根据我的经验,它与破解 armv6/7 上的调试器和模拟器所需的当前程序集相当最新

          更多背景信息的外部参考

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-03-28
            • 1970-01-01
            • 1970-01-01
            • 2021-04-22
            • 2011-04-20
            • 1970-01-01
            • 2012-10-11
            相关资源
            最近更新 更多