eclipse / paho.mqtt.cpp

Other
966 stars 422 forks source link

undefined reference to `MQTTReasonCode_toString' #243

Closed potatcode closed 2 days ago

potatcode commented 4 years ago

Linker issue when trying to compile the sample code for async/sync_consume.cpp

async/sync_publish.cpp compile and link and are able to communicate with my MQTT broker

Full error message

/usr/bin/ld: //usr/local/lib/libpaho-mqttpp3.a(async_client.cpp.o): in function `mqtt::exception::reason_code_str[abi:cxx11](int)':
async_client.cpp:(.text._ZN4mqtt9exception15reason_code_strB5cxx11Ei[_ZN4mqtt9exception15reason_code_strB5cxx11Ei]+0x28): undefined reference to `MQTTReasonCode_toString'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/sync_subscribe.dir/build.make:84: sync_subscribe] Error 1
make[1]: *** [CMakeFiles/Makefile2:110: CMakeFiles/sync_subscribe.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

Looking deeper into the logs MQTTReasonCodes.h is included in the build

nm of the static library output says

U MQTTReasonCode_toString
U MQTTReasonCode_toString
U MQTTReasonCode_toString
U MQTTReasonCode_toString
phamr39 commented 3 years ago

Hi, i met the exactly problem and i found that my Cmakelist.txt was missing MQTTReasonCodes.h file, so if you are using .a library, change it to .so, this problem was fixed with me.

jmcclin2 commented 3 years ago

I have the same issue when trying to build a completely statically linked sample; @elap4 - did you find the root cause for this issue? Linking with a shared-object does not seem to be an adequate 'fix' if you really need to build completely static.

fpagliughi commented 3 years ago

Which version of the library are you using?

jmcclin2 commented 3 years ago

@fpagliughi - Hi Frank, the C libs are tagged as version 1.3.8. When I objdump the resulting libpaho-mqtt3as.a I see the symbol defined:

`MQTTReasonCodes.c.o: file format elf32-little

