监控游戏c/c++的崩溃的解决方案

对于无法复现的bug,bugly提供的信息很有限,而且大部分还都是native层的,lua/js脚本层的信息非常少,不利于我们修复问题。

该教程就是实现一个定制版本的bugly,当游戏发生异常时,尽可能多的收集lua/js脚本层的信息,更详细的帮助我们分析定位bug。

signal

当产生崩溃时系统会向当前进程中发送一个信号量,表示进程异常。我们可以拦截这个信号量,收集相关的信息,上传到服务器,方便我们后续排查。

c 复制代码
void (*signal(int sig, void (*func)(int)))(int)
信号 含义
SIGABRT (Signal Abort) 程序异常终止。
SIGFPE (Signal Floating-Point Exception) 算术运算出错,如除数为 0 或溢出(不一定是浮点运算)。
SIGILL (Signal Illegal Instruction) 非法函数映象,如非法指令,通常是由于代码中的某个变体或者尝试执行数据导致的。
SIGINT (Signal Interrupt) 中断信号,如 ctrl-C,通常由用户生成。
SIGTERM (Signal Terminate) 发送给本程序的终止请求信号。
SIGSEGV (Signal Segmentation Violation) 非法访问存储器,如访问不存在的内存单元。

看到了熟悉的SIGSEGV,bugly收集到的大部分崩溃都是这个

案例

c 复制代码
#include <curl/curl.h>
#include <signal.h>
static size_t writeData(void* ptr, size_t size, size_t nmemb, void* stream)
{
    std::vector<char>* recvBuffer = (std::vector<char>*)stream;
    size_t sizes = size * nmemb;

    // add data to the end of recvBuffer
    // write data maybe called more than once in a single request
    recvBuffer->insert(recvBuffer->end(), (char*)ptr, (char*)ptr + sizes);

    return sizes;
}
// 使用curl同步接口上传崩溃数据
void uploadData(const std::string& uploadUrl, const std::string& data)
{
    curl_global_init(CURL_GLOBAL_ALL);
    std::vector<char> stream;
    CURL* curl = curl_easy_init();
    curl_slist* _headers = nullptr;
    _headers = curl_slist_append(_headers, "Content-Type:text/plain");
    if(curl){
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, _headers);
        curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
        curl_easy_setopt(curl, CURLOPT_POST, 1L); // post
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.size());
        curl_easy_setopt(curl, CURLOPT_URL, uploadUrl.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &stream);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData);

        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
        curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
        CURLcode res = curl_easy_perform(curl);
        if (res != CURLE_OK)
        {
            CCLOG("%s", curl_easy_strerror(res));
        }
        else
        {
            CCLOG("%s", stream.data());
        }
        curl_easy_cleanup(curl);
        curl_slist_free_all(_headers);
    }
    curl_global_cleanup();
}
void uploadCrashInfo()
{
    std::string data = getStackInfo(); // 收集的崩溃数据,至于详细的数据结构,可以自行设计
    std::string url = "https://data-box.onrender.com/log/add-log-string"; // 日志服务器
    uploadData(url, data); // 必须是同步函数,异步上传无法保证正确将数据上传到服务器。
}
void sigsegv_handler(int signum)
{
    std::string name = "unknown";
    switch (signum){
    case SIGSEGV: name = "SIGSEGV"; break;
    case SIGABRT: name = "SIGABRT"; break;
    case SIGFPE: name = "SIGFPE"; break;
    case SIGILL: name = "SIGILL"; break;
    case SIGINT: name = "SIGINT"; break;
    case SIGTERM: name = "SIGTERM"; break;
    default: break;
    }
    // 可以在这里收集一些堆栈数据和堆栈变量相关数据,上传到服务器
    uploadCrashInfo();
    signal(signum, SIG_DFL); // 将异常处理转交给系统,否则会继续触发sigsegv_handler
}
void main()
{
    signal(SIGSEGV, sigsegv_handler);
    int* p = nullptr; 
    *p = 1;// 主动触发崩溃
}

