From ebabcd41d16c0d41eaf26188e5d80dca2f388508 Mon Sep 17 00:00:00 2001 From: brenozd Date: Thu, 24 Oct 2024 21:04:00 -0300 Subject: [PATCH] adding cpp example --- src/CMakeLists.txt | 2 +- src/example_c/version.h | 2 +- src/example_cpp/CMakeLists.txt | 27 ++++ src/example_cpp/argparser/CMakeLists.txt | 9 ++ src/example_cpp/argparser/argparser.cpp | 109 +++++++++++++++ src/example_cpp/argparser/argparser.hpp | 163 ++++++++++++++++++++++ src/example_cpp/backtracer/CMakeLists.txt | 9 ++ src/example_cpp/backtracer/backtracer.cpp | 108 ++++++++++++++ src/example_cpp/backtracer/backtracer.hpp | 37 +++++ src/example_cpp/common.h | 16 +++ src/example_cpp/main.cpp | 17 +++ src/example_cpp/version.hpp | 15 ++ 12 files changed, 512 insertions(+), 2 deletions(-) create mode 100644 src/example_cpp/CMakeLists.txt create mode 100644 src/example_cpp/argparser/CMakeLists.txt create mode 100644 src/example_cpp/argparser/argparser.cpp create mode 100644 src/example_cpp/argparser/argparser.hpp create mode 100644 src/example_cpp/backtracer/CMakeLists.txt create mode 100644 src/example_cpp/backtracer/backtracer.cpp create mode 100644 src/example_cpp/backtracer/backtracer.hpp create mode 100644 src/example_cpp/common.h create mode 100644 src/example_cpp/main.cpp create mode 100644 src/example_cpp/version.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5c483f7..c865d58 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,2 +1,2 @@ add_subdirectory(example_c) -# add_subdirectory(example_cpp) +add_subdirectory(example_cpp) diff --git a/src/example_c/version.h b/src/example_c/version.h index 23560b4..6f884c2 100644 --- a/src/example_c/version.h +++ b/src/example_c/version.h @@ -10,6 +10,6 @@ #define EXAMPLE_C_VERSION_MINOR 2 #define EXAMPLE_C_VERSION_PATCH 3 #define EXAMPLE_C_COMMIT_HASH cd01d7d8d36862bc00ae88f7cd185c352a7b7eda -#define EXAMPLE_C_BUILD_TIMESTAMP 1729811627 +#define EXAMPLE_C_BUILD_TIMESTAMP 1729814020 #endif // INCLUDE_EXAMPLE_C_VERSION_H_ diff --git a/src/example_cpp/CMakeLists.txt b/src/example_cpp/CMakeLists.txt new file mode 100644 index 0000000..4a0455a --- /dev/null +++ b/src/example_cpp/CMakeLists.txt @@ -0,0 +1,27 @@ +add_executable(example_cpp main.cpp) +execute_process(COMMAND ${PROJECT_SOURCE_DIR}/scripts/versioning.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE EXAMPLE_CPP_VERSION) + +message(STATUS "Building [example_cpp] version ${EXAMPLE_CPP_VERSION}") +target_compile_options(example_cpp PRIVATE "${COMPILE_FLAGS}") +target_link_options(example_cpp PRIVATE "${LINK_FLAGS}") + +# Check if compiler supports C++11 +target_compile_features(example_cpp PUBLIC cxx_std_11) +target_link_libraries(example_cpp plog::plog) + +file(GLOB CPP_FILES CONFIGURE_DEPENDS *.cpp) +file(GLOB HPP_FILES CONFIGURE_DEPENDS *.hpp) + +target_sources(example_cpp PRIVATE ${CPP_FILES} PUBLIC ${HPP_FILES}) +target_include_directories(example_cpp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(example_cpp backtrace) +if(STATIC_ARGPARSE) + target_link_libraries(example_cpp argparse_static) +else() + target_link_libraries(example_cpp argparse_shared) +endif() + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/argparser) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/backtracer) diff --git a/src/example_cpp/argparser/CMakeLists.txt b/src/example_cpp/argparser/CMakeLists.txt new file mode 100644 index 0000000..51694d4 --- /dev/null +++ b/src/example_cpp/argparser/CMakeLists.txt @@ -0,0 +1,9 @@ +file(GLOB CPP_FILES CONFIGURE_DEPENDS *.cpp) +file(GLOB HPP_FILES CONFIGURE_DEPENDS *.hpp) + +target_sources( + example_cpp + PRIVATE ${CPP_FILES} + PUBLIC ${HPP_FILES}) + +target_include_directories(example_cpp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/example_cpp/argparser/argparser.cpp b/src/example_cpp/argparser/argparser.cpp new file mode 100644 index 0000000..1d75a6b --- /dev/null +++ b/src/example_cpp/argparser/argparser.cpp @@ -0,0 +1,109 @@ +#include "argparser.hpp" +#include + +ArgumentParser::~ArgumentParser() { + for (size_t index = 0; index < options_.size(); index++) { + auto opt = options_[index]; + + if (opt.type == ARGPARSE_OPT_END || std::string(opt.long_name) == "help") + continue; + + delete[] opt.long_name; + delete[] opt.help; + } +} + +bool ArgumentParser::parse(int argc, const char **argv) { + std::vector options = options_; + options.push_back(OPT_END()); + bool rc = true; + + std::string usage_str = application_name_ + " [options]"; + if (require_arguments_ && allowed_arguments_.empty()) + usage_str += " [args]"; + + if (!allowed_arguments_.empty()) { + for (size_t i = 0; i < allowed_arguments_.size(); i++) { + auto current_arg_list = allowed_arguments_.at(i); + usage_str += " ["; + for (size_t j = 0; j < current_arg_list.size(); j++) { + if (j != 0) + usage_str += " | "; + usage_str += current_arg_list.at(j); + } + usage_str += "]"; + } + } + + static const char *const usage[] = {usage_str.c_str(), NULL}; + + argparse_init(&argparse_, options.data(), usage, 0); + argparse_describe(&argparse_, description_.c_str(), ""); + argc = argparse_parse(&argparse_, argc, argv); + + if (argc == 0 && require_arguments_) + rc = false; + else { + for (int i = 0; i < argc; i++) { + std::string arg = std::string(argv[i]); + arguments_.push_back(arg); + bool arg_allowed = false; + for (size_t i = 0; i < allowed_arguments_.size(); i++) { + auto current_arg_list = allowed_arguments_.at(i); + auto allowed_arg = std::find(current_arg_list.begin(), current_arg_list.end(), arg); + if (allowed_arg != current_arg_list.end()) { + arg_allowed = true; + break; + } + } + if (!arg_allowed) + rc = false; + } + } + + return rc; +} + +std::vector split(const std::string &str, char delimiter) { + std::vector tokens; + std::string::size_type start = 0; + std::string::size_type end = str.find(delimiter); + + while (end != std::string::npos) { + tokens.push_back(str.substr(start, end - start)); + start = end + 1; + end = str.find(delimiter, start); + } + + tokens.push_back(str.substr(start)); + return tokens; +} + +/** + * This callback is used to process the value of an option string, which is + * typically a key-value pair. It splits the input buffer into parts based on + * the '=' character, and uses one part as the final output string. + */ +int ArgumentParser::opt_string_callback(struct argparse *parser, const struct argparse_option *flag) { + std::string buffer(parser->optvalue); + auto buffer_parts = split(buffer, '='); + if (buffer_parts.size() > 1) + buffer = buffer_parts[1]; + + std::string *dst_str = reinterpret_cast(flag->data); + dst_str->clear(); + dst_str->append(buffer); + + parser->optvalue = NULL; + return 0; +} + +/** + * This callback is used to process the value of an option boolean, which is + * typically a key only value. + */ +int ArgumentParser::opt_boolean_callback(struct argparse *parser, const struct argparse_option *flag) { + bool *dst = reinterpret_cast(flag->data); + *dst = true; + return 0; +} diff --git a/src/example_cpp/argparser/argparser.hpp b/src/example_cpp/argparser/argparser.hpp new file mode 100644 index 0000000..a77fc0e --- /dev/null +++ b/src/example_cpp/argparser/argparser.hpp @@ -0,0 +1,163 @@ +#ifndef INCLUDE_ARGPARSER_ARGPARSER_HPP_ +#define INCLUDE_ARGPARSER_ARGPARSER_HPP_ + +#include +#include +#include +#include +#include +#include + +/** + * @class ArgumentParser + * @brief A class to handle command line arguments. + * + * This class provides methods to add, get and manage known arguments from the command line. It users + * (Argparse)[https://github.com/cofyc/argparse] under the hood + */ +class ArgumentParser { +public: + /** + * Constructor with application name, description and requirement of arguments. + * + * @param application_name Name of the application. + * @param description Description of the application. + * @param require_arguments Whether the application requires arguments or not. + */ + ArgumentParser(const std::string &application_name, const std::string &description, bool require_arguments = false, + std::vector> allowed_arguments = {}) + : application_name_(application_name), description_(description), require_arguments_(require_arguments), + allowed_arguments_(allowed_arguments) {}; + + /** + * Destructor for ArgumentParser. + */ + ~ArgumentParser(); + + /** + * Adds a flag to the list of flags. + * + * @tparam T Type of the flag value (int, float, bool or std::string). + * @param short_name Short name of the flag. + * @param long_name Long name of the flag. + * @param value Pointer to the flag value. + * @param description Description of the flag. + * \todo Support callbacks for flags as in + * https://github.com/cofyc/argparse/blob/682d4520b4bc2b646cdfcf078b2fed00b3d2da30/tests/basic.c#L34 + * \todo Implement logic to allow only to choose from a specific set of choices for a variable + */ + template void add_flag(char short_name, const std::string &long_name, T *value, const std::string &description) { + const std::type_info &flag_type = typeid(T); + + // Clone strings so we dont lose reference to it + char *long_name_local = new char[long_name.size() + 1]; + std::strncpy(long_name_local, long_name.c_str(), long_name.size()); + long_name_local[long_name.size()] = '\0'; + PLOG_VERBOSE << "Adding flag " << long_name_local << " to the list of flags" << std::endl; + + char *description_local = new char[description.size() + 1]; + std::strncpy(description_local, description.c_str(), description.size()); + description_local[description.size()] = '\0'; + + struct argparse_option flag_opt = {ARGPARSE_OPT_END, short_name, long_name_local, value, description_local, NULL, 0, 0}; + + if (flag_type == typeid(int)) { + flag_opt.type = ARGPARSE_OPT_INTEGER; + PLOG_VERBOSE << "Flag " << long_name_local << " is of type int" << std::endl; + } + + if (flag_type == typeid(uint)) { + flag_opt.type = ARGPARSE_OPT_INTEGER; + PLOG_VERBOSE << "Flag " << long_name_local << " is of type unsigned int" << std::endl; + } + + else if (flag_type == typeid(float)) { + flag_opt.type = ARGPARSE_OPT_FLOAT; + PLOG_VERBOSE << "Flag " << long_name_local << " is of type float" << std::endl; + } + + else if (flag_type == typeid(bool)) { + flag_opt.type = ARGPARSE_OPT_BOOLEAN; + flag_opt.value = NULL; + flag_opt.callback = opt_boolean_callback; + flag_opt.data = reinterpret_cast(value); + PLOG_VERBOSE << "Flag " << long_name_local << " is of type bool" << std::endl; + } + + else if (flag_type == typeid(std::string)) { + flag_opt.type = ARGPARSE_OPT_STRING; + flag_opt.value = NULL; + flag_opt.callback = opt_string_callback; + flag_opt.data = reinterpret_cast(value); + PLOG_VERBOSE << "Flag " << long_name_local << " is of type str" << std::endl; + } + + options_.push_back(flag_opt); + } + + /** + * Parses the command line arguments. + * + * @param argc Number of command line arguments, including binary name. + * @param argv Array of command line arguments, including binary name at argv[0]. + * @return True if parsing is successful, false otherwise. + */ + bool parse(int argc, const char **argv); + + /** + * @brief Displays the usage message from argparse. + * + * This inline function calls `argparse_usage` with no arguments, printing the + * usage message to stdout. + * + * @note The output is not redirected or buffered; it will be printed directly to the console. + */ + inline void show_usage() { argparse_usage(&argparse_); }; + + /** + * @brief Returns a copy of the vector of command-line arguments. + * + * This method returns a copy of the internal `arguments_` vector, which is used to store + * the command-line arguments passed to the program. The returned vector is not modified. + * + * @return A copy of the `arguments_` vector. + */ + inline std::vector get_arguments() const { return arguments_; }; + +private: + /** + * Vector of argparse options. Stores all flags/arguments added through add_flag + */ + std::vector options_ = {OPT_HELP()}; + + /** + * List of command line arguments. Thing that are not flags, i.e. do not begin with -, will be stored here + */ + std::vector arguments_; + + std::string application_name_; + std::string description_; + bool require_arguments_; + struct argparse argparse_; + std::vector> allowed_arguments_; + + /** + * Callback function for string options. + * + * @param parser Argument parser object. + * @param flag Argument option structure. + * @return 0 on success, non-zero otherwise. + */ + static int opt_string_callback(struct argparse *, const struct argparse_option *); + + /** + * Callback function for boolean options. + * + * @param parser Argument parser object. + * @param flag Argument option structure. + * @return 0 on success, non-zero otherwise. + */ + static int opt_boolean_callback(struct argparse *, const struct argparse_option *); +}; + +#endif // INCLUDE_ARGPARSER_ARGPARSER_HPP_ diff --git a/src/example_cpp/backtracer/CMakeLists.txt b/src/example_cpp/backtracer/CMakeLists.txt new file mode 100644 index 0000000..d6eafec --- /dev/null +++ b/src/example_cpp/backtracer/CMakeLists.txt @@ -0,0 +1,9 @@ +file(GLOB CPP_FILES CONFIGURE_DEPENDS *.cpp) +file(GLOB HPP_FILES CONFIGURE_DEPENDS *.hpp) + +target_sources( + example_cpp + PRIVATE ${CPP_FILES} + PUBLIC ${HPP_FILES}) + +target_include_directories(example_cpp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/example_cpp/backtracer/backtracer.cpp b/src/example_cpp/backtracer/backtracer.cpp new file mode 100644 index 0000000..f4b2092 --- /dev/null +++ b/src/example_cpp/backtracer/backtracer.cpp @@ -0,0 +1,108 @@ + +#include "backtracer.hpp" +#include "backtrace.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Backtracer *Backtracer::instance = nullptr; + +Backtracer *Backtracer::get_instance() { + if (instance == nullptr) + instance = new Backtracer(); + return instance; +} + +Backtracer::Backtracer() : backtrace_state(nullptr) { initialize_backtrace(); } +Backtracer::~Backtracer() { delete instance; } + +void Backtracer::initialize_backtrace() { + backtrace_state = backtrace_create_state(nullptr, 0, backtrace_error_callback_create, nullptr); + if (!backtrace_state) { + std::cerr << "Failed to initialize backtrace engine" << std::endl; + std::abort(); + } +} + +void Backtracer::capture_backtrace() { + if (backtrace_state) { + backtrace_full(backtrace_state, 0, backtrace_callback, backtrace_error_callback, nullptr); + } else { + std::cerr << "Backtrace state is not initialized" << std::endl; + } +} + +void Backtracer::backtrace_error_callback_create(void *data, const char *msg, int errnum) { + std::cerr << "Error " << errnum << "occurred when initializing the stacktrace: " << msg << std::endl; +} + +int Backtracer::backtrace_callback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function) { + if (function && filename) { + std::cerr << "#" << reinterpret_cast(pc); + std::cerr << " - " << demangle(function); + std::cerr << " at " << filename << " (line " << lineno << ")" << std::endl; + } + return 0; +} + +void Backtracer::backtrace_error_callback(void *data, const char *msg, int errnum) { + std::cerr << "Error " << errnum << " occurred when getting the stacktrace: " << msg << std::endl; +} + +void Backtracer::signal_handler_callback(int signum) { + printf("Error signal %s caught!\n", strsignal(signum)); + Backtracer::get_instance()->capture_backtrace(); + _exit(signum); +} + +void Backtracer::register_signal_handlers() { + constexpr int n_signals = 7; + + // Program Error Signals + // https://www.gnu.org/software/libc/manual/html_node/Program-Error-Signals.html + int signals_to_backtrace[n_signals] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS}; + + // Blocking signals with set + sigset_t block_mask; + sigemptyset(&block_mask); + sigprocmask(SIG_BLOCK, &block_mask, NULL); + for (size_t i = 0; i < n_signals; i++) { + sigaddset(&block_mask, signals_to_backtrace[i]); + } + + struct sigaction sigHandler; + memset(&sigHandler, 0, sizeof(sigHandler)); + sigHandler.sa_handler = signal_handler_callback; + sigHandler.sa_mask = block_mask; + sigHandler.sa_flags = 0; + for (size_t i = 0; i < n_signals; i++) { + sigaction(signals_to_backtrace[i], &sigHandler, NULL); + } +} + +std::string Backtracer::demangle(const std::string &name) { + int status = 0; + char *demangledName = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status); + std::string result; + + if (status == 0 && demangledName) { + result = std::string(demangledName); + free(demangledName); + } else { + result = name; + } + + if (result.empty() || result.back() != ')') { + result += "()"; + } + + return result; +} diff --git a/src/example_cpp/backtracer/backtracer.hpp b/src/example_cpp/backtracer/backtracer.hpp new file mode 100644 index 0000000..2871ea9 --- /dev/null +++ b/src/example_cpp/backtracer/backtracer.hpp @@ -0,0 +1,37 @@ +#ifndef INCLUDE_BACKTRACER_BACKTRACER_HPP_ +#define INCLUDE_BACKTRACER_BACKTRACER_HPP_ + +#include +#include + +class Backtracer { +public: + static Backtracer *get_instance(); + + void capture_backtrace(); + void register_signal_handlers(); + +private: + static Backtracer *instance; + + Backtracer(); + + ~Backtracer(); + + // Deleted copy constructor and assignment operator + Backtracer(const Backtracer &) = delete; + Backtracer &operator=(const Backtracer &) = delete; + + struct backtrace_state *backtrace_state; + + static int backtrace_callback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function); + + static void backtrace_error_callback(void *data, const char *msg, int errnum); + static void backtrace_error_callback_create(void *, const char *msg, int errnum); + static void signal_handler_callback(int signum); + static std::string demangle(const std::string& name); + + void initialize_backtrace(); +}; + +#endif // INCLUDE_BACKTRACER_BACKTRACER_HPP_ diff --git a/src/example_cpp/common.h b/src/example_cpp/common.h new file mode 100644 index 0000000..5a3c5e9 --- /dev/null +++ b/src/example_cpp/common.h @@ -0,0 +1,16 @@ +#ifndef INCLUDE_SRC_COMMON_H_ +#define INCLUDE_SRC_COMMON_H_ + +#ifdef __GNUC__ +#define UNUSED(x) UNUSED_##x __attribute__((__unused__)) +#else +#define UNUSED(x) UNUSED_##x +#endif + +#ifdef __GNUC__ +#define UNUSED_FUNCTION(x) __attribute__((__unused__)) UNUSED_##x +#else +#define UNUSED_FUNCTION(x) UNUSED_##x +#endif + +#endif // INCLUDE_SRC_COMMON_H_ diff --git a/src/example_cpp/main.cpp b/src/example_cpp/main.cpp new file mode 100644 index 0000000..0421d90 --- /dev/null +++ b/src/example_cpp/main.cpp @@ -0,0 +1,17 @@ +#include "backtracer.hpp" +#include "common.h" +#include "version.hpp" +#include +#include + +void f3(char UNUSED(c) = 3) { abort(); } +void f2(float b = 2) { f3(); } +void f1(int a = 1) { f2(); } + + +int main(int UNUSED(argc), char *UNUSED(argv[])) { + Backtracer* backtracer = Backtracer::get_instance(); + backtracer->register_signal_handlers(); + printf("Example CPP Version %s\n", VERSION); + f1(); +} diff --git a/src/example_cpp/version.hpp b/src/example_cpp/version.hpp new file mode 100644 index 0000000..883b2ae --- /dev/null +++ b/src/example_cpp/version.hpp @@ -0,0 +1,15 @@ +#ifndef INCLUDE_EXAMPLE_CPP_VERSION_H_ +#define INCLUDE_EXAMPLE_CPP_VERSION_H_ + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +#define VERSION TOSTRING(EXAMPLE_CPP_VERSION_MAJOR) "." TOSTRING(EXAMPLE_CPP_VERSION_MINOR) "." TOSTRING(EXAMPLE_CPP_VERSION_PATCH) + +#define EXAMPLE_CPP_VERSION_MAJOR 1 +#define EXAMPLE_CPP_VERSION_MINOR 2 +#define EXAMPLE_CPP_VERSION_PATCH 3 +#define EXAMPLE_CPP_COMMIT_HASH cd01d7d8d36862bc00ae88f7cd185c352a7b7eda +#define EXAMPLE_CPP_BUILD_TIMESTAMP 1729814020 + +#endif // INCLUDE_EXAMPLE_CPP_VERSION_H_