Skip to content

Commit 807f0dc

Browse files
committed
Use libunwind instead of libbacktrace to build stack traces. This fixes a segfault issue with platforms using libexecinfo and is generally more portable.
1 parent 96ae461 commit 807f0dc

11 files changed

Lines changed: 252 additions & 60 deletions

File tree

.circleci/config.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,50 @@ references:
8686
cmake --version
8787
/usr/local/bin/$CC --version
8888
/usr/local/bin/$CXX --version
89+
upgrade_libunwind_pre: &upgrade_libunwind_pre
90+
restore_cache:
91+
keys:
92+
# Find the most recent cache from any branch
93+
- v1_upgrade_libunwind_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}
94+
upgrade_libunwind_post: &upgrade_libunwind_post
95+
save_cache:
96+
key: v1_upgrade_libunwind_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}
97+
paths:
98+
- /tmp/libunwind-1.2.1
99+
upgrade_libunwind: &upgrade_libunwind
100+
run:
101+
name: Upgrade Libunwind
102+
command: |
103+
# We need to install libunwind manually because Circle CI uses Ubuntu 14.04 and the default libunwind version
104+
# on that Ubuntu suffers from http://savannah.nongnu.org/bugs/?43752.
105+
# This isn't an issue for any later version of Ubuntu.
106+
107+
# Detect number of CPU cores
108+
export NUMCORES=`nproc`
109+
echo Using $NUMCORES cores
110+
# Download and prepare libunwind (only if not already present from cache)
111+
if [ ! -d "/tmp/libunwind-1.2.1" ]; then
112+
echo "Didn't find libunwind in cache. Downloading and building."
113+
wget -O /tmp/libunwind-1.2.1.tar.gz http://download.savannah.nongnu.org/releases/libunwind/libunwind-1.2.1.tar.gz
114+
if [ $(sha512sum /tmp/libunwind-1.2.1.tar.gz | awk '{print $1;}') == "af7c280d2a963779a4a2711887618bc96383011e4e5d52e4085aa7fb351e55e357468f6ff85e66a216f1c6826538f498335a917a5970575c93be74c96316319b" ]; then
115+
echo Correct sha512sum
116+
else
117+
echo Wrong sha512sum
118+
sha512sum /tmp/libunwind-1.2.1.tar.gz
119+
exit 1
120+
fi
121+
echo Extracting...
122+
tar -xf /tmp/libunwind-1.2.1.tar.gz -C /tmp
123+
rm -rf /tmp/libunwind-1.2.1.tar.gz
124+
cd /tmp/libunwind-1.2.1
125+
./configure
126+
make -j${NUMCORES}
127+
else
128+
echo Found libunwind in cache. Use cache and build.
129+
fi
130+
# Compile and install libunwind (if cached, this should be fast)
131+
cd /tmp/libunwind-1.2.1
132+
sudo make -j${NUMCORES} install
89133
upgrade_boost_pre: &upgrade_boost_pre
90134
restore_cache:
91135
keys:
@@ -189,6 +233,9 @@ references:
189233
- <<: *container_setup_pre
190234
- <<: *container_setup
191235
- <<: *container_setup_post
236+
- <<: *upgrade_libunwind_pre
237+
- <<: *upgrade_libunwind
238+
- <<: *upgrade_libunwind_post
192239
- <<: *upgrade_boost_pre
193240
- <<: *upgrade_boost
194241
- <<: *upgrade_boost_post
@@ -489,6 +536,9 @@ jobs:
489536
- <<: *container_setup_pre
490537
- <<: *container_setup
491538
- <<: *container_setup_post
539+
- <<: *upgrade_libunwind_pre
540+
- <<: *upgrade_libunwind
541+
- <<: *upgrade_libunwind_post
492542
- <<: *upgrade_boost_pre
493543
- <<: *upgrade_boost
494544
- <<: *upgrade_boost_post

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ cmake_policy(SET CMP0054 NEW)
77

88
project(cryfs)
99

10-
include(cmake-utils/utils.cmake)
10+
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake-utils)
11+
include(utils)
1112

1213
require_gcc_version(5.0)
1314
require_clang_version(4.0)

ChangeLog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Fixed bugs:
77

88
Compatibility:
99
* Fixed some incompatibilities with systems using the musl libc
10+
* Use libunwind instead of libbacktrace to build stack traces. This fixes a segfault issue with platforms using libexecinfo and is generally more portable.
1011

