有没有办法在C中保存和恢复调用堆栈

时间:2014-05-30 21:05:42

标签: c stack callstack

我想创建两个执行线程,执行两个不同的函数,然后使用相同的调用堆栈返回。执行不需要并行执行,执行的线程可以一个接一个地执行。

我试图用setcontext / getcontext解决问题,但是他们不复制完整的调用堆栈,第二个线程不能使用第一个线程的调用堆栈:

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

ucontext_t fork_context, fork_context2;
char stack[SIGSTKSZ];
int end = 0;

void func2_1() {
  printf("func2_1\n");
}


void func2_2() {
  printf("func2_2\n");
  end = 1;
}

void func2() {
  printf("func2\n");
  func2_1();

  getcontext(&fork_context);
  fork_context.uc_link          = &fork_context2;
  fork_context.uc_stack.ss_sp   = stack;
  fork_context.uc_stack.ss_size = sizeof(stack);
  makecontext(&fork_context, (void (*)(void)) func2_2, 0);
}

void func1() {
  printf("enter func1\n");
  func2();
  getcontext(&fork_context2);
  printf("return func1\n");
}


int main(void) {
  func1();
  printf("back in main\n\n");

  if (end != 1) {
    setcontext(&fork_context);
  }
  return 0;
}

func2()中,我想执行func2_1(),然后根据调用堆栈返回。在从main()返回之前,我想执行func2_2()并返回与从func2_1()返回时相同的功能。上面的代码产生以下输出:

func2_1
return func1
back in main

func2_2
return func1
正确调用

func2_2()然后在func1()继续执行,但func1()不会返回main(),因为func1()的堆栈帧}呼叫已被删除。我想要一个产生以下输出的程序:

func2_1
return func1
back in main

func2_2
return func1
back in main

一种可能性是使用POSIX fork()复制整个过程(包括调用堆栈),但如果可能的话,我宁愿不使用fork()

2 个答案:

答案 0 :(得分:3)

  

有没有办法在C

中保存和恢复调用堆栈

没有。堆栈的行为方式(是否以及哪些函数参数在寄存器或堆栈中传递,哪些寄存器必须保存/恢复等)是活动ABI和CPU的函数(例如,Linux ELF ABI有点不同)在amd64上比在x86上,并且任何操作系统可能同时支持多个ABI)并且 C 对此一无所知。可能通过某些特定于平台的API来完成,但这不是 C 问题。

  

我想创建两个执行线程,执行两个不同的函数

除了主线程之外,您是要创建两个新线程还是创建一个额外线程?如果在主线程中创建一个新线程,现在有两个执行线程,但是你只创建了一个(附加)线程。

新线程的“启动例程”调用的任何函数(或由这些函数调用的函数等)将在新线程中执行。由main()调用的所有函数,或由main()调用的函数调用的函数(依此类推)将在主线程中执行。

  

然后使用相同的调用堆栈返回。

咦?使用与堆栈相同的来从返回 的内容?就它而言,该条款只是令人困惑!

  

执行不需要并行执行,执行的线程可以一个接一个地执行。

那么为什么要创建第二个线程而不只是按顺序调用函数?

  

我试图用setcontext / getcontext

解决问题

不要那样做。这些功能在2001年正式弃用,不再出现在POSIX / SUS中。新应用程序(或过去十年中编写的应用程序)应该使用POSIX线程。

这是一个POSIX线程程序,它粗略地反映了你在代码提取中试图做的事情,但老实说,我不确定我有多接近,因为我真的不清楚你是什么试图做。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>

#define bail(err, msg) { \
    errno = err;         \
    perror(msg);         \
    exit(1);             \
}


pthread_t tid;

// Executes in the main thread
void func2_1() {
    printf("func2_1\n");
}

// Conforms to prototype expected by pthread_create(); executes in new
// thread.
void *func2_2(void *arg) {
    (void)arg;
    printf("func2_2\n");

    // I suppose we just exit here, since there's nothing else for
    // this thread to do:
    pthread_exit(NULL);
}

// Executes in main thread
void func2() {
    int rc; // Return code

    printf("enter func2\n");

    func2_1(); // Executes in main thread

    rc = pthread_create(&tid, NULL, func2_2, NULL);
    if(rc) {
        bail(rc, "pthread_create() failed")
    }

    printf("return func2\n");
}

// Executes in main thread
void func1() {
    printf("enter func1\n");
    func2();
    printf("return func1\n");
}


int main(void) {
    int rc; // Return code

    func1();
    printf("back in main\n\n");

    rc = pthread_join( tid, NULL );
    if(rc) {
        bail(rc, "pthread_join() failed");
    }

    printf("returning from main()\n");
    return 0;
}

答案 1 :(得分:2)

我无法帮助认为fork()是解决此问题的最简单方法,尽管您可能需要一些共享内存才能允许合并两个调用的结果。 fork()复制整个流程结构并不完全正确;它通常是针对单个虚拟内存页面的copy-on-write实现的,因此它最终只会复制被修改状态的那部分;例如,它没有包含流程的代码。

如果您想自己动手,可以使用posix线程库的pthread_attr_setstack创建一个具有特定堆栈的线程,您需要自己分配。由于您可以控制堆栈内存,因此您可以memcpy释放它以保留它。理论上,至少,您可以使用复制的堆栈创建另一个线程,并使用现已弃用的getcontext和朋友来保存和恢复不在堆栈中的调用状态部分。要有效地复制堆栈,您需要知道它的限制;有多种方法可以做到这一点,但它们都不是平台独立的。

如果您要提供call/cc的C实现,您可能需要查看Chicken Scheme(http://www.call-cc.org)中的经典实现,这需要很多C堆栈中有趣的自由,显然是成功的。