У меня есть функция switchContext(void*& from, void*& to)
. Он получает два указателя стека и должен изменить контекст процесса. Итак, если у меня есть сопрограмма A, и она использует определенную функцию resume()
, сопрограмма B будет продолжать работать.
В настоящее время у меня проблемы с тем, чтобы мой код работал.
Я использую Nasm & GCC для компиляции.
Программа на ассемблере: (switchContext(void*& from, void*& to)
):
switchContext:
; epb = esp
mov ebp, esp
; save registers
push ebp
push ebx
push esi
push edi
; from <= returnadress
; eax <= returnadress
mov eax, [ebp+16]
mov edx, ebp
add edx, 20 ; make edx point to 'from'
; overwrite 'from' with returnadress
mov [edx], eax
; what to do now: returnadress <= to
; eax <= to
mov eax, [ebp+24]
mov edx, ebp
add edx, 16 ; make edx point to returnadress
; overwrite returnadress with 'to'
mov [edx], eax
pop edi ; RSA = esp + 12
pop esi ; RSA = esp + 8
pop ebx ; RSA = esp + 4
pop ebp ; RSA = esp + 0
; use new returnadress to jump to 'to'
ret
А это соответствующий класс С++:
extern "C" {
void switchContext(void*& from, void*& to);
}
class Coroutine {
public:
const char* name;
Coroutine(void* tos = 0)
{
setup(tos);
}
void resume(Coroutine* next)
{
switchContext(this->sp, next->sp);
}
virtual void body() = 0;
virtual void exit() = 0;
private:
static void startup(Coroutine* obj) {
obj->body();
obj->exit();
};
void setup(void* tos) {
if (tos == 0) {
unsigned temp_stack[1024];
this->sp = &temp_stack;
return;
}
this->sp = &tos;
return;
};
void* sp;
};
В настоящее время моя программа просто падает. Но это происходит только путем перезаписи адреса возврата на ассемблере «кому».
Где я делаю ошибку в этом процессе?
Я буду аплодировать вам за то, что вы не используете встроенный ассемблер и выбрали что-то более простое.
Ваш mov ebp,esp
не на своем месте. Это должно быть после сохранения реестра.
Вы не разыменовываете ссылки. С точки зрения ассемблера ссылка на C++ — это просто указатель, поэтому ваши параметры — void **
. Поскольку вы хотите сохранить/загрузить обратные адреса на то, на что указано, вам нужна дополнительная косвенность, чтобы сохранить значение на указанный адрес.
Не имеет отношения к этой проблеме: некоторые из вычислений адреса с использованием edx
можно сократить до меньшего количества инструкций. Вы также можете отказаться от использования ebp
и использовать смещения на основе esp
.
Вы совершенно правы. Это были ошибки сбоя.
Но все же процесс не меняет контекста. Он просто останавливается после первого вызова resume()
.
@IPodFan Ваш setup
неправильный. &temp_stack
и &tos
не соответствуют значениям стека, которые вам нужны для вашего switchContext
. Другая функция сборки (для получения этого начального значения) может помочь.
Я могу что-то упустить. Вы уверены, что не нужно сохранять EDI или ESI? Хотя эти регистры не используются самой функцией, я думаю, что все энергонезависимые (сохраняемые вызываемым пользователем) регистры (EBP, ESP, EBX, EDI и ESI) должны быть сохранены в коммутаторе сопрограммы. У меня сложилось впечатление, что предполагаемое использование этих конкретных регистров было направлено на то, чтобы гарантировать, что контекст вызывающей стороны всегда может быть правильно восстановлен. Регистры volatile (EAX, ECX, EDX) не имеют значения, так как не ожидается, что они сохранят свои значения при возврате switchContext
.
Без их сохранения у вас может быть что-то вроде EDI=1, ESI=2, затем call switchContext
. Предположим, что другая сопрограмма начинает выполняться, когда switchContext
возвращается и делает EDI=3, ESI=4, а затем делает call switchContext
. Если контекст переключается обратно на исходную процедуру, то switchContext
вернется с EDI=3, ESI=4, где эта сопрограмма может предполагать, что они не были изменены в соответствии с соглашением о вызовах, и ожидала, что они будут EDI=1, ESI=2.
У меня также есть опасения по этому поводу в коде ОП unsigned temp_stack[1024]; this->sp = &temp_stack;
. Это делается внутри setup
, так что массив создается как временный в стеке, который выйдет за пределы области видимости, когда setup
вернется. Это в значительной степени будет затерто позже. Так что сохранение указателя на него не поможет. Либо поместите массив в класс, либо динамически распределите массив (в свободном хранилище) - последнее было бы моим предпочтением.
Конечно, кроме того, стек будет расти вниз от sp
. sp
должен указывать на конец выделенной области стека, чтобы стек мог расти оттуда.
Вы оба правы. 1. Мне нужно присвоить начальный адрес возврата стеку, иначе я не могу вернуться без явного вызова body(). 2. Мне нужно правильно перенести свои данные, не перезаписывая их. Сейчас я работаю над этими пунктами.
Просто чтобы завершить это, не могли бы вы предоставить небольшую тестовую программу, которая использует этот класс для настройки нескольких сопрограмм. Это, по крайней мере, сделало бы это минимальный воспроизводимый пример для всех, кто найдет этот вопрос.