Objective C - how to programmatically stop execution for debugging, while allowing continuation?

8.6k views Asked by At

I have had success getting my debug builds to stop execution when a condition is programmatically specified, using the standard NSAssert(condition_which_should_evaluate_true, @"error message") statement in Objective C, and adding in an "All Exceptions" breakpoint in the Breakpoint Navigator.

Well and good, but most of the time when I'm debugging, I'd also like to continue normal program execution after that point. Often continuing the program after a failed assertion helps to track down the source of the confusion/bug. At least as far as I remember when I was programming on a different platform.

Is there a standard way to do so in Objective C development?

7

There are 7 answers

3
rob mayoff On BEST ANSWER

There's a way. It's not an Objective-C thing, it's a Unix thing.

kill(getpid(), SIGSTOP);

or simply:

raise(SIGSTOP);

In Swift:

raise(SIGSTOP)

This will break in the debugger in the __kill or __pthread_kill function. You will need to then go up a few stack frames to look at the frame that called kill or raise. You can use the debugger`s continue command to resume execution.

Note that if you're not running under the debugger and you execute this, your app will just hang. Take a look at [Technical Q&A QA1631: Detecting the Debugger](http://developer.apple.com/library/mac/#qa/qa1361/_index.html. You can use that information to write a wrapper function or macro that only sends SIGSTOP when running under the debugger. This answer may help.

Also, the Foundation framework provides a different assert macro for use in regular functions. It's NSCAssert.

2
benzado On

Sounds like you want to use Conditional Breakpoints. If you set a breakpoint by clicking in the margin of your source code, then ctrl-click the little blue breakpoint thing, you can edit some options, including making the breakpoint conditional on the value of a variable.

Here's a blog post with some screenshots and more info.

This Stack Overflow question has some good pointers, too.


If you insist on triggering the breakpoint programmatically, then write a function and put a breakpoint inside it:

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

Then you can call this function in a similar manner to NSAssert. If you declare the function in your project's precompiled header file (Whatever.pch) it will be available in all of your source files, without having to explicitly #import anything.

0
Jessedc On

I'm by no means an expert in this field but I use code that can break into the debugger via keyboard input.

DCIntrospect by domesticcatsoftware on github does this.

Take a look at the top of it's main file DCIntrospect.m and see how it does it.

It references a few sources, but from my experience it's quite up to date with the current assembly required to break into the debugger on armv6/7 and the simulator

External references for more background info

0
Jason On

A simple trick, if you want to use the console, is:

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

Then you put your breakpoint inside that line. If the condition evaluates to NO, then your breakpoint is invoked.

0
Merk On

Using Rob's comments, and some ideas from "ios5 Programming: Pushing the Limits", and the Lumberjack framework, here's a macro to get the debugger to stop and allow for continuation during an assertion in DEBUG build, but to otherwise do as it always does during RELEASE (or actually any non-DEBUG) build.

#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

These macros require the function AmIBeingDebugged(), which you can get from Apple at the link Rob gave: Technical Q&A QA1631: Detecting the Debugger. (You will also need to define DEBUG in your build settings.)

Note that I've chosen Lumberjack's DDLogError() over NSLog() in non-DEBUG builds because it will spit out the method, file and line number where the fatal assertion occurred.

0
JKallio On

Here is how I do it:

First, in breakpoints tab I set my apps to break if any exception is raised: All Exceptions

Then In code (I usually have common header file containing common definitions like this that I import everywhere):

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__]); }

Now, You can use it like NSAssert, but instead of killing your app, you merely trigger a breakpoint:

MYAssert(bEverythingOkay, @"Something went wrong!");

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

Not sure why nobody else gave a clear straightforward answer... It's been five years but better late than never..


Preprocessor:

#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

Usage:

To use it simply just type the following: manualBreakpoint();

Notes:

This will halt the app when that code is called and display the current method in your stack trace (as well as logging the file name and line number your app has halted on) IFF you are in debug mode, if you are in production mode for appstore (aka release, distribution, or archive) it will do nothing.