aws / aws-iot-device-sdk-cpp-v2

Next generation AWS IoT Client SDK for C++ using the AWS Common Runtime
Apache License 2.0
185 stars 110 forks source link

Segmentation Fault Destroying MqttClientConnectionConfig #554

Closed Otto45 closed 1 year ago

Otto45 commented 1 year ago

Describe the bug

I'm creating an instance of MqttClientConnectionConfig using the MqttClientConnectionConfigBuilder, and when the function I'm doing this in ends, I'm getting a segfault when the MqttClientConnectionConfig's destructor is called. The segfault is technically occurring trying to destroy a CRT string on the Optional<HttpClientConnectionProxyOptions> member of MqttClientConnectionConfig. I'm not setting any proxy options using the WithHttpProxyOptions function on MqttClientConnectionConfigBuilder.

Expected Behavior

No segmentation fault should occur.

Current Behavior

Stacktrace:

Thread 1 "iot-sample-client" received signal SIGSEGV, Segmentation fault.
0x00000030 in ?? ()
(gdb) backtrace
#0  0x00000030 in ?? ()
#1  0x76f87bc4 in Aws::Crt::StlAllocator<char>::deallocate (this=0x76ff9ca4, p=0x76ebaf40 "\034") at /usr/include/aws/crt/StlAllocator.h:57
#2  std::allocator_traits<Aws::Crt::StlAllocator<char> >::deallocate (__a=..., __p=0x76ebaf40 "\034", __n=<optimized out>) at /usr/lib/arm-poky-linux-gnueabi/9.2.0/../../../include/c++/9.2.0/bits/alloc_traits.h:333
#3  std::__cxx11::basic_string<char, std::char_traits<char>, Aws::Crt::StlAllocator<char> >::_M_destroy (this=0x76ff9ca4, __size=<optimized out>) at /usr/lib/arm-poky-linux-gnueabi/9.2.0/../../../include/c++/9.2.0/bits/basic_string.h:237
#4  std::__cxx11::basic_string<char, std::char_traits<char>, Aws::Crt::StlAllocator<char> >::_M_dispose (this=0x76ff9ca4) at /usr/lib/arm-poky-linux-gnueabi/9.2.0/../../../include/c++/9.2.0/bits/basic_string.h:232
#5  std::__cxx11::basic_string<char, std::char_traits<char>, Aws::Crt::StlAllocator<char> >::~basic_string (this=0x76ff9ca4) at /usr/lib/arm-poky-linux-gnueabi/9.2.0/../../../include/c++/9.2.0/bits/basic_string.h:658
#6  Aws::Crt::Http::HttpClientConnectionProxyOptions::~HttpClientConnectionProxyOptions (this=0x76ff9c18) at /usr/include/aws/crt/http/HttpConnection.h:279
#7  0x76f87a32 in Aws::Crt::Optional<Aws::Crt::Http::HttpClientConnectionProxyOptions>::~Optional (this=0x7efff330) at /usr/include/aws/crt/Optional.h:36
#8  Aws::Iot::MqttClientConnectionConfig::~MqttClientConnectionConfig (this=0x7efff2a0) at /usr/include/aws/iot/MqttClient.h:25

Reproduction Steps

main.cpp:

#include <iostream>
#include <memory>
#include <string>

#include <aws/crt/Api.h>
#include <aws/iot/MqttClient.h>

using namespace std;

static shared_ptr<Aws::Iot::MqttClient> MqttClientFactory()
{
    return make_shared<Aws::Iot::MqttClient>(*Aws::Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap());
}

