设置堆栈后在 C 中调用 printf 时出现分段错误

Posted

技术标签:

【中文标题】设置堆栈后在 C 中调用 printf 时出现分段错误【英文标题】:Segmentation fault when calling printf in C after setting the stack 【发布时间】:2022-01-10 12:46:43 【问题描述】:

我正在为一个操作系统类做一个练习,并在使用参数调用 printf 时遇到 SegFault 错误。

练习的目的是模拟一个线程的初始化并打印一个计数器,不是很困难。我有一个包含 4 个条目的表,每个条目的大小为 4096 字节,每个条目必须代表线程的堆栈,表示为

#define STACK_SIZE 4096

char table[4][STACK_SIZE];

我定义了一个叫做协程的类型,它只会得到一个堆栈地址

typedef void* coroutine_t;

我有一个初始化代码。此代码必须在例程堆栈的末尾,附加协程的地址和寄存器的初始化,并返回将成为协程堆栈指针的指针。

coroutine_t init_coroutine(void *stack_begin, unsigned int stack_size,
                           void (*initial_pc)(void)) 
    char *stack_end = ((char *)stack_begin) + stack_size;
    void **ptr = (void**) stack_end;
    ptr--;
    *ptr = initial_pc;
    ptr--;
    *ptr = stack_end; /* Frame pointer */
    ptr--;
    *ptr = 0; /* RBX*/
    ptr--;
    *ptr = 0; /* R12 */
    ptr--;
    *ptr = 0; /* R13 */
    ptr--;
    *ptr = 0; /* R14 */
    ptr--;
    *ptr = 0; /* R15 */

    return ptr;

然后我在 x86 汇编中有这段代码来进入刚刚弹出先前推送的寄存器的协程

.global enter_coroutine /* Makes enter_coroutine visible to the linker*/
enter_coroutine:
mov %rdi,%rsp /* RDI contains the argument to enter_coroutine. */
              /* And is copied to RSP. */
pop %r15
pop %r14
pop %r13
pop %r12
pop %rbx
pop %rbp
ret /* Pop the program counter */

我的其余代码是这样的

coroutine_t cr;
void test_function() 
    int counter = 0;
    while(1) 
        printf("counter1: %d\n", counter);
        counter++;
    


int main() 
    cr = init_coroutine(table[0], STACK_SIZE, &test_function);
    enter_coroutine(cr);
    return 0;

所以对于错误 如果我按原样运行,当程序调用 printf 时,gdb 的输出会出现段错误

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7dfcfdd in __vfprintf_internal (s=0x7ffff7f9d760 <_IO_2_1_stdout_>, format=0x555555556004 "counter1: %d\n", ap=ap@entry=0x555555558f48 <table+3848>, mode_flags=mode_flags@entry=0) at vfprintf-internal.c:1385

我认为堆栈发生了一些事情有两个原因:

如果我只打印一个不带参数的字符串,我不会出错 如果我从 init_coroutine 函数中删除第一个 ptr-- 语句,它也可以工作,但会在堆栈末尾分配东西,因此在另一个线程的堆栈中

我在 Intel(R) Core(TM) i5-5200U CPU 上运行它,带有 ubuntu 21.10 和 ggc 版本 11.2.0

你能给我点灯吗?

【问题讨论】:

【参考方案1】:

我无法在我的 x86_64 Linux 机器上重现该问题,但我使用的是编译器资源管理器,问题似乎是简单的堆栈溢出(即 4096 对于 printf 来说太小了)。 增加堆栈大小(或选择 table[1]、table[2] 或 table[3] 而不是 table[0],这实际上与增加堆栈大小相同)似乎使它起作用:https://gcc.godbolt.org/z/rnfMThbjo

【讨论】:

我也相信 coro-entering code 可以只是mov %rdi,%rsp; ret; ,相应地,coro-initializing code 可以在存储初始 pc 后结束。在 coro 开始时被调用者保存的寄存器的值无关紧要,IMO。 我不能仅仅调整堆栈大小来解决问题,但是将分配*ptr = initial_pc更改为*ptr=initial_pc + 8,它只是在调用test_function时跳过堆栈和帧寄存器初始化可以解决问题,我真的不知道为什么这只是发生在我身上,这没有意义

以上是关于设置堆栈后在 C 中调用 printf 时出现分段错误的主要内容,如果未能解决你的问题,请参考以下文章

调用printf%s时出现分段错误

为啥在访问二级指针时出现分段错误错误? C语言

在调用函数中取消引用指针时出现分段错误

发生冲突后在哈希表中实现链接时出现分段错误

从 C 调用汇编函数时出现分段错误错误

调用 c 函数时出现分段错误