Переместить текущий кадр стека в C

Мне было интересно, есть ли удобный способ скопировать текущий кадр стека, переместить его в другое место, а затем "вернуться" из функции из нового места?

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

В общих чертах описать конечную цель; Цель состоит в том, чтобы иметь возможность выделять кадры стека и переходить к другому кадру стека, когда я вызываю функцию (мы можем вызвать эту функциюswitch). Однако, прежде чем я перейду к новому фрейму стека, я хотел бы получить адрес возврата изswitch поэтому, когда я (предположительно) longjmpd в новый фрейм, я смогу вернуться в позицию, которая инициировала переключение контекста.

Я уже получил некоторое вдохновение, как имитировать сопрограммы с помощью longjmp ан setjmpиз этого поста.

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

Обновить

Чтобы попытаться прояснить мои намерения, я написал этот пример на C. Он должен быть скомпилирован с помощью -fno-stack-protector. Я хочу, чтобы локальные переменныеa а также b в mainчтобы не быть рядом друг с другом в стеке (1), а скорее быть отделены друг от друга расстоянием, указанного в буфереcall. Кроме того, в настоящее время этот код будетreturn к mainдважды, в то время как я хочу сделать это только один раз (2). Предлагаю вам прочитать процедуры в таком порядке:main, call а также change.

Если бы кто-нибудь мог ответить на любой из двух вопросов, поставленных в предыдущем абзаце, я был бы безмерно благодарен. Он не обязательно должен быть красивым или портативным.

Опять же, я бы предпочел ответы на мои вопросы, а не предложения лучших способов решения проблем.

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

jmp_buf* buf;
long* retaddr;

int change(void) {
  // local variable to use when computing offsets
  long a[0];

  for(int i = 0; i < 5; i++) a[i]; // same as below, not sure why I need to read this

  // save this context
  if(setjmp(*buf) == 0) {
    return 1;
  }
  // the following code runs when longjmp was called with *buf

  // overwrite this contexts return address with the one used by call
  a[2] = *retaddr;

  // return, hopefully now to main
  return 1;
}

static void* retain;

int call() {
  buf     = (jmp_buf*)malloc(sizeof(jmp_buf));
  retaddr = (long*)   malloc(sizeof(long));
  long a[0];
  for(int i = 0; i < 5; i++) a[i]; // not sure why I need to do this. a[2] reads (nil) otherwise

  // store return address
  *retaddr = a[2];

  // allocate local variables to move the stackpointer
  char n[1024];
  retain = n; // maybe cheat the optimiser?

  // get a jmp_buf from another context
  change();

  // jump there
  longjmp(*buf, 1);
}


// It returns to main twice, I am not sure why
int main(void) {
  char a;
  call(); // this function should move stackpointer (in this case, 1024 bytes)
  char b;
  printf("address of a: %p\n", &a);
  printf("address of b: %p\n", &b);
  return 1;
}

2 ответа

Tl ;dr - нет.

(На каждом компиляторе, заслуживающем внимания): компилятор знает адрес локальных переменных по их смещению либо от sp, либо от назначенного сохраненного указателя стека, фрейма или базового указателя. a может иметь адрес (sp+1), а b может иметь адрес (sp+0). Если вам удастся успешно вернуться в главное меню с уменьшенным на 1024 указателем стека; они по-прежнему будут называться (sp+1), (sp+0); хотя технически они сейчас (sp+1-1024), (sp+0-1024), что означает, что они больше не a & b.

Вы могли бы разработать язык, который фиксировал бы локальное распределение так, как вы считаете, и это могло бы иметь некоторую интересную выразительность, но это не C. Я сомневаюсь, что какой-либо существующий компилятор мог бы придумать последовательную обработку этого. Для этого при обнаружении:

char a;

ему нужно было бы создать псевдоним этого адреса в том месте, где он его встретил; сказать:

add %sp, $0, %r1
sub %sp, $1, %sp

и когда он столкнулся

char b;
add %sp, $0, %r2
sub %sp, $1, %sp

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

Кстати, это недалеко от концепции расширенного стека (golang использует их), но, как правило, детализация находится на границе функции или метода, а не между двумя определениями переменных.

Хотя идея интересная.

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

В основном возможные шаги:

  • Определите регистры, которые содержат необходимую информацию. Выбирайте их по тому, что вам нужно, они, вероятно, отличаются от того, что компилятор использует в стеке для реализации вызовов функций.
  • Узнайте, как их содержимое может быть сохранено (скорее всего, конкретные инструкции ассемблера для каждого регистра).
  • Используйте их для непрерывного хранения всего содержимого.
  • Место для этого, вероятно, уже выделено внутри объекта, описывающего и администрирующего текущую задачу.
  • Не используйте обратный адрес. Вместо этого, когда закончите со "вставленной" задачей, выберите среди множества наборов данных задачи, которые описывают потенциальные задачи, к которым нужно вернуться. Это суть планирования. Если адрес возврата известен заранее, то он очень похож на обычный вызов функции. Т.е. идея в том, чтобы потенциально вернуться к другой задаче, нежели последняя оставшаяся. Это также причина, по которой во многих случаях задачам нужен собственный стек.

Кстати, я не думаю, что арифметика с указателями является здесь наиболее актуальным инструментом.
Содержимое регистров, составляющих кадр стека, находится в регистрах, а не в любом месте памяти, на которое может указывать указатель. (По крайней мере, в большинстве современных систем C64 не участвует в этом....).

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