Ignore certain exceptions when using Xcode's All Exceptions breakpoint
Asked Answered
A

3

82

I have an All Exceptions breakpoint configured in Xcode:

screenshot of an exception breakpoint configured in Xcode breakpoint pain, configured to make a sound when an exception is thrown

Sometimes Xcode will stop on a line like:

[managedObjectContext save:&error];

with the following backtrace:

backtrace showing NSPersistentStoreCoordinator throwing an exception inside the call to save:

but the program continues on as if nothing happened if you click Continue.

How can I ignore these "normal" exceptions, but still have the debugger stop on exceptions in my own code?

(I understand that this happens because Core Data internally throws and catches exceptions, and that Xcode is simply honoring my request to pause the program whenever an exception is thrown. However, I want to ignore these so I can get back to debugging my own code!)

Moderators: this is similar to "Xcode 4 exception breakpoint filtering", but I think that question takes too long to get around to the point and doesn't have any useful answers. Can they be linked?

Absorber answered 17/1, 2013 at 1:6 Comment(3)
Can you be more clear about "certain exceptions"?Cornela
Argh, sorry! Stack Overflow posted before I was ready (accidentally hit enter in the tags field.) I'll edit. =(Absorber
This seems like exactly the same question as the other. How about closing it and putting a bounty on the other? you could also suggest an edit to the other one to clean it up, if you think it's unclear.Cornela
M
43

I wrote an lldb script that lets you selectively ignore Objective-C exceptions with a much simpler syntax, and it handles both OS X, iOS Simulator, and both 32bit and 64bit ARM.

Installation

  1. Put this script in ~/Library/lldb/ignore_specified_objc_exceptions.py or somewhere useful.
import lldb
import re
import shlex

# This script allows Xcode to selectively ignore Obj-C exceptions
# based on any selector on the NSException instance

def getRegister(target):
    if target.triple.startswith('x86_64'):
        return "rdi"
    elif target.triple.startswith('i386'):
        return "eax"
    elif target.triple.startswith('arm64'):
        return "x0"
    else:
        return "r0"

def callMethodOnException(frame, register, method):
    return frame.EvaluateExpression("(NSString *)[(NSException *)${0} {1}]".format(register, method)).GetObjectDescription()

def filterException(debugger, user_input, result, unused):
    target = debugger.GetSelectedTarget()
    frame = target.GetProcess().GetSelectedThread().GetFrameAtIndex(0)

    if frame.symbol.name != 'objc_exception_throw':
        # We can't handle anything except objc_exception_throw
        return None

    filters = shlex.split(user_input)

    register = getRegister(target)


    for filter in filters:
        method, regexp_str = filter.split(":", 1)
        value = callMethodOnException(frame, register, method)

        if value is None:
            output = "Unable to grab exception from register {0} with method {1}; skipping...".format(register, method)
            result.PutCString(output)
            result.flush()
            continue

        regexp = re.compile(regexp_str)

        if regexp.match(value):
            output = "Skipping exception because exception's {0} ({1}) matches {2}".format(method, value, regexp_str)
            result.PutCString(output)
            result.flush()

            # If we tell the debugger to continue before this script finishes,
            # Xcode gets into a weird state where it won't refuse to quit LLDB,
            # so we set async so the script terminates and hands control back to Xcode
            debugger.SetAsync(True)
            debugger.HandleCommand("continue")
            return None

    return None

def __lldb_init_module(debugger, unused):
    debugger.HandleCommand('command script add --function ignore_specified_objc_exceptions.filterException ignore_specified_objc_exceptions')
  1. Add the following to ~/.lldbinit:

    command script import ~/Library/lldb/ignore_specified_objc_exceptions.py

    replacing ~/Library/lldb/ignore_specified_objc_exceptions.py with the correct path if you saved it somewhere else.

Usage

  • In Xcode, add a breakpoint to catch all Objective-C exceptions
  • Edit the breakpoint and add a Debugger Command with the following command: ignore_specified_objc_exceptions name:NSAccessibilityException className:NSSomeException
  • This will ignore exceptions where NSException -name matches NSAccessibilityException OR -className matches NSSomeException

It should look something like this:

Screenshot showing a breakpoint set in Xcode per the instructions

In your case, you would use ignore_specified_objc_exceptions className:_NSCoreData

See http://chen.do/blog/2013/09/30/selectively-ignoring-objective-c-exceptions-in-xcode/ for the script and more details.

