setjmp,longjump和stack重建

时间:2016-10-28 17:03:52

标签: c++ callstack longjmp setjmp

通常setjmp和longjmp不关心调用堆栈 - 而是函数只是保留和恢复寄存器。

我想使用setjmp和longjmp,以便保留调用堆栈,然后在不同的执行上下文中恢复

EnableFeature( bool bEnable )
{
if( bEnable )
{
   if( setjmp( jmpBuf ) == 0 )
   {
        backup call stack 
   } else {
        return; //Playback backuped call stack + new call stack
   }
} else {
   restore saved call stack on top of current call stack
   modify jmpBuf so we will jump to new stack ending
   longjmp( jmpBuf )
}

这种方法是否可行 - 有人可以为此编写示例代码吗?

为什么我自己相信它是可行的 - 是因为类似的代码snipet我已编码/原型:

Communication protocol and local loopback using setjmp / longjmp

有两个调用堆栈同时运行 - 彼此独立。

但只是为了帮助您完成这项任务 - 我将为您提供获取本机和托管代码的callstack的功能:

//
//  Originated from: https://sourceforge.net/projects/diagnostic/
//
//  Similar to windows API function, captures N frames of current call stack.
//  Unlike windows API function, works with managed and native functions.
//
int CaptureStackBackTrace2(
    int FramesToSkip,                   //[in] frames to skip, 0 - capture everything.
    int nFrames,                        //[in] frames to capture.
    PVOID* BackTrace                    //[out] filled callstack with total size nFrames - FramesToSkip
)
{
#ifdef _WIN64
    CONTEXT ContextRecord;
    RtlCaptureContext( &ContextRecord );

    UINT iFrame;
    for( iFrame = 0; iFrame < (UINT)nFrames; iFrame++ )
    {
        DWORD64 ImageBase;
        PRUNTIME_FUNCTION pFunctionEntry = RtlLookupFunctionEntry( ContextRecord.Rip, &ImageBase, NULL );

        if( pFunctionEntry == NULL )
        {
            if( iFrame != -1 )
                iFrame--;           // Eat last as it's not valid.
            break;
        }

        PVOID HandlerData;
        DWORD64 EstablisherFrame;
        RtlVirtualUnwind( 0 /*UNW_FLAG_NHANDLER*/,
            ImageBase,
            ContextRecord.Rip,
            pFunctionEntry,
            &ContextRecord,
            &HandlerData,
            &EstablisherFrame,
            NULL );

        if( FramesToSkip > (int)iFrame )
            continue;

        BackTrace[iFrame - FramesToSkip] = (PVOID)ContextRecord.Rip;
    }
#else
    //
    //  This approach was taken from StackInfoManager.cpp / FillStackInfo
    //  http://www.codeproject.com/Articles/11221/Easy-Detection-of-Memory-Leaks
    //  - slightly simplified the function itself.
    //
    int regEBP;
    __asm mov regEBP, ebp;

    long *pFrame = (long*)regEBP;               // pointer to current function frame
    void* pNextInstruction;
    int iFrame = 0;

    //
    // Using __try/_catch is faster than using ReadProcessMemory or VirtualProtect.
    // We return whatever frames we have collected so far after exception was encountered.
    //
    __try {
        for( ; iFrame < nFrames; iFrame++ )
        {
            pNextInstruction = (void*)(*(pFrame + 1));

            if( !pNextInstruction )     // Last frame
                break;

            if( FramesToSkip > iFrame )
                continue;

            BackTrace[iFrame - FramesToSkip] = pNextInstruction;
            pFrame = (long*)(*pFrame);
        }
    }
    __except( EXCEPTION_EXECUTE_HANDLER )
    {
    }

#endif //_WIN64
    iFrame -= FramesToSkip;
    if( iFrame < 0 )
        iFrame = 0;

    return iFrame;
} //CaptureStackBackTrace2

我认为可以修改它以获得实际的堆栈指针(x64 - eSP和x32 ​​ - 已经有一个指针)。

1 个答案:

答案 0 :(得分:2)

从法律上讲,setjmp/longjmp只能用于跳跃&#34;返回&#34;在嵌套的调用序列中。这意味着它永远不需要真正重建&#34;任何事情 - 在执行longjmp的那一刻,一切仍然完好无损,就在堆栈中。您需要做的就是在setjmplongjmp之间回滚累积的额外内容。

longjmp自动执行&#34;浅&#34;为你回滚(即它只是清除堆栈顶部的原始字节而不调用任何析构函数)。所以,如果你想做一个正确的&#34;深度&#34;回滚(就像它们在调用层次结构中飞行时的异常一样)你需要在每个需要深度清理的级别setjmp,&#34;拦截&#34;跳转,手动执行清理,然后longjmp进一步调用层次结构。

但这基本上是手工实施&#34;穷人的异常处理&#34;。你为什么要手动重新实现它?我想知道你是否想用C代码来做。但是为什么用C ++?

P.S。是的,setjmp/longjmp有时会以非标准的方式用于在C中实现协同例程,这确实涉及跳过&#34;跨越&#34;和原始形式的堆栈恢复。但这是非标准的。在一般情况下,由于与上面提到的原因相同,在C ++中实现会更加痛苦。