Get rid of backtrace machinery

It is currently very fragile, due to:

* Differing versions of compilers/DWARF version result in a variety
    of breakages in the our code which analyzes the DWARF info;

* With musl, libunwind seems to be currently unable to traverse
    beyond signal handlers, due to the DWARF information not
    being present in the signal frame.
    See <https://maskray.me/blog/2022-04-10-unwinding-through-signal-handler>.
    Note that I have not verified that the problem in the blog
    post above is indeed what we're hitting, but it seems plausible.
This commit is contained in:
Francesco Mazzoli
2023-01-30 18:17:13 +00:00
parent 4243ff71e2
commit f42ff2b219
12 changed files with 13 additions and 263 deletions

View File

@@ -25,7 +25,7 @@ set(CMAKE_CXX_REQUIRED true)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
option(BUILD_SHARED_LIBS OFF)
set(POSITION_INDEPENDENT_CODE off)
add_compile_options(-fno-pie -fno-stack-protector -fdiagnostics-color=always -fno-stack-protector -g -gdwarf-4 -Wall -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wno-gnu-string-literal-operator-template)
add_compile_options(-fno-pie -fno-stack-protector -fdiagnostics-color=always -fno-stack-protector -g -gdwarf -Wall -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wno-gnu-string-literal-operator-template)
add_link_options(-fuse-ld=lld -pthread)
# valgrind we currently use cannot digest certain instructions

View File

@@ -3,6 +3,5 @@
FROM alpine:3.17
RUN apk add --no-cache \
bash perl coreutils python3 musl gcc g++ clang lld make cmake ninja mandoc \
linux-headers zlib-dev zlib-static elfutils-dev libelf-static libdwarf-dev libdwarf-static \
xz-dev xz-static xxhash-dev libunwind-dev libunwind-static
linux-headers
ENV IN_EGGS_BUILD_CONTAINER Y

View File

@@ -261,7 +261,7 @@ private:
try {
reqHeader.unpack(reqBbuf);
} catch (const BincodeException& err) {
LOG_ERROR(_env, "%s\nstacktrace:\n%s", err.what(), err.getStackTrace());
LOG_ERROR(_env, "could not parse: %s", err.what());
RAISE_ALERT(_env, "could not parse request header from %s, dropping it.", clientAddr);
continue;
}
@@ -277,7 +277,7 @@ private:
_cdcReqContainer.unpack(reqBbuf, reqHeader.kind);
LOG_DEBUG(_env, "parsed request: %s", _cdcReqContainer);
} catch (const BincodeException& exc) {
LOG_ERROR(_env, "%s\nstacktrace:\n%s", exc.what(), exc.getStackTrace());
LOG_ERROR(_env, "could not parse: %s", exc.what());
RAISE_ALERT(_env, "could not parse CDC request of kind %s from %s, will reply with error.", reqHeader.kind, clientAddr);
err = EggsError::MALFORMED_REQUEST;
}
@@ -325,7 +325,7 @@ private:
try {
respHeader.unpack(reqBbuf);
} catch (BincodeException err) {
LOG_ERROR(_env, "%s\nstacktrace:\n%s", err.what(), err.getStackTrace());
LOG_ERROR(_env, "could not parse: %s", err.what());
RAISE_ALERT(_env, "could not parse response header, dropping response");
continue;
}

View File

@@ -1,30 +0,0 @@
#pragma once
#include <memory>
#include "SBRMUnix.hpp"
#include "Common.hpp"
struct ProgramInfo {
ProgramInfo() = default;
ProgramInfo(ProgramInfo&&) = default;
~ProgramInfo() = default;
ProgramInfo(const ProgramInfo &) = delete;
ProgramInfo& operator=(const ProgramInfo &) = delete;
static std::shared_ptr<ProgramInfo> Self();
static std::shared_ptr<ProgramInfo> ForBinary(const char *exePath, bool required = true);
size_t getBacktraceSegment(void *ip, char* buf, size_t sz, int* count = nullptr);
// private
DynamicMMapHolder<char*> _data;
};
size_t getBacktraceInstructionPointers(void** ips, size_t maxIPs) __attribute__((noinline));
size_t generateBacktrace(char * buf, size_t sz);
void loadAndSendEmptyProgramInfo(int out_fd);
void loadAndSendProgramInfo(const char* exePath, int out_fd);

View File

