diff --git a/main/example/04_initiate_runtime_error/.gitignore b/main/example/04_initiate_runtime_error/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ae6321f86df48737f7a584bc524e879d019a5b2d --- /dev/null +++ b/main/example/04_initiate_runtime_error/.gitignore @@ -0,0 +1 @@ +/sample diff --git a/main/example/04_initiate_runtime_error/Makefile b/main/example/04_initiate_runtime_error/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..494b8430b398d924352009575e3b2bbcf9a87066 --- /dev/null +++ b/main/example/04_initiate_runtime_error/Makefile @@ -0,0 +1,32 @@ +include ../build_env.mk + +.PHONY : all clean distclean + +TARGET := sample +CXXFLAGS += -ggdb -DLOG_MODULE_ID='"demo"' +LDFLAGS += -L.. \ + -ltbox_main \ + -ltbox_log \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_util \ + -ltbox_base \ + -lpthread \ + -rdynamic \ + +CXXFLAGS += -fsanitize=address -fno-omit-frame-pointer +LDFLAGS += -fsanitize=address -static-libasan +OBJECTS := main.o + +all : $(TARGET) + +$(TARGET): $(OBJECTS) + $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS) + +clean: + rm -rf $(OBJECTS) + +distclean: clean + rm -rf $(TARGET) diff --git a/main/example/04_initiate_runtime_error/README.txt b/main/example/04_initiate_runtime_error/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..2df846f189205ab3505eb6220fb95e6bc3951e1e --- /dev/null +++ b/main/example/04_initiate_runtime_error/README.txt @@ -0,0 +1 @@ +本示例模拟程序出现异常,使程序打印调用栈 diff --git a/main/example/04_initiate_runtime_error/main.cpp b/main/example/04_initiate_runtime_error/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7ced007d84366fa0d726f0055b5117929c1e9703 --- /dev/null +++ b/main/example/04_initiate_runtime_error/main.cpp @@ -0,0 +1,30 @@ +#include + +namespace tbox { +namespace main { + +void RegisterApps(Module &apps, Context &ctx) +{ + static_cast(nullptr)[0] = 0; +} + +std::string GetAppDescribe() +{ + return "One app sample"; +} + +std::string GetAppBuildTime() +{ + return __DATE__ " " __TIME__; +} + +void GetAppVersion(int &major, int &minor, int &rev, int &build) +{ + major = 0; + minor = 0; + rev = 1; + build = 0; +} + +} +} diff --git a/main/signal.cpp b/main/signal.cpp index 4d0bb452fe35d50c0c75b51d1590243ef7f86fc1..fa6fa5cd1ea97a1fde80d07d91e3e6d101188234 100644 --- a/main/signal.cpp +++ b/main/signal.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace tbox { namespace main { @@ -14,35 +15,18 @@ extern std::function error_exit_func; //!< 出错异常退出前要做 namespace { -//! 打印调用栈 -void PrintCallStack() -{ - const int buffer_size = 1024; - - void *buffer[buffer_size]; - int n = backtrace(buffer, buffer_size); - - std::stringstream ss; - char **symbols = backtrace_symbols(buffer, n); - if (symbols != NULL) { - for (int i = 0; i < n; i++) - ss << '[' << i << "] " << symbols[i] << std::endl; - free(symbols); - } else { - ss << "" << std::endl; - } - - LogFatal("\n-----call stack-----\n%s", ss.str().c_str()); -} - //! 处理程序运行异常信号 void OnErrorSignal(int signo) { + const std::string &stack_str = util::Backtrace::DumpCallStack(32, 5); + LogFatal("Receive signal %d", signo); - PrintCallStack(); - if (error_exit_func) + LogFatal("\n-----call stack-----\n%s", stack_str.c_str()); + + if (error_exit_func) //! 执行一些善后处理 error_exit_func(); - exit(EXIT_FAILURE); + + ::exit(EXIT_FAILURE); } void OnWarnSignal(int signo) diff --git a/sample/Makefile b/sample/Makefile index c9fb70080d7865999b9fd04e42a5994487621959..b22e4d3f66fcba8580415077ebd24d413c29f2c4 100644 --- a/sample/Makefile +++ b/sample/Makefile @@ -20,7 +20,8 @@ LDFLAGS += \ -ltbox_log \ -ltbox_util \ -ltbox_base \ - -lpthread + -lpthread \ + -rdynamic TEST_LDFLAGS += $(LDFLAGS) diff --git a/util/Makefile b/util/Makefile index a1bad14d44a8a0e0f34f84f222348d3a658326a3..797633b004400c443c0619d01c0eef2a4c076c67 100644 --- a/util/Makefile +++ b/util/Makefile @@ -14,6 +14,7 @@ HEAD_FILES = \ time_counter.h \ state_machine.h \ async_pipe.h \ + backtrace.h \ CPP_SRC_FILES = \ pid_file.cpp \ @@ -26,6 +27,7 @@ CPP_SRC_FILES = \ time_counter.cpp \ state_machine.cpp \ async_pipe.cpp \ + backtrace.cpp \ CXXFLAGS := -DLOG_MODULE_ID='"util"' $(CXXFLAGS) diff --git a/util/backtrace.cpp b/util/backtrace.cpp new file mode 100644 index 0000000000000000000000000000000000000000..de9dcba7399fd77b119aeae010d6042fdc16150c --- /dev/null +++ b/util/backtrace.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include +#include +#include "backtrace.h" + +namespace tbox { +namespace util { + +Backtrace& Backtrace::instance() +{ + static Backtrace ins; + return ins; +} + +void Backtrace::submit(std::initializer_list signals) +{ + /// catch segment fault action + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = Backtrace::HandleErrorSignal; + sa.sa_flags = SA_SIGINFO; + + for (auto iter = signals.begin(); iter != signals.end(); ++iter) + sigaction(*iter, &sa, nullptr); +} + +Backtrace& Backtrace::maxFrames(unsigned int max) +{ + max_frames_ = max; + return *this; +} + +Backtrace& Backtrace::skipFrames(unsigned int skip) +{ + skip_frames_ = skip; + return *this; +} + +void Backtrace::HandleErrorSignal(int signal_number, siginfo_t *signal_info, void *arg) +{ + (void) signal_info; + (void) arg; + + std::cerr << "catch signal:" << signal_number << ", call-stack dumped:" << std::endl; + std::string dumpinfo = Backtrace::DumpCallStack(Backtrace::instance().max_frames_, Backtrace::instance().skip_frames_); + std::cerr << dumpinfo << std::endl; + + _exit(signal_number); +} + + +std::string Backtrace::DumpCallStack(const unsigned int max_frames, const unsigned int skip_frames) +{ + char buf[1024]; + Dl_info info; + + void *callstack[max_frames]; + std::ostringstream oss; + + unsigned int number_frames = ::backtrace(callstack, max_frames); + char **symbols = ::backtrace_symbols(callstack, number_frames); + + constexpr const char *BACKTRACE_FORMAT = "[%d]: %s <%p>"; // callstack number | address | symbols + + for (unsigned int i = skip_frames; i < number_frames; ++i) { + /// try to translate an address to symbolic information + if (dladdr(callstack[i], &info)) { + int status = -1; + char *demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status); + snprintf(buf, sizeof(buf), BACKTRACE_FORMAT, i, (status == 0 ? demangled : info.dli_sname), callstack[i]); + if (demangled != nullptr) + free(demangled); + } else { + snprintf(buf, sizeof(buf), BACKTRACE_FORMAT, i, (symbols != nullptr ? symbols[i] : "null"), callstack[i]); + } + + oss << buf << std::endl; + } + + if (symbols != nullptr) + free(symbols); + + if (number_frames >= max_frames) + oss << "[truncated]" << std::endl; + + return oss.str(); +} + +} +} diff --git a/util/backtrace.h b/util/backtrace.h new file mode 100644 index 0000000000000000000000000000000000000000..93ebc84f0e3d86af9d55619721244cc2a20c49bb --- /dev/null +++ b/util/backtrace.h @@ -0,0 +1,50 @@ +#ifndef _BACKTRACE_H +#define _BACKTRACE_H + +#include +#include + +namespace tbox { +namespace util { + +/* + * @brief - Aims to capture signals and formart human-readable backtrace info if + * recved SIGSEGV signal + * @note - You should add -ldl and -rdynamic to your link parameters + * + * # Examples: + * + * Backtrace::instance() + * .maxFrames(24) // if not set, default is 32 + * .skipFrames(2) // if not set, default is 1 + * .submit({SIGSEGV, SIGABRT}); // submit SIGSEGV and SIGABRT as error + */ +class Backtrace { + public: + static Backtrace& instance(); + + public: + Backtrace& maxFrames(unsigned int max); // default is 32 + Backtrace& skipFrames(unsigned int skip); // default is 1(will note skip) + + void submit(std::initializer_list signals); + + /* + * @brief Generate a human-readable backtrace string + * + * @return backtrace string + */ + static std::string DumpCallStack(const unsigned int max_frames, const unsigned int skip = 1); + + private: + static void HandleErrorSignal(int signal_number, siginfo_t *signal_info, void *arg); + + private: + unsigned int max_frames_ = 32; + unsigned int skip_frames_ = 1; +}; + +} +} + +#endif // _BACKTRACE_H diff --git a/util/example/backtrace/.gitignore b/util/example/backtrace/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ae6321f86df48737f7a584bc524e879d019a5b2d --- /dev/null +++ b/util/example/backtrace/.gitignore @@ -0,0 +1 @@ +/sample diff --git a/util/example/backtrace/Makefile b/util/example/backtrace/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..6b5404849aa8ee8aeae35a2e484bff7f1de274a5 --- /dev/null +++ b/util/example/backtrace/Makefile @@ -0,0 +1,18 @@ +include ../build_env.mk + +CXXFLAGS += -ggdb -DLOG_MODULE_ID='"demo"' +LDFLAGS += -L.. -ltbox_util + +CXXFLAGS += -fsanitize=address -fno-omit-frame-pointer +LDFLAGS += -fsanitize=address -static-libasan + +all : sample + +sample: sample.o + $(CXX) -o $@ $^ $(LDFLAGS) -rdynamic + +clean: + rm -rf *.o + +distclean: clean + rm -f sample diff --git a/util/example/backtrace/sample.cpp b/util/example/backtrace/sample.cpp new file mode 100644 index 0000000000000000000000000000000000000000..274e6ad25acd846052e5ad85c1a8f6110ce74d10 --- /dev/null +++ b/util/example/backtrace/sample.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +void DoSomeThing(int deep) +{ + if (deep == 0) + *static_cast(nullptr) = 0; //! 触发异常 + else + DoSomeThing(deep - 1); +} + +int main() { + tbox::util::Backtrace::instance() + .maxFrames(10) + .skipFrames(2) + .submit({SIGINT, SIGSEGV}); + + std::cout << "sleep 5sec, press ctrl+c exit." << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(5)); + + DoSomeThing(40); + return 0; +} diff --git a/util/example/build_env.mk b/util/example/build_env.mk new file mode 100644 index 0000000000000000000000000000000000000000..58481118dd79b3db71ee35ab91baf0bfe01358c0 --- /dev/null +++ b/util/example/build_env.mk @@ -0,0 +1,29 @@ +STAGING_DIR := ../../../.staging + +STAGING_INCLUDE := $(STAGING_DIR)/include +STAGING_LIB := $(STAGING_DIR)/lib + +CCFLAGS := -I$(STAGING_INCLUDE) -I$(CONSTANT_INCLUDE) +CFLAGS := $(CCFLAGS) -std=c99 +CXXFLAGS := $(CCFLAGS) -std=c++11 +LDFLAGS := -L$(STAGING_LIB) +INSTALL_DIR := $(STAGING_DIR) +DESTDIR := $(STAGING_DIR) +prefix := + +TOOLCHAIN_PREFIX := +AR := $(TOOLCHAIN_PREFIX)ar +AS := $(TOOLCHAIN_PREFIX)as +CXX := $(TOOLCHAIN_PREFIX)g++ +CC := $(TOOLCHAIN_PREFIX)gcc +NM := $(TOOLCHAIN_PREFIX)nm +OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy +OBJDUMP := $(TOOLCHAIN_PREFIX)objdump +STRINGS := $(TOOLCHAIN_PREFIX)strings +SSTRIP := $(TOOLCHAIN_PREFIX)sstrip +LSTRIP := $(TOOLCHAIN_PREFIX)lstrip +STRIP := $(TOOLCHAIN_PREFIX)strip + +export STAGING_INCLUDE STAGING_LIB INSTALL_DIR DESTDIR prefix +export AR AS CC NM OBJCOPY OBJDUMP CXX STRIP SSTRIP STRINGS LSTRIP +export CFLAGS CXXFLAGS LDFLAGS