C上下文切换堆栈变量损坏

C Context switch stack variable corruption

我正在尝试通过创建一个简单的上下文切换函数和一个 FCFS 调度程序来在 C 中实现自定义线程。

我要执行的第一步是将整个函数堆栈帧复制到堆中,并将其替换为队列中的第一个帧。

我遇到的问题是,在完成第一个任务后,第二个任务的堆栈被破坏。我不知道为什么。

我的代码如下:

#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 的 var i 为 8 时,它会在下一次迭代中损坏。因此进入死循环。

如有任何帮助或建议,我们将不胜感激!祝你有个愉快的一天。

[编辑] 我想代码可能有点难以理解,所以在这里稍微澄清一下:

main 使用空参数调用 kyield。

kyield 检测到它并调用 f1

f1 执行直到 kwrite 被调用

kwrite 调用 kyield 并传递其当前堆栈帧

kyield 检测到最后一个堆栈帧为空,因此它复制 kwrite 给出的堆栈帧(从现在开始为 sf),然后调用 f2 f2 和 f1

一样

当 kyield 下次执行时,from 和 last 都不会为 NULL,因此它将用 last 中的那个覆盖它当前的 s f,将它与 from 中的那个交换,最后它将 return,作为堆栈已被更改,它将跳转到最后一个 kwrite 的 return 地址,而不是实际地址,因此。从f1线程跳转到f2.

Your memcpy(frame, a, SSIZE*sizeof(int)) looks wrong. Your SSIZE is defined to 15, but a has only a size of 10.

这是故意的,因为通过复制 4 个字节的 15 个元素,我们复制了 rax 最后一个值、最后一个 ebp 和函数的 return 地址。

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

您的 memcpy(frame, a, SSIZE*sizeof(int)) 看起来不对。您的 SSIZE 定义为 15,但 a 的大小仅为 10。

我发现设计存在一些问题。调度不同线程的正常方式是让它们获得完整的堆栈,而不是共享同一个堆栈。

在这种情况下,这意味着堆栈取决于所使用的地址。

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

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

您正在获取的堆栈切片独立于它前面的函数中使用的数量,因此如果调用 kwrite 的函数具有不同的堆栈要求(需要不同数量的状态),则会被破坏。

也是因为栈上抓取的信息量不完整,所以也坏了。执行状态基于堆栈和非易失性寄存器中的当前值。这些值会污染备用线程。

最后堆栈也包含地址。只有当所有执行的线程具有相同的堆栈要求时,该方案才有效,就好像 yield 函数将需要地址的值粘贴回去,那么它们总是必须对齐。