Могут ли адреса неизмененных местных жителей оказаться поврежденными в setjmp / longjmp?

Если кто-то попадает в ситуацию застревания при использовании setjmp / longjmp (не спрашивайте), то компилятор выдает много хороших предупреждений о том, когда вы можете сделать что-то не так.

Но с -Wall -Wextra -pedantic сборкой при использовании Address Sanitizer в Clang я получил примерно параллельно:

void outer() {
    jmp_buf buf;
    ERR error;

    if (setjmp(buf) ? helper(&error) : FALSE) {
        // process whatever helper decided to write into error
        return;
    }

    // do the stuff you wanted to guard that may longjmp.
    // error is never modified
}

В longjmp при просмотре кадра стека helper указатель ошибки равен нулю. Если я смотрю в outer() фрейм, он говорит, что ошибка «оптимизирована».

Это озадачивает, потому что я компилирую с -O0. Так что "оптимизировано" странно, чтобы это говорить. Но, как и в случае с большинством вещей longjmp-y, мне интересно, что мешает компилятору принять решение о том, в какой регистр он будет помещать адрес ошибки раньше времени ... а затем сделать это недействительным.

Меня бьет дезинфицирующее средство адресов, или мне действительно нужно написать что-то вроде:

void outer() {
    jmp_buf buf;
    ERR error;
    volatile ERR* error_ptr = &error;

    if (setjmp(buf) ? helper(error_ptr) : FALSE) {
        // process whatever helper decided to write into error
        return;
    }

    // do the stuff you wanted to guard that may longjmp.
    // error is never modified
}

Изучая это, я заметил, что jmp_buf не являются местными ни в одном из примеров, которые я вижу. Вы не можете этого сделать? : - /


ПРИМЕЧАНИЕ. См. ответ и комментарии @ AnT ниже по вопросу "language-lawyer", касающемуся конструкции setjmp() ? ... : .... Но на самом деле то, что я здесь делал, оказалось неработающим вызовом longjmp, который произошел после выхода из функции. Согласно longjmp() docs (также: здравый смысл), это определенно сломан; Я просто не понимал, что произошло:

Если функция, которая вызвала setjmp, завершилась, поведение не определено (другими словами, разрешены только длинные прыжки вверх по стеку вызовов)


person HostileFork says dont trust SE    schedule 23.05.2015    source источник
comment
В longjmp при просмотре кадра вспомогательного стека указатель ошибки равен нулю ... Можете ли вы предоставить более подробное объяснение того, на что вы смотрите и когда? Может быть, минимальный компилируемый пример, воспроизводящий поведение?   -  person AnT    schedule 23.05.2015
comment
Возможно, это зависит от содержимого функции helper (). Если функция на самом деле не использует свой аргумент (например, функцию empy или простой printf (Error! \ N)), компилятор может решить выбросить переменную ошибки, поскольку она бесполезна. Но странно то, что оптимизации явно отключены. Эээ ...   -  person Giuseppe Guerrini    schedule 23.05.2015
comment
Это странно. Значение error не определено, но переменная все еще должна существовать. Я не думаю, что достаточно смотреть на код, примерно параллельный неисправному; вам нужно будет опубликовать MCVE.   -  person user2357112 supports Monica    schedule 23.05.2015
comment
@AnT Хотя MVCE обычно очень важны, для их создания здесь потребуется описание того, как настроить средство очистки адресов и много другого мусора. Я ищу пример языкового юриста и предлагаю образец, чтобы сформулировать обсуждение. Если свести его к репро-примеру, я узнаю только, чья это ошибка; и мне нужен специальный ответ, чтобы это имело значение. Мне нужно знать, что он должен делать, а не то, что он делает.   -  person HostileFork says dont trust SE    schedule 23.05.2015


Ответы (1)


Есть ли причина, по которой вызов helper "встроен" в управляющее выражение оператора if через ?:? На самом деле это нарушение языковых требований, в которых говорится

7.13.1.1 Макрос setjmp

4 Вызов макроса setjmp должен появляться только в одном из следующих контекстов:

- все управляющее выражение оператора выбора или итерации;

- один операнд оператора отношения или равенства с другим операндом представляет собой целочисленное постоянное выражение, при этом результирующее выражение является всем управляющим выражением оператора выбора или итерации;

