使用Android Studio调试C++库

30
我正在开发一个Android项目,该项目使用了一个Java类作为C++库的封装。C++库是公司内部库,我们可以访问其源代码,但在Android项目中,它只是动态链接,因此仅以头文件(.h)和共享对象(.so)的形式使用。有了库的源代码访问权限,是否可以指定源代码路径给Android Studio,以便我可以使用调试器进入库中?
调试器可以正常工作,我可以进入Java_clory_engine_sdk_CloryNative_nativeInit函数,但我还想进一步调试对应于Clory::Engine类的库,正如我之前提到的,这是我们拥有源代码访问权限的内部库。

c_clory

例如,Clory::Engine::instance是库的一部分,我想指定给Android Studio CloryEngine.cpp文件的位置,以便我可以使用调试器进入Clory::Engine::instance,从而调试这个静态成员函数。
我正在使用Android Studio 3.1.4。
这可能吗?
编辑: clory-sdk.gradle文件指定了CMakeLists.txt文件,该文件配置了C++层。
externalNativeBuild {
    cmake {
        path "CMakeLists.txt"
    }
}

我正在使用一个内部应用程序,该应用程序使用 Clory SDK。在 app.gradle 文件中,我使用:

dependencies {
...
    compile project(':clory-sdk-core')
    compile project(':clory-sdk')
...
}

所以我认为我们没有在app.gradle项目中使用aar。aar会被发送到客户端,但在此之前我们将使用app.gradle项目来测试我们的小型SDK功能。JNI层位于clory-sdk-core项目中。
编辑2: 这是处理JNI层的CMakeLists.txt:
cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_BUILD_TYPE Debug)

add_library(
    clory-lib
    SHARED
    # JNI layer and other helper classes for transferring data from Java to Qt/C++
    src/main/cpp/clory-lib.cpp
    src/main/cpp/JObjectHandler.cpp
    src/main/cpp/JObjectResolver.cpp
    src/main/cpp/JObjectCreator.cpp
    src/main/cpp/DataConverter.cpp
    src/main/cpp/JObjectHelper.cpp
    src/main/cpp/JEnvironmentManager.cpp
)

find_library(
    log-lib
    log
)

target_compile_options(clory-lib
    PUBLIC
        -std=c++11
)

# Hardcoded for now...will fix later...
set(_QT_ROOT_PATH /Users/jacob/Qt/5.8)

if(${ANDROID_ABI} MATCHES ^armeabi-v7.*$)
    set(_QT_ARCH android_armv7)
elseif(${ANDROID_ABI} MATCHES ^x86$)
    set(_QT_ARCH android_x86)
else()
    message(FATAL_ERROR "Unsupported Android architecture!!!")
endif()

set(CMAKE_FIND_ROOT_PATH ${_QT_ROOT_PATH}/${_QT_ARCH})

find_package(Qt5 REQUIRED COMPONENTS
    Core
    CONFIG
)

target_include_directories(clory-lib
    PUBLIC
        ${CMAKE_CURRENT_LIST_DIR}/src/main/cpp
)

set(_CLORYSDK_LIB_PATH ${CMAKE_CURRENT_LIST_DIR}/src/main/jniLibs/${ANDROID_ABI})

target_link_libraries(clory-lib
    ${log-lib}
    -L${_CLORYSDK_LIB_PATH}
    clorysdk
    Qt5::Core
)

这个库clorysdk实际上是我们内部的库,我之前提到过,其中包含Clory::Engine::instance,我想要用调试器进入它。它是使用qmake构建的,并且在调试模式下构建(在有效的qmake调用中添加了CONFIG+=debug)。

编辑3:

在打中断点Java_clory_engine_sdk_CloryNative_nativeInit后打开的LLDB会话中,我得到了以下信息:

