Ignore ciertas excepciones al usar el punto de interrupción de todas las excepciones de Xcode

78

Tengo un punto de interrupción de todas las excepciones configurado en Xcode:

captura de pantalla de un punto de interrupción de excepción configurado en Xcode breakpoint pain, configurado para hacer un sonido cuando se lanza una excepción

A veces, Xcode se detendrá en una línea como:

[managedObjectContext save:&error];

con el siguiente rastreo inverso:

backtrace mostrando NSPersistentStoreCoordinator lanzando una excepción dentro de la llamada para guardar:

pero el programa continúa como si nada hubiera pasado si hace clic en Continuar.

¿Cómo puedo ignorar estas excepciones "normales", pero aún así hacer que el depurador se detenga en las excepciones en mi propio código?

(Entiendo que esto sucede porque Core Data lanza y detecta excepciones internamente, y que Xcode simplemente está cumpliendo mi solicitud de pausar el programa cada vez que se lanza una excepción. Sin embargo, quiero ignorarlos para poder volver a depurar mi propio código !)

Moderadores: esto es similar al "filtrado de puntos de interrupción de excepción de Xcode 4" , pero creo que esa pregunta tarda demasiado en llegar al grano y no tiene respuestas útiles. ¿Pueden estar vinculados?

Phil Calvin
fuente
¿Puede ser más claro acerca de "ciertas excepciones"?
Jesse Rusak
¡Argh, lo siento! Stack Overflow publicado antes de que estuviera listo (accidentalmente presione enter en el campo de etiquetas). = (
Phil Calvin
Esta parece exactamente la misma pregunta que la otra. ¿Qué tal cerrarlo y poner una recompensa por el otro? también puede sugerir una edición al otro para limpiarlo, si cree que no está claro.
Jesse Rusak

Respuestas:

41

Escribí un script lldb que te permite ignorar selectivamente las excepciones de Objective-C con una sintaxis mucho más simple, y maneja OS X, iOS Simulator y ARM de 32 bits y 64 bits.

Instalación

  1. Coloque este script en ~/Library/lldb/ignore_specified_objc_exceptions.pyalgún lugar útil.
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. Agregue lo siguiente a ~/.lldbinit:

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

    reemplazándolo ~/Library/lldb/ignore_specified_objc_exceptions.pycon la ruta correcta si lo guardó en otro lugar.

Uso

  • En Xcode, agregue un punto de interrupción para detectar todas las excepciones de Objective-C
  • Edite el punto de interrupción y agregue un comando de depuración con el siguiente comando: ignore_specified_objc_exceptions name:NSAccessibilityException className:NSSomeException
  • Esto ignorará las excepciones donde NSException -namecoincide NSAccessibilityExceptionO -classNamecoincideNSSomeException

Debería verse algo como esto:

Captura de pantalla que muestra un punto de interrupción establecido en Xcode según las instrucciones

En su caso, usaría ignore_specified_objc_exceptions className:_NSCoreData

Consulte http://chen.do/blog/2013/09/30/selectively-ignoring-objective-c-exceptions-in-xcode/ para obtener el script y más detalles.

chendo
fuente
Esto me está funcionando muy bien. ¿Estaría dispuesto a contribuir con su secuencia de comandos e instrucciones de instalación directamente a Stack Overflow (y por lo tanto licenciarlos como cc-wiki ?) Si es así, ¡aceptaré esta respuesta!
Phil Calvin
Debería haber etiquetado a @chendo en esa última respuesta.
Phil Calvin
@PhilCalvin ¿Sería mejor licenciarlo como MIT?
chendo
1
Funciona genial. Solo para estresarse nuevamente, debe establecer el punto de interrupción en solo "Objective-C", ya que también hay una excepción de C ++ lanzada aquí.
Matej Bukovinski
1
Funciona muy bien en Xcode 5.1. Un detalle importante: usted tiene que seleccionar Objective-C como el tipo de excepción (como se menciona en las instrucciones.)
Phil Calvin
87

Para las excepciones de Core Data, lo que normalmente hago es eliminar el punto de interrupción "Todas las excepciones" de Xcode y, en su lugar:

  1. Agregar un punto de interrupción simbólico en objc_exception_throw
  2. Establezca una condición en el punto de interrupción para (BOOL)(! (BOOL)[[(NSException *)$x0 className] hasPrefix:@"_NSCoreData"])

El punto de interrupción configurado debería verse así: Configurar el punto de interrupción

Esto ignorará cualquier excepción de datos básicos privados (según lo determinado por el nombre de clase con el prefijo _NSCoreData) que se utilizan para el flujo de control. Tenga en cuenta que el registro apropiado dependerá del dispositivo / simulador de destino en el que esté ejecutando. Consulte esta tabla como referencia.

Tenga en cuenta que esta técnica se puede adaptar fácilmente a otros condicionales. La parte complicada fue crear los moldes BOOL y NSException para que lldb estuviera satisfecho con la condición.

Blake Watters
fuente
¡Muy útil, especialmente cuando se usan Core Data en múltiples subprocesos y estas falsas excepciones se lanzan con mucha más frecuencia! ¡Muchas gracias!
bmasters
1
Estoy tratando esto en el dispositivo con $r0: (BOOL)(! (BOOL)[[(NSException *)$r0 className] hasPrefix:@”_NSCoreData”]). Sin embargo, esto no funciona. Obtengo lo siguiente en la consola. 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 expression
lammert
@lammert, es posible que desee reemplazar las comillas que copió del ejemplo con comillas reales. los contenidos que copiaste incluyen citas embellecidas.
Jeremy Foo
Intenté esto en Xcode 4.6.3 (4H1503) y no me gustó el currentNameselector. Cambiarlo a [(NSException *)$eax name]funcionó para mí.
Adam Sharp
4
Xcode 6.2 con iOS 8.2 en iPhone6 ​​requirió que cambiara el $r0a $x0(como se define aquí: sealiesoftware.com/blog/archive/2013/09/12/… ). La condición se convierte así en:(BOOL)(! (BOOL)[[(NSException *)$x0 className] hasPrefix:@"_NSCoreData"])
Wim Fikkert
16

Aquí hay una respuesta rápida alternativa para cuando tiene un bloque de código, por ejemplo, una biblioteca de tercera parte que arroja múltiples excepciones que desea ignorar:

  1. Establezca dos puntos de interrupción, uno antes y otro después de la excepción que arroja el bloque de código que desea ignorar.
  2. Ejecute el programa, hasta que se detenga en una excepción, escriba 'lista de puntos de interrupción' en la consola del depurador y busque el número del punto de interrupción de 'todas las excepciones', debería verse así:

2: nombres = {'objc_exception_throw', '__cxa_throw'}, ubicaciones = 2 Opciones: deshabilitado 2.1: donde = libobjc.A.dylib objc_exception_throw, address = 0x00007fff8f8da6b3, unresolved, hit count = 0 2.2: where = libc++abi.dylib__cxa_throw, dirección = 0x00007fff8d19fab7, sin resolver, número de aciertos = 0

  1. Esto significa que es el punto de interrupción 2. Ahora en xcode, edite el primer punto de interrupción (antes del código de lanzamiento de excepción) y cambie la acción a 'comando de depuración' y escriba 'punto de interrupción deshabilitar 2' (y establezca la casilla de verificación 'continuar automáticamente ... ).

  2. Haga lo mismo para el punto de interrupción después de la línea ofensiva y haga que el comando 'breakpoint enable 2'.

La excepción de todos los puntos de interrupción ahora se activará y desactivará, por lo que solo estará activa cuando la necesite.

james_alvarez
fuente
¡gracias! Exactamente lo que estaba buscando. Aunque no pude encontrar el ID de excepción, solo lo forcé brutalmente. ¿Puede describir esa parte con más detalles? Quiero decir, ¿dónde exactamente en Xcode pegar esa 'lista de puntos de interrupción' para ver la identificación / ubicación del punto de interrupción?
vir us
¡Asombroso! ¡Funciona a las mil maravillas!
Sergey Grischyov
1
Excelente: esta solución es mucho más sencilla que algunas de las otras respuestas y funciona perfectamente.
ajgryc
¡Brillante! Esta es una joya de respuesta SO y debería ser la aceptada.
jb