C Повреждение переменной стека переключения контекста - PullRequest
0 голосов
/ 19 декабря 2018

Я пытаюсь реализовать пользовательские потоки в C, создав простую функцию переключения контекста и планировщик FCFS.

Первый шаг, который я хочу выполнить, - это скопировать весь кадр стека функций в кучу в сохраненныйкадр и заменить его первым в очереди.

Проблема, с которой я столкнулся, заключается в том, что после завершения первого задания стек второго поврежден.Я не имею ни малейшего представления о том, почему.

У меня следующий код:

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define ITERATIONS 10
#define SSIZE 15

int * last;

void kwrite(const char*);
void kyield(int *);

void f1() {
    int i = ITERATIONS;
    while (i--) kwrite("A\n");
}

void f2() {
    int i = ITERATIONS*2;
    while (i--) {
        printf("[%d]", i);
        kwrite("B\n");
        getchar();
    }
}

void kwrite(const char* str) {
    int a[10] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5};
    write(1, str, strlen(str));

    int *frame = malloc(sizeof(int)*SSIZE);
    memcpy(frame, a, SSIZE*sizeof(int));
    kyield(frame);
    printf("ERROR\n");
}

void kyield(int * from) {
    if (from == NULL) {
        f1();
        from = malloc(sizeof(int)*SSIZE);
        memcpy(from, last, SSIZE*sizeof(int));
    }
    if (last == NULL) {
        last = malloc(sizeof(int)*SSIZE);
        memcpy(last, from, SSIZE*sizeof(int));
        free(from);
        f2();
        exit(0);
    }

    int a[10] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
    memcpy(a, last, SSIZE*sizeof(int));
    memcpy(last, from, SSIZE*sizeof(int));
    free(from);
}

int main(int argc, char** argv) {
    kyield(NULL);
    free(last);
}

Он должен вызвать 10 раз f1 и 20 f2, а затем выйти.Но когда переменная f2 равна 8, она будет повреждена на следующей итерации.Поэтому вход в бесконечный цикл.

Любая помощь или предложения будут оценены!Хорошего дня.

[edit] Я полагаю, что код может быть немного сложным для понимания, поэтому здесь есть небольшое пояснение:

main вызывает kyield с нулевыми параметрами.

kyield обнаруживает его и вызывает f1

f1 выполняется до тех пор, пока kwrite не вызывается

kwrite вызывает kyield, передавая его текущий кадр стека

kyield обнаруживает, что последний кадр стека равен нулю, поэтому онкопирует кадр стека (sf с этого момента), заданный kwrite, затем вызывает f2 f2 делает то же самое, что и f1

, когда kyield выполняется следующим образом, и от, и от последнего не будет NULL, поэтому он будет перезаписывать свой текущий sf споследний в последнем, поменяйте его местами с последним и, наконец, он вернется, так как стек был изменен, поэтому он перейдет к адресу возврата последнего kwrite, а не фактического, таким образом.Переход с потока f1 на f2.

Ваш memcpy (frame, a, SSIZE * sizeof (int)) выглядит неправильно.Ваш SSIZE определен как 15, но a имеет только размер 10.

это преднамеренно, так как при копировании 15 элементов по 4 байта мы копируем последнее значение rax, последнее ebp иобратный адрес функции.

https://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/

Ответы [ 2 ]

0 голосов
/ 19 декабря 2018

Я вижу пару проблем с дизайном.Обычный способ планирования различных потоков состоит в том, что они получают полные стеки, а не разделяют один и тот же стек.

В данном случае это означает, что стек зависит от используемых адресов.

+---------+--------+---------+--------+---------+
| main    | kyield | f1      | kwrite |  kyield |
|         |        |         |a[10]   |         |
+---------+--------+---------+--------+---------+

                        |-------------|  << copied by the slice of the stack.

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

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

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

0 голосов
/ 19 декабря 2018

Ваш memcpy(frame, a, SSIZE*sizeof(int)) выглядит неправильно.Ваш SSIZE определен как 15, но a имеет размер только 10.

...