iosxcodedebuggingxcode9-beta

Xcode 9 - Framework breakpoints


In Xcode 7 and 8, I have been able to debug a framework I've been developing by running the containing application within an xcworkspace that also contains the framework project. If I set breakpoints in the framework, they would get triggered at runtime.

In Xcode 9 beta 6, this is no longer the case. So far, execution is only stopping at the framework breakpoints when debugging on simulator. When I debug on a physical device, the framework breakpoints do not stop execution and it appears they are completely ignored.

How can I get framework breakpoints to work properly in order to debug my framework on iOS 11 in Xcode 9?

FWIW: The xcworkspace was created by running pod install inside the framework root directory. I then added the sample application's xcodeproj to the xcworkspace. This has been functional up until testing on Xcode 9 beta 6.

Edit: Confirmed that this behavior still takes place on the GM seed of Xcode 9.0.


Solution

  • TL;DR - I needed to change which directory my archive script reads from when debugging or when preparing a release. Now when debugging, I need to be sure to set my framework scheme's archive config to "Debug" if I want breakpoints to work properly at runtime. I only use "Release" when preparing a production-ready .framework.

    I reached out to Apple developer support via bug report. I will paste the response below. When he mentions "lipo", he is referring to a call I make in a post-archive script that creates a universal framework from the simulator and physical device builds.

    Xcode has to match up the binary that's running with the debug symbols that are still on your machine. Xcode should do that automatically, but it sounds like some stuff is moved around behind Xcode's back. To know whether the debug information matches you can look at the output of (lldb) image list and /Mac/path/to/Build/Products/Debug-iphoneos% dwarfdump --uuid iOS-App.app/iOS-App that will work on dylibs too.

    Does your framework have parenthesis around the address? That's a sure sign that lldb can't find your symbols.

    if the UUID in image list doesn't match the dwarfdump, then something has modified the executable before it ran and doesn't match your built products. We’re not sure if lipo might do that, which I see in your script but definitely check. Not much can be done if the debug-info no longer exists.

    If you can find the right executable with a matching UUID on your disk, you can simply (lldb) image add /Mac/path/to/DerivedData-asdfasdfadf/Products/Debug-iphoneos/iOS-App.app/Frameworks/Framework

    Additionally, Xcode uses Spotlight to find symbols on your machine. To avoid constantly re-indexing while building, the Intermediates/ directory containing .o files and other place where debug information is stored was blacklisted. This happened fairly late in Xcode 9.0 so may be the reason your stuff was working before.

    When I ran (lldb) image list at runtime, I saw that the UUID of my framework did not match that which was reported by dwarfdump at /Mac/path/to/Build/Products/Debug-iphoneos.

    I ended up modifying my post-archive script to change which build directory it reads from when creating the framework. When I set my archive config to "Debug", it will read from Debug-iphoneos now. When I set it to "Release" it reads from ${BUILD_DIR}/${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}

    # NOTE: This script creates a universal framework (device and simulator). However, *** for this to work: a Simulator target must be built first, and then Archive on the Device ***
    
    BUILD_PRODUCTS="${SYMROOT}/../../../../Products"
    SIM_PATH="${BUILD_PRODUCTS}/Debug-iphonesimulator/${TARGET_NAME}.framework"
    if [ "${CONFIGURATION}" = "Debug" ]; then
     DEV_PATH="${BUILD_PRODUCTS}/Debug-iphoneos/${TARGET_NAME}.framework"
    elif [ "${CONFIGURATION}" = "Release" ]; then
     DEV_PATH="${BUILD_DIR}/${CONFIGURATION}${EFFECTIVE_PLATFORM_NAME}/${TARGET_NAME}.framework"
    fi
    DEST_PATH="${PROJECT_DIR}/../Frameworks/${TARGET_NAME}.framework"
    
    rm -rf "${DEST_PATH}"
    mkdir "${DEST_PATH}"
    cp -r "${DEV_PATH}/" "${DEST_PATH}/"
    rm -f "${DEST_PATH}/${TARGET_NAME}"
    cp -Rn "${SIM_PATH}/Modules/" "${DEST_PATH}/Modules/"
    
    lipo -create "${SIM_PATH}/${TARGET_NAME}" "${DEV_PATH}/${TARGET_NAME}" -output "${DEST_PATH}/${TARGET_NAME}"
    

    If the paths are confusing, I don't blame you. Essentially, workspace looks like:

    RootDirectory
    |__SampleApp
       |__SampleApp.xcodeproj
    |__Frameworks
       |__MyFramework.framework
       |__AnotherFramework.framework
    |__MyFramework
       |__MyFramework.xcworkspace
       |__MyFramework.xcodeproj
       |__Podfile (etc..)