(lldb) image lookup -vrn Clory::Engine::instance
2 matches found in /Users/jacob/.lldb/module_cache/remote-android/.cache/6EDE4F0A-0000-0000-0000-000000000000/libclorysdk.so:
        Address: libclorysdk.so[0x0001bb32] (libclorysdk.so..text + 8250)
        Summary: libclorysdk.so`Clory::Engine::instance(Clory::Engine::Purpose)
         Module: file = "/Users/jacob/.lldb/module_cache/remote-android/.cache/6EDE4F0A-0000-0000-0000-000000000000/libclorysdk.so", arch = "arm"
         Symbol: id = {0x0000005e}, range = [0xcb41eb32-0xcb41ebc0), name="Clory::Engine::instance(Clory::Engine::Purpose)", mangled="_ZN4Clory2Engine8instanceENS0_7PurposeE"
        Address: libclorysdk.so[0x0001b82c] (libclorysdk.so..text + 7476)
        Summary: libclorysdk.so`Clory::Engine::instance(Clory::RuntimeConfiguration const&, Clory::Engine::Purpose)
         Module: file = "/Users/jacob/.lldb/module_cache/remote-android/.cache/6EDE4F0A-0000-0000-0000-000000000000/libclorysdk.so", arch = "arm"
         Symbol: id = {0x000000bd}, range = [0xcb41e82c-0xcb41e970), name="Clory::Engine::instance(Clory::RuntimeConfiguration const&, Clory::Engine::Purpose)", mangled="_ZN4Clory2Engine8instanceERKNS_20RuntimeConfigurationENS0_7PurposeE"

(lldb) settings show target.source-map
target.source-map (path-map) =

首先,在命令image lookup -vrn Clory::Engine::instance的结果中没有CompileUnit部分。如果libclorysdk.so是在Debug模式下构建的,为什么没有定义source-map(第二个lldb命令)?是否可以显式设置以便调试器在那里搜索库的源文件?
编辑4: 经过更多搜索,我发现创建APK的过程实际上会从调试符号中剥离*.so库。在调试模式下构建的libclorysdk.so大约有10MB,而我从生成的*.apk文件解压缩后提取的libclorysdk.so文件只有350KB。 如此处所述,在调试版本上运行greadelf --debug-dump=decodedline libclorysdk.so会输出对源文件的引用,但如果在*.apk提取的库上运行该命令,则不会输出任何内容。
有没有办法阻止Android Studio剥离*.so文件?我尝试了如何避免Android应用程序本机代码符号被剥离,但没有效果,*.apk文件与之前相同大小,调试本机库仍然无法工作。我正在使用Gradle 3.1.4。
编辑5: 剥离解决方案有效,但在我的情况下,在打中断点之前需要清理和构建才能进入库。部署未被剥离的*.so允许您拥有调试会话并进入本机库。
注意:
如果使用Qt for Android工具链构建库,则部署到$SHADOW_BUILD/android-build的*.so也会被剥离(其中$SHADOW_BUILD是通常以build-*开头的构建目录)。因此,为了调试它们,您应该从生成每个*.so之外的android-build目录中复制它们。

1
我自己进行了一些测试,从另一个项目中进入预构建的共享库中的代码对我来说很好用。但是,我只在同一台机器上测试了这个功能。也许你的机器上的项目结构与构建共享库的机器不同?您可以尝试运行 readelf --string-dump=.debug_str mylib.so 来查看源文件应该存在的位置。 - Michael
你的项目中是否有ndkcmake配置?还是你使用的库被打包为aar格式? - ahasbini
@ahasbini,感谢您的回答,我已经编辑了我的问题以提供您所要求的信息。如果不清楚,请问,我会提供更多的信息。 - Jacob Krieg
你能发一下 CMakeLists.txt 吗?为了更清晰和纠正我是否理解错误,你的项目包含 app 模块是一个常规应用程序,其中在 app.gradle 中定义了 plugin: 'com.android.application'clory-sdk-coreclory-sdk 也是项目模块,它们的 gradle 文件包含 plugin: 'com.android.library',唯一的区别是 clory-sdk 包含 CMakeLists.txt 文件和 C/C++ 代码。 - ahasbini
@ahasbini非常感谢您的问题。您所描述的情况正是如此,只有一个例外:clory-sdk-core包含CMakeLists.txt,而不是clory-sdk。因此,clory-sdk-core.gradle包含externalNativeBuild {cmake {path "CMakeLists.txt"}},而clory-sdkclory-sdk-core作为依赖项:dependencies {compile project(':glas.ai-sdk-core')}。我已发布了编辑,并添加了CMakeLists.txt以获得更清晰的说明。 - Jacob Krieg
显示剩余4条评论
2个回答

20

调试信息记录了源文件在构建时的位置。

(lldb) image lookup -vrn Clory::Engine::instance

CompileUnit行显示源文件。假设它说:

"/BuildDirectory/Sources/Clory/CloryEngine.cpp"

假设您在计算机上拥有源代码:

"Users/me/Sources/Clory"

所以你可以告诉lldb,在用户的me/Sources/Clory中找到以/BuildDirectory/Sources/Clory为根目录的源文件。

(lldb) settings set target.source-map /BuildDirectory/Sources/Clory Users/me/Sources/Clory