Mcphail answered 9/10, 2013 at 2:52 Comment(11)
This is working really well for me. Would you be willing to contribute your script and installation instructions directly to Stack Overflow (and thus license them as cc-wiki?) If so, I'll accept this answer!Absorber
I should have tagged @Mcphail in that last response.Absorber
@PhilCalvin Would licensing it as MIT be better?Mcphail
Well, my goal is to get it onto Stack Overflow proper. If it's on someone's site, it will inevitably disappear from the Internet. I'm not sure if you can sensibly contribute MIT-licensed content to Stack Overflow—it would probably end up as dual-licensed, but IANAL.Absorber
(Relevant language: "You agree that all Subscriber Content that You contribute to the Network is perpetually and irrevocably licensed to Stack Exchange under the Creative Commons Attribution Share Alike license.")Absorber
Here are some additional thoughts on link-only answers: meta.stackexchange.com/a/8259Absorber
This worked for me, i followed the instruction to curl and install automatically via your blog, worked great. Thanks a ton for thisOrpiment
Works great. Just to stress out again - you need to set the breakpoint to just "Objective-C" since there's also a C++ exception thrown here.Journalistic
@Mcphail This worked for me although the suggestion above for skipping the breakpoints does not. Go to Chendo's blog and use the "Lazy" option. Thanks!Dennadennard
Working really well in Xcode 5.1. An important detail: you have to select Objective-C as the exception type (as mentioned in the instructions.)Absorber
This continues to be a lifesaver. I set it up for every project I work in. I use ignore_specified_objc_exceptions className:NSCoreData className:_NSCoreData name:NSUnknownKeyException and that seems to keep the false positives down.Absorber
C
95

For Core Data exceptions, what I typically do is remove the "All Exceptions" breakpoint from Xcode and instead:

  1. Add a Symbolic Breakpoint on objc_exception_throw
  2. Set a Condition on the Breakpoint to (BOOL)(! (BOOL)[[(NSException *)$x0 className] hasPrefix:@"_NSCoreData"])

The configured breakpoint should look something like this: Configuring the Breakpoint

This will ignore any private Core Data exceptions (as determined by the class name being prefixed by _NSCoreData) that are used for control flow. Note that the appropriate register is going to be dependent on the target device / simulator that you are running in. Take a look at this table for reference.

Note that this technique can be adapted easily to other conditionals. The tricky part was in crafting the BOOL and NSException casts to get lldb happy with the condition.

Couloir answered 17/1, 2013 at 3:17 Comment(10)
Very helpful especially when using Core Data on multiple threads and these false exceptions are thrown much more often! Thank you very much!Casual
I'm trying this on the device with $r0: (BOOL)(! (BOOL)[[(NSException *)$r0 className] hasPrefix:@”_NSCoreData”]). This does not work though. I get the following in the console. Stopped due to an error evaluating condition of breakpoint 1.1: "(BOOL)(! (BOOL)[[(NSException *)$r0 className] hasPrefix:@‚Äù_NSCoreData‚Äù])" error: unexpected '@' in program error: 1 errors parsing expressionSarilda
@Sarilda you might want to replace the quotes that you copied from the example with actual quotes. the contents you copied include prettified quotes.Equivocation
I just tried this in Xcode 4.6.3 (4H1503), and it didn't like the currentName selector. Changing it to [(NSException *)$eax name] worked for me.Howbeit
@AdamSharp thanks for the tip. On XCode 4.6.3 I've been getting breaks that actually crash (with no stack trace). Changing to name still breaks and crashes, but now I am getting the stack trace which leads to crashing. Any ideas?Orpiment
@Orpiment eww that doesn't sound like much fun. Sorry but I'm not really sure what I'd do with that. Except for di -s my way down that stack trace in the debugger, looking for clues.Howbeit
@Blake-Watters This workaround apparently no longer works in XCode 5.0.2. Every time I hit one of those exceptions I get an Invalid Instruction instead skipping the breakpoint. Tried both className and name. Any more ideas?Dennadennard
this doesn't work for me either. I'm seeing that $eax is an undeclared selector.Archibaldo
Xcode 6.2 with iOS 8.2 on iPhone6 required me to change the $r0 to $x0 (as defined here: sealiesoftware.com/blog/archive/2013/09/12/…). The condition thus becomes: (BOOL)(! (BOOL)[[(NSException *)$x0 className] hasPrefix:@"_NSCoreData"])Aeroballistics
Using XCode9 I modified this to "(BOOL)(!(BOOL)[[(NSException *)$arg1 className] hasPrefix:@"_NSCoreData"])". I like this because it doesn't enable C++ exceptions either (AVPlayer uses a lot of internal C++ exceptions for flow...)Ombudsman
M
43

I wrote an lldb script that lets you selectively ignore Objective-C exceptions with a much simpler syntax, and it handles both OS X, iOS Simulator, and both 32bit and 64bit ARM.

Installation

  1. Put this script in ~/Library/lldb/ignore_specified_objc_exceptions.py or somewhere useful.
import lldb
import re
import shlex

# This script allows Xcode to selectively ignore Obj-C exceptions
# based on any selector on the NSException instance

def getRegister(target):
    if target.triple.startswith('x86_64'):
        return "rdi"
    elif target.triple.startswith('i386'):
        return "eax"
    elif target.triple.startswith('arm64'):
        return "x0"
    else:
        return "r0"

def callMethodOnException(frame, register, method):
    return frame.EvaluateExpression("(NSString *)[(NSException *)${0} {1}]".format(register, method)).GetObjectDescription()

