C++ standard library modules fail to link on some bare metal platforms · Issue #18351 · micropython/micropython · GitHub
Skip to content

C++ standard library modules fail to link on some bare metal platforms #18351

@sfe-SparkFro

Description

@sfe-SparkFro

Port, board and/or hardware

All ports that use Make (I think)

MicroPython version

4efc5e1

Reproduction

Modify examples/cppexample/example.cpp to use a std::vector (or anything else that actually requires libstdc++.a or any other libs):

#include <vector>
#include <numeric>

extern "C" {
#include <examplemodule.h>
#include <py/objstr.h>

// Here we implement the function using C++ code, but since it's
// declaration has to be compatible with C everything goes in extern "C" scope.
mp_obj_t cppfunc(mp_obj_t a_obj, mp_obj_t b_obj) {
    // The following no-ops are just here to verify the static assertions used in
    // the public API all compile with C++.
    MP_STATIC_ASSERT_STR_ARRAY_COMPATIBLE;
    if (mp_obj_is_type(a_obj, &mp_type_BaseException)) {
    }

    // Prove we have (at least) C++11 features.
    const auto a = mp_obj_get_int(a_obj);
    const auto b = mp_obj_get_int(b_obj);
    std::vector<int> vec;
    vec.push_back(a);
    vec.push_back(b);
    int std_accumulate = std::accumulate(vec.begin(), vec.end(), 0);
    const auto sum = [&]() {
        return mp_obj_new_int(std_accumulate);
    } ();
    // Prove we're being scanned for QSTRs.
    mp_obj_t tup[] = {sum, MP_ROM_QSTR(MP_QSTR_hellocpp)};
    return mp_obj_new_tuple(2, tup);
}
}

Try to build firmware for any port that uses Make (I've tested stm32 and mimxrt, but I assume others are the same):

cd ports/stm32
make USER_C_MODULES=../../examples/usercmodule

Expected behaviour

The firmware should build without errors.

Observed behaviour

The linker can't find any of the required symbols from libstdc++.a:

LINK build-PYBV10/firmware.elf
/usr/lib/gcc/arm-none-eabi/13.2.1/../../../arm-none-eabi/bin/ld: build-PYBV10/cppexample/example.o: in function `std::__new_allocator<int>::deallocate(int*, unsigned int)':
/usr/include/newlib/c++/13.2.1/bits/new_allocator.h:168:(.text._ZNSt12_Vector_baseIiSaIiEED2Ev[_ZNSt12_Vector_baseIiSaIiEED5Ev]+0x8): undefined reference to `operator delete(void*)'
/usr/lib/gcc/arm-none-eabi/13.2.1/../../../arm-none-eabi/bin/ld: build-PYBV10/cppexample/example.o: in function `std::vector<int, std::allocator<int> >::_M_check_len(unsigned int, char const*) const':
/usr/include/newlib/c++/13.2.1/bits/stl_vector.h:1896:(.text._ZNSt6vectorIiSaIiEE17_M_realloc_insertIJRKiEEEvN9__gnu_cxx17__normal_iteratorIPiS1_EEDpOT_[_ZNSt6vectorIiSaIiEE17_M_realloc_insertIJRKiEEEvN9__gnu_cxx17__normal_iteratorIPiS1_EEDpOT_]+0x22): undefined reference to `std::__throw_length_error(char const*)'
/usr/lib/gcc/arm-none-eabi/13.2.1/../../../arm-none-eabi/bin/ld: build-PYBV10/cppexample/example.o: in function `void std::vector<int, std::allocator<int> >::_M_realloc_insert<int const&>(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&)':
/usr/include/newlib/c++/13.2.1/bits/new_allocator.h:147:(.text._ZNSt6vectorIiSaIiEE17_M_realloc_insertIJRKiEEEvN9__gnu_cxx17__normal_iteratorIPiS1_EEDpOT_[_ZNSt6vectorIiSaIiEE17_M_realloc_insertIJRKiEEEvN9__gnu_cxx17__normal_iteratorIPiS1_EEDpOT_]+0x40): undefined reference to `operator new(unsigned int)'
/usr/lib/gcc/arm-none-eabi/13.2.1/../../../arm-none-eabi/bin/ld: build-PYBV10/cppexample/example.o: in function `std::__new_allocator<int>::deallocate(int*, unsigned int)':
/usr/include/newlib/c++/13.2.1/bits/new_allocator.h:168:(.text._ZNSt6vectorIiSaIiEE17_M_realloc_insertIJRKiEEEvN9__gnu_cxx17__normal_iteratorIPiS1_EEDpOT_[_ZNSt6vectorIiSaIiEE17_M_realloc_insertIJRKiEEEvN9__gnu_cxx17__normal_iteratorIPiS1_EEDpOT_]+0x7c): undefined reference to `operator delete(void*)'
/usr/lib/gcc/arm-none-eabi/13.2.1/../../../arm-none-eabi/bin/ld: build-PYBV10/cppexample/example.o: in function `std::vector<int, std::allocator<int> >::~vector()':
/usr/include/newlib/c++/13.2.1/bits/stl_vector.h:735:(.text.cppfunc+0x6e): undefined reference to `__cxa_end_cleanup'
/usr/lib/gcc/arm-none-eabi/13.2.1/../../../arm-none-eabi/bin/ld: build-PYBV10/cppexample/example.o:(.ARM.extab.text.cppfunc+0x0): undefined reference to `__gxx_personality_v0'
collect2: error: ld returned 1 exit status
make: *** [Makefile:658: build-PYBV10/firmware.elf] Error 1

Additional Information

The root problem is in these 3 locations:

LDFLAGS += $(LDFLAGS_USERMOD)

The linker requires arguments to be passed in a certain order. -L flags must come before -l flags, like so:

arm-none-eabi-gcc -Lpath/to/lib src.o -lstdc++

However when building with V=1, the actual output is like so:

arm-none-eabi-gcc -lstdc++ -Lpath/to/lib src.o

The resolution really requires LDFLAGS_USERMOD += -lstdc++ to change to something like LIBS_USERMOD += -lstdc++, then adding a LIBS += $(LIBS_USERMOD) to py/py.mk. LDFLAGS_USERMOD should remain, in case the module requires additional libraries. I can make a PR for this.

Code of Conduct

Yes, I agree

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions