30

I'm working on an Android project which uses a Java class that is a wrapper on a C++ library. The C++ library is a company internal library and we have access to its source code, but in the Android project it is only dynamically linked, so it is used only in the form of headers (.h) and shared objects (.so). Having access to the library source code, is it possible to specify to Android Studio the path to the source code so I can step inside the library using the debugger?

The debugger works, I can step inside the Java_clory_engine_sdk_CloryNative_nativeInit function, but I would also like to further debug the library corresponding to the Clory::Engine class which, as I mentioned, is an internal library we have source code access to.

c_clory

For example, Clory::Engine::instance is part of the library and I would like to specify to Android Studio the location of the CloryEngine.cpp file so I can step inside Clory::Engine::instance with the debugger, thus debugging this static member function.

I am using Android Studio 3.1.4.

Is this possible?

EDIT:

The clory-sdk.gradle file specifies the CMakeLists.txt file which configures the C++ layer.

externalNativeBuild {
    cmake {
        path "CMakeLists.txt"
    }
}

So I am using an internal application which uses the Clory SDK. Inside the app.gradle file I use:

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

so I don't think we're using the aars for the app.gradle project. The aars are shipped to the client, but we are using app.gradle project to test our little SDK functionalities before doing that. The JNI layer is inside clory-sdk-core project.

EDIT 2:

Here is the CMakeLists.txt which handles the JNI layer:

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
)

The library clorysdk is actually our internal library I was talking about, which contains e.g. Clory::Engine::instance I would like to step into with the debugger. It was built with qmake and is built in debug mode (CONFIG+=debug was added in the effective qmake call).

EDIT 3:

In the LLDB session which has opened after it hit the Java_clory_engine_sdk_CloryNative_nativeInit breakpoint, I got the following:

(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) =

First of all, there was no CompileUnit section in the result of the command image lookup -vrn Clory::Engine::instance. How is this possible to have no source-map defined(second lldb command) if the libclorysdk.so was built in Debug mode? Is it possible to explicitly set it so that the debugger would search there for the library's source files?

EDIT 4:

After searching more I found out that the process of creating the APK actually strips the *.so libraries from their debugging symbols. libclorysdk.so built in debug mode has about 10MB while the libclorysdk.so file which I extracted after unarchiving the generated *.apk file is just 350KB. As stated here, running greadelf --debug-dump=decodedline libclorysdk.so on the debug version outputs references to the source files, but if the command is run on the *.apk extracted library, it outputs nothing.

Is there a way to stop Android Studio from stripping the *.sos? I tried How to avoid stripping for native code symbols for android app but didn't have any effect, *.apk file is the same size as before and debugging the native libraries still doesn't work.

I'm using Gradle 3.1.4.

EDIT 5:

The stripping solution works, but in my case, it needed a Clean & Build before hitting the breakpoints in the library. Deploying *.sos which are not stripped is allowing you to have debugging sessions and step inside the native libraries.

Note:

If the libraries are built using the Qt for Android toolchain, the *.sos deployed to $SHADOW_BUILD/android-build are also stripped(where $SHADOW_BUILD is the build directory usually starting with build-*). So in order to debug those you should copy them from outside the android-build directory where each *.so is generated.

donjuedo
  • 2,475
  • 18
  • 28
Jacob Krieg
  • 2,834
  • 15
  • 68
  • 140
  • Are you using a debug build of the prebuilt shared library? If so, stepping into code within that library should work. – Michael Oct 29 '18 at 16:12
  • @Michael I am using a debug build of the prebuilt shared library. But the source code is not within the project which uses the shared library. So it can't know where to search for the source code (I assume) unless I tell it where to search. And I don't know how to do that. I remember if you want to step inside a library in Visual Studio you get a dialog box that lets you browse for the source file corresponding to the function you're stepping into, like so: https://i.imgur.com/doPgOs4.png I would expect some sort of functionality that gives you something similar... – Jacob Krieg Oct 29 '18 at 17:32
  • 1
    I did a little test myself, and stepping into code within a prebuilt shared library from another project worked fine for me. However, I've only tested this with both projects being built on the same machine. Perhaps the project structure looks different on your machine compared to the machine that build the shared library? You can try running `readelf --string-dump=.debug_str mylib.so` to see where the source files are expected to exist. – Michael Oct 29 '18 at 18:21
  • @Michael Thank you very much for the answer. Hmmm...I will build tonight a small project with which I will try to reproduce my scenario on a smaller scale and let you know what I found. – Jacob Krieg Oct 29 '18 at 19:15
  • Do you have `ndk` or `cmake` configuration for your project? Or is the lib you're using packaged as an `aar`? – ahasbini Oct 31 '18 at 18:32
  • @ahasbini Thank you for your answer, I have edited my question to provide the information you're asking for. Please ask if it is not clear, I will provide further information. – Jacob Krieg Nov 01 '18 at 10:30
  • Could you post the `CMakeLists.txt`? As for further clarity and correct me if I'm wrong, your project contains `app` module is a regular app with `plugin: 'com.android.application'` defined in the `app.gradle`. The `clory-sdk-core` and `clory-sdk` are also project modules which have the their `gradle` files containing `plugin: 'com.android.library'` with the only difference is `clory-sdk` containing `CMakeLists.txt` file and C/C++ code. – ahasbini Nov 04 '18 at 22:38
  • @ahasbini Thank you very much for your questions. What you described is exactly the case, with just one exception: `clory-sdk-core` contains `CMakeLists.txt`, and _NOT_ `clory-sdk`. So `clory-sdk-core.gradle` contains `externalNativeBuild {cmake {path "CMakeLists.txt"}}` while `clory-sdk` has `clory-sdk-core` as a dependency: `dependencies {compile project(':glas.ai-sdk-core')`. I have posted an edit and I added the `CMakeLists.txt` for more clarity. – Jacob Krieg Nov 06 '18 at 09:13
  • I have a test application that uses/tests an apk. This apk library is dependent on a .so file. I want to be able to add breakpoints and debug into the .so file. I built all of the projects in debug mode with the "donot strip" packaging option but still I am not able to step into the library. Can you help? – Jay Snayder Jun 24 '20 at 03:49

