Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to find NimBLEServerCallbacks during linking in IDF as Lib environment #255

Open
JouleStijn opened this issue Dec 4, 2024 · 11 comments

Comments

@JouleStijn
Copy link

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!

@h2zero
Copy link
Owner

h2zero commented Dec 4, 2024

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.

@JouleStijn
Copy link
Author

JouleStijn commented Dec 5, 2024

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?

@h2zero
Copy link
Owner

h2zero commented Dec 5, 2024

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

@JouleStijn
Copy link
Author

JouleStijn commented Dec 5, 2024

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.

@h2zero
Copy link
Owner

h2zero commented Dec 5, 2024

What commit are you using from this repo?

@JouleStijn
Copy link
Author

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

@h2zero
Copy link
Owner

h2zero commented Dec 5, 2024

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.

@JouleStijn
Copy link
Author

JouleStijn commented Dec 5, 2024

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.

@h2zero
Copy link
Owner

h2zero commented Dec 5, 2024

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

@JouleStijn
Copy link
Author

JouleStijn commented Dec 6, 2024

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.

@Jason2866
Copy link
Contributor

Jason2866 commented Dec 13, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants