esp-nimble-cpp icon indicating copy to clipboard operation
esp-nimble-cpp copied to clipboard

Unable to find NimBLEServerCallbacks during linking in IDF as Lib environment

Open JouleStijn opened this issue 1 year ago • 13 comments

I am attempting to replicate the Advanced NimBLE Server example provided in this repository within an environment where ESP-IDF is used as a library (IDF as Lib). This setup minimizes dependence on the ESP-IDF build system and instead uses a more traditional CMake workflow, as outlined in the Espressif IDF as Lib example.

The integration involves the following steps:

  1. Adding esp-nimble-cpp as a submodule and including it as an external component.
  2. Building esp-nimble-cpp using idf_build_component(component_dir).
  3. Successfully linking the library with idf::esp-nimble-cpp.

While basic functionality of the NimBLE example works, implementing custom callback methods fails during the linking stage with errors indicating that the NimBLEServerCallbacks class cannot be found.

Steps to Reproduce:

  1. Set up a project using the IDF as Lib structure.

  2. Add esp-nimble-cpp as an external component using CMake:

    set(NIMBLE_CPP_PATH "${CMAKE_SOURCE_DIR}/external/esp-nimble-cpp")
    idf_build_component(${NIMBLE_CPP_PATH})
    
  3. Build the project with idf_build_component.

  4. Implement and use custom callbacks that inherit from NimBLEServerCallbacks as done in the example:

    #include "NimBLEServer.h"
    
    class MyServerCallbacks : public NimBLEServerCallbacks {
    public:
        void onConnect(NimBLEServer* pServer) override {
            // Custom logic for client connection
        }
        void onDisconnect(NimBLEServer* pServer) override {
            // Custom logic for client disconnection
        }
    };
    
  5. Link and build the project.

Expected Behavior:

The project should build and link successfully, allowing the use of NimBLEServerCallbacks and custom callback methods without errors.

Actual Behavior:

The linker fails to resolve NimBLEServerCallbacks methods, resulting in errors similar to the following:

undefined reference to `_ZTI21NimBLEServerCallbacks`

This issue suggests that the linker is unable to locate the NimBLEServerCallbacks symbols, despite them being defined in esp_nimble_cpp.

Findings and Investigations:

Linker Error:

The full linker error output is as follows (paths obfuscated):

