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,这本书有
- PART
- Untyped Systems - PART
- Simple Types - PART
- Subtyping - PART
- Recursive Types - PART
- Polymorphism - PART
- Higher-Order Systems
一口气把
其他
超算队作业有这么一个问题:你在图书馆 SSH 连到计算节点,运行 python3 work.py 在 bash 前台启动了一个单进程的长时间计算任务。但是图书馆马上闭馆了,离开图书馆会让 SSH 断联,怎么让这个程序继续运行?
当然,首先是 Ctrl + Z 和 bg; 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(®s_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, ®s_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, ®s_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, ®s_orig) == -1)
DIE("PTRACE_GETREGS (initial)");
long ret;
// 4. 在目标中执行 setpgid(0, container)
ret = remote_syscall(target, ®s_orig,
SYS_setpgid,
0,
(unsigned long)container,
0, 0, 0, 0);
// 5. 在目标中执行 setsid()
ret = remote_syscall(target, ®s_orig,
SYS_setsid,
0, 0, 0, 0, 0, 0);
// 6. 在目标进程里申请一块内存并写入 tty_path
unsigned long remote_str = remote_write_string(target, ®s_orig, tty_path);
// 7. 远程 openat(AT_FDCWD, remote_str, O_RDWR)
int flags = O_RDWR;
ret = remote_syscall(target, ®s_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, ®s_orig,
SYS_dup3,
(unsigned long)remote_fd,
(unsigned long)fd_local,
0, 0, 0, 0);
}
}
// 9. 恢复原始寄存器
if (ptrace(PTRACE_SETREGS, target, NULL, ®s_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
-
后来也实现了窃取终端的功能 ↩
评论区
最新评论
--