static shared_ptr<Aws::Crt::Mqtt::MqttConnection> CreateAndEstablishMqttConnection(
    const shared_ptr<Aws::Iot::MqttClient>& p_mqttClient,
    const string& mqttClientId,
    const string& awsIotCoreMqttEndpoint,
    const string& amazonCaPemFile,
    const string& deviceCertPemFile,
    const string& devicePrivateKeyPemFile,
    bool cleanSession)
{
    auto mqttClientConnectionConfigBuilder = Aws::Iot::MqttClientConnectionConfigBuilder(deviceCertPemFile.c_str(), devicePrivateKeyPemFile.c_str());
    mqttClientConnectionConfigBuilder.WithCertificateAuthority(amazonCaPemFile.c_str());

    Aws::Crt::String awsIotCoreMqttEndpointAwsString(awsIotCoreMqttEndpoint.c_str(), awsIotCoreMqttEndpoint.length());
    mqttClientConnectionConfigBuilder.WithEndpoint(awsIotCoreMqttEndpointAwsString);

    auto mqttClientConnectionConfig = mqttClientConnectionConfigBuilder.Build();

    if (!mqttClientConnectionConfig)
    {
        return nullptr;
    }

    auto p_mqttConnection = p_mqttClient->NewConnection(mqttClientConnectionConfig);
    if (!p_mqttConnection)
    {
        return nullptr;
    }

    promise<bool> connectionCompletedPromise;

    auto onConnectionCompleted = [&](Aws::Crt::Mqtt::MqttConnection &, int errorCode, Aws::Crt::Mqtt::ReturnCode returnCode, bool)
    {
        if (errorCode)
        {
            connectionCompletedPromise.set_value(false);
        }
        else
        {
            if (returnCode != AWS_MQTT_CONNECT_ACCEPTED)
            {
                connectionCompletedPromise.set_value(false);
            }
            else
            {
                connectionCompletedPromise.set_value(true);
            }
        }
    };

    auto onConnectionInterrupted = [&](Aws::Crt::Mqtt::MqttConnection &, int error)
    {
        cout << "Connection interrupted with error " << Aws::Crt::ErrorDebugString(error) << endl;
    };

    auto onResumed = [&](Aws::Crt::Mqtt::MqttConnection &, Aws::Crt::Mqtt::ReturnCode, bool)
    {
        cout << "Connection resumed" << endl;
    };

    p_mqttConnection->OnConnectionCompleted = move(onConnectionCompleted);
    p_mqttConnection->OnConnectionInterrupted = move(onConnectionInterrupted);
    p_mqttConnection->OnConnectionResumed = move(onResumed);

    if (!p_mqttConnection->Connect(mqttClientId.c_str(), cleanSession, 60 /*keepAliveTimeSecs*/, 1000 /*pingTimeoutMs*/))
    {
        return nullptr;
    }

    if (!connectionCompletedPromise.get_future().get())
    {
        return nullptr;
    }

    return p_mqttConnection;
}

int main()
{
    Aws::Crt::ApiHandle apiHandle;

    {
        string mqttClientId = "iot-sample-client";
        string awsIotCoreMqttEndpoint = ""; // your iot core endpoint
        string amazonCaPemFile = ""; // an aws ca file in pem format
        string provisioningClaimCertificatePath = ""; // your device provisioning claim cert
        string provisioningClaimPrivateKeyPath = ""; // your device provisioning claim private key in pem format

        auto p_mqttClient = MqttClientFactory();

        auto p_mqttConnection = CreateAndEstablishMqttConnection(
            p_mqttClient,
            mqttClientId,
            awsIotCoreMqttEndpoint,
            amazonCaPemFile,
            provisioningClaimCertificatePath,
            provisioningClaimPrivateKeyPath,
            true /* cleanSession */);

        // Code to Register Thing

        promise<void> connectionClosedPromise;

        auto onConnectionDisconnect = [&connectionClosedPromise](Aws::Crt::Mqtt::MqttConnection &)
        {
            connectionClosedPromise.set_value();
        };

        p_mqttConnection->OnDisconnect = move(onConnectionDisconnect);

        if (p_mqttConnection->Disconnect())
        {
            connectionClosedPromise.get_future().wait();
        }
    }
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.8)
project(iot-sample-client C CXX)

add_executable(${PROJECT_NAME} main.cpp)

target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_11)

set_target_properties(${PROJECT_NAME} PROPERTIES CXX_EXTENSIONS OFF)

find_package(aws-crt-cpp REQUIRED)
find_package(IotIdentity-cpp REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-crt-cpp AWS::IotIdentity-cpp)

Possible Solution

No response

Additional Information/Context

The code works just fine when compiled for x86 and ran on my laptop, but the segfault is occurring when the code is cross-compiled and running on our SoM boards.

SoM Board Specs:

SDK version used

v1.21.0

Environment details (OS name and version, etc.)

Yocto Zeus FSL 5.4.3

jmklix commented 1 year ago

It looks like you're missing the InitAPI call

Otto45 commented 1 year ago

@jmklix, the samples in this repo show that simply creating an instance of ApiHandle does the global initialization for the SDK. I also understand this initialization needs to be done before everything else, and the object must outlive any other AWS SDK/CRT objects. As I mentioned above, this works on my laptop, so what is different about using InitAPI? I don't see any reference to it in this repo or the CRT C++ repo, am I missing something?

jmklix commented 1 year ago

My mistake, I was confusing this with aws-sdk-cpp. Looking into this segfault

Otto45 commented 1 year ago

@jmklix after setting up a cross-compilation toolchain and remote debugging using our generated yocto sdk, we were able to finally determine the issue has to do with setting the optimization level to 2 with the compiler flag -O2. Because we use yocto to build our OS image, it automatically sets this compiler flag for standard, or non-debug, builds. We overrode this functionality (setting the optimization to -O0) in the recipes for all the aws libraries and our libraries / binaries, and everything is working now.

jmklix commented 1 year ago

Glad you were able to get this working. Let me know if you have any other problems with this sdk

github-actions[bot] commented 1 year ago

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.