Подтвердить что ты не робот

Зачем использовать параметр функции "foo" следующим образом: * (& foo)?

Фрагмент кода в ядре Linux 0.12 использует параметр функции следующим образом:

int do_signal(int signr, int eax /* other parameters... */) {
    /* ... */

    *(&eax) = -EINTR;

    /* ... */
}

Цель кода - поместить -EINTR в память, где живет eax, но я не могу сказать, почему это не сработает, если просто назначить eax:

eax = -EINTR

Как компилятор мог бы сделать разницу между eax и * (& eax)?

4b9b3361

Ответ 1

Одним из возможных намерений может быть сохранение переменной eax из регистра. Если мы посмотрим на проект стандарта C99, мы увидим, что раздел 6.5.3.2 Операторы адреса и косвенности говорят (внимание мое):

Унарный и оператор дает адрес своего операнда. [...] Если операнд является результатом унарного * оператора, ни то, что оператор или оператор и вычисляется, а результат , как если бы оба были опущены, за исключением того, что ограничения на операторы по-прежнему применяютсяи результат не является lvalue. [...]С >

в сноске 87 говорится (акцент мой идет вперед):

Таким образом, & * E эквивалентно E (даже если E является нулевым указателем) и & (E1 [E2]) до ((E1) + (E2)). Всегда верно, что если E - функция обозначение или значение l, которое является допустимым операндом унарного & оператор, * & E - обозначение функции или lvalue, равное E.

находим следующее ограничение на & operator:

Операнд унарного и оператора должен быть либо функцией обозначение, результат оператора [] или унарного * или lvalue, что обозначает объект, который не является битовым полем и не объявляется с спецификатор класса хранения регистра.

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

Как указывает ouah, это не мешает компилятору оптимизировать то, что эффективно не работает, но как описано в GCC-хаки в ядре Linux. Linux полагался на многие расширения gcc и считал, что 0.12 - очень старое ядро ​​gcc, возможно, гарантировало, что поведение или может быть случайно надежно сработало таким образом, но я не могу найти документацию, которая так говорит.

Ответ 2

Старый Linux, который вы опубликовали, пытался выполнить очень хрупкий взлом. Функция была определена следующим образом:

int do_signal(long signr,long eax,long ebx, long ecx, long edx, long orig_eax,
    long fs, long es, long ds,
    long eip, long cs, long eflags,
    unsigned long * esp, long ss)

Аргументы функции фактически не представляют аргументы функции (кроме signr), но значения, вызывающие функцию (обработчик прерывания ядра/обработчик исключений, записанных в сборке), сохраняются в стеке перед вызовом do_signal. Оператор *(&eax) = -EINTR предназначен для изменения сохраненного значения EAX в стеке. Аналогично утверждение *(&eip) = old_eip -= 2 предназначено для изменения адреса возврата обработчика вызова. После возврата do_signal обработчик выдает первые 9 "аргументов" из стека, восстанавливая их в названные регистры. Затем он выполняет инструкцию IRETD, которая выводит оставшиеся аргументы из стека и возвращается в пользовательский режим.

Излишне говорить, что этот хак невероятно ненадежный. Он зависит от кода генерации компилятора точно так, как он ожидал. Я удивлен, что он даже работал компилятором GCC той эпохи, я сомневаюсь, что задолго до того, как GCC представила оптимизацию, которая сломала его.