1112
Other:
1213
* Updated to crypto++ 8.1

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,15 @@ Requirements
5656
- libFUSE version >= 2.8.6 (including development headers), on Mac OS X instead install osxfuse from https://osxfuse.github.io/
5757
- Python >= 2.7
5858
- OpenMP
59+
- Libunwind
5960

6061
You can use the following commands to install these requirements
6162

6263
# Ubuntu
63-
$ sudo apt-get install git g++ cmake make libcurl4-openssl-dev libboost-filesystem-dev libboost-system-dev libboost-chrono-dev libboost-program-options-dev libboost-thread-dev libssl-dev libfuse-dev python
64+
$ sudo apt install git g++ cmake make libcurl4-openssl-dev libboost-filesystem-dev libboost-system-dev libboost-chrono-dev libboost-program-options-dev libboost-thread-dev libssl-dev libfuse-dev python libunwind-dev
6465

6566
# Fedora
66-
sudo dnf install git gcc-c++ cmake make libcurl-devel boost-devel boost-static openssl-devel fuse-devel python
67+
sudo dnf install git gcc-c++ cmake make libcurl-devel boost-devel boost-static openssl-devel fuse-devel python libunwind-devel
6768

6869
# Macintosh
6970
brew install cmake boost openssl libomp

cmake-utils/FindLibunwind.cmake

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Taken from https://github.com/monero-project/monero/blob/31bdf7bd113c2576fe579ef3a25a2d8fef419ffc/cmake/FindLibunwind.cmake
2+
# modifications:
3+
# - remove linkage against gcc_eh because it was causing segfaults in various of our unit tests
4+
5+
# - Try to find libunwind
6+
# Once done this will define
7+
#
8+
# LIBUNWIND_FOUND - system has libunwind
9+
# LIBUNWIND_INCLUDE_DIR - the libunwind include directory
10+
# LIBUNWIND_LIBRARIES - Link these to use libunwind
11+
# LIBUNWIND_DEFINITIONS - Compiler switches required for using libunwind
12+
13+
# Copyright (c) 2006, Alexander Dymo, <adymo@kdevelop.org>
14+
#
15+
# Redistribution and use is allowed according to the terms of the BSD license.
16+
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
17+
18+
find_path(LIBUNWIND_INCLUDE_DIR libunwind.h
19+
/usr/include
20+
/usr/local/include
21+
)
22+
23+
find_library(LIBUNWIND_LIBRARIES NAMES unwind )
24+
if(NOT LIBUNWIND_LIBRARIES STREQUAL "LIBUNWIND_LIBRARIES-NOTFOUND")
25+
if (CMAKE_COMPILER_IS_GNUCC)
26+
set(LIBUNWIND_LIBRARIES "${LIBUNWIND_LIBRARIES}")
27+
endif()
28+
endif()
29+
30+
# some versions of libunwind need liblzma, and we don't use pkg-config
31+
# so we just look whether liblzma is installed, and add it if it is.
32+
# It might not be actually needed, but doesn't hurt if it is not.
33+
# We don't need any headers, just the lib, as it's privately needed.
34+
message(STATUS "looking for liblzma")
35+
find_library(LIBLZMA_LIBRARIES lzma )
36+
if(NOT LIBLZMA_LIBRARIES STREQUAL "LIBLZMA_LIBRARIES-NOTFOUND")
37+
message(STATUS "liblzma found")
38+
set(LIBUNWIND_LIBRARIES "${LIBUNWIND_LIBRARIES};${LIBLZMA_LIBRARIES}")
39+
endif()
40+
41+
include(FindPackageHandleStandardArgs)
42+
find_package_handle_standard_args(Libunwind "Could not find libunwind" LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARIES)
43+
# show the LIBUNWIND_INCLUDE_DIR and LIBUNWIND_LIBRARIES variables only in the advanced view
44+
mark_as_advanced(LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARIES )

src/cpp-utils/CMakeLists.txt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,14 @@ set(SOURCES
6161

6262
add_library(${PROJECT_NAME} STATIC ${SOURCES})
6363

64-
65-
if(NOT MSVC)
66-
find_package(Backtrace REQUIRED)
67-
target_include_directories(${PROJECT_NAME} PUBLIC ${Backtrace_INCLUDE_DIRS})
68-
target_link_libraries(${PROJECT_NAME} PUBLIC ${Backtrace_LIBRARIES})
69-
else()
70-
target_link_libraries(${PROJECT_NAME} PUBLIC DbgHelp)
64+
if(MSVC)
65+
target_link_libraries(${PROJECT_NAME} PUBLIC DbgHelp)
66+
elseif(NOT APPLE)
67+
# note: We use the libunwind code on Apple, but we don't seem to need these lines to link against it.
68+
find_package(Libunwind REQUIRED)
69+
target_include_directories(${PROJECT_NAME} PRIVATE ${LIBUNWIND_INCLUDE_DIR})
70+
target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBUNWIND_LIBRARIES})
71+
target_compile_definitions(${PROJECT_NAME} PRIVATE ${LIBUNWIND_DEFINITIONS})
7172
endif()
7273

