Этот подход хорошо? - PullRequest
       10

Этот подход хорошо?

3 голосов
/ 11 января 2012

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

#define SAVE_STACK()    __asm__ __volatile__ ( "mov %%rsp, %0; mov %1, %%rsp" : \
"=m" (saved_sp) : "m" (temp_sp) );
#define RESTORE_STACK() __asm__ __volatile__ ( "mov %0, %%rsp" : \
"=m" (saved_sp) );

Здесь temp_sp и сохраненный_sp являются локальными переменными потока. temp_sp указывает на временный стек, который мы используем. Для функции, исходный стек которой я хочу изменить, я помещаю SAVE_STACK в начале и RESTORE_STACK внизу. Например, вот так.

int some_func(int param1, int param2)
{
 int a, b, r;
 SAVE_STACK();
 // Function Body here
 .....................
 RESTORE_STACK();
 return r;
}

Теперь мой вопрос: хорош ли этот подход? В x86 (64 бита) локальные переменные и параметры доступны через регистр rbp , а rsp соответственно вычитается в прологе функции и не затрагивается до тех пор, пока в эпилоге функции, где он добавлен для вывода это вернуться к первоначальному значению. Поэтому я не вижу здесь никаких проблем.

Я не уверен, правильно ли это при наличии переключений контекста и сигналов. Также я не уверен, правильно ли это, если функция встроенная или применяется оптимизация хвостового вызова (где используется jmp вместо call ). Видите ли вы какие-либо проблемы или побочные эффекты при таком подходе?

1 Ответ

4 голосов
/ 11 января 2012

С кодом, который вы показали выше, я могу думать о следующей поломке:

  1. На x86 / x64 GCC «расшифрует» вашу функцию прологами / эпилогами, если сочтет это целесообразным, и вы не можете помешать этому (как в ARM, где __attribute__((__naked__)) форсирует создание кода без прологи / эпилоги, иначе без настройки стекового кадра).
    Это может привести к выделению стека / созданию ссылок на ячейки памяти стека до того, как вы переключите стек. Еще хуже, если, опять же, из-за выбора компилятора, такой адрес помещается в энергонезависимый регистр до того, как вы переключите стек, он может иметь псевдоним в двух местах (один из измененных вами относительно указателя стека и другой относительно другого) это то же самое).

  2. Опять же, на x86 / x64 ABI предлагает оптимизацию для конечных функций («красная зона»), где ни один стековый кадр не выделен, но 128 байтов стека «ниже» конца используются функцией. Если ваш буфер памяти не примет это во внимание, может произойти переполнение, которое вы не ожидаете.

  3. Сигналы обрабатываются в альтернативных стеках (см. sigaltstack()), и переключение стека может сделать ваш код не вызываемым из обработчиков сигналов. Это определенно сделает его не реентерабельным, и в зависимости от того, где / как вы извлекаете «расположение стека», это также определенно сделает его не поточным.

Как правило, если вы хотите запустить определенный фрагмент кода в другом стеке, почему бы и нет:

  • запустить его в другом потоке (каждый поток получает свой стек)?
  • триггер, например SIGUSR1 и запустить свой код в обработчике сигналов (который можно настроить для использования другого стека)?
  • запустить его через makecontext() / swapcontext() (см. Пример на странице руководства)?

Edit:

Поскольку вы говорите «вы хотите сравнить память двух процессов», опять же, для этого есть разные методы, в частности внешнее отслеживание процесса - присоедините «отладчик» (это может быть процесс, который вы напишите себе, что использует ptrace() для управления тем, что вы хотите отслеживать, и пусть он обрабатывает, например, точки останова / контрольные точки от имени тех, кого вы отслеживаете, для выполнения необходимых проверок). Это также было бы более гибко, поскольку не требуется изменять проверяемый вами код.

...