你可以在Android Studio的lldb控制台中使用这些命令,或将它们放入.lldbinit文件以供通用使用。


非常感谢您的回答!我运行了 $ lldb libclorysdk.so,然后运行了 (lldb) image lookup -vrn Clory::Engine::instance,但只得到了4个部分:AddressSummaryModuleSymbol。它看起来像这样:https://pastebin.com/2wBrD1nJ 不知道为什么我没有得到 CompileUnit 信息?我的库都是在调试模式下构建的。 - Jacob Krieg
2
似乎 libclorysdk.so 没有调试信息。尝试使用调试信息重新构建 SDK 或使用调试版本的 SDK(如果有)。在 Android Studio 中使用这些 lldb 命令。在“Java_clory_engine_sdk_CloryNative_nativeInit”函数中设置一个断点。开始调试。当您停在断点时,使用 Android Studio 的 lldb 窗口运行上面的 lldb 命令。 - Peter

10

如果没有调试符号可用,您可能需要使用-DCMAKE_BUILD_TYPE=DEBUG在调试模式下构建所引用的库。

defaultConfig {
    externalNativeBuild {
        cmake {
            arguments "-DANDROID_TOOLCHAIN=gcc", "-DCMAKE_BUILD_TYPE=DEBUG"
            cppFlags "-std=c++14 -fexceptions -frtti"
        }
    }
}

externalNativeBuild {
    cmake {
        path file('src/main/cpp/CMakeLists.txt')
    }
}
将以下内容添加到库的CMakeLists.txt中:
set(CMAKE_BUILD_TYPE Debug)

请参阅 CMake 文档和 使用 LLDB 进行符号化

其他地方,它解释了 (lldb) settings set target.source-map /buildbot/path /my/path

重新映射调试会话中的源文件路径名。 如果程序的源文件不再位于构建程序时的同一位置---可能是在另一台计算机上构建的程序---则需要告诉调试器如何通过本地文件路径而不是构建系统的文件路径找到源文件。

还有 (lldb) settings show target.source-map,可以查看已映射的内容。 (lldb) set append target.source-map /buildbot/path /my/path 很适合,以免覆盖现有的映射。


非常感谢您的回答!我在处理JNI层的Android项目的CMakeLists.txt中添加了set(CMAKE_BUILD_TYPE Debug)。我还确保所有库都是以Debug模式构建的,因为该库是使用CONFIG+=debug(我正在使用Qt 5.8GCC 4.9 - 我认为这并不重要,因为我读到,使用lldb应该可以调试gcc编译的代码)。 现在我刚开始学习在Android中桥接C++和Java,可能会犯一些愚蠢的错误并提出一些愚蠢的问题。这是我正在做的事情:在Android Studio终端中运行$ lldb libclorysdk.so - Jacob Krieg
问题在于当我运行 (lldb) settings show target.source-map 时,会得到以下消息:target.source-map (path-map) =。这意味着该变量没有关联任何路径(我猜测)。现在我尝试了 settings set target.source-map "" /Users/jacob/Work/clory/sdk/native/sdk/basebase 文件夹是生成我的 libclorysdk.so 库的项目的位置,即 qmake .pro 项目文件的位置),当我运行 (lldb) settings show target.source-map 时,我得到了 target.source-map (path-map) = [0] "" -> "/Users/jacob/Work/clory/sdk/native/sdk/base" - Jacob Krieg
在这一点上,我仍然无法使用调试器进入Clory::Engine::instance。我还尝试将键(箭头之前的值)设置为我的libclorysdk.so路径(位于Android项目内),并将值(箭头之后的值)设置为我的_clory_库SDK源代码的路径;因此,当我运行(lldb) settings show target.source-map时,我得到target.source-map (path-map) = [0] "/Users/jacob/Work/clory/sdk/android/clory-sdk-core/src/main/jniLibs/armeabi-v7a" -> "/Users/jacob/Work/clory/sdk/native/sdk/" - Jacob Krieg
此时,当我运行调试器时,仍然无法进入 Clory::Engine::instance。另一个我认为很重要的事情是,当我用 (lldb)exit 退出lldb并再次运行 $libclorysdk.so 时,所有映射都消失了,因此操作不是持久的。我可能做错了什么? - Jacob Krieg
1
@zwcloud,你需要将arguments添加到defaultConfig > externalNativeBuild > cmake > arguments中...并且将CMakeLists.txt的路径放在defaultConfig之外。我已经更新了我的答案,使其更加清晰明了。 - Martin Zeitler
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接