同学们面对的困境

理论很美好,现实很残酷

  • 我们的框架直接运行在虚拟机上
    • 根本没法带这些 sanitizers
  • 我们根本不可能 “观测每一次共享内存访问”
    • 直接摆烂
    • (困难是摆烂的第一原因)

另一种思路 (rule of thumb)

  • 不实现 “完整” 的检查
  • 允许存在误报/漏报
  • 实现简单、非常有用

例子:Buffer Overrun 检查

Canary (金丝雀) 对一氧化碳非常敏感

  • 用生命预警矿井下的瓦斯泄露 (since 1911)

计算机系统中的 canary

  • “牺牲” 一些内存单元,来预警 memory error 的发生
    • (程序运行时没有动物受到实质的伤害)

Canary 的例子:保护栈空间 (Stack Guard)

#define MAGIC 0x55555555
#define BOTTOM (STK_SZ / sizeof(u32) - 1)
struct stack { char data[STK_SZ]; };

void canary_init(struct stack *s) {
  u32 *ptr = (u32 *)s;
  for (int i = 0; i < CANARY_SZ; i++)
    ptr[BOTTOM - i] = ptr[i] = MAGIC;
}

void canary_check(struct stack *s) {
  u32 *ptr = (u32 *)s;
  for (int i = 0; i < CANARY_SZ; i++) {
    panic_on(ptr[BOTTOM - i] != MAGIC, "underflow");
    panic_on(ptr[i] != MAGIC, "overflow");
  }
}

烫烫烫、屯屯屯和葺葺葺

msvc 中 debug mode 的 guard/fence/canary

  • 未初始化栈: 0xcccccccc
  • 未初始化堆: 0xcdcdcdcd
  • 对象头尾: 0xfdfdfdfd
  • 已回收内存: 0xdddddddd
    • 手持两把锟斤拷,口中疾呼烫烫烫
    • 脚踏千朵屯屯屯,笑看万物锘锘锘
    • (它们一直在无形中保护你)

(b'\xcc' * 80).decode('gb2312')

防御性编程:低配版 Lockdep

不必大费周章记录什么上锁顺序

  • 统计当前的 spin count
    • 如果超过某个明显不正常的数值 (1,000,000,000) 就报告
int count = 0;
while (xchg(&lk, LOCKED) == LOCKED) {
  if (count++ > SPIN_LIMIT) {
    panic("Spin limit exceeded @ %s:%d\n", __FILE__, __LINE__);
  }
}
  • 配合调试器和线程 backtrace 一秒诊断死锁

防御性编程:低配版 AddressSanitizer (L1)

内存分配要求:已分配内存 $S = [\ell_0, r_0) \cup [\ell_1, r_1) \cup \ldots$

  • kalloc($s$) 返回的 $[\ell, r)$ 必须满足 $[\ell, r) \cap S = \varnothing$
    • thread-local allocation + 并发的 free 还蛮容易弄错的
// allocation
for (int i = 0; (i + 1) * sizeof(u32) <= size; i++) {
  panic_on(((u32 *)ptr)[i] == MAGIC, "double-allocation");
  arr[i] = MAGIC;
}

// free
for (int i = 0; (i + 1) * sizeof(u32) <= alloc_size(ptr); i++) {
  panic_on(((u32 *)ptr)[i] == 0, "double-free");
  arr[i] = 0;
}