Надежное обнаружение рекурсии даже при наличии нелокальных переходов

У меня есть конкретная функция (обработчик сигналов), для которой я хотел бы обнаружить рекурсию, т.е. выяснить, вызывала ли функция прямо или косвенно себя. Хитрость заключается в том, что функция в какой-то момент вызывает некоторый код, не находящийся под ее контролем, и этот код может делать что угодно.

Обычно я бы просто написал что-то вроде

void foo() {
    static int recursed = 0;
    if(recursed) {
        ...
    }
    recursed = 1;
    othercode();
    recursed = 0;
}

но в этом случае я обеспокоен тем, что othercode может использовать longjmp или что-то подобное для выхода, в результате чего recursed останется равным 1. В случае, если моя функция выскочит таким образом, я хочу убедиться, что она не видеть себя рекурсивным, если вызывается позже (тот факт, что он longjmp не является проблемой в противном случае).

Примечание. Я считаю longjmp вероятным. othercode — это цепочный обработчик сигналов из какого-то другого кода в дикой природе, и существуют обработчики, например, для. SIGSEGV, которые используют longjmp для восстановления контекста (например, в качестве обработчиков исключений «защиты от сбоев»). Обратите внимание, что использование longjmp в синхронном обработчике сигнала, как правило, безопасно. В любом случае, меня не особенно волнует, безопасен ли вообще другой код, потому что я не могу его контролировать.


person nneonneo    schedule 05.06.2013    source источник
comment
Вы беспокоитесь о том, что longjmp вероятно, или просто о том, что это возможно?   -  person Patashu    schedule 05.06.2013
comment
@Patashu: Вероятно. Смотрите мою правку.   -  person nneonneo    schedule 05.06.2013
comment
Заголовок вопроса следует отредактировать, чтобы он был немного более конкретным, что-то вроде «Обнаружение повторного входа, даже когда используется longjmp».   -  person Patashu    schedule 05.06.2013
comment
@Patashu: спасибо за предложение, отредактировано   -  person nneonneo    schedule 05.06.2013
comment
Если вы знаете, куда идет jongjmp, вы можете поместить туда код, чтобы очистить индикацию рекурсии.   -  person ugoren    schedule 05.06.2013


Ответы (2)


Не уверен, как именно будет выглядеть код для этого, но вместо статического int у вас может быть статический void *. Вместо того, чтобы устанавливать его равным 1, установите его так, чтобы он указывал на текущий фрейм стека. В дополнение к проверке, отличен ли он от нуля, вы проверяете, что адрес возврата из следующего кадра стека после recursed фактически указывает на место в коде foo, а также что recursed находится над текущим указателем стека, т. е. не извлекается.

Звучит очень хрупко и зависит от архитектуры.

person morningstar    schedule 05.06.2013
comment
Или, может быть, проще посмотреть на адрес возврата из точек кадра стека foo сразу после инструкции вызова foo, но это определенно будет непереносимым. - person morningstar; 05.06.2013
comment
Я обдумал это. Это работает очень хорошо, за исключением одного крошечного углового случая: предположим, что othercode переходит к функции намного выше foo, которая вызывает какую-то другую функцию, которая выделяет (но не использует полностью) гигантский буфер стека, а затем вызывает foo. Если мне не повезет, гигантский буфер стека перемещает указатель стека ниже foo и сохраняет исходный стек foo, что делает его очень похожим на рекурсию. - person nneonneo; 05.06.2013
comment
Конечно, это довольно маловероятно, поэтому я, вероятно, в конечном итоге воспользуюсь этим решением. Но в идеале я хотел бы найти решение, в котором нет этой проблемы. - person nneonneo; 05.06.2013
comment
Возможно, я имею в виду указатель базы/фрейма, а не указатель стека. - person morningstar; 05.06.2013
comment
Ничего, такая же проблема. Выделенный, но не инициализированный буфер может находиться в предыдущем кадре. - person morningstar; 05.06.2013

Согласно стандарту POSIX для signal(7), longjmp( ) не является одним из вызовов, которые безопасно вызывать из обработчика сигналов. Прежде чем даже подумать об этой проблеме, документация longjmp(3) говорит , вам нужно убедиться, что код, который вы вызываете, использует sigsetjmp() и siglongjmp()

Если код, который вы вызываете, выпрыгивает из обработчика сигнала, то я не понимаю, как вы можете узнать, когда обновлять переменную recursed, если вы также не управляете функцией обратного вызова, которую этот неизвестный код вызывает обратно в ваше приложение.

person Benjamin Leinweber    schedule 05.06.2013
comment
Они применяются только к асинхронным сигналам. Я пишу обработчик сигналов для SIGSEGV и других обычно синхронных сигналов, поэтому не ожидается, что это будет проблемой. - person nneonneo; 05.06.2013