adding cpp example

This commit is contained in:
brenozd
2024-10-24 21:04:00 -03:00
parent f240e70f03
commit ebabcd41d1
12 changed files with 512 additions and 2 deletions

View File

@@ -1,2 +1,2 @@
add_subdirectory(example_c)
# add_subdirectory(example_cpp)
add_subdirectory(example_cpp)

View File

@@ -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_

View File

@@ -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)

View File

@@ -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})

View File

@@ -0,0 +1,109 @@
#include "argparser.hpp"
#include <algorithm>
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<struct argparse_option> 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<std::string> split(const std::string &str, char delimiter) {
std::vector<std::string> 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<std::string *>(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<bool *>(flag->data);
*dst = true;
return 0;
}

View File

@@ -0,0 +1,163 @@
#ifndef INCLUDE_ARGPARSER_ARGPARSER_HPP_
#define INCLUDE_ARGPARSER_ARGPARSER_HPP_
#include <argparse.h>
#include <cstring>
#include <plog/Log.h>
#include <string>
#include <typeinfo>
#include <vector>
/**
* @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<std::vector<std::string>> 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 <typename T> 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<intptr_t>(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<intptr_t>(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<std::string> get_arguments() const { return arguments_; };
private:
/**
* Vector of argparse options. Stores all flags/arguments added through add_flag
*/
std::vector<struct argparse_option> 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<std::string> arguments_;
std::string application_name_;
std::string description_;
bool require_arguments_;
struct argparse argparse_;
std::vector<std::vector<std::string>> 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_

View File

@@ -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})

View File

@@ -0,0 +1,108 @@
#include "backtracer.hpp"
#include "backtrace.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cxxabi.h>
#include <iostream>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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<void *>(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;
}

View File

@@ -0,0 +1,37 @@
#ifndef INCLUDE_BACKTRACER_BACKTRACER_HPP_
#define INCLUDE_BACKTRACER_BACKTRACER_HPP_
#include <stdint.h>
#include <string>
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_

16
src/example_cpp/common.h Normal file
View File

@@ -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_

17
src/example_cpp/main.cpp Normal file
View File

@@ -0,0 +1,17 @@
#include "backtracer.hpp"
#include "common.h"
#include "version.hpp"
#include <stdio.h>
#include <stdlib.h>
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();
}

View File

@@ -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_