/path/to/toolchain/xtensa-esp-elf/bin/ld: libmy_component.a(bluetooth_gatt_server.cpp.obj):(.rodata._ZTI15ServerCallbacks[_ZTI15ServerCallbacks]+0x8): undefined reference to `_ZTI21NimBLEServerCallbacks'
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.
make: *** [Makefile:86: dev] Error 1

Symbols Verification:

Using nm to inspect the esp_nimble_cpp library confirms that the NimBLEServerCallbacks symbols are present:

nm build/esp-idf/esp_nimble_cpp/libesp_nimble_cpp.a | grep NimBLEServerCallbacks
00000000 T _ZN12NimBLEServer12setCallbacksEP21NimBLEServerCallbacksb
00000000 T _ZN21NimBLEServerCallbacks10onIdentityER14NimBLEConnInfo
00000000 T _ZN21NimBLEServerCallbacks12onDisconnectEP12NimBLEServerR14NimBLEConnInfoi
00000000 W _ZN21NimBLEServerCallbacksD0Ev
00000000 V _ZTV21NimBLEServerCallbacks
...

TypeInfo Issues:

The missing _ZTI21NimBLEServerCallbacks (type info) suggests that the runtime type information (RTTI) or vtable for this class is not being linked correctly.

Environment:

  • ESP-IDF Version: 5.2.1
  • esp-nimble-cpp Version: Latest master branch
  • Board/Chip: ESP32
  • Operating System: Linux
  • Build System: Pure CMake with ESP-IDF as Lib

Additional Context:

CMake Setup for Library:

The esp-nimble-cpp component is built as follows in the root CMakeLists.txt:

set(COMPONENT_NAME my_component)

add_library(${COMPONENT_NAME}
    "bluetooth_gatt_server.cpp"
)

target_include_directories(${COMPONENT_NAME} PUBLIC 
    "${CMAKE_CURRENT_LIST_DIR}/include"
)

target_link_libraries(${COMPONENT_NAME}
    idf::app_update
    idf::esp_http_client
    idf::esp-nimble-cpp
)

This setup works for most components but fails to resolve the symbols for NimBLEServerCallbacks.

Potential Setup Differences:

I acknowledge that my setup diverges from what is described in the project’s README. This is somewhat unconventional, and I realize it might cause unforeseen issues.

Despite this, I am reaching out in case you have any insights or ideas that could help resolve this problem. Any guidance would be greatly appreciated!

Thank you for your time and support!

JouleStijn avatar Dec 04 '24 19:12 JouleStijn

Interesting, I've not done this before so I don't have a lot to offer. It looks like something is either being built differently this way (flags of some kind?) or it is not able to find an instance of the class declared.

h2zero avatar Dec 04 '24 20:12 h2zero

Thank you for your response,

After further investigation, I found that the issue was indeed related to a compiler option, as you suggested. Specifically, enabling RTTI in the SDK config resolves the issue, as described in the ESP-IDF documentation. This enables the -frtti compiler flag.

However, since RTTI is disabled by default in ESP-IDF, I'm hesitant to use it due to its potential drawbacks, such as increased code size and memory usage.

I have two follow-up questions:

  1. Is RTTI typically enabled by default in Arduino or PlatformIO environments? If you happen to know.
  2. Are there any alternative approaches you could suggest for handling NimBLEServerCallbacks without relying on RTTI? For instance, using function pointers, static callbacks, or compile-time polymorphism?

JouleStijn avatar Dec 05 '24 08:12 JouleStijn

Interesting indeed, there should be no need for RTTI though. It's not used in platformio or Arduino either.

h2zero avatar Dec 05 '24 12:12 h2zero

Strange, indeed. When RTTI is enabled, the _ZTI21NimBLEServerCallbacks symbol appears in the compiled library, resolving the issue. However, I can't pinpoint why RTTI seems necessary in my specific setup. The example works perfectly in a pure ESP-IDF environment, yet somehow my configuration implicitly depends on RTTI features, even though I’m not using dynamic_cast or typeid.

I’ll investigate the matter and the impact of enabling RTTI further when I have more time. If the impact on code size and performance is minimal, I may consider using it as a workaround. However, since you mentioned RTTI shouldn’t be necessary, I’d prefer not to rely on it unless required.

That said, if you have any insights or ideas in the meantime, I’d greatly appreciate them.

JouleStijn avatar Dec 05 '24 12:12 JouleStijn

What commit are you using from this repo?

h2zero avatar Dec 05 '24 14:12 h2zero

Currently I am on commit 70c6e89. But with the latest master commit (a55489f) I face the same issues.

JouleStijn avatar Dec 05 '24 15:12 JouleStijn

Okay, one more question. If you remove the callbacks completely from your application code does it build?

The reason I am asking is because I have not really done any updates to those examples and some of the signatures could be off, which doesn't show up compiling with esp-idf.

h2zero avatar Dec 05 '24 18:12 h2zero

The callbacks themselves build fine, until I add the following line:

pServer->setCallbacks(new ServerCallbacks());

So no callbacks, no setCallbacks, no issue. The example works fine without custom callbacks at all.

Declaring a class that inherits from NimBLEServerCallbacks does not cause the issue, nor does instantiating it. The problem arises when passing the child class to the setCallbacks method it seems.

JouleStijn avatar Dec 05 '24 19:12 JouleStijn

If you try the client example do you get this issue with it's callbacks?

h2zero avatar Dec 05 '24 20:12 h2zero

Yes I face the same issues with the client example. Additionally I want to clarify that I was wrong earlier. Declaring a class that inherits from NimBLEServerCallbacks or NimBLEClientCallbacks for that matter works fine. However declaring an instance like the following does seem to be the issue:

static ServerCallbacks serverCallbacks; // Inherited from NimBLEServerCallbacks

So generally I think creating instances of callback child classes seems to be the problem in my case.

JouleStijn avatar Dec 06 '24 09:12 JouleStijn

I can answer this question

Is RTTI typically enabled by default in Arduino or PlatformIO environments? If you happen to know.

It is disabled everywhere. It has major impact in code size. It is disabled by default in IDF projects too. https://github.com/espressif/esp-idf/blob/083aad99cfc1a7981009ac7f18e29824c47ffba2/CMakeLists.txt#L60-L65

Jason2866 avatar Dec 13 '24 16:12 Jason2866

I had the same problem and found a solution. When RTTI is disabled in the ESP-IDF, but you build the component without "-fno-rtti" you get this error. So either add "-fno-rtti", or better the compile options that are used by the ESP-IDF:

idf_build_get_property(compile_options COMPILE_OPTIONS GENERATOR_EXPRESSION)
idf_build_get_property(cxx_compile_options CXX_COMPILE_OPTIONS GENERATOR_EXPRESSION)
target_compile_options(
    ${COMPONENT_NAME}
    PUBLIC ${compile_options} ${cxx_compile_options})

mrsch avatar Feb 25 '25 15:02 mrsch

While this looks promising, the issue still remains for me unfortunately. Are you using IDF as Lib? Where did you specify the target link libraries and for what target?

If I specify it for the ${elf_file} target from the IDF as Lib CMakeLists.txt, it builds, but I still get the same linker error.

Could you provide more details?

JouleStijn avatar Feb 26 '25 10:02 JouleStijn