2 Answers2

20

The debug info records the location of the source files when they were built.

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

The CompileUnit line shows the source file. Suppose it says:

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

Let's assume you have the source on your machine here:

"Users/me/Sources/Clory"

So you can tell lldb: find the source file rooted at /BuildDirectory/Sources/Clory in Users/me/Sources/Clory instead.

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

You can use these commands in the lldb console of Android Studio or put into a .lldbinit file for general use.

Peter
  • 1,591
  • 6
  • 19
  • Thank you very much for your answer! I ran `$ lldb libclorysdk.so` and after that I ran `(lldb) image lookup -vrn Clory::Engine::instance` I get only 4 sections: `Address`, `Summary`, `Module` and `Symbol`. It looks something like this: https://pastebin.com/2wBrD1nJ Any idea why I don't get the `CompileUnit` information? My libraries are all built in debug mode. – Jacob Krieg Nov 01 '18 at 09:44
  • 2
    It seems that the libclorysdk.so has no debug information. Try to rebuild the sdk with debug info or use the debug version of the sdk if it's available. Use these lldb commands in Android Studio. Set a breakpoint in the `Java_clory_engine_sdk_CloryNative_nativeInit` function. Start the debuging. When you stop at the breakpoint use the lldb window of Android Studio to run the lldb commands above. – Peter Nov 02 '18 at 16:13
10

If there no debug symbols available, you might have to build the referenced library in debug mode.

Either with -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')
    }
}

Or add this to the CMakeLists.txt of the library:

set(CMAKE_BUILD_TYPE Debug)

See the CMake documentation and Symbolicating with LLDB.

Elsewhere it explains (lldb) settings set target.source-map /buildbot/path /my/path:

Remap source file path-names for the debug session. If your source files are no longer located in the same location as when the program was built --- maybe the program was built on a different computer --- you need to tell the debugger how to find the sources at their local file path instead of the build system's file path.

There's also (lldb) settings show target.source-map, to see what is mapped. (lldb) set append target.source-map /buildbot/path /my/path seems rather suitable, in order not to overwrite existing mappings.

Martin Zeitler
  • 1
  • 19
  • 155
  • 216
  • Thank you so very much for your answer! I added `set(CMAKE_BUILD_TYPE Debug)` to my `CMakeLists.txt` in my android project which handles the JNI layer. I also made sure that all my libraries are built in Debug mode as the library is built with `CONFIG+=debug`(i'm using `Qt 5.8` with `GCC 4.9` - I don't think it matters tough as, I read, debugging `gcc` compiled code should be possible with `lldb`). Now I'm new to bridging C++ and Java in Android and I may make stupid mistakes and ask stupid questions. Here's what I'm doing: in the Android Studio terminal I run `$ lldb libclorysdk.so`. – Jacob Krieg Nov 01 '18 at 09:20
  • The problem is that when I run and then `(lldb) settings show target.source-map` I get the following message: `target.source-map (path-map) =`. This means that no path is associated with that variable(I suppose). Now I tried `settings set target.source-map "" /Users/jacob/Work/clory/sdk/native/sdk/base`(_base_ folder is the location of the project which generates my `libclorysdk.so` library i.e. location of the qmake _.pro_ project file) and when I run `(lldb) settings show target.source-map` I get `target.source-map (path-map) = [0] "" -> "/Users/jacob/Work/clory/sdk/native/sdk/base"`. – Jacob Krieg Nov 01 '18 at 09:21
  • In this point I still couldn't enter inside `Clory::Engine::instance` with the debugger. I also tried to set the key(value before "->") to be the path to my `libclorysdk.so`(located inside the android project) and the value(value after "->") to be the path to my _clory_ library sdk source code; so when I ran `(lldb) settings show target.source-map` I get `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 Nov 01 '18 at 09:22
  • At this point when I ran the debugger I still couldn't enter inside `Clory::Engine::instance`. Another i think important thing is that when I exit `lldb` with `(lldb) exit` and I run `$ libclorysdk.so` againe, all the mappings ar gone; so the operations are not persistent. What could I be doing wrong? – Jacob Krieg Nov 01 '18 at 09:22
  • When you run `$ lldb libclorysdk.so` you create a separate new lldb session. The command what you type here will be valid for that session. After `(lldb) exit` the session will be closed. Use lldb console of Android Studio instead of the command line tool as I mentioned at my answer. – Peter Nov 05 '18 at 10:28
  • @Peter Thank you again! Please see _Edit 3_ section, where I explained the outcome of what you suggested. Could there be a further solution I could try? – Jacob Krieg Nov 06 '18 at 10:53
  • I got `ERROR: Could not find method arguments() for arguments` after modifying build.gradle. – zwcloud Apr 25 '19 at 06:58
  • 1
    @zwcloud you need to add the `arguments` into `defaultConfig > externalNativeBuild > cmake > arguments`... and the path to `CMakeLists.txt` outside of the `defaultConfig`. I've updated my answer to make this more clear. – Martin Zeitler Apr 25 '19 at 07:22