7374
if (NOT MSVC)

src/cpp-utils/assert/backtrace_nonwindows.cpp

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
#if !defined(_MSC_VER)
22

3-
#include "backtrace.h"
4-
#include <execinfo.h>
53
#include <csignal>
6-
#include <iostream>
7-
#include <unistd.h>
84
#include <cxxabi.h>
9-
#include <string>
105
#include <sstream>
11-
#include <string>
12-
#include <dlfcn.h>
6+
137
#include "../logging/logging.h"
148
#include <cpp-utils/process/SignalHandler.h>
159

10+
#define UNW_LOCAL_ONLY
11+
#include <libunwind.h>
12+
1613
// TODO Add file and line number on non-windows
1714

1815
using std::string;
@@ -30,7 +27,12 @@ namespace {
3027
demangledName = abi::__cxa_demangle(mangledName.c_str(), NULL, NULL, &status);
3128
if (status == 0) {
3229
result = demangledName;
30+
} else if (status == -2) {
31+
// mangledName was not a c++ mangled name, probably because it's a C name like for static
32+
// initialization or stuff. Let's just return the name instead.
33+
result = mangledName;
3334
} else {
35+
// other error
3436
result = "[demangling error " + std::to_string(status) + "]" + mangledName;
3537
}
3638
free(demangledName);
@@ -41,46 +43,55 @@ namespace {
4143
}
4244
}
4345

