diff --git a/buildtestpublish_all.sh b/buildtestpublish_all.sh index faa3b50..e368fba 100755 --- a/buildtestpublish_all.sh +++ b/buildtestpublish_all.sh @@ -101,6 +101,8 @@ function buildtestpublish_all() { title "🔨 BUILDING ALL TOOLS 🔨" +curl https://getbin.xyz/dropshell-tool-install | bash + buildtestpublish_all title "🚀 Deployment Complete! 🚀" diff --git a/dropshell-tool/CMakeLists.txt b/dropshell-tool/CMakeLists.txt index 3ef33db..6140aaa 100644 --- a/dropshell-tool/CMakeLists.txt +++ b/dropshell-tool/CMakeLists.txt @@ -1,116 +1,44 @@ -# Get the current date in yyyy.mmdd.hhmm format -execute_process( - COMMAND date "+%Y.%m%d.%H%M" - OUTPUT_VARIABLE PROJECT_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE -) +cmake_minimum_required(VERSION 3.16) -cmake_minimum_required(VERSION 3.10) -get_filename_component(PROJECT_EXE_NAME "${CMAKE_CURRENT_SOURCE_DIR}" NAME) -project(${PROJECT_EXE_NAME} VERSION ${PROJECT_VERSION} LANGUAGES CXX) - -# Force static linking globally -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") -set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") -set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries" FORCE) -set(CMAKE_POSITION_INDEPENDENT_CODE OFF) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static") -set(ZLIB_USE_STATIC_LIBS "ON") -set(OPENSSL_USE_STATIC_LIBS ON) - -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_C_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Set default build type to Release if not specified -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build (Debug or Release)" FORCE) +# Project setup +if(NOT DEFINED PROJECT_NAME) + message(FATAL_ERROR "PROJECT_NAME is not defined. Pass it via -DPROJECT_NAME=") endif() -# Configure build-specific compiler flags -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG") +string(TIMESTAMP PROJECT_VERSION "%Y.%m%d.%H%M") +project(${PROJECT_NAME} VERSION ${PROJECT_VERSION} LANGUAGES CXX) -# Configure version.hpp file -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/src/version.hpp.in" - "${CMAKE_CURRENT_BINARY_DIR}/src/autogen/version.hpp" - @ONLY -) +# Build configuration +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXE_LINKER_FLAGS "-static") +set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +set(BUILD_SHARED_LIBS OFF) +set(CMAKE_PREFIX_PATH /usr/local) -# Set CMAKE_MODULE_PATH to include our custom find modules -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) - -# Auto-detect source files +# Create executable file(GLOB_RECURSE SOURCES "src/*.cpp") -file(GLOB_RECURSE HEADERS "src/*.hpp") -file(GLOB_RECURSE HEADERS "src/*.h") +add_executable(${PROJECT_NAME} ${SOURCES}) -# Add custom target to run cmake_prebuild.sh at the start of the build process -add_custom_target(run_prebuild_script ALL - COMMAND ${CMAKE_COMMAND} -E echo "Running cmake_prebuild.sh..." - COMMAND ${CMAKE_COMMAND} -E env bash ${CMAKE_CURRENT_SOURCE_DIR}/cmake_prebuild.sh - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -) +# Configure version.hpp +configure_file("src/version.hpp.in" "src/autogen/version.hpp" @ONLY) -# Add executable -add_executable(${PROJECT_EXE_NAME} ${SOURCES}) -add_dependencies(${PROJECT_EXE_NAME} run_prebuild_script) +# Pre-build script +add_custom_target(run_prebuild_script ALL + COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/cmake_prebuild.sh + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +add_dependencies(${PROJECT_NAME} run_prebuild_script) -# Set include directories -# build dir goes first so that we can use the generated version.hpp -target_include_directories(${PROJECT_EXE_NAME} PRIVATE - $ - ${CMAKE_CURRENT_SOURCE_DIR}/src -) +# Include directories +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/src/autogen + src) -# Configure libassert -include(FetchContent) -FetchContent_Declare( - libassert - GIT_REPOSITORY https://github.com/jeremy-rifkin/libassert.git - GIT_TAG v2.1.5 -) -FetchContent_MakeAvailable(libassert) - -# Add cpptrace -FetchContent_Declare( - cpptrace - GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git - GIT_TAG v0.8.3 -) -FetchContent_MakeAvailable(cpptrace) - -# Add nlohmann/json -FetchContent_Declare( - nlohmann_json - GIT_REPOSITORY https://github.com/nlohmann/json.git - GIT_TAG v3.11.3 -) -FetchContent_MakeAvailable(nlohmann_json) +# Find packages +find_package(nlohmann_json REQUIRED) # Link libraries -target_link_libraries(${PROJECT_EXE_NAME} PRIVATE - libassert::assert - cpptrace::cpptrace +target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json::nlohmann_json ) - -# Find OpenSSL (will use -DOPENSSL_ROOT_DIR etc. if set) -find_package(OpenSSL REQUIRED) - -# Link OpenSSL imported targets if found -if(TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto) - target_link_libraries(${PROJECT_EXE_NAME} PRIVATE - OpenSSL::SSL - OpenSSL::Crypto - ) -else() - message(FATAL_ERROR "OpenSSL targets not found. Set OPENSSL_ROOT_DIR and try again.") -endif() - -# Set static linking flags -set_target_properties(${PROJECT_EXE_NAME} PROPERTIES - LINK_FLAGS "-static" -) + \ No newline at end of file diff --git a/dropshell-tool/Dockerfile.dropshell-build b/dropshell-tool/Dockerfile.dropshell-build new file mode 100644 index 0000000..fe58a8d --- /dev/null +++ b/dropshell-tool/Dockerfile.dropshell-build @@ -0,0 +1,72 @@ +ARG IMAGE_TAG +FROM gitea.jde.nz/public/dropshell-build-base:latest AS builder + +ARG PROJECT +ARG CMAKE_BUILD_TYPE=Debug + +# Set working directory +WORKDIR /app + +# Create cache directories +RUN mkdir -p /ccache + +# Set up ccache +ENV CCACHE_DIR=/ccache +ENV CCACHE_COMPILERCHECK=content +ENV CCACHE_MAXSIZE=2G + +# Copy only build files first (for better layer caching) +COPY CMakeLists.txt cmake_prebuild.sh ./ +COPY src/version.hpp.in src/ + +# Run prebuild script early (this rarely changes) +RUN bash cmake_prebuild.sh + +# Copy source files (this invalidates cache when source changes) +COPY src/ src/ + +# Configure project (this step is cached unless CMakeLists.txt changes) +RUN --mount=type=cache,target=/ccache \ + --mount=type=cache,target=/build \ + mkdir -p /build && \ + SSL_LIB=$(find /usr/local -name "libssl.a" | head -1) && \ + CRYPTO_LIB=$(find /usr/local -name "libcrypto.a" | head -1) && \ + echo "Found SSL: $SSL_LIB, Crypto: $CRYPTO_LIB" && \ + cmake -G Ninja -S /app -B /build \ + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=mold -static -g" \ + -DCMAKE_CXX_FLAGS="-g -fno-omit-frame-pointer" \ + -DCMAKE_C_FLAGS="-g -fno-omit-frame-pointer" \ + -DPROJECT_NAME="${PROJECT}" \ + -DCMAKE_STRIP=OFF \ + -DOPENSSL_SSL_LIBRARY="$SSL_LIB" \ + -DOPENSSL_CRYPTO_LIBRARY="$CRYPTO_LIB" \ + -DOPENSSL_INCLUDE_DIR=/usr/local/include \ + ${CMAKE_TOOLCHAIN_FILE:+-DCMAKE_TOOLCHAIN_FILE=$CMAKE_TOOLCHAIN_FILE} + +# Run prebuild script +RUN --mount=type=cache,target=/ccache \ + --mount=type=cache,target=/build \ + cmake --build /build --target run_prebuild_script + +# Build project (ccache will help here when only some files change) +RUN --mount=type=cache,target=/ccache \ + --mount=type=cache,target=/build \ + cmake --build /build + +# Copy the built executable to a regular directory for the final stage +RUN --mount=type=cache,target=/build \ + mkdir -p /output && \ + find /build -type f -executable -name "*${PROJECT}*" -exec cp {} /output/${PROJECT} \; || \ + find /build -type f -executable -exec cp {} /output/${PROJECT} \; + +# Final stage that only contains the binary +FROM scratch AS project + +ARG PROJECT + +# Copy the actual binary from the regular directory +COPY --from=builder /output/${PROJECT} /${PROJECT} + diff --git a/dropshell-tool/build.sh b/dropshell-tool/build.sh index 1cd8a10..c4959bb 100755 --- a/dropshell-tool/build.sh +++ b/dropshell-tool/build.sh @@ -1,21 +1,22 @@ #!/bin/bash + set -euo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -TEMP_DIR=$(mktemp -d) -trap 'rm -rf "${TEMP_DIR}"' EXIT -function getbin() { - local BIN_NAME="$1" - - curl -L -o "${TEMP_DIR}/${BIN_NAME}" "https://getbin.xyz/${BIN_NAME}" - chmod +x "${TEMP_DIR}/${BIN_NAME}" -} +export CMAKE_BUILD_TYPE="Debug" -getbin "dropshell-build" +rm -rf "${SCRIPT_DIR}/output" +mkdir -p "${SCRIPT_DIR}/output" -rm -rf "${SCRIPT_DIR}/build" +PROJECT="dropshell-tool" -"${TEMP_DIR}/dropshell-build" -r -m "${SCRIPT_DIR}" +docker build \ + -t "${PROJECT}-build" \ + -f "${SCRIPT_DIR}/Dockerfile.dropshell-build" \ + --build-arg PROJECT="${PROJECT}" \ + --build-arg CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" \ + --output "${SCRIPT_DIR}/output" \ + "${SCRIPT_DIR}" diff --git a/dropshell-tool/dropshell-tool-install b/dropshell-tool/install.sh similarity index 66% rename from dropshell-tool/dropshell-tool-install rename to dropshell-tool/install.sh index 538e560..12df676 100755 --- a/dropshell-tool/dropshell-tool-install +++ b/dropshell-tool/install.sh @@ -8,9 +8,9 @@ echo "Installing dropshell-tool" ARCH=$(uname -m) -wget "https://getbin.xyz/dropshell-tool:$ARCH" -O bootstrap && chmod a+x bootstrap +wget "https://getbin.xyz/dropshell-tool:latest-${ARCH}" -O bootstrap && chmod a+x bootstrap -./bootstrap install dropshell tool +./bootstrap install dropshell-tool rm ./bootstrap diff --git a/dropshell-tool/old_CMakeLists.txt b/dropshell-tool/old_CMakeLists.txt deleted file mode 100644 index a7df71b..0000000 --- a/dropshell-tool/old_CMakeLists.txt +++ /dev/null @@ -1,132 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -set(PROJECT_EXE_NAME dropshell-tool) -project(${PROJECT_EXE_NAME} VERSION 1.0.0 LANGUAGES CXX) -cmake_policy(SET CMP0135 NEW) - -# Force static linking globally -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") -set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") -set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries" FORCE) -set(CMAKE_POSITION_INDEPENDENT_CODE OFF) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static") -set(ZLIB_USE_STATIC_LIBS ON) - -# Ensure zlib is built as static only -set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) -set(ZLIB_BUILD_SHARED OFF CACHE BOOL "" FORCE) -set(ZLIB_BUILD_STATIC ON CACHE BOOL "" FORCE) -# Disable zlib examples and minigzip -set(ZLIB_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) -set(ZLIB_BUILD_MINIGZIP OFF CACHE BOOL "" FORCE) - -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_C_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Set default build type to Release if not specified -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build (Debug or Release)" FORCE) -endif() - -# Configure build-specific compiler flags -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG") - -# Configure version information -string(TIMESTAMP CURRENT_YEAR "%Y") -string(TIMESTAMP CURRENT_MONTH "%m") -string(TIMESTAMP CURRENT_DAY "%d") -string(TIMESTAMP CURRENT_HOUR "%H") -string(TIMESTAMP CURRENT_MINUTE "%M") -set(PROJECT_VERSION "${CURRENT_YEAR}.${CURRENT_MONTH}${CURRENT_DAY}.${CURRENT_HOUR}${CURRENT_MINUTE}") -string(TIMESTAMP RELEASE_DATE "%Y-%m-%d") - -# Configure version.hpp file -configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/src/version.hpp.in" - "${CMAKE_CURRENT_BINARY_DIR}/src/autogen/version.hpp" - @ONLY -) - -# Set CMAKE_MODULE_PATH to include our custom find modules -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) - -# Auto-detect source files -file(GLOB_RECURSE SOURCES "src/*.cpp") -file(GLOB_RECURSE HEADERS "src/*.hpp") - -# Add custom target to run cmake_prebuild.sh at the start of the build process -add_custom_target(run_prebuild_script ALL - COMMAND ${CMAKE_COMMAND} -E echo "Running cmake_prebuild.sh..." - COMMAND ${CMAKE_COMMAND} -E env bash ${CMAKE_CURRENT_SOURCE_DIR}/cmake_prebuild.sh - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -) - -# Add executable -add_executable(${PROJECT_EXE_NAME} ${SOURCES}) -add_dependencies(${PROJECT_EXE_NAME} run_prebuild_script) - -# Set include directories -# build dir goes first so that we can use the generated version.hpp -target_include_directories(${PROJECT_EXE_NAME} PRIVATE - $ - ${CMAKE_CURRENT_SOURCE_DIR}/src - ${CMAKE_CURRENT_SOURCE_DIR}/src/autogen -) - -# Configure libassert -include(FetchContent) -FetchContent_Declare( - libassert - GIT_REPOSITORY https://github.com/jeremy-rifkin/libassert.git - GIT_TAG v2.1.5 -) -FetchContent_MakeAvailable(libassert) - -# Add cpptrace -FetchContent_Declare( - cpptrace - GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git - GIT_TAG v0.8.3 -) -FetchContent_MakeAvailable(cpptrace) - -# Add nlohmann/json -FetchContent_Declare( - nlohmann_json - GIT_REPOSITORY https://github.com/nlohmann/json.git - GIT_TAG v3.12.0 -) -FetchContent_MakeAvailable(nlohmann_json) - -# Fetch and build zlib from source (static) -FetchContent_Declare( - zlib - URL https://zlib.net/zlib-1.3.1.tar.gz -) -FetchContent_MakeAvailable(zlib) - -# Link libraries -target_link_libraries(${PROJECT_EXE_NAME} PRIVATE - libassert::assert - cpptrace::cpptrace - nlohmann_json::nlohmann_json - zlibstatic -) - -# Set static linking flags -set_target_properties(${PROJECT_EXE_NAME} PROPERTIES - LINK_FLAGS "-static" -) - -find_package(OpenSSL REQUIRED) -target_compile_definitions(${PROJECT_EXE_NAME} PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT) -target_link_libraries(${PROJECT_EXE_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto) - -# --- MUSL CROSS-COMPILATION --- -# To ensure all dependencies (including zlib) are built with musl, set your toolchain file or: -# set(CMAKE_C_COMPILER "/path/to/musl-gcc") -# set(CMAKE_CXX_COMPILER "/path/to/musl-g++") -# BEFORE the project() call above. - diff --git a/dropshell-tool/publish.sh b/dropshell-tool/publish.sh index 1605495..0f24898 100755 --- a/dropshell-tool/publish.sh +++ b/dropshell-tool/publish.sh @@ -1,59 +1,46 @@ -#!/bin/bash set -euo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -TEMP_DIR=$(mktemp -d) +ARCH=$(uname -m) +TEMP_DIR="${SCRIPT_DIR}/temp" +SOS="${TEMP_DIR}/sos" +echo "Publishing ${PROJECT} to gitea.jde.nz/public/${PROJECT}:latest" -die() { - echo -e "Error: $1" >&2 +function die() { + title "error: $1" exit 1 } -function publish_tool() { - local TOOLPATH="$1" - local TOOL; - TOOL=$(basename "$TOOLPATH") +[[ -n $SOS_WRITE_TOKEN ]] || die "SOS_WRITE_TOKEN not specified" - echo "Publishing $TOOL to getbin.xyz" - echo "(from $TOOLPATH)" +# clear output dir +rm -rf "${SCRIPT_DIR}/output" +mkdir -p "${SCRIPT_DIR}/output" - "${TEMP_DIR}/sos" upload "getbin.xyz" "$TOOLPATH" "${TOOL}:latest" -} +# build release version +export CMAKE_BUILD_TYPE="Release" +export PROJECT="dropshell-tool" -function publish_executables() { - OUTPUT_DIR="${SCRIPT_DIR}/output" +docker build \ + -t "${PROJECT}-build" \ + -f "${SCRIPT_DIR}/Dockerfile.dropshell-build" \ + --build-arg PROJECT="${PROJECT}" \ + --build-arg CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" \ + --output "${SCRIPT_DIR}/output" \ + "${SCRIPT_DIR}" - # Find all dropshell-tool.ARCH files in output/ - TOOLS=() - for tool in "${OUTPUT_DIR}"/*dropshell-tool.*; do - [ -f "$tool" ] || continue - tool_name=$(basename "$tool") - TOOLS+=("$tool_name") - done +[ -f ${SCRIPT_DIR}/output/dropshell-tool ] || die "Build failed." - if [ ${#TOOLS[@]} -eq 0 ]; then - echo "No tools found in ${SCRIPT_DIR}/output/. Exiting." - exit 1 - fi +# download the sos binary +mkdir -p "${TEMP_DIR}" +trap 'rm -rf "${TEMP_DIR}"' EXIT +curl -L -o "${SOS}" "https://getbin.xyz/sos" +chmod +x "${SOS}" - for TOOL in "${TOOLS[@]}"; do - publish_tool "$OUTPUT_DIR/$TOOL" - done -} +# upload arch-specific binary +"${SOS}" upload "getbin.xyz" "${SCRIPT_DIR}/output/${PROJECT}" "${PROJECT}:latest-${ARCH}" -function getbin() { - local BIN_NAME="$1" - - curl -L -s -o "${TEMP_DIR}/${BIN_NAME}" "https://getbin.xyz/${BIN_NAME}" || die "Failed to download ${BIN_NAME}" - chmod +x "${TEMP_DIR}/${BIN_NAME}" -} - - - -getbin "sos" -publish_executables - -rm -rf "${TEMP_DIR}" -echo "Done" \ No newline at end of file +# upload generic install script (ok if multiple times as we iterate through arch's) +"${SOS}" upload "getbin.xyz" "${SCRIPT_DIR}/install.sh" "${PROJECT}-install:latest" \ No newline at end of file diff --git a/dropshell-tool/src/assert.hpp b/dropshell-tool/src/assert.hpp new file mode 100644 index 0000000..eb421dd --- /dev/null +++ b/dropshell-tool/src/assert.hpp @@ -0,0 +1,245 @@ +#pragma once + +#include +#include +#include +#include +// execinfo.h not available in Alpine Linux - using alternative approach +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ANSI color codes +namespace colors { + constexpr const char* reset = "\033[0m"; + constexpr const char* red = "\033[31m"; + constexpr const char* green = "\033[32m"; + constexpr const char* yellow = "\033[33m"; + constexpr const char* blue = "\033[34m"; + constexpr const char* magenta = "\033[35m"; + constexpr const char* cyan = "\033[36m"; +} + +// Simple RAII wrapper for file descriptor +class FileDescriptor { + int fd_ = -1; +public: + explicit FileDescriptor(int fd) : fd_(fd) {} + ~FileDescriptor() { if (fd_ != -1) close(fd_); } + operator int() const { return fd_; } + FileDescriptor(const FileDescriptor&) = delete; + FileDescriptor& operator=(const FileDescriptor&) = delete; +}; + +struct SourceInfo { + std::string function; + std::string file; + int line = 0; +}; + +std::vector get_source_info_batch(const char* executable, const std::vector& addresses) { + if (addresses.empty()) return {}; + + // Build command with all addresses + std::string cmd = std::string("addr2line -f -e ") + executable; + for (void* addr : addresses) { + char addr_buf[32]; + snprintf(addr_buf, sizeof(addr_buf), " %p", addr); + cmd += addr_buf; + } + cmd += " 2>/dev/null"; + + FILE* pipe = popen(cmd.c_str(), "r"); + if (!pipe) return std::vector(addresses.size()); + + std::vector results(addresses.size()); + char buffer[1024] = {0}; + size_t current_frame = 0; + + // Read function names and file/line info for each address + while (current_frame < addresses.size() && fgets(buffer, sizeof(buffer), pipe)) { + // Remove trailing newline + size_t len = strlen(buffer); + if (len > 0 && buffer[len-1] == '\n') { + buffer[len-1] = '\0'; + } + + // Demangle function name + int status = 0; + char* demangled = abi::__cxa_demangle(buffer, nullptr, nullptr, &status); + std::string func_name; + if (demangled) { + func_name = demangled; + free(demangled); + } else { + func_name = buffer; + } + + // Simplify function signature: replace argument list with (...) + size_t paren_pos = func_name.find('('); + if (paren_pos != std::string::npos) { + func_name = func_name.substr(0, paren_pos) + "(...)"; + } + + results[current_frame].function = func_name; + + // Read file and line number + if (fgets(buffer, sizeof(buffer), pipe)) { + char* colon = strchr(buffer, ':'); + if (colon) { + *colon = '\0'; + results[current_frame].line = atoi(colon + 1); + + // Extract just the filename + const char* slash = strrchr(buffer, '/'); + results[current_frame].file = (slash ? slash + 1 : buffer); + } + } + + current_frame++; + } + + pclose(pipe); + return results; +} + +// Helper function to get the program counter (return address) in a portable way +inline void* get_pc() { + void* pc; + #if defined(__aarch64__) || defined(__arm__) + // For ARM/AArch64, we can use the link register (x30 on AArch64, r14 on ARM) + #if defined(__aarch64__) + asm volatile ("mov %0, x30" : "=r" (pc)); + #else + asm volatile ("mov %0, lr" : "=r" (pc)); + #endif + #else + // For x86/x86_64, use __builtin_return_address + pc = __builtin_return_address(0); + #endif + return pc; +} + +void print_stacktrace() { + std::cerr << colors::yellow << "Stack trace:" << colors::reset << "\n"; + + char exe_path[1024] = {0}; + ssize_t count = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); + if (count == -1) { + strcpy(exe_path, "[unknown]"); + } else { + exe_path[count] = '\0'; + } + + std::vector addresses; + + // Get current frame pointer and return address + void* frame_ptr = __builtin_frame_address(0); + void* return_addr = get_pc(); + + // Add current return address + if (return_addr) { + addresses.push_back(return_addr); + } + + // Try to walk up the stack if we have a valid frame pointer + if (frame_ptr) { + void** frame = static_cast(frame_ptr); + // Limit the number of frames to prevent potential issues + for (int i = 0; i < 5; ++i) { + // The return address is typically at frame[1] + if (frame[1] == nullptr) break; + addresses.push_back(frame[1]); + // Move to the next frame + if (frame[0] == nullptr) break; + frame = static_cast(frame[0]); + } + } + + if (addresses.empty()) { + std::cerr << " " << colors::red << "[no frames available]" << colors::reset << "\n"; + return; + } + + // Get source info + std::vector results = get_source_info_batch(exe_path, addresses); + + // Filter out frames from assert.hpp + results.erase(std::remove_if(results.begin(), results.end(), + [](const SourceInfo& frame) { + if (frame.file.empty()) return false; + std::string_view file(frame.file); + return file.find("assert.hpp") != std::string_view::npos; + }), + results.end() + ); + + if (results.empty()) { + std::cerr << " " << colors::red << "[no user frames available]" << colors::reset << "\n"; + return; + } + + // First pass: find the maximum function name length for alignment + size_t max_func_length = 0; + for (const auto& frame : results) { + size_t length = frame.function.empty() + ? strlen("[unknown function]") + : frame.function.length(); + if (length > max_func_length) { + max_func_length = length; + } + } + + // Second pass: print with aligned output + for (size_t i = 0; i < results.size(); ++i) { + const auto& frame = results[i]; + std::cerr << " "; + + // Print function name with alignment + size_t current_length; + if (!frame.function.empty()) { + std::cerr << colors::green << frame.function << colors::reset; + current_length = frame.function.length(); + } else { + std::cerr << colors::red << "[unknown function]" << colors::reset; + current_length = strlen("[unknown function]"); + } + + // Add padding to align the file/line text + if (!frame.file.empty() && frame.line > 0) { + // Calculate padding (minimum 1 space) + size_t padding = (max_func_length > current_length) + ? (max_func_length - current_length + 1) + : 1; + std::cerr << std::string(padding, ' ') + << " " << colors::blue << frame.file << colors::reset + << ":" << colors::magenta << frame.line << colors::reset; + } + + std::cerr << "\n"; + } +} + +void assert_failed( + bool condition, + std::string_view message, + std::source_location location +) { + if (!condition) { + std::cout << std::flush; + std::cerr << std::flush; + + std::cerr << colors::red << "Assertion failed at " << location.file_name() << ":" << location.line() << ": " + << location.function_name() << ": " << message << colors::reset << "\n"; + print_stacktrace(); + std::abort(); + } +} + +#define ASSERT(condition, message) assert_failed(condition, message, std::source_location::current()) diff --git a/dropshell-tool/src/main.cpp b/dropshell-tool/src/main.cpp index 79c5083..980f3e3 100644 --- a/dropshell-tool/src/main.cpp +++ b/dropshell-tool/src/main.cpp @@ -22,10 +22,10 @@ dropshell-tool publish - checks that dropshell-tool-config.json exists in the folder, and is valid (see above) - - creates a tgz archive of the folder, and uploads it to tools.dropshell.app's simple object storage server. + - creates a tgz archive of the folder, and uploads it to tools.dropshell.app, a simple object server. - prints the URL and hash of the uploaded archive - - uses the token ~/.config/tools.dropshell.app/write_token.txt, or prompts the user for a token if it is not found and writes it to the file - + - uses the token from env variable SOS_WRITE_TOKEN to write to tools.dropshell.app + dropshell-tool update - compares the hash from the ~/.config/dropshell-tool/tool_name.json file with the hash from tools.dropshell.app (tool_name:ARCH), and continues only if they are different - checks the version from the ~/.config/dropshell-tool/tool_name.json file with the version from tools.dropshell.app (tool_name:ARCH), and continues only if the remote version is newer (installed is older) diff --git a/dropshell-tool/test.sh b/dropshell-tool/test.sh index fb15b82..466feb7 100755 --- a/dropshell-tool/test.sh +++ b/dropshell-tool/test.sh @@ -4,11 +4,6 @@ set -euo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" - -MYARCH=$(uname -m) - -VERSION=$("${SCRIPT_DIR}/output/dropshell-tool.${MYARCH}" version) +VERSION=$("${SCRIPT_DIR}/output/dropshell-tool" version) echo "Version: $VERSION" - -