收集崩溃的堆栈数据

lua

lua_getstack,lua_getinfo获取每一层堆栈,通过lua_getlocal获取每层堆栈的变量情况,具体代码如下:

c++ 复制代码
std::string getStackInfo()
{
    std::string ret;
    lua_State* state = cocos2d::LuaEngine::getInstance()->getLuaStack()->getLuaState();
    if (state)
    {
        lua_Debug ar;
        int level = 0;
        std::string lua = "";
        int deep = 2;
        while (lua_getstack(state, level, &ar))
        {
            // lua的调用堆栈信息
            lua_getinfo(state, "nSl", &ar);
            std::ostringstream ss;
            if (ar.name)
            {
                ss << "<" << ar.name << "> at ";
            }
            else if (ar.what)
            {
                ss << "[" << ar.what << "] at ";
            }
            ss << (ar.source ? ar.source : "?") << ":" << ar.currentline << std::endl;
            ret += ss.str();
            
            // lua堆栈的变量信息
            std::ostringstream vs;
            if (deep > 0)
            {
                deep--;
                int i = 0;
                while (++i)
                {
                    const char* name = lua_getlocal(state, &ar, i);
                    if (!name)
                    {
                        break;
                    }
                    if (name[0] != '(')
                    {
                        int type = lua_type(state, -1);
                        vs << varPrefix << name << ":";
                        switch (type)
                        {
                        case LUA_TNUMBER:
                            vs << lua_tonumber(state, -1);
                            break;
                        case LUA_TSTRING:
                            vs << lua_tostring(state, -1);
                            break;
                        case LUA_TBOOLEAN:
                            vs << lua_toboolean(state, -1);
                            break;
                        case LUA_TTABLE:
                        {
                            vs << "table";
                            break;
                        }
                        case LUA_TNIL:
                            vs << "nil";
                            break;
                        case LUA_TFUNCTION:
                            vs << "<function>" << lua_topointer(state, -1);
                            break;
                        case LUA_TUSERDATA:
                            vs << "<userdata>";
                            break;
                        default:
                            vs << "<type" << type<<">";
                            break;
                        }
                        vs << endl;
                    }
                    lua_pop(state, 1);
                }
            }
            ret += ss.str();
            ++level;
        }
    }
    return ret;
}

js

js引擎一般都会提供类似上边lua的接口,我没有用到js引擎,所以这里不再赘述。

日志服务器

我自己编写了一个简单的可用日志服务器,方便测试

上传地址为:https://data-box.onrender.com/log/add-log-string,直接发送post数据即可。

上传的日志可以在 https://tidys.gitee.io/data-box-viewer/index.html进行查看。

以上都是在详细阐述原理,具体移植到项目中,还需要自行适配。

相关推荐
小高007几秒前
🔥「从零到一」我用 Node BFF 手撸一个 Vue3 SSR 项目(附源码)
前端·javascript·vue.js
SailingCoder2 分钟前
AI 流式对话该怎么做?SSE、fetch、axios 一次讲清楚
前端·javascript·人工智能·ai·node.js
hxjhnct7 分钟前
Vue 实现多行文本“展开收起”
前端·javascript·vue.js
橙子的AI笔记8 分钟前
2025年全球最受欢迎的JS鉴权框架Better Auth,3分钟带你学会
前端·ai编程
百锦再8 分钟前
Vue大屏开发全流程及技术细节详解
前端·javascript·vue.js·微信小程序·小程序·架构·ecmascript
独自破碎E13 分钟前
你知道Spring Boot配置文件的加载优先级吗?
前端·spring boot·后端
一树山茶15 分钟前
Vue变化响应
前端
黑土豆17 分钟前
一次真实的流式踩坑:fetchEventSource vs fetch流读取的本质区别
前端·javascript·ai编程
代码猎人20 分钟前
substring和substr有什么区别
前端