前段时间我们发表过类似得 Try-Catch 实现,但是该实现本身是存在局限性的,因为它们不能无视 SIGSEGV,SIGSYS,SIGTRAP等崩溃信号,对于某些3RD代码根本无法处理(这些代码没有源代码只有库,总不可能去静态反编译在改汇编)或者是短时间无法解决得内存BUG、例如毫无问题得代码在ARM平台会发生结构内存对齐问题(SIGBUS)导致强行崩溃(仅限于恶意崩溃,即运行相同代码+结构上万次以后才会发生SIGBUS提示内存对齐故障)别认为不可能,这在ARM上面存在潜在发生得可能性,解决办法查汇编查代码对所有的结构进行对齐检查,等等。
本文代码对于下文链接得扩展:
C/C++ 11 try-catch-finally 扩展(函数式)_liulilittle的博客-CSDN博客_c++ finally
假设...我们希望:类似以下的代码不要导致程序停止工作那么应该怎么处理?
int64_t llPtr = 0;
char* byPtr = (char*)llPtr;
*byPtr = rand() & 0xff;
... ...
printf("%s\n", "OK!");
我们知道上述代码务必会遭遇 “SIGSEGV” 导致崩溃并且产生 dump 文件/信息(若设置)那么我们不希望这里出现故障应该怎么做?
SIGSEGV 是可以被认为捕获得,有一个偷懒得宏定义叫做 SIG_IGN(忽略信号)但要注意,它的确忽略信号,确保程序不会因为崩溃信号停止工作,但这是有限度的,类似 SIGSEGV、SIGBUS 等信号是无效的,不要考虑这种没有意义得东西,就算要放弃某个信号也不应该是这么粗放式得操作,SIG_IGN 这类型仅适用于程序主动得 raise 得崩溃信号,比如:SIG_PIPE。
OK,那么可以适用最新改进得:“Try-Catch-Finally” 进行处理,但发生异常时可能潜在导致内存泄漏问题。
Try([] {
int64_t llPtr = 0;
char* byPtr = (char*)llPtr;
*byPtr = rand() & 0xff;
}).Catch<std::exception>([] (std::exception& e) {
printf("%s\n", "Error!");
});
printf("%s\n", "OK!");
以上代码是可以正确执行到 OK!部分,并且不会发生内存泄漏问题,但一旦我们在 lambda 内操作关于内存相关的东西就有可能发生异常,比如在函数内复制了一个 shared_ptr 并且在析构以前出现异常,那么 shared_ptr 得引用就无法在减少导致内存资源泄露,那么类似得好办法是不要使用 shared_ptr,或者在可以确定不会发生异常得情况下使用,如果你真不确定还非得头铁用得话,有个办法:weak_ptr 可能很适合您。
潜在发生异常得 lambda 函数是可以捕获类似 shared_ptr 之类的东西,它们并不会引起不能调用捕获 shared_ptr 得析构函数,所以是是线上内存安全的,但是 lambda 内部操作不是内存安全的,这个必须要说清楚!
Try.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <memory>
#include <exception>
#include <mutex>
#include <unordered_map>
#ifndef MAX_STACK_FRAME
#define MAX_STACK_FRAME 10
#endif
namespace My {
class Try {
private:
typedef sigjmp_buf Context;
struct Stack {
private:
mutable std::shared_ptr<Context> rbp;
mutable Context* rsp;
public:
Stack();
void New() const;
void Release() const;
inline Context* Ptr() const {
return this->rbp.get();
}
inline Context* Push() const {
Context* max = this->rbp.get() + MAX_STACK_FRAME;
if (this->rsp >= max) {
return NULL;
}
return this->rsp++;
}
inline Context* Pop() const {
if (this->rsp <= this->rbp.get()) {
return NULL;
}
return --this->rsp;
}
inline Context* Peek() const {
Context* max = this->rbp.get() + MAX_STACK_FRAME;
if (this->rsp > max || this->rsp <= this->rbp.get()) {
return NULL;
}
return this->rsp - 1;
}
};
typedef std::unordered_map<int, Stack> StackTable;
friend class Hosting;
friend class __TRY_SEH_SIGNAL__;
public:
inline Try(const std::function<void()>& block)
: ___try(block) {
if (!block) {
throw std::runtime_error("The block part of the try is not allowed to be Null References");
}
}
inline ~Try() {
___try = NULL;
___catch = NULL;
___finally = NULL;
}
public:
template<typename TException>
inline Try& Catch() const {
return Catch<TException>([](TException& e) {});
}
template<typename TException>
inline Try& Catch(const std::function<void(TException& e)>& block) const {
return Catch<TException>(block, NULL);
}
template<typename TException>
inline Try& Catch(const std::function<void(TException& e)>& block, const std::function<bool(TException& e)>& when) const {
if (!block) {
throw std::runtime_error("The block part of the try-catch is not allowed to be Null References");
}
std::function<bool(std::exception*)> previous = ___catch;
___catch = [block, previous, when](std::exception* e) {
if (previous) {
if (previous(e)) {
return true;
}
}
TException* exception = dynamic_cast<TException*>(e);
if (!exception) {
return false;
}
if (when) {
if (!when(*exception)) {
return false;
}
}
if (block) {
block(*exception);
}
return true;
};
return const_cast<Try&>(*this);
}
inline Try& Finally(const std::function<void()>& block) const {
___finally = block;
return const_cast<Try&>(*this);
}
inline void Invoke() const {
Context* stack_context = Try::PushContext();
std::exception* stack_exception = NULL;
try {
if (!stack_context) {
___try();
}
else {
if (sigsetjmp(*stack_context, 1) == 0) {
___try();
}
else {
stack_exception = &Try::___exception;
if (___catch) {
if (___catch(stack_exception)) {
stack_exception = NULL;
}
}
}
}
}
catch (std::exception& throwable) {
stack_exception = &throwable;
if (___catch) {
if (___catch(stack_exception)) {
stack_exception = NULL;
}
}
}
catch (...) {
stack_exception = &Try::___exception;
if (___catch) {
if (___catch(stack_exception)) {
stack_exception = NULL;
}
}
}
if (___finally) {
___finally();
}
if (stack_context) {
Try::PopContext();
}
if (stack_exception) {
throw *stack_exception;
}
}
#ifndef ANDROID
static void PrintStackTrace();
#endif
private:
static Context* PushContext();
static Context* PopContext();
static Context* PeekContext();
static void AllocStack(bool important);
static void ReleaseStack();
private:
mutable std::function<void()> ___try;
mutable std::function<bool(std::exception*)> ___catch;
mutable std::function<void()> ___finally;
static std::runtime_error ___exception;
static std::mutex ___syncobj;
static StackTable ___stack;
static struct sigaction ___osas[15];
};
}
Try.cpp
#include <stdio.h>
#include <signal.h>
#include <limits.h>
#include <sys/file.h>
#ifndef ANDROID
#include <unwind.h>
#include <execinfo.h>
#include <sys/resource.h>
#endif
#include <My/Try.h>
#include <My/Environment.h>
#ifndef ANDROID
#define MAX_BACKTRACE_SIZE 100
#endif
namespace My {
class __TRY_SEH_SIGNAL__ {
public:
inline __TRY_SEH_SIGNAL__() {
memset(Try::___osas, 0, sizeof(Try::___osas));
/*retrieve old and set new handlers*/
/*restore prevouis signal actions*/
#ifdef ANDROID
Watch(35, 0); // FDSCAN(SI_QUEUE)
#endif
Watch(SIGBUS, 1);
Watch(SIGPIPE, 2);
Watch(SIGFPE, 3);
Watch(SIGSEGV, 4);
Watch(SIGILL, 5);
Watch(SIGTRAP, 6);
Watch(SIGSYS, 7);
Watch(SIGQUIT, 8);
Watch(SIGIOT, 9);
Watch(SIGUSR1, 10);
Watch(SIGUSR2, 11);
Watch(SIGXCPU, 12);
Watch(SIGXFSZ, 13);
Watch(SIGSTKFLT, 14);
}
inline ~__TRY_SEH_SIGNAL__() {
struct sigaction* osas = Try::___osas;
/*restore prevouis signal actions*/
#ifdef ANDROID
Unwatch(35, 0); // FDSCAN(SI_QUEUE)
#endif
Unwatch(SIGBUS, 1);
Unwatch(SIGPIPE, 2);
Unwatch(SIGFPE, 3);
Unwatch(SIGSEGV, 4);
Unwatch(SIGILL, 5);
Unwatch(SIGTRAP, 6);
Unwatch(SIGSYS, 7);
Unwatch(SIGQUIT, 8);
Unwatch(SIGIOT, 9);
Unwatch(SIGUSR1, 10);
Unwatch(SIGUSR2, 11);
Unwatch(SIGXCPU, 12);
Unwatch(SIGXFSZ, 13);
Unwatch(SIGSTKFLT, 14);
}
public:
inline int Watch(int signo, int index) {
struct sigaction sa;
struct sigaction* osas = Try::___osas;
memset(&sa, 0, sizeof(sa));
/*init new handler struct*/
sa.sa_handler = [] (int signo) {
Try::Context* context = Try::PeekContext();
if (context) {
siglongjmp(*context, 1);
} else {
#ifndef ANDROID
Try::PrintStackTrace();
#endif
signal(signo, SIG_DFL);
raise(signo);
}
};
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
return sigaction(signo, &sa, &osas[index]);
}
inline int Unwatch(int signo, int index) {
struct sigaction* osas = Try::___osas;
return sigaction(signo, &osas[index], NULL);
}
} g__TRY_SEH_SIGNAL__;
std::unordered_map<int, Try::Stack> Try::___stack;
std::mutex Try::___syncobj;
std::runtime_error Try::___exception("Received an error signal thrown by the system or application during code execution.");
struct sigaction Try::___osas[15];
Try::Stack::Stack()
: rbp(NULL)
, rsp(NULL) {
this->New();
}
void Try::Stack::New() const {
this->rbp = make_shared_alloc<Context>(MAX_STACK_FRAME);
this->rsp = this->rbp.get();
}
void Try::Stack::Release() const {
this->rbp = NULL;
this->rsp = NULL;
}
Try::Context* Try::PushContext() {
std::lock_guard<std::mutex> scope(Try::___syncobj);
return Try::___stack[Hosting::GetCurrentThreadId()].Push();
}
Try::Context* Try::PopContext() {
std::lock_guard<std::mutex> scope(Try::___syncobj);
return Try::___stack[Hosting::GetCurrentThreadId()].Pop();
}
Try::Context* Try::PeekContext() {
std::lock_guard<std::mutex> scope(Try::___syncobj);
return Try::___stack[Hosting::GetCurrentThreadId()].Peek();
}
void Try::AllocStack(bool important) {
std::lock_guard<std::mutex> scope(Try::___syncobj);
int id = Hosting::GetCurrentThreadId();
StackTable::iterator kv = Try::___stack.find(id);
if (kv != Try::___stack.end()) {
const Stack& stack = kv->second;
if (important || !stack.Ptr()) {
stack.New();
}
}
else {
Stack stack;
Try::___stack.insert(std::make_pair(id, stack));
}
}
void Try::ReleaseStack() {
std::lock_guard<std::mutex> scope(Try::___syncobj);
StackTable::iterator kv = Try::___stack.find(Hosting::GetCurrentThreadId());
if (kv != Try::___stack.end()) {
const Stack& stack = kv->second;
stack.Release();
Try::___stack.erase(kv);
}
}
#ifndef ANDROID
void Try::PrintStackTrace() {
char maps_string[1000] = "0";
sprintf(maps_string, "cat /proc/%d/maps", getpid());
system(maps_string);
{
printf("\nStack Addresses:\n");
_Unwind_Backtrace([](struct _Unwind_Context* context, void* arg) -> _Unwind_Reason_Code {
uintptr_t pc = _Unwind_GetIP(context);
if (pc) {
printf(" at pc:0x%llx\n", (unsigned long long)pc);
}
return _URC_NO_REASON;
}, 0); // _Unwind_Reason_Code rc, rc == _URC_END_OF_STACK ? 0 : -1;
}
void* stack_frames[MAX_BACKTRACE_SIZE];
int nptrs = backtrace(stack_frames, MAX_BACKTRACE_SIZE);
{
printf("\nStack Frame: %d\n", nptrs);
}
char** frame_strings = backtrace_symbols(stack_frames, nptrs);
if (NULL != frame_strings) {
for (int i = 0; i < nptrs; i++) {
printf(" at [%02d] %s\n", i, frame_strings[i]);
}
free(frame_strings);
}
}
#endif
}