Далее я попытаюсь объяснить, как дважды вернуться из функции.
Я предупреждаю вас с самого начала, что это все взломать.
Но есть много мест, где используются подобные хаки.
Сначала скажем, у нас есть следующая программа на Си.
#include <stdio.h>
uint64_t saved_ret;
int main(int argc, char *argv[])
{
if (saveesp()) {
printf("here! esp = %llX\n", saved_ret);
jmpback();
} else {
printf("there! esp = %llX\n", saved_ret);
}
return 0;
}
Теперь мы хотим, чтобы saveesp () возвращал дважды, чтобы мы могли достичь обоих printf.
Итак, вот как реализована функция saveesp ():
#define _ENTRY(x) \
.text; .globl x; .type x,@function; x:
#define NENTRY(y) _ENTRY(y)
NENTRY(saveesp)
movq (%rsp), %rax
movq %rax, saved_ret
movl $1, %eax
ret
NENTRY(jmpback)
xorq %rax, %rax
pushq saved_ret
ret
Это ни в коем случае не переносимый код. Но вы можете написать одинаковые сборочные заглушки для всех платформ, которые вы хотите поддерживать.
Что делает saveesp (), так это то, что он берет адрес возврата, сохраненный в стеке, и сохраняет его в локальной переменной. После этого возвращается 1. Это ненулевое возвращение, которое приводит нас к первому printf.
После printf () мы вызываем jmpback (). Что является настоящим взломом. Эта функция делает так, что saveesp () возвращает второй раз.
Он делает это, помещая сохраненный адрес возврата в стек и выполняя команду ret. Ret вытолкнет адрес из стека и перейдет к нему. На этот раз код возврата устанавливается равным нулю. Поэтому, когда мы «возвращаемся» к нашей подпрограмме C, кажется, что мы только что вернулись из saveesp () с нулевым возвращаемым значением. Таким образом достигается вторая печать.
Если вас интересуют подобные хаки, вам следует прочитать немного больше о setjmp и longjmp из стандарта C, которые используются для реализации обработки исключений.
Кроме того, мы фактически используем это в ядре OpenBSD в пути кода приостановки / возобновления.
Взгляните здесь на строки 231 и 250, это в значительной степени тот же код C, что и выше. А затем взгляните на ассемблерный код здесь в строке 542 - это функция savecpu, которая возвращает первый раз при приостановке, а в строке 375 мы возвращаемся второй раз, когда возвращаемся к возобновлению.