- операнд унарного! оператор, результирующее выражение которого является всем управляющим выражением оператора выбора или итерации; или

- все выражение оператора выражения (возможно, приведенное к недействительности).

5 Если вызов появляется в любом другом контексте, поведение не определено.

Весь смысл этого требования состоит в том, чтобы убедиться, что «непредсказуемый» возврат от setjmp, инициированный longjmp, не должен попадать в середину оценки выражения, то есть в неупорядоченном контексте. В вашем конкретном примере довольно очевидно, что с точки зрения абстрактного языка C переменная error не может быть изменена вызовом setjmp, что открывает двери для многих оптимизаций.

Трудно сказать, что здесь произошло, поскольку helper получает указатель &error, а не прямое значение error. На первый взгляд с практической точки зрения все кажется прекрасным. Но формально поведение не определено.

В вашем случае вам не следует пытаться исправить ситуацию, создав переменные volatile, а следует упростить контекст, в котором используется setjmp, чтобы соответствовать вышеуказанным требованиям. Что-то вроде

if (setjmp(buf) != 0) {
  helper(&error);
  ...
  return;
}
person AnT    schedule 23.05.2015
comment
Хммм ... Мне действительно было интересно об этом, когда я читал больше об этом. Причина в том, что в коде это сделано как макрос (я просто расширил его здесь для упрощенного случая). Макрос хочет немного обработать, прежде чем он вернет управление ветке. Не законно, а? - person HostileFork says dont trust SE; 23.05.2015
comment
@HostileFork: По-видимому, нет. - person AnT; 23.05.2015
comment
Интересно, что цитируемый вами раздел перенесен на cppreference (должен посмотрел там, но это C!). Все обращения, связанные с C для setjmp, как этот don похоже, не упоминает об этом ... и этого нет в оговорках и ограничениях в статье Википедии но похоронен под примером. Кто-то должен ... исправить это. :-) - person HostileFork says dont trust SE; 23.05.2015
comment
Я думаю, что пункт 3 ISO / IEC 9899: 2011 §7.13.2.1 Функция longjmp также актуальна: Все доступные объекты имеют значения, а все другие компоненты абстрактной машины имеют состояние, когда функция longjmp была вызывается, за исключением того, что значения объектов с автоматической продолжительностью хранения, которые являются локальными для функции, содержащей вызов соответствующего макроса setjmp, не имеющего изменяемого типа и измененных между вызовом setjmp и вызовом longjmp, являются неопределенными. Я все же думаю, что volatile ERR error; было бы достаточно. - person Jonathan Leffler; 23.05.2015
comment
Такое поведение не столько «незаконно», сколько «неопределенное поведение», и это плохо. - person Jonathan Leffler; 23.05.2015
comment
Интересно, почему Стандарт не разрешает присвоение локальной переменной volatile в качестве опции для возврата setjmp? Да, компиляторам, возможно, придется немного поработать, чтобы гарантировать, что такое присвоение работает, но без возможности захвата значения, переданного в longjmp, это не кажется очень полезным. Можно сказать `int val = 0; переключатель (setjmp (...)) {case 0: main_code; ломать; случай 1: val = 1; ломать; случай 2: val = 2; ломать; case 3: val = 3; ломать; ...} но это кажется невероятно хоккейным. - person supercat; 24.05.2015
comment
Даже если некоторые машины не могут обрабатывать хранилище в изменчивом состоянии, я бы подумал, что сохранение в изменчивое состояние должно быть как минимум стандартной формой расширения, которое можно было бы протестировать; подобно сравнениям, включающим несвязанные указатели, было бы гораздо разумнее сказать, что код может использовать такие функции на платформах, чем предоставлять их, чем требовать, чтобы даже программы, написанные для платформ, которые могут легко предоставлять такие функции, должны использовать неуклюжие обходные пути для решения своих проблем. отсутствие. - person supercat; 24.05.2015
comment
Оказалось, что более насущная проблема заключалась в том, что макросы скрывали то, что я делал что-то еще незаконное ... а именно, что была область, в которой был создан один setjmp, который завершился до того, как был выполнен longjmp. Мне удалось это не заметить (хотя я знал, что ты не сможешь этого сделать). Так что да, MVCE помог бы мне решить эту проблему ... но спасибо за ответ языкового юриста по другому вопросу, на который важно знать ответ! - person HostileFork says dont trust SE; 27.05.2015