【问题标题】:C Context switch stack variable corruptionC上下文切换堆栈变量损坏
【发布时间】:2019-05-19 12:24:45
【问题描述】:

我试图通过创建一个简单的上下文切换函数和一个 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 调用带有 null 参数的 kyield。

kyield 检测到它并调用 f1

f1 一直执行到 kwrite 被调用

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

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

当接下来执行 kyield 时,from 和 last 都不会为 NULL,因此它将用 last 覆盖其当前 sf,将其与 from 交换,最后它将返回,因为堆栈已被更改,它将跳转到最后一个 kwrite 的返回地址,而不是实际的,因此。从 f1 线程跳转到 f2。

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

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

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

【问题讨论】:

  • memcpy(from, last, SSIZE*sizeof(int));被执行时,last == NULL。你希望这个memcpy() 会发生什么?
  • @chux 这似乎很令人困惑,但我认为f1 调用kwrite 调用kyieldfrom != NULL,因此在第一个kyield 调用到达这一行之前,@987654332 @ 将通过另一个kyield 调用分配给memcpyed。仍然没有说明意图。
  • @user10605163 同意。
  • @chux 你必须明白这里有两个并发线程。尽管代码看起来很简单,但它确实很棘手。回答您的问题,最后执行提到的 memcpy 时不会为空,因为另一个 kyield 调用先前已分配它。
  • Tretorn,这里只有一个thread。 @user10605163 已经解释了代码。

标签: c multithreading stack stack-frame


【解决方案1】:

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

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

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

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

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

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

最后,堆栈还包含地址。这种方案只有在所有执行的线程都具有相同的堆栈要求时才有效,就像 yield 函数将一个值粘贴回需要地址一样,那么它们总是必须对齐。

【讨论】:

  • 感谢您的回答。我知道我必须给他们不同的堆栈。我以为我是。对于这种方法,您有任何参考/文档/示例吗?
【解决方案2】:

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

【讨论】:

  • 同意,int a[SSIZE] 会更有意义。
  • @Georg 我回答了为什么这不能出现在主帖中。也许我对我所做的事情的背景太少了。
  • @Tretorn "我们正在复制 rax 的最后一个值、最后一个 ebp 和函数的返回地址。" ---> 这取决于编译器/平台上memcpy() 的选择未定义行为 (UB)(帖子未使用任何一个标记)以及您对这些的正确解释。没有这些信息,问题就不清楚了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-08
  • 1970-01-01
  • 1970-01-01
  • 2012-11-05
  • 1970-01-01
  • 2013-07-28
  • 1970-01-01
相关资源
最近更新 更多