首页 · 日记 · 笔记

11.15

2025-11-16

周六

考试总结

淑芬考了 103/110,如果不是犯傻,当然是能拿 110 的!但是,10.27 里提到的同学,称为 c_wlnf 吧,却是实实在在地拿了 109 分!

组合数学考了 30/50,这门课的机制是期中 50 分,期末 100 分,这 150 分里拿到 100 分就是满分。如果认为期中和期末的得分难度相同,那么期中的折算满分就是 33.3

如果不认为任何树上的两条最长路径都至多有一个公共点,就能得到高于 33.3 分了!我的论证是:

假设存在两个公共点,那么可以找到一个包含这两个公共点的环,与该图是树矛盾

考试的时候我在想什么,现在也不知道了, 这个规模为 的树就是反例…

下周二还要考一门线代,这学期线代还没听过课,也没自己写过作业,非常危险啊!

学习

TAPL

考完试这几天看了一点 TAPL,这本书有 个部分:

一口气把 都看完了,但是跳过了所有 ML 代码实现,只看了理论

其他

超算队作业有这么一个问题:你在图书馆 SSH 连到计算节点,运行 python3 work.py 在 bash 前台启动了一个单进程的长时间计算任务。但是图书馆马上闭馆了,离开图书馆会让 SSH 断联,怎么让这个程序继续运行?

当然,首先是 Ctrl + Zbg; disown 小连招。但是这不能解决问题,进程的输出流仍然绑定在一个即将被销毁的 TTY 上,这会在销毁后,试图写入时收到异常信号,中断程序

reptyr 实现了迁移进程终端1的功能,从而解决了这个问题,思路在 reptyr: Changing a process’s controlling terminal 中阐述,技术是通过 ptrace 让指定进程执行一系列系统调用,把自己绑定到新的 TTY 上

根据这个思路,让 AI 搓了一份土制代码出来,能够成功实现迁移终端:

// Usage:
//   ./move <TARGET_PID> <NEW_TTY_PATH>
//
// 思路:
//   1. fork 一个 child,让它 setpgid(0,0),充当“容器 pgrp leader”
//   2. ptrace 附着目标,保存 regs_orig(用户态现场)
//   3. 在目标内部 remote_syscall 执行:
//        setpgid(0, container_pid);
//        setsid();
//        mmap(len);
//        写入 NEW_TTY_PATH 字符串到 mmap 出来的内存;
//        openat(AT_FDCWD, remote_str, O_RDWR);
//        dup3(fd, 0/1/2);
//      每次 syscall 都基于 regs_orig 拷贝 regs_tmp,不污染 regs_orig。
//   4. 最后用 regs_orig 恢复目标寄存器,detach,让它继续跑。

#define _GNU_SOURCE
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>      // struct user_regs_struct
#include <sys/syscall.h>   // SYS_* numbers
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

#define DIE(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)

// 通用远程 syscall:基于 regs_orig 构造 regs_tmp,不回写 regs_orig
static long remote_syscall(pid_t pid,
               const struct user_regs_struct *regs_orig,
               long nr,
               unsigned long arg0,
               unsigned long arg1,
               unsigned long arg2,
               unsigned long arg3,
               unsigned long arg4,
               unsigned long arg5)
{
    int status;
    struct user_regs_struct regs_tmp;
    struct user_regs_struct regs_after;
    unsigned long rip = regs_orig->rip;
    long saved_word;
    unsigned long patched_word = 0;
    unsigned char *patch_bytes = (unsigned char *)&patched_word;

    // syscall; int3; nop ... to make sure we re-trap after executing syscall
    memset(patch_bytes, 0x90, sizeof(patched_word));
    patch_bytes[0] = 0x0f;
    patch_bytes[1] = 0x05;
    patch_bytes[2] = 0xcc;

    errno = 0;
    saved_word = ptrace(PTRACE_PEEKTEXT, pid, (void *)rip, NULL);
    if (saved_word == -1 && errno != 0)
        DIE("PTRACE_PEEKTEXT (save original instruction)");

    if (ptrace(PTRACE_POKETEXT, pid, (void *)rip, (void *)patched_word) == -1)
        DIE("PTRACE_POKETEXT (install syscall stub)");

    memcpy(&regs_tmp, regs_orig, sizeof(regs_tmp));
    regs_tmp.rax = nr;
    regs_tmp.rdi = arg0;
    regs_tmp.rsi = arg1;
    regs_tmp.rdx = arg2;
    regs_tmp.r10 = arg3;
    regs_tmp.r8  = arg4;
    regs_tmp.r9  = arg5;
    regs_tmp.rip = rip;

    if (ptrace(PTRACE_SETREGS, pid, NULL, &regs_tmp) == -1)
        DIE("PTRACE_SETREGS (before remote syscall)");

    if (ptrace(PTRACE_CONT, pid, NULL, NULL) == -1)
        DIE("PTRACE_CONT (remote syscall)");
    if (waitpid(pid, &status, 0) == -1)
        DIE("waitpid (remote syscall)");
    if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP)
        DIE("target did not trap after remote syscall");

    if (ptrace(PTRACE_GETREGS, pid, NULL, &regs_after) == -1)
        DIE("PTRACE_GETREGS (after remote syscall)");

    if (ptrace(PTRACE_POKETEXT, pid, (void *)rip, (void *)saved_word) == -1)
        DIE("PTRACE_POKETEXT (restore instruction)");
    if (ptrace(PTRACE_SETREGS, pid, NULL, regs_orig) == -1)
        DIE("PTRACE_SETREGS (restore registers)");

    return (long)regs_after.rax;
}