SYMBOL TABLE: 00000000 l df ABS 00000000 MQTTReasonCodes.c 00000000 l d .text 00000000 .text 00000000 l d .data 00000000 .data 00000000 l d .bss 00000000 .bss 00000000 l d .rodata 00000000 .rodata 00000000 l .rodata 00000000 $d 00000000 l d .data.rel.local 00000000 .data.rel.local 00000000 l .data.rel.local 00000000 $d 00000000 l O .data.rel.local 00000168 nameToString 00000000 l .text 00000000 $a 00000094 l .text 00000000 $d 00000000 l d .debug_info 00000000 .debug_info 00000000 l d .debug_abbrev 00000000 .debug_abbrev 00000000 l d .debug_aranges 00000000 .debug_aranges 00000000 l d .debug_line 00000000 .debug_line 00000000 l d .debug_str 00000000 .debug_str 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .debug_frame 00000000 .debug_frame 00000000 l d .comment 00000000 .comment 00000000 l d .ARM.attributes 00000000 .ARM.attributes 00000000 g F .text 0000009c .hidden MQTTReasonCode_toString`

I cannot find any obvious reason why it's not found. I have compiled/linked the entire thing using shared objects without issue though. I currently have paho c and c++ included as submodules in my project and pulled from the tip of master for both. I am cross-compiling for ARM (Raspberry PI 4).

Sorry to keep editing this comment - but hoping to add some relevant content. I was wondering if it was a name mangling issue of some sort, though, it looks like it's all handled correctly via MQATTAsync.h. An objdump of libpaho-mqttpp3.a shows that MQTTReasonCode_toString is referenced by the same symbol name, but is undefined (in the resulting c++ lib).

I specify the exact lib to paho c++:

set(PAHO_MQTT_C_LIBRARIES ${CMAKE_CURRENT_SOURCE_DIR}/build/paho.mqtt.c/src/libpaho-mqtt3as.a) set(PAHO_MQTT_C_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/paho.mqtt.c/src)

And link against both the c and c++ lib for my application. Currently this is the only error I am experiencing, though maybe there will be other linker errors after resolving this one?

jmcclin2 commented 3 years ago

@fpagliughi - Hello Frank, just an update on this, I believe I may have misunderstood the issue as logged. I am able to build the included sample code ( async_consume.cpp in particular) without issue, using static libs, as long as it is built as part of the mqtt cpp project, by setting PAHO_BUILD_SAMPLES TRUE. Where I encounter the issue is if I try to compile the same async_consume.cpp code independently/externally while linking to the static lib artifacts of mqtt c/c++.

I realize that I may have been setting PAHO_MQTT_C_LIBRARIES incorrectly in the sample I shared above (when I examine your cmake it appears that it is sufficient to only pass the directory, not the exact path to the lib, though it still seems to have built properly).

I am wondering if @elap4 was having the same issue as me, building the example code external to the mqtt c++ project? Can you think of any compiler/linker flag or option that mqtt c++ uses that may be resolving this dependency in particular that I may be neglecting when building the code externally?

I am confident that I am correctly linking against paho-mqtt3as and paho-mqttpp3 for my application (which is identical code to async_consume.cpp) because if I remove either lib I get obvious undefined references. It's only MQTTReasonCode_toString that seems to be undefined. Thanks for your assistance.

*** One more point of data - if I build an mqtt c example externally (MQTTAsync_publish_time.c in this case), it builds/links without issue. I explicitly added a call to MQTTReasonCode_toString() just to ensure that it was defined and resolved properly.

fpagliughi commented 3 years ago

Yeah. This is weird. Do you have a minimal CMake file that produces the problem? (I have a bunch of different ARM cross compilers installed).

jmcclin2 commented 3 years ago

@fpagliughi - A simplified outline of my project is arranged like below (I point to the static lib artifacts in the default build/paho.mqtt.c/cpp/src locations). This is still just a work-in-progress, so for now I initially comment out the add_subdirectory(src) on the initial build (because there are no paho mqtt artifacts yet), then comment in and build again. As mentioned before, if main.cpp contains a cpp sample (async_consume.cpp) I get the linker error, but if it's MQTTAsync_publish_time.c it builds ok. Doing this with VSCode on Windows 10 FYI, the only relevant arguments passed to cmake not shown are the path to toolchain file and build type. Just a possibly interesting side note, if I define a version in the cmake project() declaration, I get a not fatal cmake warning/error out of paho.mqtt.c about empty values of the version (which seems to be the opposite of what is actually occurring, because if I don't include a version explicitly, I don't get the warning/error).

build -->paho.mqtt.c ---->src -------->libpaho-mqtt3as.a -->paho.mqtt.cpp ---->src -------->paho-mqttpp3.a paho.mqtt.c paho.mqtt.cpp src -->CmakeLists.txt (src) -->main.cpp CmakeLists.txt (top-level) toolchain.cmake

Top-level CMakeLists.txt:

cmake_minimum_required(VERSION 3.0.0)
#project(mqtt_pi_client VERSION 0.1.0.0)
project(mqtt_pi_client C CXX)

#include(CTest)
#enable_testing()

# Build Paho MQTT C as lib to be used by Paho MQTT C++
set(PAHO_ENABLE_TESTING FALSE)
set(PAHO_BUILD_STATIC TRUE)
set(PAHO_BUILD_SHARED FALSE)
set(PAHO_BUILD_SAMPLES TRUE)
set(PAHO_WITH_SSL TRUE)
set(PAHO_HIGH_PERFORMANCE TRUE)
add_subdirectory(paho.mqtt.c)

# Specify where the MQTT C libs/headers can be found
set(PAHO_MQTT_C_LIBRARIES ${CMAKE_CURRENT_SOURCE_DIR}/build/paho.mqtt.c/src/libpaho-mqtt3as.a)
set(PAHO_MQTT_C_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/paho.mqtt.c/src)

# Many of the above Paho MQTT C settings apply to C++ as well
#list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build/paho.mqtt.cpp/src")
add_subdirectory(paho.mqtt.cpp)

# Primary application
add_subdirectory(src)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

Toolchain cmake:

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

# THIS MUST BE CORRECTLY CONFIGURED FOR RASPBERRY PI TOOLCHAIN
set(sysroot_target C:/SysGCC/raspberry/arm-linux-gnueabihf/sysroot)
set(tools C:/SysGCC/raspberry/bin)

set(CMAKE_C_COMPILER ${tools}/arm-linux-gnueabihf-gcc.exe)
set(CMAKE_CXX_COMPILER ${tools}/arm-linux-gnueabihf-g++.exe)
set(CMAKE_SYSROOT ${sysroot_target})

# COMPILER SETTINGS FOR RASPBERRY PI 4 - found at https://www.valvers.com/open-software/raspberry-pi/bare-metal-programming-in-c-part-1/
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
SET(CMAKE_CXX_FLAGS "-v -O2 -mfpu=crypto-neon-fp-armv8 -mfloat-abi=hard -march=armv8-a+crc -mcpu=cortex-a72 --sysroot=${sysroot_target}")
elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
SET(CMAKE_CXX_FLAGS "-v -mfpu=crypto-neon-fp-armv8 -mfloat-abi=hard -march=armv8-a+crc -mcpu=cortex-a72 --sysroot=${sysroot_target}")
endif()

MESSAGE(STATUS "Toolchain build type:${CMAKE_BUILD_TYPE}")

SET(CMAKE_C_FLAGS ${CMAKE_CXX_FLAGS})
SET(CMAKE_EXE_LINKER_FLAGS "--sysroot=${sysroot_target}")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

src CMakeLists.txt:

configure_file(../config.hpp.in project_config.hpp)

set(SOURCE_FILES
    "main.cpp"
)

add_executable(mqtt_pi_client ${SOURCE_FILES})

target_include_directories(mqtt_pi_client
    PUBLIC
    .
    ../paho.mqtt.c/src
    ../paho.mqtt.cpp/src
)

find_library(PAHO_MQTT_C_LIB_PRO NAMES paho-mqtt3as PATHS ../build/paho.mqtt.c/src)
find_library(PAHO_MQTT_CPP_LIB_PRO NAMES paho-mqttpp3 PATHS ../build/paho.mqtt.cpp/src)

message("***PAHO_MQTT_C_LIB_PRO:${PAHO_MQTT_C_LIB_PRO}")
message("***PAHO_MQTT_CPP_LIB_PRO:${PAHO_MQTT_CPP_LIB_PRO}")

target_link_libraries(mqtt_pi_client
    ${PAHO_MQTT_C_LIB_PRO}
    ${PAHO_MQTT_CPP_LIB_PRO}
    pthread
    ssl
    crypto
)
jmcclin2 commented 3 years ago

@fpagliughi - Hi Frank, just an update. If I make the simple workaround shown below, the async_consume.cpp code builds without issue (external from the mqtt c++ project). It seems like the issue is isolated to just this function. The signature looks to be the same, and I see the symbol defined in the c lib. I will experiment a bit more. Just FYI, the MQTTAsync_publish_time.c sample runs fine on my RP4 as I can modify the server/topic/etc. that it is publishing to and confirm reception of messages on hivemq's public broker, so I have confidence that the project is compiling/linking ok other than this singular issue.

static string reason_code_str(int reasonCode) {
        if (reasonCode != MQTTPP_V3_CODE) {
            //auto msg = ::MQTTReasonCode_toString(MQTTReasonCodes(reasonCode));
            const char * msg = "undefined!";
            if (msg) return string(msg);
        }
        return string();
    }

Further experiment - If I reduce async_consume.cpp's main to the following, it builds without issue:

int main(int, char**) {

    const char * pResponse = MQTTReasonCode_toString(MQTTREASONCODE_SUCCESS);
    return 0;
}

A further test seems to build correctly using statically linked mqtt cpp lib, but doesn't seem to produce the correct result string:

int main(int, char**) {

    //const char * pResponse = MQTTReasonCode_toString(MQTTREASONCODE_SUCCESS);
    cout << "Exception test..." << endl;
    mqtt::exception myExc(MQTTASYNC_FAILURE);
    std::string result = myExc.get_reason_code_str();
    cout << result << " is the failure" << endl;
    return 0;
}

In the debugger you can see the reason code value is correct (-1), but the reason code string ends up being "SUCCESS" instead of failure.

image

jmcclin2 commented 3 years ago

@fpagliughi - Frank, if I just add this line at the beginning of main.cpp for the async_consume.cpp example, it builds without issue and runs correctly:

const char * pResponse = MQTTReasonCode_toString(MQTTREASONCODE_SUCCESS);

There must be some issue with the linker being unable to resolve/find the symbol under 'normal' circumstances, but is resolved (in my case) by explicitly calling the function in question in main early.

I think there may be an issue with get_reason_code_str() in the exception class as outlined in the previous comment; it appears that the instantiation of other classes used in the exception constructor (when only the int reason code is passed) may overwrite or ignore the string which should result from passing MQTTASYNC_FAILURE. I don't see get_reason_code_str() used in any sample code or tests, so maybe this was missed.

kensmith180 commented 3 years ago

I ran into the same issue when I switched from shared to static libs. Try changing the order of the libs you are supplying to target_link_libraries.

This gave me the error: image

This links successfully: image

vrince commented 2 years ago

@kensmith180 you saved my life, was about to give up. Thank you.

To recap, if you use static lib order matters link paho-mqttpp3.a (cpp) before paho-mqtta.a (c).

xirius commented 1 year ago

Same issue for me and changing the order doesn't seem to help in my code.

fpagliughi commented 7 months ago

I'm suspecting there are some issues with transient dependencies in the CMake for Paho C. It would be great to modernize and clean it up.

fpagliughi commented 3 weeks ago

This one has been a head-scratcher. Although I don't have some of the same cross-compiler setups to test, I have never been able to recreate this.

One of the earliest problems noticed by @phamr39 (MQTTReasonCodes.h file not being installed) could have cause import issues with Windows DLLs. That was fixed shortly after the problem was reported.

Numerous transitive dependency issues were fixed in the CMake files over the years, which certainly caused problems... but why this one function? Putting a call directly to the function from the application probably worked around a lost lib-to-lib dependency.

Switching to a shared library probably solves this, because shared libraries generally need to resolve their dependencies when the libraries are built. Static libs wait to resolve everything until the apps are built.

So, I'm hoping these issues are resolved in the latest v1.4 release that's about to go out (what's in master now). If anyone who was having the problem and can try out the new code, please report back!

fpagliughi commented 2 days ago

I still can't find an issue in the latest releases, so I'm going to assume this is fix. If anyone still has this happening, please re-open this with any new information.