def filterException(debugger, user_input, result, unused):
    target = debugger.GetSelectedTarget()
    frame = target.GetProcess().GetSelectedThread().GetFrameAtIndex(0)

    if frame.symbol.name != 'objc_exception_throw':
        # We can't handle anything except objc_exception_throw
        return None

    filters = shlex.split(user_input)

    register = getRegister(target)


    for filter in filters:
        method, regexp_str = filter.split(":", 1)
        value = callMethodOnException(frame, register, method)

        if value is None:
            output = "Unable to grab exception from register {0} with method {1}; skipping...".format(register, method)
            result.PutCString(output)
            result.flush()
            continue

        regexp = re.compile(regexp_str)

        if regexp.match(value):
            output = "Skipping exception because exception's {0} ({1}) matches {2}".format(method, value, regexp_str)
            result.PutCString(output)
            result.flush()

            # If we tell the debugger to continue before this script finishes,
            # Xcode gets into a weird state where it won't refuse to quit LLDB,
            # so we set async so the script terminates and hands control back to Xcode
            debugger.SetAsync(True)
            debugger.HandleCommand("continue")
            return None

    return None

def __lldb_init_module(debugger, unused):
    debugger.HandleCommand('command script add --function ignore_specified_objc_exceptions.filterException ignore_specified_objc_exceptions')
  1. Add the following to ~/.lldbinit:

    command script import ~/Library/lldb/ignore_specified_objc_exceptions.py

    replacing ~/Library/lldb/ignore_specified_objc_exceptions.py with the correct path if you saved it somewhere else.

Usage

  • In Xcode, add a breakpoint to catch all Objective-C exceptions
  • Edit the breakpoint and add a Debugger Command with the following command: ignore_specified_objc_exceptions name:NSAccessibilityException className:NSSomeException
  • This will ignore exceptions where NSException -name matches NSAccessibilityException OR -className matches NSSomeException

It should look something like this:

Screenshot showing a breakpoint set in Xcode per the instructions

In your case, you would use ignore_specified_objc_exceptions className:_NSCoreData

See http://chen.do/blog/2013/09/30/selectively-ignoring-objective-c-exceptions-in-xcode/ for the script and more details.

Mcphail answered 9/10, 2013 at 2:52 Comment(11)
This is working really well for me. Would you be willing to contribute your script and installation instructions directly to Stack Overflow (and thus license them as cc-wiki?) If so, I'll accept this answer!Absorber
I should have tagged @Mcphail in that last response.Absorber
@PhilCalvin Would licensing it as MIT be better?Mcphail
Well, my goal is to get it onto Stack Overflow proper. If it's on someone's site, it will inevitably disappear from the Internet. I'm not sure if you can sensibly contribute MIT-licensed content to Stack Overflow—it would probably end up as dual-licensed, but IANAL.Absorber
(Relevant language: "You agree that all Subscriber Content that You contribute to the Network is perpetually and irrevocably licensed to Stack Exchange under the Creative Commons Attribution Share Alike license.")Absorber
Here are some additional thoughts on link-only answers: meta.stackexchange.com/a/8259Absorber
This worked for me, i followed the instruction to curl and install automatically via your blog, worked great. Thanks a ton for thisOrpiment
Works great. Just to stress out again - you need to set the breakpoint to just "Objective-C" since there's also a C++ exception thrown here.Journalistic
@Mcphail This worked for me although the suggestion above for skipping the breakpoints does not. Go to Chendo's blog and use the "Lazy" option. Thanks!Dennadennard
Working really well in Xcode 5.1. An important detail: you have to select Objective-C as the exception type (as mentioned in the instructions.)Absorber
This continues to be a lifesaver. I set it up for every project I work in. I use ignore_specified_objc_exceptions className:NSCoreData className:_NSCoreData name:NSUnknownKeyException and that seems to keep the false positives down.Absorber
V
18

Here is an alternative quick answer for when you have a block of code e.g. a 3rd part library that throws multiple exceptions that you want to ignore:

  1. Set two breakpoints, one before and one after the exception throwing block of code you want to ignore.
  2. Run the program, until it stops at an exception, and type 'breakpoint list' into the debugger console, and find the number of the 'all exceptions' break point, it should look like this:

2: names = {'objc_exception_throw', '__cxa_throw'}, locations = 2 Options: disabled 2.1: where = libobjc.A.dylibobjc_exception_throw, address = 0x00007fff8f8da6b3, unresolved, hit count = 0 2.2: where = libc++abi.dylib__cxa_throw, address = 0x00007fff8d19fab7, unresolved, hit count = 0

  1. This means it is breakpoint 2. Now in xcode, edit the first breakpoint (before the exception throwing code) and change the action to 'debugger command' and type in 'breakpoint disable 2' (and set 'automatically continue...' checkbox ).

  2. Do the same for the break point after the offending line and have the command 'breakpoint enable 2'.

The all breakpoints exception will now turn on and off so it's only active when you need it.

Visually answered 5/12, 2014 at 13:35 Comment(3)
thx! exactly what I was looking for. Though I wasn't able to find the exception id - just brute forced it. Can you describe that part in more details? I mean where exactly in Xcode to paste that 'breakpoint list' to see the breakpoint id/location?Lodicule
Excellent - this solution is much more straightforward than some of the other answers and works perfectly.Trimer
Brilliant! This is a gem of a SO answer and should be the accepted one.Volpe

© 2022 - 2024 — McMap. All rights reserved.