check_object()是 SLUB 调试一致性检查的核心函数:校验单个对象的所有区域(红区、poison、padding)是否完好,发现破坏则报告并尝试恢复。它综合调用前面讲过的多个辅助函数。
一、函数签名
static int check_object(struct kmem_cache *s, struct slab *slab, void *object, u8 val)
| 参数 | 含义 |
|---|---|
s | 缓存 |
slab | 对象所在 slab 页 |
object | 要校验的对象指针 |
val | 期望的 poison 值(POISON_FREE或POISON_INUSE) |
返回int:1 表示对象完好,0 表示发现破坏。
val的含义
val由调用者传入,表示该对象当前应处于什么状态:
POISON_FREE(0x6b):对象空闲,应填0x6b。POISON_INUSE(0x5a):对象已分配使用中,应填0x5a。
调用者根据对象是否在 freelist 上判断:
val = test_bit(index, object_map) ? POISON_INUSE : POISON_FREE;
二、典型实现
static int check_object(struct kmem_cache *s, struct slab *slab, void *object, u8 val) { u8 *p = object; u8 *endobject = object + s->object_size; if (s->flags & SLAB_RED_ZONE) { // 左红区 if (!check_bytes_and_report(s, slab, object, "Left Redzone", object - s->red_left_pad, val, s->red_left_pad, true)) return 0; // 右红区 if (!check_bytes_and_report(s, slab, object, "Right Redzone", endobject, val, s->inuse - s->object_size, true)) return 0; } else { // 未开红区,对象首尾的 padding 也应检查 if ((unsigned long *)object != slab->freelist && !check_bytes_and_report(s, slab, object, "Object head", object, val, sizeof(void *), true)) return 0; } if (s->flags & SLAB_POISON) { // 对象数据区后的 poison if (!check_bytes_and_report(s, slab, object, "Poison", p + s->inuse, val, s->size - s->inuse, true)) return 0; } // 对象内部 padding if (!check_pad_bytes(s, slab, p)) return 0; return 1; }逻辑顺序:
红区检查(若
SLAB_RED_ZONE):左红区:
object - red_left_pad到object,期望val。右红区:
endobject到inuse,期望val。
对象头检查(无红区时):对象开头
sizeof(void *)字节,排除 freelist 头对象。poison 检查(若
SLAB_POISON):p + inuse到s->size,期望val。padding 检查:
check_pad_bytes()校验元数据后到槽位结束的填充。全部通过返回 1,任一失败返回 0。
三、各部分解析
1. 左红区
check_bytes_and_report(s, slab, object, "Left Redzone", object - s->red_left_pad, val, s->red_left_pad, true)
起点
object - red_left_pad,长度red_left_pad。期望值
val(对象状态对应的 poison)。左红区被越界写破坏时,这里会检测到。
2. 右红区
check_bytes_and_report(s, slab, object, "Right Redzone", endobject, val, s->inuse - s->object_size, true)
起点
object + object_size,长度inuse - object_size。右红区被越界写破坏时检测到。
3. 无红区时的对象头
if ((unsigned long *)object != slab->freelist && !check_bytes_and_report(s, slab, object, "Object head", object, val, sizeof(void *), true))
无红区时,对象开头可能被 freelist 复用为下一个指针。
若
object正是slab->freelist头,其开头是 freepointer,不是 poison,跳过检查。否则检查对象开头
sizeof(void *)字节是否为val。
4. poison 区
check_bytes_and_report(s, slab, object, "Poison", p + s->inuse, val, s->size - s->inuse, true)
对象有效数据后到槽位结束,期望
val。检测对象数据区是否被越界写、是否被释放后改写。
5. padding
if (!check_pad_bytes(s, slab, p)) return 0;
委托
check_pad_bytes()校验元数据(track、orig_size 等)后的填充区。与上面几个
check_bytes_and_report互补,覆盖元数据之后的空间。
四、返回值与调用者的处理
| 返回 | 含义 | 调用者动作 |
|---|---|---|
| 1 | 对象完好 | 继续校验其他对象 |
| 0 | 发现破坏(已报告+恢复) | 计入错误,可能影响 slab 状态判断 |
validate_slab()里遍历所有对象:
for_each_object(p, s, addr, slab->objects) { u8 val = test_bit(__obj_to_index(s, slab, p), map) ? POISON_INUSE : POISON_FREE; if (!check_object(s, slab, p, val)) return 0; // 该 slab 校验失败 }五、与相关函数的关系
check_object() ├─ check_bytes_and_report() // 红区、poison、对象头 │ ├─ memchr_inv() │ ├─ slab_bug() │ ├─ print_trailer() │ └─ restore_bytes() │ └─ check_pad_bytes() // 对象内部 padding └─ check_bytes_and_report()
| 函数 | 检查区域 | 期望值 |
|---|---|---|
check_bytes_and_report(左红区) | 对象前 | val |
check_bytes_and_report(右红区) | 对象后 | val |
check_bytes_and_report(对象头) | 对象开头 | val |
check_bytes_and_report(poison) | 数据区后 | val |
check_pad_bytes | 元数据后 | POISON_INUSE |
六、val与对象状态
check_object()的关键在于val的正确选择:
对象在 freelist 上(空闲):
val = POISON_FREE,期望空闲区域填0x6b。对象已分配(使用中):
val = POISON_INUSE,期望使用区域填0x5a。
如果期望值与实际不符,说明:
空闲对象被改写 → use-after-free。
已分配对象的 poison 被覆盖 → 未初始化使用或越界写。
红区被破坏 → 越界写。
七、与前面知识的呼应
| 前面讲过 | 这里体现 |
|---|---|
check_bytes_and_report() | 核心校验子函数 |
check_pad_bytes() | 对象内部 padding 检查 |
restore_bytes() | 破坏恢复 |
print_trailer() | 现场打印 |
SLAB_RED_ZONE/SLAB_POISON | 控制检查哪些区域 |
red_left_pad/inuse/size | 区域边界计算 |
POISON_FREE/POISON_INUSE | val取值 |
object_map/__obj_to_index | 判断对象是否已分配,决定val |
for_each_object() | 遍历对象调用check_object |
八、一句话总结
check_object(s, slab, object, val)校验单个对象的左红区、右红区、对象头、poison 区、内部 padding是否都等于期望的val(空闲POISON_FREE/ 使用POISON_INUSE):逐段调用check_bytes_and_report()和check_pad_bytes(),发现破坏就报告、打印现场并恢复。返回 1 表示对象完好,0 表示发现并修复了破坏。它是validate_slab()的核心子函数,是 SLUB 调试一致性检查的落地点。