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

C++ exceptions are aborting the process, even if caught #289

Closed
borrrden opened this issue Jan 28, 2017 · 45 comments
Closed

C++ exceptions are aborting the process, even if caught #289

borrrden opened this issue Jan 28, 2017 · 45 comments
Assignees
Labels
Milestone

Comments

@borrrden
Copy link

borrrden commented Jan 28, 2017

Description

The setup is slightly complicated but I am using command line CMake (Android Studio not involved) to compile a native library that I need to then test. For simplicity sake, and since CMake already outputs a native test runner executable anyway, I simply upload it to an arbitrary directory and run it via adb shell. However, some of the unit tests will try and catch exceptions. On arm and arm64 this causes the process to abort instead of throwing an exception which is then caught. This sounds like exceptions are disabled, but I have confirmed many times that -fexceptions is present during compilation. Furthermore the __cpp_exceptions compiler directive is present. This only seems to happen when using clang and libc++ (Which we must use for other reasons). libstdc++ does not exhibit the same behavior, and neither does NDK r11c. The output from the test runner is as follows:

FAILED:
due to a fatal error condition:
SIGABRT - Abort (abnormal termination) signal

vs. on x86

All tests passed (5 assertions in 1 test case)

The build is as follows:
Several static libs are linked together into one shared lib, which is linked to libsqlite3.so (which is also built to comply with new Android rules) and then an executable is built which links to both of those

The big shared lib has a C interface which will catch any exceptions before they bubble past it and store the details into an error variable or ignore them and return a generic error state. This is where the exception is thrown (and supposed to be caught) during the crashing test runner.

The source for the shared library being built is here and the unit test runner in question is here. The latest commit as of this writing is 3aeb671.

Here are the build artifacts for armv7 zipped -> C4Tests.zip and the steps to reproduce are:

  1. adb mkdir -p /data/local/tmp/LiteCore
  2. adb push *.so C4Tests /data/local/tmp/LiteCore
  3. adb shell
  4. cd /data/local/tmp/LiteCore
  5. export TMPDIR=/data/local/tmp
  6. export LD_LIBRARY_PATH=/data/local/tmp/LiteCore
  7. ./C4Tests "parse invalid blob keys"

Environment Details

Not all of these will be relevant to every bug, but please provide as much
information as you can.

  • NDK Version: 13.1.3345770
  • Build system: CMake 3.7.2 command line
  • Host OS: Noticed in both Ubuntu 16.0.4 and macOS Sierra
  • Compiler: clang
  • ABI: armeabi-v7a or arm64-v8a (x86 does not have this problem)
  • STL: c++_static
  • NDK API level: 16 (32-bit) 21(64-bit)
  • Device API level: 24
@borrrden
Copy link
Author

Additional helpful info for navigating the repo:

The unit test in question is here.

The method the unit tests calls is here

The catchExceptions() macro is here

The exception causing this issue is thrown here

The actual throw statement is here which throws a struct which is defined here

@borrrden
Copy link
Author

borrrden commented Jan 31, 2017

I was able to get a stack trace:

#0 0xb6ae6d80 in tgkill () from target:/system/lib/libc.so
#1 0xb6ae4516 in pthread_kill () from target:/system/lib/libc.so
#2 0xb6aba618 in raise () from target:/system/lib/libc.so
#3 0xb6ab6164 in __libc_android_abort () from target:/system/lib/libc.so
#4 0xb6ab402c in abort () from target:/system/lib/libc.so
#5 0xb6e2bd62 in __cxxabiv1::readEncodedPointer (data=0x0,
encoding=201 '\311')
at /Volumes/Android/buildbot/src/android/ndk-r13-release/external/libcxx/../../external/libcxxabi/src/cxa_personality.cpp:277
#6 0xb6e2b690 in __cxxabiv1::scan_eh_tab (results=...,
actions=, native_exception=,
unwind_exception=0xb69d4028, context=)
at /Volumes/Android/buildbot/src/android/ndk-r13-release/external/libcxx/../../external/libcxxabi/src/cxa_personality.cpp:623
#7 0xb6e2b4bc in __gxx_personality_v0 (state=,
unwind_exception=0xb69d4028, context=0xbeffe588)
at /Volumes/Android/buildbot/src/android/ndk-r13-release/external/libcxx/../../external/libcxxabi/src/cxa_personality.cpp:1092
#8 0xb6edffa8 in __gnu_Unwind_RaiseException ()
from target:/system/lib/liblog.so
#9 0xb6ee0afc in ___Unwind_RaiseException () from target:/system/lib/liblog.so
#10 0xb6e2abd2 in __cxa_throw (thrown_object=0xb69d4080,
tinfo=, dest=0xb6cb0215 litecore::error::~error())
at /Volumes/Android/buildbot/src/android/ndk-r13-release/external/libcxx/../../external/libcxxabi/src/cxa_exception.cpp:238
#11 0xb6cd985a in litecore::error::_throw (domain=litecore::error::LiteCore,
code=30)
at /Users/borrrden/Development/couchbase-lite-core/LiteCore/Support/Error.cc:245