44-
void pretty_print(std::ostream& str, const void *addr) {
45-
Dl_info info;
46-
if (0 == dladdr(addr, &info)) {
47-
str << "[failed parsing line]";
46+
void pretty_print(std::ostringstream& str, unw_cursor_t* cursor) {
47+
constexpr unsigned int MAXNAMELEN=256;
48+
char name[MAXNAMELEN];
49+
unw_word_t offp = 0, ip = 0;
50+
51+
int status = unw_get_reg(cursor, UNW_REG_IP, &ip);
52+
if (0 != status) {
53+
str << "[unw_get_reg error: " << status << "]: ";
4854
} else {
49-
if (nullptr == info.dli_fname) {
50-
str << "[no dli_fname]";
51-
} else {
52-
str << info.dli_fname;
53-
}
54-
str << ":" << std::hex << info.dli_fbase << " ";
55-
if (nullptr == info.dli_sname) {
56-
str << "[no symbol name]";
57-
} else if (info.dli_sname[0] == '_') {
58-
// is a mangled name
59-
str << demangle(info.dli_sname);
60-
} else {
61-
// is not a mangled name
62-
str << info.dli_sname;
63-
}
64-
str << " : " << std::hex << info.dli_saddr;
55+
str << "0x" << std::hex << ip << ": ";
6556
}
66-
}
6757

68-
string backtrace_to_string(void *array[], size_t size) {
69-
ostringstream result;
70-
for (size_t i = 0; i < size; ++i) {
71-
result << "#" << std::dec << i << " ";
72-
pretty_print(result, array[i]);
73-
result << "\n";
58+
status = unw_get_proc_name(cursor, name, MAXNAMELEN, &offp);
59+
if (0 != status) {
60+
str << "[unw_get_proc_name error: " << status << "]";
61+
} else {
62+
str << demangle(name);
7463
}
75-
return result.str();
64+
str << " +0x" << std::hex << offp;
7665
}
7766
}
7867

7968
string backtrace() {
80-
constexpr unsigned int MAX_SIZE = 100;
81-
void *array[MAX_SIZE];
82-
size_t size = ::backtrace(array, MAX_SIZE);
83-
return backtrace_to_string(array, size);
69+
std::ostringstream result;
70+
71+
unw_context_t uc;
72+
int status = unw_getcontext(&uc);
73+
if (0 != status) {
74+
return "[unw_getcontext error: " + std::to_string(status) + "]";
75+
}
76+
77+
unw_cursor_t cursor;
78+
status = unw_init_local(&cursor, &uc);
79+
if (0 != status) {
80+
return "[unw_init_local error: " + std::to_string(status) + "]";
81+
}
82+
83+
84+
size_t line = 0;
85+
while ((status = unw_step(&cursor)) > 0) {
86+
result << "#" << std::dec << (line++) << " ";
87+
pretty_print(result, &cursor);
88+
result << "\n";
89+
}
90+
if (status != 0) {
91+
result << "[unw_step error :" << status << "]";
92+
}
93+
94+
return result.str();
8495
}
8596

8697
namespace {

src/cpp-utils/process/SignalCatcher.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ class SignalCatcherImpl final {
8888
, _registerer(signal, this)
8989
, _handler(signal) {
9090
// note: the order of the members ensures that:
91-
// - when registering the signal handler fails, the registerer will be destroyed, unregistering the signal_occurred_flag,
91+
// - when registering the signal handler, the SignalCatcher impl already has a valid _signal_occurred_flag set.
92+
// - when registering the signal handler fails, the _registerer will be destroyed again, unregistering this SignalCatcherImpl,
9293
// i.e. there is no leak.
9394

9495
// Allow only the set of signals that is supported on all platforms, see for Windows: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal?view=vs-2017
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,66 @@
11
#include "SignalHandler.h"
2+
3+
#if !defined(_MSC_VER)
4+
5+
namespace cpputils {
6+
namespace detail {
7+
namespace {
8+
9+
std::atomic<bool> already_checked_for_libunwind_bug(false);
10+
11+
}
12+
13+
sigset_t _sigemptyset() {
14+
sigset_t result;
15+
int error = sigemptyset(&result);
16+
if (0 != error) {
17+
throw std::runtime_error("Error calling sigemptyset. Errno: " + std::to_string(errno));
18+
}
19+
return result;
20+
}
21+
22+
void _sigmask(sigset_t* new_value, sigset_t* old_value) {
23+
int error = pthread_sigmask(SIG_SETMASK, new_value, old_value);
24+
if (0 != error) {
25+
throw std::runtime_error("Error calling pthread_sigmask. Errno: " + std::to_string(errno));
26+
}
27+
}
28+
29+
// Check that we're not running into http://savannah.nongnu.org/bugs/?43752
30+
void check_against_libunwind_bug() {
31+
if (already_checked_for_libunwind_bug.exchange(true)) {
32+
return;
33+
}
34+
35+
// set new signal handler
36+
sigset_t old_value = _sigemptyset();
37+
sigset_t new_value = _sigemptyset();
38+
39+
_sigmask(&new_value, &old_value);
40+
41+
sigset_t before_exception = _sigemptyset();
42+
_sigmask(nullptr, &before_exception);
43+
44+
// throw an exception
45+
try {
46+
throw std::runtime_error("Some exception");
47+
} catch (const std::exception &e) {}
48+
49+
sigset_t after_exception = _sigemptyset();
50+
_sigmask(nullptr, &after_exception);
51+
52+
// reset to old signal handler
53+
_sigmask(&old_value, nullptr);
54+
55+
// check that the exception didn't screw up the signal mask
56+
if (0 != std::memcmp(&before_exception, &after_exception, sizeof(sigset_t))) { // NOLINT(cppcoreguidelines-pro-type-union-access)
57+
ASSERT(false,
58+
"Throwing an exception screwed up the signal mask. You likely ran into this bug: http://savannah.nongnu.org/bugs/?43752 . Please build CryFS against a newer version of libunwind or build libunwind with --disable-cxx-exceptions.");
59+
}
60+
}
61+
62+
63+
}
64+
}
65+
66+
#endif

src/cpp-utils/process/SignalHandler.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@ using SignalHandlerFunction = void(int);
2020

2121
#if !defined(_MSC_VER)
2222

23+
namespace detail {
24+
void check_against_libunwind_bug();
25+
}
26+
2327
template<SignalHandlerFunction* handler>
2428
class SignalHandlerRAII final {
2529
public:
2630
explicit SignalHandlerRAII(int signal)
2731
: _old_handler(), _signal(signal) {
32+
detail::check_against_libunwind_bug();
33+
2834
struct sigaction new_signal_handler{};
2935
std::memset(&new_signal_handler, 0, sizeof(new_signal_handler));
3036
new_signal_handler.sa_handler = handler; // NOLINT(cppcoreguidelines-pro-type-union-access)

0 commit comments

Comments
 (0)