// 方便封装:在目标中申请一块内存并写入字符串,返回地址
static unsigned long
remote_write_string(pid_t pid,
                    const struct user_regs_struct *regs_orig,
                    const char *s)
{
    size_t len = strlen(s) + 1;  // 带 '\0'

    // x86_64 red zone: [rsp-128, rsp)
    unsigned long remote_addr = regs_orig->rsp - 128;

    size_t i = 0;
    while (i < len) {
        unsigned long word = 0;
        size_t j;
        for (j = 0; j < sizeof(long) && i + j < len; ++j) {
            ((unsigned char *)&word)[j] = (unsigned char)s[i + j];
        }
        if (ptrace(PTRACE_POKEDATA, pid,
                   (void *)(remote_addr + i),
                   (void *)word) == -1) {
            DIE("PTRACE_POKEDATA");
        }
        i += j;
    }

    return remote_addr;
}

int main(int argc, char **argv)
{
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <TARGET_PID> <NEW_TTY_PATH>\n", argv[0]);
        return EXIT_FAILURE;
    }

    pid_t target = (pid_t)strtol(argv[1], NULL, 10);
    const char *tty_path = argv[2];

    if (kill(target, 0) == -1) {
        perror("kill(target, 0)");
        return EXIT_FAILURE;
    }

    // 1. fork 容器进程,让它成为新的 pgrp leader
    pid_t container = fork();
    if (container < 0) {
        DIE("fork");
    } else if (container == 0) {
        if (setpgid(0, 0) == -1)
            DIE("child setpgid(0,0)");
        for (;;)
            pause();
        _exit(0);
    }
    
    // 2. 附着目标
    if (ptrace(PTRACE_ATTACH, target, NULL, NULL) == -1)
        DIE("PTRACE_ATTACH");

    int status;
    if (waitpid(target, &status, 0) == -1)
        DIE("waitpid (attach)");

    // 3. 保存原始寄存器
    struct user_regs_struct regs_orig;
    if (ptrace(PTRACE_GETREGS, target, NULL, &regs_orig) == -1)
        DIE("PTRACE_GETREGS (initial)");

    long ret;

    // 4. 在目标中执行 setpgid(0, container)
    ret = remote_syscall(target, &regs_orig,
                         SYS_setpgid,
                         0,
                         (unsigned long)container,
                         0, 0, 0, 0);

    // 5. 在目标中执行 setsid()
    ret = remote_syscall(target, &regs_orig,
	                    SYS_setsid,
                         0, 0, 0, 0, 0, 0);

    // 6. 在目标进程里申请一块内存并写入 tty_path
    unsigned long remote_str = remote_write_string(target, &regs_orig, tty_path);

    // 7. 远程 openat(AT_FDCWD, remote_str, O_RDWR)
    int flags = O_RDWR;
    ret = remote_syscall(target, &regs_orig,
                         SYS_openat,
                         (unsigned long)AT_FDCWD,
                         remote_str,
                         (unsigned long)flags,
                         0, 0, 0);
    int remote_fd = -1;
    if (ret < 0) {
        errno = -ret;
        perror("remote openat failed");
    } else {
        remote_fd = (int)ret;

        // 8. dup3(fd, 0/1/2)
        for (int fd_local = 0; fd_local <= 2; ++fd_local) {
            long r2 = remote_syscall(target, &regs_orig,
                                     SYS_dup3,
                                     (unsigned long)remote_fd,
                                     (unsigned long)fd_local,
                                     0, 0, 0, 0);
        }


    }

    // 9. 恢复原始寄存器
    if (ptrace(PTRACE_SETREGS, target, NULL, &regs_orig) == -1)
        DIE("PTRACE_SETREGS (restore)");

    // 10. detach,让目标继续跑
    if (ptrace(PTRACE_DETACH, target, NULL, NULL) == -1)
        DIE("PTRACE_DETACH");

    // 11. 容器进程已经无用,可以杀掉
    kill(container, SIGTERM);

    return 0;
}

收获是更深入地了解了 linux 系统中的 session, TTY, process group, group leader 等机制

Footnotes

  1. 后来也实现了窃取终端的功能

最新评论

--