Вызов printf предотвращает сегрегирование

Студент информатики здесь. Нас попросили поиграть с переключением контекста, и одно конкретное задание заставило нас реализовать довольно грубую систему try / throw. Вот код, который мы писали:

struct ctx_s {
  int esp;
  int ebp;
};

struct ctx_s * pctx;

typedef int (func_t)(int); /* a function that returns an int from an int */

int try(func_t *f, int arg)
{
  /* saving context by storing values of %esp and %ebp */      
    asm ("movl %%esp, %0"
    : "=r"((*pctx).esp) 
    :
    );

    asm ("movl %%ebp, %0"
    : "=r"((*pctx).ebp) 
    :
    );

    /* calling the function sent to try(), returning whatever it returns */
    return f(arg);
}

int throw(int r)
{
    printf("MAGIC PRINT\n");

    static int my_return = 0;
    /* ^ to avoid "an element from initialisation is not a constant" */
    my_return = r;
    /* restituting context saved in try() */
    asm ("movl %0, %%esp"
    : 
    : "r"((*pctx).esp) 
    );

    asm ("movl %0, %%ebp"
    : 
    : "r"((*pctx).ebp) 
    );

    /* this return will go back to main() since we've restored try()'s context
     so the return address is whatever called try... */
    /* my_return is static (=> stored in the heap) so it's not been corrupted,
     unlike r which is now the second parameter received from try()'s context, 
     and who knows what that might be */
    return my_return;
}

pctx - это глобальный указатель на простую структуру, содержащую два целых числа, f - это функция, которая вызывает throw (), отправляя некоторый код возврата #define'd в 42, а main () по существу выделяет pctx, действительно, result=try(f, 0) и печатает результат. Мы ожидаем, что результат будет 42.

Теперь вы, возможно, заметили ВОЛШЕБНЫЙ ПЕЧАТИ в throw (). Это здесь по причинам, не совсем понятным; в основном, большинство (не все) ученики были разбиты на сегменты внутри throw (); вызов printf () внутри этой функции заставил программу работать на вид правильно, и учителя считают, что любой системный вызов также сработал бы.

Так как я не получил их объяснения, я попытался сравнить ассемблерные коды, сгенерированные с помощью gcc -S для обеих версий (с и без printf()), но я не смог ничего сделать из этого. Установка точки останова на открывающей скобке throw () (строка 33) и разборка с помощью gdb дали мне следующее:

Без printf():

Breakpoint 1, throw (r=42) at main4.c:38
(gdb) disass
Dump of assembler code for function throw:
0x0804845a <throw+0>:   push   %ebp
0x0804845b <throw+1>:   mov    %esp,%ebp
0x0804845d <throw+3>:   mov    0x8(%ebp),%eax
0x08048460 <throw+6>:   mov    %eax,0x8049720
0x08048465 <throw+11>:  mov    0x8049724,%eax
0x0804846a <throw+16>:  mov    (%eax),%eax
0x0804846c <throw+18>:  mov    %eax,%esp
0x0804846e <throw+20>:  mov    0x8049724,%eax
0x08048473 <throw+25>:  mov    0x4(%eax),%eax
0x08048476 <throw+28>:  mov    %eax,%ebp
0x08048478 <throw+30>:  mov    0x8049720,%eax
0x0804847d <throw+35>:  pop    %ebp
0x0804847e <throw+36>:  ret    
End of assembler dump.
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0xb7e846c0 in ?? ()

С printf():

Breakpoint 1, throw (r=42) at main4.c:34
(gdb) disassemble 
Dump of assembler code for function throw:
0x0804845a <throw+0>:   push   %ebp
0x0804845b <throw+1>:   mov    %esp,%ebp
0x0804845d <throw+3>:   sub    $0x18,%esp
0x08048460 <throw+6>:   movl   $0x80485f0,(%esp)
0x08048467 <throw+13>:  call   0x8048364 <puts@plt>
0x0804846c <throw+18>:  mov    0x8(%ebp),%eax
0x0804846f <throw+21>:  mov    %eax,0x804973c
0x08048474 <throw+26>:  mov    0x8049740,%eax
0x08048479 <throw+31>:  mov    (%eax),%eax
0x0804847b <throw+33>:  mov    %eax,%esp
0x0804847d <throw+35>:  mov    0x8049740,%eax
0x08048482 <throw+40>:  mov    0x4(%eax),%eax
0x08048485 <throw+43>:  mov    %eax,%ebp
0x08048487 <throw+45>:  mov    0x804973c,%eax
0x0804848c <throw+50>:  leave  
0x0804848d <throw+51>:  ret    
End of assembler dump.
(gdb) c
Continuing.
MAGIC PRINT
result = 42

Program exited normally.

Я действительно не знаю, что с этим делать. Очевидно, что все происходит по-другому, но мне трудно понять, что происходит в обоих случаях... Итак, мой вопрос, по сути: как вызвать printf make throw, а не segfault?

2 ответа

Решение

Хорошо, это немного свободный анализ, так как я не вижу части try, но, судя по стандартным соглашениям о вызовах, ваш метод, содержащий try, сохранит %esp в %ebp, уменьшить %esp освободить место для локальных переменных и запустить свой "пробный" код, который сохраняет %esp а также %ebp,

Обычно, когда функция завершается, она отменяет эти изменения, используя leaveдо возвращения. Выйти восстановлю %ebp в %esp, поп %ebp и сделать его возвращение. Это гарантирует, что %esp восстанавливается до своей точки, прежде чем место для локальных переменных было зарезервировано.

Проблема в версии без printf это то, что он не использует leave который выскакивает %ebp без предварительного восстановления его содержимого в %esp, ret инструкция выведет локальную переменную и вернется к ней. Не самый лучший результат.

Я подозреваю, что, поскольку ваша функция не имеет локальных переменных, компилятор не видит причин для восстановления %esp от %ebp, поскольку printf резервирует место в стеке, компилятор знает в этой версии, что %esp должны быть восстановлены до возвращения.

Если вы хотите проверить теорию, просто скомпилируйте ассемблер, замените;

0x0804847d <throw+35>:  pop    %ebp

с инструкцией выхода и собрать результат. Это должно работать так же хорошо.

С другой стороны, я подозреваю, что вы могли указать gcc в ваших инструкциях asm, что %esp был засорен, и тем самым заставил его генерировать вместо этого отпуск.

РЕДАКТИРОВАТЬ: Видимо маркировка %esp поскольку clobbered по сути является NOOP в gcc:-/

Вы "восстанавливаете" ESP к значению, сохраненному в другой функции. Вероятно, не полезное значение здесь.

Разница с "магическим" кодом заключается в том, что он заставляет компилятор сохранять и восстанавливать кадр стека в throw функция.

leave инструкция в конце эквивалентна

mov    %ebp, %esp
pop    %ebp

который может просто вернуть указатель стека к тому, что было в записи функции.

Другие вопросы по тегам