为何用eBPF分析LuaJIT GC时无法捕获GCT为Str类型的对象?
我正在使用eBPF分析LuaJIT的GC对象内存占用情况,根据lj_obj.h中的定义,字符串类型对应的GCT为~4u。但无论通过何种方式创建字符串对象(如以下Lua代码中的字符串定义与拼接操作),在GC对象统计结果中始终无法捕获到GCT为字符串类型的对象。
相关定义、代码及统计数据如下:
LuaJIT字符串类型定义
// luajit lj_obj.h #define LJ_TSTR (~4u)
eBPF核心代码
static long loop_gc_obj(u32 index, callback_ctx *ctx) { int res; GCRef p = ctx->p; // read o GCobj *o; res = bpf_probe_read(&o, sizeof(o), (GCobj *)(p.gcptr64)); if (res != 0) { return 1; } // read gct type uint8_t gct; res = bpf_probe_read(&gct, sizeof(gct), &(o->gch.gct)); if (res != 0) { return 1; } int gct_size = luajit_objlen(o, gct); // next gco res = bpf_probe_read(&p, sizeof(p), &(o->gch.nextgc)); if (res != 0) { return 1; } ctx->p = p; return 0; } SEC("kprobe/handle_entry_lua") int handle_entry_lua(struct pt_regs *ctx) { // ... GCRef p; bpf_probe_read(&p, sizeof(p), &(g->gc.root)); callback_ctx loop_ctx = { .p = p, }; bpf_loop(1 << 23, loop_gc_obj, &loop_ctx, 0); return 0; }
GC统计结果
+--------------------------------------------------------------------+ | Gct Type Name Count Sum Max Min | +--------------------------------------------------------------------+ | 5 upvalue 183 8784 48 48 | | 6 thread 1 0 0 0 | | 7 proto 77 30043 1868 120 | | 8 function 215 11984 160 40 | | 9 trace 8 0 0 0 | | 10 cdata 203 0 0 0 | | 11 table 72 21568 3136 64 | | 12 udata 2 144 80 64 | +--------------------------------------------------------------------+
测试Lua代码
local str1 = "Hello, " local str2 = "World!" local result = str1 .. str2
一、LuaJIT何时创建字符串类型GC对象
LuaJIT会在以下场景创建字符串GC对象:
- 显式字符串字面量:如
local s = "hello",短字符串会被驻留到全局字符串池,但依然属于GC管理的LJ_TSTR类型对象; - 字符串拼接:如
s1 .. s2,若拼接结果未在字符串池中存在,会创建新字符串对象(短字符串为LJ_TSTR,长字符串为LJ_TSTRL,对应~40u); - 动态生成字符串:如
tostring(123)、string.format("num: %d", 456)等运行时生成的字符串; - C API传入字符串:通过
lua_pushstring等C API向Lua栈中传入的字符串,也会被封装为GC对象。
二、eBPF无法捕获字符串对象的可能原因
1. GCT类型读取或解析错误
LJ_TSTR的定义~4u在32位无符号整数中是0xFFFFFFFC,截取为uint8_t时是0xFB(十进制251)。你的统计结果中Gct列仅显示5-12,说明大概率是将gct作为有符号8位整数处理:0xFB作为int8_t是-5,若统计时取绝对值或错误转换,会导致无法匹配到正确的字符串类型值。
另外,LuaJIT的长字符串类型LJ_TSTRL对应~40u,uint8_t值为0xD8(216),作为有符号数是-40,同样不会出现在你的统计列表中。
2. GC遍历范围不全
你的eBPF代码仅从g->gc.root开始遍历,但LuaJIT的字符串对象主要存储在全局字符串哈希表(g->strhash)中,部分字符串可能不在gc.root链上,导致遍历遗漏。
3. 内存读取偏移错误
GCobj的gch字段是GCheader结构体,若LuaJIT为64位编译,结构体的内存对齐规则可能导致gct字段的偏移与你预期不符,bpf_probe_read读取到的是错误字节。可以用pahole工具查看编译后的结构体布局,确认字段偏移是否正确。
4. GCRef指针转换错误
p.gcptr64转换为GCobj*时,若指针类型处理错误(如未考虑地址空间或对齐要求),会导致读取到无效内存,无法正确获取gct值。
排查建议
- 在eBPF代码中用
bpf_printk输出gct的原始uint8_t数值,确认是否能读取到251(LJ_TSTR)或216(LJ_TSTRL); - 补充遍历
g->strhash哈希表中的字符串对象,覆盖完整的字符串存储区域; - 使用
pahole工具验证GCheader结构体中gct字段的内存偏移; - 统计时确保将
gct作为无符号整数处理,避免有符号转换导致数值失真。
内容的提问来源于stack exchange,提问作者pea

