本文所述问题已在诸位助教学长努力下修复。向助教们致敬!

背景声明

本文适用于武汉大学『计算机系统基础』 配套的 Shell Lab 实验。 因本人精力有限,并未对 CSAPP 上游资源进行测试。 因此,本文所述内容对你的 Shell Lab 环境可能无效。请仔细甄别。

二次更新:经检查,CMU 上游无此 trace。 请允许我在此一并向此实验的非官方扩充者们致意! 使用套接字通信来测试时序问题无疑是个天才般的主意, 即便出现了细微漏洞,在总体上也瑕不掩瑜。

AIGC 使用声明

本文所述漏洞在 AIGC 辅助下发现。

今天运行 Shell Lab 对拍时我注意到一些奇妙的报错:

47. Running trace28.txt...
Oops: test and reference outputs for trace28.txt differed.

Test output:
#
# trace28.txt - Robustness: job continued by outside your shell
#
tsh> ./mycont 10
# Stop and check the job state
Job [1] (186197) stopped by signal 20
tsh> jobs
[1] (186197) Stopped    ./mycont 10
# Now continue it, but not through fg/bg in shell
tsh> jobs
[1] (186197) Running    ./mycont 10

Reference output:
#
# trace28.txt - Robustness: job continued by outside your shell
#
tsh> ./mycont 10
# Stop and check the job state
Job [1] (186207) stopped by signal 20
tsh> jobs
[1] (186207) Stopped    ./mycont 10
# Now continue it, but not through fg/bg in shell
tsh> jobs
[1] (186207) Stopped    ./mycont 10

Output of 'diff test reference':
6c6
< Job [1] (186197) stopped by signal 20
---
> Job [1] (186207) stopped by signal 20
8c8
< [1] (186197) Stopped    ./mycont 10
---
> [1] (186207) Stopped    ./mycont 10
11c11
< [1] (186197) Running    ./mycont 10
---
> [1] (186207) Stopped    ./mycont 10

我们注意到例程 tshref 在对子进程的 SIGCONT 信号发送后 仍然输出了 Stopped 状态。

我起初以为是 tshref 例程存在问题,当成笑话发给 AI。 AI 一通分析后指出是这里的测试点错了。

trace28.txt 的内容中有这样一段:

# Now continue it, but not through fg/bg in shell
SIGNAL # inform the child to continue its parent

/bin/echo -e tsh\076 jobs
NEXT
jobs

这里发送了一个信号给 mycont 程序。 让我们再来看看该程序中处理此信号的方式:

if (fork() == 0) {
    /* Child. */
    /* ... */
    ssize_t rc;
    if ((rc = send(syncfd, cmdp, strlen(cmdp), 0)) < 0) {
        perror("send");
        exit(1);
    }
    if ((rc = recv(syncfd, buf, MAXBUF, 0)) < 0) {
        perror("recv");
        exit(1);
    }
    /* Send SIGCONT after handshake */
    kill(getppid(), SIGCONT);
    exit(0);
}

我们注意到子进程在收到测评机发来的信号后, 将一个 SIGCONT 信号发给了自己的亲进程, 即是从我们的 tsh 中启动的那个。

但注意到,系统调度的次序是任意的, 即孙进程可能在 kill 后立即被调度走, 待评测机和 tsh 运行 jobs 命令结束后 子进程才被继续执行,收到信号, 然后才触发 tsh 的信号处理函数, 更新 Running 状态。

由此导致了如上的偶发错误。 在我的机器上,此错误的复现率约为 1%。

显然此错误在其他测试点中很可能同样存在, 但由于本人能力不足,暂时无法查清究竟哪些测试点受到影响。

因此,各位同学在做 Shell Lab 遇到玄学问题时, 不要怀疑,因为:

错的真的可能不是你,而是题。