@borrrden
Copy link
Author

Removing the link to liblog.so and disabling logging temporarily gives me this trace:

#0 0xb6e95d80 in tgkill () from target:/system/lib/libc.so
#1 0xb6e93516 in pthread_kill () from target:/system/lib/libc.so
#2 0xb6e69618 in raise () from target:/system/lib/libc.so
#3 0xb6e65164 in __libc_android_abort () from target:/system/lib/libc.so
#4 0xb6e6302c in abort () from target:/system/lib/libc.so
#5 0xb6db9d52 in __cxxabiv1::readEncodedPointer (data=0x0,
encoding=232 '\350')
at /Volumes/Android/buildbot/src/android/ndk-r13-release/external/libcxx/../../external/libcxxabi/src/cxa_personality.cpp:277
#6 0xb6db9680 in __cxxabiv1::scan_eh_tab (results=...,
actions=, native_exception=,
unwind_exception=0xb69d4028, context=)
at /Volumes/Android/buildbot/src/android/ndk-r13-release/external/libcxx/../../external/libcxxabi/src/cxa_personality.cpp:623
#7 0xb6db94ac in __gxx_personality_v0 (state=,
unwind_exception=0xb69d4028, context=0xbeffe598)
at /Volumes/Android/buildbot/src/android/ndk-r13-release/external/libcxx/../../external/libcxxabi/src/cxa_personality.cpp:1092
#8 0xb6b303cc in __gnu_Unwind_RaiseException (ucbp=0xb69d4028,
entry_vrs=0xbeffe794)
at /Volumes/Android/buildbot/src/android/gcc/toolchain/build/../gcc/gcc-4.9/libgcc/unwind-arm-common.inc:432
#9 0xb6b30f20 in ___Unwind_RaiseException ()
at /Volumes/Android/buildbot/src/android/gcc/toolchain/build/../gcc/gcc-4.9/libgcc/config/arm/libunwind.S:356
#10 0xb6db8bc2 in __cxa_throw (thrown_object=0xb69d4080,
tinfo=, dest=0xb6c3e245 litecore::error::~error())
at /Volumes/Android/buildbot/src/android/ndk-r13-release/external/libcxx/../../external/libcxxabi/src/cxa_exception.cpp:238
#11 0xb6c678ae in litecore::error::_throw (domain=litecore::error::LiteCore,
code=30)
at /Users/borrrden/Development/couchbase-lite-core/LiteCore/Support/Error.cc

@DanAlbert DanAlbert self-assigned this Jan 31, 2017
@DanAlbert DanAlbert added this to the r15 milestone Jan 31, 2017
@DanAlbert
Copy link
Member

Haven't been able to look closely at this yet, but hopefully will find some time to this week.

@borrrden
Copy link
Author

This is an area where I woefully lack any expertise but from what I've been able to figure out the throw call is somehow generating an encoded pointer that is not supported by libc++abi. I still am not ruling out something that I've set up badly. It troubles me that things are going through hand coded GCC based assembly for throwing exceptions, but maybe that was the point of libc++abi (to be able to have other logics be called and then sent back for analysis?)

Let me know if there is other places I can check out, because I'm at an impasse as to what to do further to figure out this odd problem.

@borrrden
Copy link
Author

One more thing that might be noteworthy: I have installed logic to log backtraces on Android. Although this path doesn't get called during the test in question, if I remove this logic and the calls to logBacktrace then I no longer get an abort but instead I get a SIGSEGV inside of readEncodedPointer:

Program received signal SIGSEGV, Segmentation fault.
0xb6e47f10 in __cxxabiv1::readEncodedPointer (data=0xbeffe514, encoding=132 '\204')
at /Volumes/Android/buildbot/src/android/ndk-r13-release/external/libcxx/../../external/libcxxabi/src/cxa_personality.cpp:301

@borrrden
Copy link
Author

borrrden commented Feb 9, 2017

Is there anything I can do to figure this out more? Stuff I should look for? I'd really like to get this sorted out.

@DanAlbert
Copy link
Member

Sorry, I haven't had a chance to look at this yet.

@DanAlbert
Copy link
Member

