Обычно 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 )
}
Возможен ли такой подход — может ли кто-нибудь написать мне пример кода для этого?
Почему я считаю, что это выполнимо — из-за похожего фрагмента кода, который я уже кодировал / прототипировал:
Протокол связи и локальный шлейф с использованием setjmp / longjmp
Существует два стека вызовов, работающих одновременно — независимо друг от друга.
Но просто чтобы помочь вам с этой задачей — я дам вам функцию для получения стека вызовов для нативного и управляемого кода:
//
// 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 — указатель уже есть).
С юридической точки зрения setjmp/longjmp
может использоваться только для перехода «назад» во вложенной последовательности вызовов. Это означает, что ему никогда не нужно что-либо «реконструировать» — в тот момент, когда вы выполняете longjmp
все еще в порядке, прямо в стеке. Все, что вам нужно сделать, это откатить дополнительный материал, накопленный поверх этого между моментом setjmp
и момент longjmp
,
longjmp
автоматически выполняет «мелкий» откат для вас (то есть он просто удаляет необработанные байты с вершины стека без вызова каких-либо деструкторов). Итак, если вы хотите сделать правильный «глубокий» откат (например, что делают исключения, когда они поднимаются по иерархии вызовов), вам придется setjmp
на каждом уровне, который требует глубокой очистки, «перехватить» прыжок, выполнить очистку вручную, а затем longjmp
далее вверх по иерархии вызовов.
Но в основном это была бы ручная реализация «обработки исключений для бедняков». Почему вы хотите переопределить это вручную? Я бы понял, если бы вы хотели сделать это в C-коде. Но почему в C ++?
Постскриптум И да, setjmp/longjmp
иногда используются нестандартным способом для реализации сопрограмм в C, что включает в себя перепрыгивание и грубую форму восстановления стека. Но это нестандартно. И в общем случае было бы гораздо сложнее реализовать на C ++ по тем же причинам, о которых я упоминал выше.
Других решений пока нет …