0

Normally setjmp and longjmp does not care about call stack - instead functions are just preserving and restoring registers.

I would like to use setjmp and longjmp so that call stack would be preserved, and then restored at different executing context

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

Is this kind of approach possible - can someone code me a sample code for this ?

Why I believe by myself it's doable - is because of similar code snipet I have already coded / prototyped:

Communication protocol and local loopback using setjmp / longjmp

There is two call stack running simultaneously - independently from each other.

But just to help you out with this task - I'll give you function for getting callstack for native and managed code:

//
//  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

I think it can be modified to obtain actual stack pointer (x64 - eSP and for x32 - there is a pointer already).

Community
  • 1
  • 1
TarmoPikaro
  • 4,723
  • 2
  • 50
  • 62
  • 1
    The `setjmp`/`longjmp` mechanism is quite delicate; if your code is to be remotely portable, you won't interfere with it. If the `return` is executed, the `jmpBuf` will become invalid. I'm not clear what you're thinking you might do to the `jmpBuf`, but it looks extremely dubious to me. You should almost certainly rethink what you're doing altogether. (Especially if you're using C++ — you should effectively never use `setjmp` or `longjmp` in C++. You've got exceptions; use them. They deal with the issues that `setjmp` and `longjmp` ignore, like resource management via destructors.) – Jonathan Leffler Oct 28 '16 at 17:11
  • You're looking for Boost's [stackful coroutines](http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/overview/core/spawn.html). Whatever you're doing yourself will eventually end up where boost is at, so you might as well call it a day and be done using something that works :) – Kuba hasn't forgotten Monica Oct 28 '16 at 17:43
  • Added more comments into question to the ones who don't believe it's possible. :-) – TarmoPikaro Oct 28 '16 at 18:53

1 Answers1

3

Legally, setjmp/longjmp can only be used to jump "back" in the nested call sequence. Which means that it never needs to really "reconstruct" anything - at the moment when you execute the longjmp everything is still intact, right there in the stack. All you need to do is rollback the extra stuff accumulated on top of that between the moment of setjmp and the moment of longjmp.

longjmp automatically does a "shallow" rollback for you (i.e. it simply purges the raw bytes off the top of the stack without calling any destructors). So, if you wanted to do a proper "deep" rollback (like what exceptions do as they fly up the call hierarchy) you'd have to setjmp at each level that needs deep cleanup, "intercept" the jump, perform the cleanup manually and then longjmp further up the call hierarchy.

But this would basically be a manual implementation of "poor-man's exception handling". Why would you want to reimplement it manually? I'd understand if you wanted to do it in C code. But why in C++?

P.S. And yes, setjmp/longjmp are sometimes used in a non-standard way to implement co-routines in C, which does involve jumping "across" and a raw form of stack restoration. But this is non-standard. And in general case it would be much more painful to implement in C++ for the very same reasons that I mentioned above.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Is there a good use case for `setjmp` and `longjmp` in C++? One of the points of exception handling is to ensure that destructors are executed properly, and `longjmp` bypasses that altogether. – Jonathan Leffler Oct 28 '16 at 17:14
  • @JonathanLeffler: I can sort of see a possibility for implementing coroutines in C++, where you want to quit executing the code in a scope, but want to maintain that scope in a form that it can be re-entered for continued execution later (i.e., as the coroutine yields, you do *not* want to execute the dtors for it or its calling sequence). That said, I doubt this can be implemented with (only) portable use of `setmmp`/`longjmp`. – Jerry Coffin Oct 28 '16 at 17:39
  • 1
    @JerryCoffin Yes, but even so there are problems with, for example, mingws and MSVCs setjmp/longjmp implementations, so boost does not reply on setjmp/longjmp – user1095108 Nov 12 '16 at 19:40