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

7

13

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?

Ethnic answered 7/8, 2012 at 0:4 Comment(6)
If you want to continue the execution, just print the error to the screen.Lashanda
Why not just have an assert macro that calls off to a routine where you always have a breakpoint set?Boomkin
@Hot Licks: It's annoying to have to climb the stack to find out where the call was made, access memory, etc. The kill method below doesn't have that inconvenience.Ethnic
The debugger shows you the stack -- "where the call was made" is just a click away.Boomkin
@Hot Licks: Just tried, I couldn't get it it to work anyway; it would log but not break, probably because a macro is not "code" but a substitution instruction for the preprocessor which the breakpoint setter ignores. In any case if you use something on the same principle like a static function those extra 'clicks' would add up quickly for me at debug time when concentration and attention are at a premium. Also, the static function method would only work when breakpoints were turned on. So the kill method offers more flexibility FWIW.Ethnic
Like I said, the macro needs to CALL OFF TO a routine where the breakpoint is set.Boomkin
G
17

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.

Ganley answered 7/8, 2012 at 3:4 Comment(3)
Thanks much. After experimenting for a couple weeks, I've combined your comments with some tips I got on error handling in "iOS 5 Programming Pushing the Limits" that introduced me to the Lumberjack logging framework. I've put the result in a separate answer in case anyone else finds it useful.Ethnic
Unfortunately this doesn't wor if used from a non-main thread (at least on OS X 10.10). Something somewhere is intercepting the SIGSTOP signal and redirecting to the main thread so the debugger incorrectly breaks there (libsystem_kernel.dylibmach_msg_trap + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP`)Shackle
Include <signal.h> for the declaration of the raise function.Bakki
S
2

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.

Surrey answered 7/8, 2012 at 0:9 Comment(2)
Conditional breakpoints help in debugging, no doubt, but as I said in the title I want to stop execution programatically (i.e., in code), for three reasons: (1) specifying breakpoints is a pain when I'm in the flow of coding (switch to mouse, right-click, click in text field, etc.; or do a long sequence of obscure keyboard shortcuts); (2) the way I use assertions means I usually want them organized separately from breakpoints, which as far as I can tell cannot be done in the breakpoint window; and (3) most important, breakpoints are not portable between projects, as assertions should be.Ethnic
@Ethnic I added an alternate, which is to just write your own function and put a breakpoint in that, once, and you're done. I think it's a little easier than the signal/trap solutions, which will then require you to mess around with stack frames.Surrey
G
2

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]);
Gazo answered 18/10, 2015 at 6:51 Comment(0)
B
1

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

Brawn answered 7/8, 2012 at 1:14 Comment(0)
S
1

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.

Shoveler answered 28/4, 2017 at 4:50 Comment(0)
E
0

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.

Ethnic answered 24/8, 2012 at 2:55 Comment(0)
M
0

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.

Monostome answered 24/10, 2013 at 20:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.