Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"cwd": "${command:cmake.getLaunchTargetDirectory}",
"env": [
"LD_LIBRARY_PATH=./libs/",
"PATH=${env:PATH}:${command:cmake.getLaunchTargetDirectory}"
"PATH=${env:PATH}:${command:cmake.getLaunchTargetDirectory}",
"LLVM_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-18"
],
"disableASLR": false,
"initCommands": ["settings set target.disable-aslr false"]
Expand Down
2 changes: 1 addition & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"name": "release",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
}
],
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ ENV PATH="/root/.bun/bin:/root/.local/bin:${PATH}"
RUN pipx install conan==2.7.0

RUN conan profile detect --force --name default && \
conan remote update conancenter --url https://center2.conan.io && \
cat <<EOF >> /root/.conan2/profiles/default
compiler=clang
compiler.version=18
Expand Down Expand Up @@ -120,6 +121,7 @@ rm -rf /var/lib/apt/lists/*
EOF

COPY --from=production-build /build/bin /app
COPY --from=production-build /src /src

RUN <<EOF
mkdir -p /app/persistence/
Expand Down
3 changes: 3 additions & 0 deletions cmake/deps.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ target_link_libraries(deps INTERFACE libcoro::libcoro)

find_package(scope-lite REQUIRED)
target_link_libraries(deps INTERFACE nonstd::scope-lite)

find_package(cpptrace REQUIRED)
target_link_libraries(deps INTERFACE cpptrace::cpptrace)
1 change: 1 addition & 0 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def requirements(self):
self.requires("libcoro/0.12.1")
self.requires("scope-lite/0.2.0")
self.requires("cli11/2.4.2")
self.requires("cpptrace/0.7.3")

if self.options.testing:
print("Testing enabled")
Expand Down
7 changes: 5 additions & 2 deletions daemon/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ if(BUILD_TESTING)
endif()

file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "*.cpp")
list(FILTER SOURCES EXCLUDE REGEX "tests/.*")
list(FILTER SOURCES EXCLUDE REGEX "(tests/.*|signal_tracer\.cpp)")

################################################################################
# Executable Configuration
Expand Down Expand Up @@ -46,4 +46,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
reflectcpp
)

add_dependencies(${PROJECT_NAME} deploy_swagger daemon_tests)
add_executable(signal_tracer ${CMAKE_CURRENT_SOURCE_DIR}/signal_tracer.cpp)
target_link_libraries(signal_tracer PRIVATE cpptrace::cpptrace)

add_dependencies(${PROJECT_NAME} deploy_swagger daemon_tests signal_tracer)
4 changes: 4 additions & 0 deletions daemon/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "presentation/cli-controllers/DeploymentOptions.h"
#include "presentation/JwtOptions.h"
#include "presentation/web-controllers/SectionController.h"
#include "sigtrace.h"
#include "utilities/configuration/Configuration.h"
#include "utilities/Error.h"
#include "utilities/errors/DatabaseError.h"
Expand Down Expand Up @@ -227,6 +228,9 @@ void setup_defaults(kgr::container& container) {
}

int main() {
register_signal_handler();
cpptrace::register_terminate_handler();

setup_logger();

kgr::container container;
Expand Down
21 changes: 21 additions & 0 deletions daemon/signal_tracer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <cpptrace/cpptrace.hpp>
#include <iostream>

int main() {
cpptrace::object_trace trace;
while (true) {
cpptrace::safe_object_frame frame;
// fread used over read because a read() from a pipe might not read the full frame
std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
if (res == 0) {
break;
} else if (res != 1) {
std::cerr << "Something went wrong while reading from the pipe" << res << " "
<< std::endl;
break;
} else {
trace.frames.push_back(frame.resolve());
}
}
trace.resolve().print();
}
84 changes: 84 additions & 0 deletions daemon/sigtrace.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#pragma once

// Based on https://github.com/jeremy-rifkin/cpptrace/blob/main/docs/signal-safe-tracing.md

#include <cpptrace/cpptrace.hpp>
#include <csignal>
#include <cstring>
#include <sys/wait.h>
#include <unistd.h>

// This is just a utility I like, it makes the pipe API more expressive.
struct pipe_t {
union {
struct {
int read_end;
int write_end;
};
int data[2];
};
};

void do_signal_safe_trace(cpptrace::frame_ptr* buffer, std::size_t count) {
// Setup pipe and spawn child
pipe_t input_pipe;
pipe(input_pipe.data);
pid_t const pid = fork();
if (pid == -1) {
char const* fork_failure_message = "fork() failed\n";
write(STDERR_FILENO, fork_failure_message, strlen(fork_failure_message));
return;
}
if (pid == 0) { // child
dup2(input_pipe.read_end, STDIN_FILENO);
close(input_pipe.read_end);
close(input_pipe.write_end);
execl("signal_tracer", "signal_tracer", nullptr);
char const* exec_failure_message =
"exec(signal_tracer) failed: Make sure the signal_tracer executable is in "
"the current working directory and the binary's permissions are correct.\n";
write(STDERR_FILENO, exec_failure_message, strlen(exec_failure_message));
_exit(1);
}
// Resolve to safe_object_frames and write those to the pipe
for (std::size_t i = 0; i < count; i++) {
cpptrace::safe_object_frame frame;
cpptrace::get_safe_object_frame(buffer[i], &frame);
write(input_pipe.write_end, &frame, sizeof(frame));
}
close(input_pipe.read_end);
close(input_pipe.write_end);
// Wait for child
waitpid(pid, nullptr, 0);
}

void handler(int signo, siginfo_t* info, void* context) {
// Print basic message
char const* message = "SIGSEGV occurred:\n";
write(STDERR_FILENO, message, strlen(message));
// Generate trace
constexpr std::size_t N = 100;
cpptrace::frame_ptr buffer[N];
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, N);
do_signal_safe_trace(buffer, count);
// Up to you if you want to exit or continue or whatever
_exit(1);
}

void warmup_cpptrace() {
// This is done for any dynamic-loading shenanigans
cpptrace::frame_ptr buffer[10];
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10);
cpptrace::safe_object_frame frame;
cpptrace::get_safe_object_frame(buffer[0], &frame);
}

void register_signal_handler() {
warmup_cpptrace();
struct sigaction action {};
action.sa_flags = 0;
action.sa_sigaction = &handler;
action.sa_mask = {};

sigaction(SIGSEGV, &action, nullptr);
}