(saw the email thread pinged, so figured I'd leave another update)

Still haven't looked in to this. Just to give people an idea of how things work, I've had to focus on getting r14 out the door for a while now. That's shipping any time now, at which point I need to prioritize getting the feature for r15 (stabilizing libc++ as much as possible) done before I can really look at other bugs.

The time between beta 1 and beta 2 is when I usually look in to bugs like this.

@borrrden
Copy link
Author

Wouldn't this be considered part of stabilizing libc++? Anyway I thought I'd update that r14 still has this issue for me.

@Jaykob
Copy link

Jaykob commented Mar 24, 2017

This needs to be fixed ASAP. I'm having the same problem here...

@DanAlbert
Copy link
Member

@borrrden: readelf on your libraries shows they were built improperly:

$ readelf -sW libsqlite3.so | grep _Unwind | grep UND
   319: 00000000     0 FUNC    WEAK   DEFAULT  UND __gnu_Unwind_Find_exidx
  2841: 00000000     0 FUNC    WEAK   DEFAULT  UND __gnu_Unwind_Find_exidx
$ readelf -sW libLiteCore.so | grep _Unwind | grep UND
     6: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Resume
  1437: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Backtrace
  1438: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_VRS_Get
  2567: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_DeleteException
  2568: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_VRS_Set
  2571: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_RaiseException
  2573: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetRegionStart
  2574: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetLanguageSpecificData
 23472: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Resume
 24903: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Backtrace
 24904: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_VRS_Get
 26033: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_DeleteException
 26034: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_VRS_Set
 26037: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_RaiseException
 26039: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetRegionStart
 26040: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetLanguageSpecificData
$ readelf -sW C4Tests | grep _Unwind | grep UND
     7: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Resume
  4344: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Resume

No linked binary should ever have undefined references to pieces of the unwinder. When this happens, the two unwinders in play on arm7 (the LLVM unwinder that works for exception handling with libc++ and libgcc) can both end up being half used, and the two are not ABI compatible.

I'm guessing this is a cmake issue, but I need to dig a bit deeper and can't do that while my machine is busy running tests. Will get back to it in a bit.

@DanAlbert
Copy link
Member

Adding -v to my ldflags for a trivial project using libc++ static, everything looks fine:

ld ... --exclude-libs libunwind.a ... CMakeFiles/foo.dir/foo.cpp.o -lm \
    path/to/libc++.a -lstdc++ -lm -lgcc -ldl -lc -lgcc -ldl ...

It would probably be smart for us to add -Wl,--exclude-libs,libc++_static.a -Wl,--exclude-libs,libc++abi.a -Wl,--exclude-libs,libandroid_support.a to the default flags to be safe, but we don't even do that for ndk-build, and that still wouldn't explain the problem.

@DanAlbert
Copy link
Member

@borrrden: looking at the build files (Android/build_cmake/*) you're not using our cmake toolchain?

@borrrden
Copy link
Author

Do you mean it is required to use the version of CMake that comes with the NDK, or that I should manually specify a build toolchain? The CMake website says differently, it just indicates these instructions.

I'm happy to try out a different way of building, though. Here is the exact script from the build server (the one you linked to is similar and what my colleague is experimenting with until we can get a shared cmake build between Xamarin Android and regular Android)

git submodule update --init --recursive
version=16
if [ "$arch" = "arm64-v8a" ]; then
    version=21
fi
cd build_cmake
cmake -DCMAKE_SYSTEM_NAME=Android -DCMAKE_ANDROID_NDK=/root/Development/android-ndk-r13b \
-DCMAKE_ANDROID_ARCH_ABI=$arch -DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=clang \
-DCMAKE_SYSTEM_VERSION=$version -DCMAKE_ANDROID_STL_TYPE=c++_static -DCMAKE_BUILD_TYPE=Release \

..
make -j8

Switching this to gnustl_static is what we are doing for our workaround. We've had other issues with it before (incomplete C++ 11 support, etc) but for now it is working enough for our tests to pass.

@DanAlbert
Copy link
Member

Following your build instructions, I still get complaints about it not finding sqlite headers. I assume I'm not actually supposed to apt-get those, because that would be a Linux package and not an Android one...

Do you mean it is required to use the version of CMake that comes with the NDK

Not the cmake binary that comes in the NDK (there isn't one), but we only support the CMake toolchain file that we provide. It's in $NDK/build/cmake/android.toolchain.cmake. You use it by passing -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake when running CMake. More docs here: https://cmake.org/cmake/help/v3.6/manual/cmake-toolchains.7.html

This is the only CMake process that I have control over. CMake started shipping their own incompatible Android support after we shipped ours, and it's beyond our control. Based on this bug, it looks like their support has bugs. I'd recommend giving ours a shot and see if that works. Note that because of CMake's own incompatible solution, you actually need CMake 3.6 and not 3.7 for this to work (see #254). If it turns out that it does work with our toolchain file and you want to go back to the stock CMake support, you'll want to file a bug against CMake.

@borrrden
Copy link
Author

Sorry about the difficulty building! That header should be included in a submodule on the repo, but your explanation sounds reasonable so I will give it a shot. I can't imagine it will take more than an hour or two to try it out.

@DanAlbert
Copy link
Member

Following your build instructions, I still get complaints about it not finding sqlite headers. I assume I'm not actually supposed to apt-get those, because that would be a Linux package and not an Android one...

Installing from apt didn't fix this, so it seems I was right on that.

That header should be included in a submodule on the repo

Yeah, it's definitely there, it's just not being included in the build.

but your explanation sounds reasonable so I will give it a shot

Okay, I'll wait for your response rather than simultaneously hammering on it :)

@borrrden
Copy link
Author

FYI, I switched to CMake 3.6 and changed the build invocation to:

cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DANDROID_NATIVE_API_LEVEL=$version -DANDROID_TOOLCHAIN=clang -DANDROID_ABI=$arch -DANDROID_STL=c++_static -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_CPP_FEATURES="rtti exceptions"

But the above unwind stuff is still undefined.

@borrrden
Copy link
Author

Same result from the above, unfortunately.

@DanAlbert
Copy link
Member

Hmm. I'm going to need to take a closer look then.

(Figured out my build problem. git clean -df wasn't purging the intermediate cmake files like I was expecting because the .gitignore ignores that whole directory, so I hadn't actually re-run cmake when I rebuilt.)

@borrrden
Copy link
Author

Here are some samples of the compiler commands that are getting executed from the Makefile by the way:

sqlite3.c

/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang -target armv7-none-linux-androideabi -gcc-toolchain /Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 --sysroot=/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/platforms/android-16/arch-arm -DC4DB_THREADSAFE -DCMAKE -DSQLITE_OMIT_LOAD_EXTENSION -Dsqlite3_EXPORTS -I/Users/borrrden/Development/couchbase-lite-core/vendor/fleece/Fleece -I/Users/borrrden/Development/couchbase-lite-core/vendor/fleece/vendor -I/Users/borrrden/Development/couchbase-lite-core/vendor/SQLiteCpp/include -I/Users/borrrden/Development/couchbase-lite-core/vendor/sqlite3-unicodesn -I/Users/borrrden/Development/couchbase-lite-core/vendor/openssl/libs/include -I/Users/borrrden/Development/couchbase-lite-core/vendor/SQLiteCpp/sqlite3 -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Android -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/BlobStore -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Database -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Indexes -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Query -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/RevTrees -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Storage -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Support -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/VersionVectors -I/Users/borrrden/Development/couchbase-lite-core/C/include -I/Users/borrrden/Development/couchbase-lite-core/C -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fno-integrated-as -mthumb -Wa,--noexecstack -Wformat -Werror=format-security -Os -DNDEBUG -fPIC -DSQLITE_ENABLE_FTS4_UNICODE61 -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS3_TOKENIZER -o CMakeFiles/sqlite3.dir/vendor/SQLiteCpp/sqlite3/sqlite3.c.o -c /Users/borrrden/Development/couchbase-lite-core/vendor/SQLiteCpp/sqlite3/sqlite3.c

BlobStore.cc

/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++ -target armv7-none-linux-androideabi -gcc-toolchain /Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 --sysroot=/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/platforms/android-16/arch-arm -DC4DB_THREADSAFE -DCMAKE -DSQLITE_OMIT_LOAD_EXTENSION -D_CRYPTO_OPENSSL -I/Users/borrrden/Development/couchbase-lite-core/vendor/fleece/Fleece -I/Users/borrrden/Development/couchbase-lite-core/vendor/fleece/vendor -I/Users/borrrden/Development/couchbase-lite-core/vendor/SQLiteCpp/include -I/Users/borrrden/Development/couchbase-lite-core/vendor/sqlite3-unicodesn -I/Users/borrrden/Development/couchbase-lite-core/vendor/openssl/libs/include -I/Users/borrrden/Development/couchbase-lite-core/vendor/SQLiteCpp/sqlite3 -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Android -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/BlobStore -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Database -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Indexes -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Query -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/RevTrees -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Storage -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/Support -I/Users/borrrden/Development/couchbase-lite-core/LiteCore/VersionVectors -I/Users/borrrden/Development/couchbase-lite-core/C/include -I/Users/borrrden/Development/couchbase-lite-core/C -isystem /Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/include -isystem /Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/android/support/include -isystem /Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++abi/include -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fno-integrated-as -mthumb -Wa,--noexecstack -Wformat -Werror=format-security -fno-exceptions -fno-rtti -std=c++11 -frtti -fexceptions -Os -DNDEBUG -fPIC -o CMakeFiles/LiteCoreStatic.dir/LiteCore/BlobStore/BlobStore.cc.o -c /Users/borrrden/Development/couchbase-lite-core/LiteCore/BlobStore/BlobStore.cc

libLiteCoreStatic.a linking

/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-ar qc libLiteCoreStatic.a CMakeFiles/LiteCoreStatic.dir/LiteCore/BlobStore/BlobStore.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/BlobStore/Stream.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Database/Database.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Database/SequenceTracker.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Database/TreeDocument.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Database/VectorDocument.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Query/Query.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Query/QueryParser.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Query/SQLiteFleeceEach.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Query/SQLiteFleeceFunctions.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Query/SQLiteFTSRankFunction.cpp.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Query/SQLiteQuery.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/RevTrees/RawRevTree.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/RevTrees/RevID.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/RevTrees/RevTree.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/RevTrees/VersionedDocument.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Storage/DataFile.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Storage/DocumentMeta.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Storage/KeyStore.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Storage/Record.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Storage/RecordEnumerator.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Storage/SQLiteDataFile.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Storage/SQLiteEnumerator.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Storage/SQLiteKeyStore.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Support/EncryptedStream.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Support/Error.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Support/Error_android.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Support/FilePath.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Support/Logging.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Support/PlatformIO.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Support/RefCounted.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Support/SecureRandomize.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Support/StringUtil.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/VersionVectors/CASRevisionStore.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/VersionVectors/Revision.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/VersionVectors/RevisionStore.cc.o CMakeFiles/LiteCoreStatic.dir/LiteCore/VersionVectors/VersionVector.cc.o CMakeFiles/LiteCoreStatic.dir/C/c4.c.o CMakeFiles/LiteCoreStatic.dir/C/c4Base.cc.o CMakeFiles/LiteCoreStatic.dir/C/c4BlobStore.cc.o CMakeFiles/LiteCoreStatic.dir/C/c4Database.cc.o CMakeFiles/LiteCoreStatic.dir/C/c4DocEnumerator.cc.o CMakeFiles/LiteCoreStatic.dir/C/c4DocExpiration.cc.o CMakeFiles/LiteCoreStatic.dir/C/c4Document.cc.o CMakeFiles/LiteCoreStatic.dir/C/c4Observer.cc.o CMakeFiles/LiteCoreStatic.dir/C/c4Query.cc.o CMakeFiles/LiteCoreStatic.dir/vendor/SQLiteCpp/src/Backup.cpp.o CMakeFiles/LiteCoreStatic.dir/vendor/SQLiteCpp/src/Column.cpp.o CMakeFiles/LiteCoreStatic.dir/vendor/SQLiteCpp/src/Database.cpp.o CMakeFiles/LiteCoreStatic.dir/vendor/SQLiteCpp/src/Exception.cpp.o CMakeFiles/LiteCoreStatic.dir/vendor/SQLiteCpp/src/Statement.cpp.o CMakeFiles/LiteCoreStatic.dir/vendor/SQLiteCpp/src/Transaction.cpp.o CMakeFiles/LiteCoreStatic.dir/LiteCore/Android/strlcat.c.o
/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-ranlib libLiteCoreStatic.a

libLiteCore.so linking

/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 --sysroot=/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/platforms/android-16/arch-arm -fPIC -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fno-integrated-as -mthumb -Wa,--noexecstack -Wformat -Werror=format-security -fno-exceptions -fno-rtti -std=c++11 -frtti -fexceptions -Os -DNDEBUG -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,--fix-cortex-a8 -Wl,--exclude-libs,libunwind.a -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -shared -Wl,-soname,libLiteCore.so -o libLiteCore.so CMakeFiles/LiteCore.dir/empty.cpp.o -Wl,--whole-archive libLiteCoreStatic.a vendor/fleece/libFleeceStatic.a vendor/sqlite3-unicodesn/libSQLite3_UnicodeSN.a -Wl,--no-whole-archive libsqlite3.so ../../../../vendor/openssl/libs/android/clang/armeabi-v7a//libcrypto.a -latomic -llog -lm "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a" "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++abi.a" "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libunwind.a" "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libandroid_support.a"

C4Tests linking

/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++ -target armv7-none-linux-androideabi -gcc-toolchain /Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 --sysroot=/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/platforms/android-16/arch-arm -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fno-integrated-as -mthumb -Wa,--noexecstack -Wformat -Werror=format-security -fno-exceptions -fno-rtti -std=c++11 -frtti -fexceptions -Os -DNDEBUG -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,--fix-cortex-a8 -Wl,--exclude-libs,libunwind.a -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -Wl,--gc-sections -Wl,-z,nocopyreloc -pie -fPIE CMakeFiles/C4Tests.dir/c4AllDocsPerformanceTest.cc.o CMakeFiles/C4Tests.dir/c4BlobStoreTest.cc.o CMakeFiles/C4Tests.dir/c4DatabaseTest.cc.o CMakeFiles/C4Tests.dir/c4DocumentTest.cc.o CMakeFiles/C4Tests.dir/c4ObserverTest.cc.o CMakeFiles/C4Tests.dir/c4PerfTest.cc.o CMakeFiles/C4Tests.dir/c4QueryTest.cc.o CMakeFiles/C4Tests.dir/c4Test.cc.o CMakeFiles/C4Tests.dir/c4ThreadingTest.cc.o CMakeFiles/C4Tests.dir///LiteCore/tests/main.cpp.o CMakeFiles/C4Tests.dir///LiteCore/Support/Logging.cc.o CMakeFiles/C4Tests.dir///LiteCore/Support/StringUtil.cc.o -o C4Tests ../../libLiteCore.so -llog ../../libsqlite3.so -lm "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a" "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++abi.a" "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libunwind.a" "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libandroid_support.a"

@DanAlbert
Copy link
Member

Thanks, that actually helps a lot!

CMakeFiles/LiteCore.dir/empty.cpp.o -Wl,--whole-archive libLiteCoreStatic.a vendor/fleece/libFleeceStatic.a vendor/sqlite3-unicodesn/libSQLite3_UnicodeSN.a -Wl,--no-whole-archive libsqlite3.so ../../../../vendor/openssl/libs/android/clang/armeabi-v7a//libcrypto.a -latomic -llog -lm "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a" "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++abi.a" "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libunwind.a" "/Users/borrrden/Library/Developer/Xamarin/android-ndk/android-ndk-r13b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libandroid_support.a"

Tough to say if this is the problem, but there's definitely a problem here.

Right in the middle: you have libsqlite3.so being linked before some static libraries. Linking needs to happen in the following order or things go wrong:

  1. crtbegin
  2. Object files and static libraries
  3. Shared libraries
  4. crtend

2 includes the STL and libgcc/libatomic/etc, assuming those are static (as is the case here). As you can see the STL static libraries are actually being linked at the end (can't say where libgcc is being linked since it's not clang++ -v output, but probably at the end).

The problem that happens is that sqlite3.so will have libgcc linked into it. When the objects ahead of them are linked, they'll use the re-exported libgcc symbols from libsqlite rather than getting their own copy. Afterwards you get the libc++ stuff linked, which is using a different unwinder. When things are built correctly each linked binary has a full (and hidden!) copy of the unwinder it was built against, so any unwind beginning in that code will use exactly the unwinder it was built against.

The next question is whether this incorrect behavior stems from cmake itself or your build files. I don't see anything suspicious in your build files so I'm tempted to say the former, but it will take more experimenting to be sure.

One thing you might try: add -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libunwind.a to your ldflags. That will ensure that both unwinders end up hidden in anything they get included in so you don't have the problem above for at least those libraries. Note: the -Wl,--exclude-libs,libunwind.a is actually only needed on arm32.

@borrrden
Copy link
Author

Hey, what do you know...it stopped crashing when I added those, and also the undefined unwind stuff is down to just two things:

2428: 00000000 0 FUNC GLOBAL DEFAULT UND __gnu_Unwind_Find_exidx
28781: 00000000 0 FUNC GLOBAL DEFAULT UND __gnu_Unwind_Find_exidx

@borrrden
Copy link
Author

Oh, and the exclude libunwind.a is added by default by the cmake toolchain (but not libgcc.a)

@swex
Copy link

swex commented Jun 6, 2018

I'm facing above problem with current(v.17) NDK.
Have c++ project with two shared libs one depends on another.
Here is my example, somebody please explain what is wrong with it
PS: yes I know gnustl and gcc deprecated, but for now I have to use them

@DanAlbert
Copy link
Member

Solid bug report 👍

$ readelf -sW android-ndk-r17/sources/cxx-stl/gnu-libstdc++/4.9/libs/armeabi-v7a/libgnustl_shared.so | grep _Unw | head
   179: 0009bffc    42 FUNC    GLOBAL DEFAULT   12 _Unwind_Resume
   197: 0009b8d5     2 FUNC    GLOBAL DEFAULT   12 _Unwind_Complete
   198: 0009b8d7    16 FUNC    GLOBAL DEFAULT   12 _Unwind_DeleteException
   223: 0009c3c1     6 FUNC    GLOBAL DEFAULT   12 _Unwind_GetDataRelBase
   224: 0009c3c7     6 FUNC    GLOBAL DEFAULT   12 _Unwind_GetTextRelBase
   225: 0009c3a5    10 FUNC    GLOBAL DEFAULT   12 _Unwind_GetRegionStart
   226: 0009b931    52 FUNC    GLOBAL DEFAULT   12 _Unwind_VRS_Set
   227: 0009c3af    18 FUNC    GLOBAL DEFAULT   12 _Unwind_GetLanguageSpecificData
   228: 0009b8e7    52 FUNC    GLOBAL DEFAULT   12 _Unwind_VRS_Get
   255: 0009bfd0    42 FUNC    GLOBAL DEFAULT   12 _Unwind_RaiseException

So gnustl exposes its unwinder. It's a little odd that that would be causing a problem though unless one of your libraries is also using libc++. The reason the unwind symbols need to be hidden is so that they behave correctly in the event that there is more than one unwinder being used in the application. This is the common case with libc++ on Android ARM because we use LLVM's unwinder for libc++ on ARM, but still use libgcc to provide compiler runtime support. Unfortunately libgcc also includes an unwinder, and the two clash.

This shouldn't be an issue so long as gnustl is the only STL in your application, since it only uses libgcc. If your app is using both gnustl and libc++, that's your problem. That's more or less always expected to have bugs.

There's one other case that could explain this, especially if you're seeing this on r17 but not r16: if you're using RTTI/exceptions and also using the system STL (i.e. bionic's libstdc++, -DANDROID_STL=system or APP_STL := system). In that case you again get LLVM's libunwind for exception unwinding and it will clash with gnustl.

@swex
Copy link

swex commented Jun 6, 2018

@DanAlbert sorry, I still don't get it. Both prebuilt libraries are mine. And they both compiled with gnustl_shared STL. Then I put them in plain java project(no other libs involved). And still getting segfault on std::string destruction if destroyed on unwinding...
Everything works perfectly if I link lib1 to lib2 statically.

@swex
Copy link

swex commented Jun 7, 2018

please look at my readelf output

    32: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_unwind_cpp_pr0
    47: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_unwind_cpp_pr1
    65: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Resume
    83: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Complete
    84: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_DeleteException
   115: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetDataRelBase
   116: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetTextRelBase
   117: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetRegionStart
   118: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_VRS_Set
   119: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetLanguageSpecificData
   120: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_VRS_Get
   121: 00000000     0 FUNC    GLOBAL DEFAULT  UND __gnu_unwind_frame
   128: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_RaiseException
   129: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Resume_or_Rethrow
   570: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_unwind_cpp_pr0
   585: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_unwind_cpp_pr1
   603: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Resume
   621: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Complete
   622: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_DeleteException
   653: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetDataRelBase
   654: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetTextRelBase
   655: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetRegionStart
   656: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_VRS_Set
   657: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_GetLanguageSpecificData
   658: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_VRS_Get
   659: 00000000     0 FUNC    GLOBAL DEFAULT  UND __gnu_unwind_frame
   666: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_RaiseException
   667: 00000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Resume_or_Rethrow

So real questions are: Why Unwind* functions records doubled? How to make them hidden and copied in so?

@DanAlbert
Copy link
Member

It shouldn't matter for gnustl, but you can use -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libgnustl_shared.so in your ldflags to prevent re-exporting things from those libs (or better yet, use version scripts to control your libraries' ABI surfaces explicitly).

@cristiterpea
Copy link

I believe I have the same problem, only in my case I'm linking with c++_shared instead of static
Is there a way to solve this?
readelf -sW mylib.so | grep Unwind_Resume
3: 00000000 0 FUNC GLOBAL DEFAULT UND _Unwind_Resume
609: 00000000 0 FUNC GLOBAL DEFAULT UND _Unwind_Resume

The symbols might be coming from libQt5Core.so, which I'm linking against:
readelf -sW libQt5Core.so | grep Unwind_Resume
29: 00234ed0 36 FUNC GLOBAL DEFAULT 10 _Unwind_Resume
5899: 002343c0 116 FUNC GLOBAL DEFAULT 10 __gnu_Unwind_Resume
5900: 00234434 32 FUNC GLOBAL DEFAULT 10 __gnu_Unwind_Resume_or_Rethrow
5917: 00234ed0 36 FUNC GLOBAL DEFAULT 10 ___Unwind_Resume
5918: 00234ef4 36 FUNC GLOBAL DEFAULT 10 ___Unwind_Resume_or_Rethrow
5919: 00234ef4 36 FUNC GLOBAL DEFAULT 10 _Unwind_Resume_or_Rethrow
20383: 00234ed0 36 FUNC GLOBAL DEFAULT 10 _Unwind_Resume
26253: 002343c0 116 FUNC GLOBAL DEFAULT 10 __gnu_Unwind_Resume
26254: 00234434 32 FUNC GLOBAL DEFAULT 10 __gnu_Unwind_Resume_or_Rethrow
26271: 00234ed0 36 FUNC GLOBAL DEFAULT 10 ___Unwind_Resume
26272: 00234ef4 36 FUNC GLOBAL DEFAULT 10 ___Unwind_Resume_or_Rethrow
26273: 00234ef4 36 FUNC GLOBAL DEFAULT 10 _Unwind_Resume_or_Rethrow

I tried linking with -Wl,--exclude-libs,libQt5Core.so but no luck.

@DanAlbert
Copy link
Member

--exclude-libs doesn't do anything for a shared library. It forces symbols added from a static library to have hidden visibility. It doesn't prevent the symbols from a shared library from being used. libQt5Core.so needs to be built such that these symbols are hidden (-Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libunwind.a).

@cybertk
Copy link

cybertk commented Aug 7, 2018

@cristiterpea @DanAlbert I found some version of NDK will not append --exclude-libs libgcc.a, you can consider using these versions of NDK as shown below

  • r16b
  • r12b
  • r11c

@DanAlbert
Copy link
Member

I strongly recommend against r11 and r12. r17 is what you should be using, r16 if r17 has a bug you can't work around.

@sztomi
Copy link

sztomi commented May 6, 2019

the -Wl,--exclude-libs,libunwind.a is actually only needed on arm32

Why is it only needed on that arch? We build for arm32 and and aarch64 and indeed, it doesn't happen on aarch64. But why?

@kishan2412
Copy link

I am using ndk-r10e and building the native code. The code is running fine in arm-v7 but it is crashing in arm64 when native tries to throw the exception.
I have applied the exception flag in application.mk file. The issue is-
A/libc: Fatal signal 6 ( SIGABRT), code -6(SI_ TKILL).
someone please help me, it is very urgent to fix it.

@enh-google
Copy link
Collaborator

I am using ndk-r10e and building the native code.

please try again with r21. we can't fix the past.

@kishan2412
Copy link

kishan2412 commented Sep 21, 2020

Hello, I have updated the NDK version to r21 but still facing the same issue (as the app is crashing when the .so native library throwing an exception). To simulate the issue I have created a sample app which I am attaching with this comment and below are the steps mentioned to reproduce the issue-
Steps to build -

  1. Set "ANDROID_NDK" as NDK path in environment variable.
  2. Extract the attached file.
  3. Run make_jni, It will create jar and related .so files and copy into MyApp sample.
  4. From Android studio build the sample
  5. Run sample. Click on Button in android device
  6. The sample get crashed in x64 bit device otherwise it will shows an exception

SwigEx_wrap_crash.zip

Someone please help to get the issue resolved.
Thanks

@DanAlbert
Copy link
Member

Your repro case isn't using a valid STL. After fixing the repro script and converting it to bash:

./make_jni.sh
+ ANDROID_HOME=/usr/local/google/home/danalbert/Android/Sdk
+ ANDROID_NDK=/work/src/android-ndk-r21d
+ cd swig
+ swig -c++ -java -package com.test -outdir ./ -I../include SampleInterface_Classes.i
+ cd ..
+ javac -g -d ./class ./swig/Coder.java ./swig/test.java ./swig/testJNI.java -classpath /usr/local/google/home/danalbert/Android/Sdk/platforms/android-19/android.jar
+ jar -cf ./lib/test.jar -C ./class com/test
+ /work/src/android-ndk-r21d/ndk-build
/work/src/android-ndk-r21d/build/core/add-application.mk:178: *** Android NDK: APP_STL gnustl_static is no longer supported. Please switch to either c++_static or c++_shared. See https://developer.android.com/ndk/guides/cpp-support.html for more information.    .  Stop.

If I fix that, Studio gives:

/work/src/SwigEx_wrap_crash/MyApp/app/src/main/java/com/example/myapplication/MainActivity.java:14: error: cannot access Coder
import com.test.Coder;
               ^
  bad class file: /usr/local/google/home/danalbert/.gradle/caches/transforms-2/files-2.1/f918ea8ed6187165dd3bcd6ccb1f8249/jetified-test.jar(com/test/Coder.class)
    class file has wrong version 55.0, should be 52.0
    Please remove or make sure it appears in the correct subdirectory of the classpath.

If you can provide a working sample I'll look more. I strongly recommend migrating your build to https://developer.android.com/studio/projects/gradle-external-native-builds. Using that will cut down on a lot of the "works on my machine" issues.

@kishan2412
Copy link

Hello DanAlbert,
Neha from my team is working on this issue. So she will further carry forward this issue with your help.
Thanks

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

No branches or pull requests

9 participants