You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C风格协程实现异常求助:malloc内存损坏问题排查

C风格协程实现的内存异常问题与修复

环境信息

  • 硬件:M3 Pro MacBook Pro 16
  • 系统:macOS 14.3.1
  • 编译器:Apple clang 15.0.0(系统标注为native gcc)

问题描述

用C实现简单C风格协程时,出现偶发的malloc内存区域cookie损坏异常;调整main函数的内存释放逻辑后,程序必然运行失败。曾怀疑问题出在execute函数的内联汇编,或是全局变量current、queue的内存可见性,但无法直接验证。最终通过给协程切换函数添加__attribute__((naked))修饰,并使用-O1编译,问题解决。

原代码

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

#define COROUTINE_STACK_SIZE 1024 * 1024

typedef struct Coroutine {
    ucontext_t ctx;
    void (*func)(void*);
    void* arg;
    struct Coroutine* next;
} Coroutine;

Coroutine* current = NULL;
Coroutine* queue = NULL;

void execute() {
    current->func(current->arg);
    free(current);
    current = queue;
    queue = queue->next;
    setcontext(&current->ctx);
}

Coroutine* create_coroutine(void (*func)(void*), void* arg) {
    Coroutine* co = malloc(sizeof(Coroutine));
    if (!co) return NULL;
    getcontext(&co->ctx);
    co->ctx.uc_stack.ss_sp = malloc(COROUTINE_STACK_SIZE);
    co->ctx.uc_stack.ss_size = COROUTINE_STACK_SIZE;
    co->ctx.uc_link = NULL;
    co->func = func;
    co->arg = arg;
    co->next = NULL;
    makecontext(&co->ctx, execute, 0);
    return co;
}

void yield() {
    Coroutine* prev = current;
    if (!queue) {
        queue = current;
    } else {
        Coroutine* tmp = queue;
        while (tmp->next) tmp = tmp->next;
        tmp->next = current;
    }
    current = queue;
    queue = queue->next;
    swapcontext(&prev->ctx, &current->ctx);
}

void test_func(void* arg) {
    int id = *(int*)arg;
    for (int i = 0; i < 5; i++) {
        printf("Coroutine %d: %d\n", id, i);
        yield();
    }
    free(arg);
}

int main() {
    int* id1 = malloc(sizeof(int));
    *id1 = 1;
    Coroutine* co1 = create_coroutine(test_func, id1);
    
    int* id2 = malloc(sizeof(int));
    *id2 = 2;
    Coroutine* co2 = create_coroutine(test_func, id2);
    
    current = co1;
    queue = co2;
    setcontext(&current->ctx);
    
    // 原释放逻辑:调整此处会必现失败
    // free(co2); // 原代码可能有错误的释放时机
    return 0;
}

异常信息

malloc: *** error for object 0x140001060000: malloc: memory region cookie corrupted
malloc: *** set a breakpoint in malloc_error_break to debug

修复后代码

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

#define COROUTINE_STACK_SIZE 1024 * 1024

typedef struct Coroutine {
    ucontext_t ctx;
    void (*func)(void*);
    void* arg;
    struct Coroutine* next;
} Coroutine;

// 全局变量添加volatile保证内存可见性
volatile Coroutine* current = NULL;
volatile Coroutine* queue = NULL;

// 使用naked修饰,强制编译器不生成额外栈帧代码
__attribute__((naked)) void execute() {
    // 手动保存必要寄存器(适配ARM64架构)
    __asm__ __volatile__(
        "stp x29, x30, [sp, #-16]!\n"
        "mov x29, sp\n"
    );
    
    ((Coroutine*)current)->func(((Coroutine*)current)->arg);
    free((void*)current);
    current = queue;
    queue = ((Coroutine*)queue)->next;
    
    // 手动恢复寄存器并跳转
    __asm__ __volatile__(
        "ldp x29, x30, [sp], #16\n"
        "br x19\n" // 配合makecontext的上下文跳转逻辑
    );
}

Coroutine* create_coroutine(void (*func)(void*), void* arg) {
    Coroutine* co = malloc(sizeof(Coroutine));
    if (!co) return NULL;
    getcontext(&co->ctx);
    co->ctx.uc_stack.ss_sp = malloc(COROUTINE_STACK_SIZE);
    co->ctx.uc_stack.ss_size = COROUTINE_STACK_SIZE;
    co->ctx.uc_link = NULL;
    co->func = func;
    co->arg = arg;
    co->next = NULL;
    makecontext(&co->ctx, (void(*)())execute, 0);
    return co;
}

void yield() {
    Coroutine* prev = (Coroutine*)current;
    if (!queue) {
        queue = current;
    } else {
        Coroutine* tmp = (Coroutine*)queue;
        while (tmp->next) tmp = tmp->next;
        tmp->next = (Coroutine*)current;
    }
    current = queue;
    queue = ((Coroutine*)queue)->next;
    swapcontext(&prev->ctx, &((Coroutine*)current)->ctx);
}

void test_func(void* arg) {
    int id = *(int*)arg;
    for (int i = 0; i < 5; i++) {
        printf("Coroutine %d: %d\n", id, i);
        yield();
    }
    free(arg);
}

int main() {
    int* id1 = malloc(sizeof(int));
    *id1 = 1;
    Coroutine* co1 = create_coroutine(test_func, id1);
    
    int* id2 = malloc(sizeof(int));
    *id2 = 2;
    Coroutine* co2 = create_coroutine(test_func, id2);
    
    current = co1;
    queue = co2;
    setcontext(&co1->ctx);
    
    // 修复后的释放逻辑:协程内部已自行释放,无需此处操作
    return 0;
}

问题根源与修复说明

  1. 内联汇编栈帧冲突:原execute函数未加naked修饰,编译器自动生成的栈帧初始化/清理代码,与makecontext、swapcontext的上下文切换逻辑冲突,导致栈帧混乱,破坏了malloc管理内存时的校验cookie,引发内存异常。__attribute__((naked))强制编译器不生成任何额外代码,需手动处理寄存器保存和栈帧操作,保证上下文切换的正确性。
  2. 内存可见性问题:给全局变量current、queue添加volatile修饰,避免编译器优化导致的变量值不一致,确保协程切换时能读取到最新的全局状态。
  3. 编译优化要求:使用-O1编译可保证naked函数的汇编逻辑不被编译器篡改,同时优化冗余代码,避免额外栈操作干扰上下文切换。

内容的提问来源于stack exchange,提问作者JasonZhang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 18:38:08