@@ -1,126 +0,0 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <array>
#include "Common.hpp"
#include "SBRMUnix.hpp"
// Fuctions in this file can be called when current process is in SIGSEGV handler,
// so they must not call and non async-signal-safe functions, or throw exceptions, or allocate memory (malloc can be corrupted).
// Only simple syscall wrapper functions and functions in http://man7.org/linux/man-pages/man7/signal-safety.7.html are used below.
#define throw EXCEPTIONS_NOT_ALLOWED_HERE
static void read_all(int fd, void *buf, size_t cnt) {
while (cnt != 0) {
ssize_t s = read(fd, buf, cnt);
if (s == -1) {
if (errno == EINTR) continue;
dieWithError("read");
} else if (s == 0) {
dieWithError("read EOF");
}
buf = reinterpret_cast<void*>(reinterpret_cast<char*>(buf) + s);
cnt -= s;
}
}
static char* WriteEnv(char *out, const char *key, const char *val) {
char *res = out;
out = stpcpy(out, key);
out = stpcpy(out, "=");
out = stpcpy(out, val);
return res;
}
static char* WriteEnv(char *out, const char *key, unsigned int val) {
std::array<char, 30> val_buf;
char *it = val_buf.end() - 1;
*it = '\0';
if (val == 0) {
--it;
*it = '0';
} else {
while (val) {
--it;
*it = '0' + (val % 10);
val /= 10;
}
}
return WriteEnv(out, key, it);
}
static
int non_glibc_memfd_create(const char *name, unsigned int flags) {
return syscall(319, name, flags);
}
// Launches a child process and waits for it to collect ProgramInfo, and mmap's the result.
DynamicMMapHolder<char*> MMapProgramInfo(const char *exePath, bool required) noexcept {
if (getenv("EGGS_GET_PROGRAM_INFO")) dieWithError("fork bomb avoided");
FDHolder fd = non_glibc_memfd_create("debug_data", 0);
if (*fd == -1) dieWithError("memfd_create");
{
char e0[300];
char e1[100];
char *envp[] = {
WriteEnv(e0, "EGGS_GET_PROGRAM_INFO", exePath),
WriteEnv(e1, "EGGS_GET_PROGRAM_INFO_OUT_MEMFD", *fd),
nullptr,
};
// valgrind doesn't like us executing /proc/self/exe
char exe[1024];
{
ssize_t written = readlink("/proc/self/exe", exe, sizeof(exe));
if (written < 0 || written == sizeof(exe)) {
dieWithError("readlink");
}
exe[written] = '\0';
}
const char *argv[] = {exe, nullptr};
pid_t childPid = vfork();
if (childPid == -1) dieWithError("vfork");
if (childPid == 0) {
execve(argv[0], const_cast<char* const*>(argv), envp);
dieWithError("execve");
}
int wstatus;
while (true) {
pid_t res = waitpid(childPid, &wstatus, 0);
if (res == -1 && errno == EINTR) continue;
if (res == childPid) break;
dieWithError("waitpid");
}
if (WEXITSTATUS(wstatus) != 0) {
if (required) {
dieWithError("Unable to map program info");
} else {
return {};
}
}
}
if (-1 == lseek(*fd, 0, SEEK_SET)) dieWithError("lseek");
size_t sz;
read_all(*fd, (void*)&sz, 8);
char *data = (char*) mmap(nullptr, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (data == (char*)MAP_FAILED) dieWithError("mmap");
read_all(*fd, data, sz);
return DynamicMMapHolder<char*>(data, sz);
}

View File

@@ -2,8 +2,8 @@
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Backtrace.hpp"
#include <string.h>
#include <unistd.h>
// Throwing in static initialization is nasty, and there is no useful stacktrace
// Also use direct syscalls to write the error as iostream might not be initialized
@@ -26,28 +26,6 @@ void dieWithError(const char *err) {
_exit(1);
}
Globals::Globals()
{
// Checks whether program info collection is requested on static initialization,
// If so, does it and exits the process.
{
const char *exePath = getenv("EGGS_GET_PROGRAM_INFO");
if (exePath != nullptr) {
_collectingProgramInfo = true;
try {
int out_fd = std::stoi(getenv("EGGS_GET_PROGRAM_INFO_OUT_MEMFD"));
loadAndSendProgramInfo(exePath, out_fd);
exit(0);
} catch (std::exception &e) {
dieWithError(e.what());
} catch (...) {
}
_exit(1);
}
}
}
Globals _globals;
std::ostream& operator<<(std::ostream& out, struct sockaddr_in& addr) {
char buf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr.sin_addr, buf, sizeof(buf));

View File

@@ -9,17 +9,6 @@
// Safer alternative to throwing when in static initialization or signal handler
void dieWithError(const char *err);
struct Globals {
Globals();
bool _collectingProgramInfo = false;
};
extern Globals _globals;
inline bool collectingProgramInfo() {
return _globals._collectingProgramInfo;
}
////////////////////////////////////////////
// Compiler hints
////////////////////////////////////////////

View File

@@ -4,12 +4,9 @@
#include <atomic>
#include <fcntl.h>
#include <cxxabi.h>
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include "Common.hpp"
#include "BinaryFormatter.hpp"
#include "Backtrace.hpp"
#include "Exception.hpp"
const char *translateErrno(int _errno) {
@@ -181,15 +178,6 @@ std::string removeTemplates(const std::string & s) {
}
AbstractException::AbstractException() {
generateBacktrace(_stacktrace, sizeof(_stacktrace));
}
const char * AbstractException::getStackTrace() const {
return _stacktrace;
}
const char *EggsException::what() const throw() {
return _msg.c_str();
}

View File

@@ -17,12 +17,7 @@ const char *translateErrno(int _errno);
class AbstractException : public std::exception {
public:
AbstractException();
const char * getStackTrace() const;
virtual const char *what() const throw() override = 0;
private:
char _stacktrace[4000];
};

View File

@@ -1,45 +0,0 @@
#pragma once
#include "Common.hpp"
#define ON_BLOCK_EXIT_INNER(line) SCOPEGUARD ## line
#define ON_BLOCK_EXIT UNUSED const auto & ON_BLOCK_EXIT_INNER(__LINE__) = makeScopeGuard
template<typename L>
class ScopeGuard {
public:
explicit ScopeGuard(L &&l) :
_committed(false),
_rollback(std::forward<L>(l)) {
}
// This is important, the class can be moved, this means
// we can return by value and even if the copy is not elided
// then we will only execute the commit once.
ScopeGuard(ScopeGuard &&rhs) :
_committed(rhs._committed),
_rollback(std::move(rhs._rollback)) {
rhs._committed = true;
}
~ScopeGuard() {
if (!_committed) {
_rollback();
}
}
void commit() noexcept {
_committed = true;
}
private:
bool _committed;
L _rollback;
};
template<typename L>
ScopeGuard<L> makeScopeGuard(L &&r) {
return ScopeGuard<L>(std::forward<L>(r));
}

View File

@@ -154,7 +154,7 @@ public:
try {
reqHeader.unpack(reqBbuf);
} catch (const BincodeException& err) {
LOG_ERROR(_env, "%s\nstacktrace:\n%s", err.what(), err.getStackTrace());
LOG_ERROR(_env, "Could not parse: %s", err.what());
RAISE_ALERT(_env, "could not parse request header from %s, dropping it.", clientAddr);
continue;
}
@@ -175,7 +175,7 @@ public:
reqContainer->unpack(reqBbuf, reqHeader.kind);
LOG_DEBUG(_env, "parsed request: %s", *reqContainer);
} catch (const BincodeException& exc) {
LOG_ERROR(_env, "%s\nstacktrace:\n%s", exc.what(), exc.getStackTrace());
LOG_ERROR(_env, "Could not parse: %s", exc.what());
RAISE_ALERT(_env, "could not parse request of kind %s from %s, will reply with error.", reqHeader.kind, clientAddr);
err = EggsError::MALFORMED_REQUEST;
}
@@ -276,7 +276,8 @@ public:
for (;;) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (_shared.stop.load()) {
return;
LOG_DEBUG(_env, "got told to stop, stopping");
break;
}
if (eggsNow() - successfulIterationAt < 1_mins) {
continue;

View File

@@ -23,7 +23,8 @@
REGISTER_EXCEPTION_TRANSLATOR(AbstractException& ex) {
std::stringstream ss;
ss << std::endl << ex.what() << std::endl << ex.getStackTrace() << std::endl;
// Before, we had stack traces and this was useful, now a bit less
ss << std::endl << ex.what() << std::endl;
return doctest::String(ss.str().c_str());
}