Задать массив C в качестве нового стека вызовов (ESP) из встроенного asm? - PullRequest
1 голос
/ 06 апреля 2020

Как подсказывает мой заголовок, меня интересует, можно ли выделить массив C и заставить его вести себя как стек, указав на него регистр ESP.

Пример кода. ..

void foo(){
   int x = 99;
   int y = 89;
   return;
}
char myStack[1024];
void main(){
     int main_num = 66;
     __asm volatile("movl %0, %%esp": : "rm" (&myStack+1)); //Move ESP to the end of the array
     foo();
     return 0;
}

Идея этого кода заключается в том, чтобы создать своего рода отдельный стек, специально для foo (), сначала указав ESP в конец массива myStack (поскольку стек будет расти в направлении младших адресов). ) и затем вызвал foo (), теперь его адрес возврата и локальные переменные хранятся в этом новом стеке (нашем массиве C).

Мне интересно, возможен ли такой подход? И если да, то как этого добиться?

Пытаясь реализовать приведенный выше код, я запустил GDB, просто чтобы посмотреть некоторую информацию о моем стеке (например, команда info stack в GDB), я продолжал получать "No Stack", который, вероятно, означает, что указатель стека был отправлен в бездну.

PS: я реализую это как код уровня ядра

1 Ответ

1 голос
/ 06 апреля 2020

Может быть, это может работать как огромный небезопасный хак, который работает только в игрушечных экспериментах. Если вы хотите установить новый стек, сделайте это в рукописной asm перед вызовом функции C.

Этот хак может сработать для вызова на foo(), но как насчет return 0;? Сгенерированный компилятором код будет пытаться извлечь адрес возврата из текущего% esp.

(или, если оптимизация отключена, будет использоваться leave, который устанавливает ESP = EBP, прежде чем выдавать сохраненный EBP. Это переключится обратно к начальному стеку. Таким образом, поведение зависит от уровня оптимизации! Вы не хотите этого.)

Используйте GDB, чтобы пошагово выполнить свой код и фактически наблюдать изменение значений reg, например, с помощью layout reg.

Но да, &myStack + 1 является адресом одного конца конца массива, и в результате получается movl $myStack+1024, %eax в качестве настройки для оператора asm (где расширяется %0 на %eax в шаблоне, потому что компилятор выбрал этот регистр для операнда "rm". Вы не дали ему возможность немедленной константы, или он просто сделал бы это с movl $myStack+1024, %esp).

https://godbolt.org/z/Nz6DgA показывает, что он «работает» и затем сразу же обработает sh, когда достигнет такта с включенной оптимизацией, потому что он пытается pop с ESP, указывающим на один за другим -конец myStack.

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

Особенно, если main должен фактически return, тогда да, вам нужно настроить стек перед , используя его для call чего угодно. В противном случае этот конечный адрес возврата будет находиться в неправильном стеке!

Например, в Linux библиотека pthread создает новый поток с новым стеком, выделяя его с помощью mmap(), а затем передает этот адрес стека как операнд к clone(). Таким образом, новый поток никогда не использует стек родителя, а только свой собственный стек. Я предполагаю, что создание стека потоков на стороне ядра для новой задачи аналогично. Вы выделяете новый стек, , затем используете его для нового контекста потока.

Вы можете поместить "адрес возврата" вверху, чтобы первая функция, вызванная в новом потоке, фактически возвращала в функцию выхода / очистки потока. Возможно, с некоторой ассм за это. Или сделайте фактическую точку входа потока функцией, которая не возвращает, вместо этого очистите контекст потока и переключитесь на другой поток или вызовите свой планировщик или что-то в этом роде.

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

К сожалению, это слишком просто и не на самом деле работает.

Да, вы можете использовать массив C для стека потоков (если у вас ровно один дополнительный поток ...), проблема в том, как вы переключаетесь на него.

Вам нужно в какой-то момент написать функцию переключения контекста, которая сохраняет один контекст регистра и загружает другой. (Например, Google, вы можете найти несколько здесь, в Переполнении стека, и, вероятно, что-то в https://www.osdev.org/.)

Создать новую структуру контекста потока в памяти с указателем стека, указывающим на вершина стека потока и его EIP, указывающий на точку входа потока. Вызовите функцию переключения контекста, чтобы переключиться на этот новый контекст.

Из POV компилятора C функция переключения контекста выглядит как любой другой вызов функции. Он возвращает , в конечном итоге , и, возможно, изменил любые глобально достижимые C объекты. Неважно, что ESP временно указывал куда-то еще. «Как и любой другой вызов функции» включает в себя сжатые регистры с закрытыми вызовами, BTW, поэтому вам не нужно сохранять / восстанавливать EAX / ECX / EDX. Вызывающая функция переключения контекста уже предполагает, что они уничтожены.

Обычно вы должны писать это вручную в asm, а не в asm. Смена ESP с inline asm чревата опасностью, и официально задокументировано как не поддерживаемое G CC.

Это связано с тем, что компилятор требует, чтобы значение указателя стека было таким же после оператора asm, как и при входе в оператор

См. Также https://gcc.gnu.org/wiki/DontUseInlineAsm

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...