使用__attribute__((musttail))编译时尾调用损坏问题求助
clang搭配
__attribute__((musttail))时线程化虚拟机调用栈损坏问题 问题分析
你遇到的问题根源有两点:
__attribute__((musttail))的严格约束:clang要求尾调用的参数必须是原函数参数的直接传递/提取值,无优化下按值传递包含指针的复杂结构体Block时,编译器无法正确复用栈帧,导致指针指向无效内存。- 栈容量初始化错误:
cap被初始化为128 * sizeof(int64_t)(即1024字节),但sc是栈元素计数,当sc >= cap时,栈实际只能容纳128个int64_t元素,此时扩容逻辑会导致越界写入,破坏调用栈。
此外,移除Fn/Scope结构体后问题消失,是因为这简化了Block的内存布局,降低了编译器处理musttail时出错的概率;开启优化后编译器会自动修复栈布局和参数传递的问题,而fsanitize=memory会对内存访问做额外检查,避免了越界写入的影响。
解决方案
1. 修正栈容量逻辑
将栈容量的计数单位从字节改为元素数量,避免越界:
uint32_t cap = 128; // 初始容纳128个int64_t元素 void vm_init(void) { stack = malloc(cap * sizeof(int64_t)); } void stack_push(int64_t v) { if (++sc >= cap) { cap *= 2; stack = realloc(stack, cap * sizeof(int64_t)); } stack[sc - 1] = v; }
2. 改用指针传递Block结构体
避免按值传递复杂结构体,减少musttail处理时的栈帧冲突:
// 修改函数定义宏 #define F_INS(x) char x(int ic, Block *b) // 修改尾调用宏 #define INSRET MUSTTAIL return b->ins[ic + 1].op(ic + 1, b); // 对应修改各个指令函数 F_INS(Done) { return 0; } F_INS(Imm) { printf("IC: %u\n", ic); stack_push(b->ins[ic].imm); INSRET; } F_INS(Add) { int64_t y = stack[--sc]; int64_t x = stack[--sc]; stack_push(x + y); INSRET; } // main函数中传递指针 (*b.ins[0].op)(0, &b);
3. 确保musttail的参数传递符合clang要求
clang的musttail要求尾调用的参数必须是原函数的输入参数,或仅对输入参数做简单提取(如指针访问),不能传递经过修改的局部结构体副本。改用指针传递后,参数直接复用原指针,完全符合musttail的约束。
复现原代码(修正转义后)
#include <stdio.h> #include <stdlib.h> #include <stdint.h> typedef enum { TypeInt = 0 } Tag; typedef struct { int n; /* item count */ int64_t *val; /* pointer */ } List; typedef struct Block { struct Ins *ins; int n; struct Fn *fn; struct Scope *locals; struct Scope *mod; } Block; typedef struct Scope { int n; const char **names; int64_t *vals; Block first; } Scope; typedef struct Ins { char (*op)(int, Block); int64_t imm; } Ins; typedef struct Fn { Scope locals; Scope *mod; } Fn; int64_t *stack; uint32_t sc; /* stack counter */ uint32_t fc; /* frame counter */ uint32_t cap = 128 * sizeof(int64_t); #define F_INS(x) char x(int ic, Block b) #define MUSTTAIL __attribute__((musttail)) #define INSRET MUSTTAIL return b.ins[ic + 1].op(ic + 1, b); #define N_BUILTINS 1 const char *builtins[N_BUILTINS] = { "+" }; void vm_init(void) { stack = malloc(cap); } F_INS(Done) { return 0; } void stack_push(int64_t v) { if (++sc >= cap) stack = realloc(stack, cap *= 2); stack[sc - 1] = v; } F_INS(Imm) { printf("IC: %u\n", ic); stack_push(b.ins[ic].imm); INSRET; } F_INS(Add) { int64_t y = stack[--sc]; int64_t x = stack[--sc]; stack_push(x + y); INSRET; } int main(int argc, char **argv) { Block b = {0}; vm_init(); b.ins = realloc(b.ins, ++b.n * sizeof(Ins)); b.ins[b.n - 1].op = &Imm; b.ins[b.n - 1].imm = 3; b.ins = realloc(b.ins, ++b.n * sizeof(Ins)); b.ins[b.n - 1].op = &Imm; b.ins[b.n - 1].imm = 4; b.ins = realloc(b.ins, ++b.n * sizeof(Ins)); b.ins[b.n - 1].op = &Add; b.ins = realloc(b.ins, ++b.n * sizeof(Ins)); b.ins[b.n - 1].op = &Done; (*b.ins[0].op)(0, b); }
环境信息
clang version 14.0.6 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin
内容的提问来源于stack exchange,提问作者Jade
相关产品推荐
相关产品推荐

