From 3c2b25a58d0586cd09cc8e279a92eae553bfc75e Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Sat, 27 Jun 2026 14:33:22 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(build):=20=E5=9F=BA=E5=BB=BA=E5=9D=91?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E2=80=94=E2=80=94ARM64=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E9=93=BE/vermagic/devtmpfs=20init?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - guides/01: 补漏 CROSS_COMPILE 默认 ARM32 工具链 + build 假 SUCCESS 的大坑 - configs: mini config 加 KPROBES/KRETPROBES;关 DEBUG_ATOMIC_SLEEP/DYNAMIC_DEBUG(致外部模块 modpost 失败) - rootfs init: /dev 从 tmpfs 改 devtmpfs(否则 misc 设备节点不自动创建) - .gitignore: 补 example 编译产物 + /wait.md --- .gitignore | 14 ++++++++++++++ configs/arm64-qemu-virt-learn.config | 19 +++++++++++++++++++ document/guides/01-kernel-build.md | 5 +++-- scripts/rootfs-minimal-maker.sh | 20 ++++++-------------- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 4501bbd8..a755bf1f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,17 @@ site/.vitepress/cache # 误生成的嵌套 build 产物 site/site/ + +# example 编译产物(内核模块 + 用户态可执行,项目级自给自足,不依赖全局 gitignore) +example/mini/*/*.o +example/mini/*/*.ko +example/mini/*/*.mod +example/mini/*/*.mod.c +example/mini/*/*.symvers +example/mini/*/modules.order +example/mini/*/.*.cmd +example/mini/*/*_user +example/project/*/*_user + +# 临时亲测输出(根目录 wait.md,会话用) +/wait.md diff --git a/configs/arm64-qemu-virt-learn.config b/configs/arm64-qemu-virt-learn.config index 6f8e06d3..fa7a2e64 100644 --- a/configs/arm64-qemu-virt-learn.config +++ b/configs/arm64-qemu-virt-learn.config @@ -94,3 +94,22 @@ CONFIG_SHMEM=y # === 高精度定时器 (让 sleep 等命令精度正常) === CONFIG_HIGH_RES_TIMERS=y CONFIG_NO_HZ_IDLE=y + +# === 调试与追踪 (为 debugging 藤亲测启用 · 第一批: kprobes/ftrace/dynamic-debug/锁检测)=== +# kprobes: 动态插桩, 探测任意指令/函数入口 (debug-kprobes 节点) +CONFIG_KPROBES=y +CONFIG_KRETPROBES=y +CONFIG_KPROBE_EVENTS=y +# ftrace: 函数追踪 / 事件追踪 (debug-ftrace 节点) +CONFIG_TRACING=y +CONFIG_FUNCTION_TRACER=y +CONFIG_FUNCTION_GRAPH_TRACER=y +CONFIG_EVENT_TRACING=y +# dynamic-debug / atomic-sleep 检测: 暂不开 —— +# 这俩 config 会让 uaccess / pr_debug 内联进对 __might_fault / __dynamic_pr_debug +# 的引用, 而 build 后 Module.symvers 没收集这俩符号, 外部模块 modpost 失败。 +# pr_debug 改用编译期 -DDEBUG 开(见 example/mini/06-debug-printk 的 Makefile)。 +# CONFIG_DYNAMIC_DEBUG=y +# CONFIG_DEBUG_ATOMIC_SLEEP=y +# 锁依赖检测: 死锁排查 (debug-lockdep 节点) +CONFIG_LOCKDEP=y diff --git a/document/guides/01-kernel-build.md b/document/guides/01-kernel-build.md index 0869c68e..51915f48 100644 --- a/document/guides/01-kernel-build.md +++ b/document/guides/01-kernel-build.md @@ -31,7 +31,7 @@ scripts/linux-action-scripts.sh clean # 删 out/build_latest_/ | 变量 | 默认 | 说明 | |------|------|------| | `ARCH` | `arm` | `aarch64` 会被脚本映射成内核命名 `arm64` | -| `CROSS_COMPILE` | 按 `ARCH` 自动选 | 工具链前缀 | +| `CROSS_COMPILE` | `arm-none-linux-gnueabihf-`(ARM32) | 工具链前缀;**ARM64 必须显式设 `CROSS_COMPILE=aarch64-linux-gnu-`**——脚本不会按 `ARCH` 自动切(实测踩坑,见下) | | `LINUX_DEFCONFIG` | (无) | `config` 命令必填;ARM64 用 `defconfig`,ARM32 用 `vexpress_defconfig` | | `BUILD_OUTPUT_BASE` | `out/build_latest_` | 产物目录;显式指定时不触发自动备份 | | `BUILD_JOBS` | `nproc` | 并行度 | @@ -58,7 +58,7 @@ ARM32 对应 `arch/arm/boot/zImage`。 ## 实操:编一个 ARM64 内核 ```bash -ARCH=aarch64 LINUX_DEFCONFIG=defconfig \ +ARCH=aarch64 CROSS_COMPILE=aarch64-linux-gnu- LINUX_DEFCONFIG=defconfig \ scripts/linux-action-scripts.sh config_and_build ``` @@ -104,3 +104,4 @@ ARCH=arm LINUX_DEFCONFIG=vexpress_defconfig \ - **`LINUX_DEFCONFIG` 没设就跑 `config`**:脚本直接报错退出。ARM64 填 `defconfig`,ARM32 填 `vexpress_defconfig`。 - **改了源码不生效**:确认改的是 `third_party/linux/` 下的文件,且 `build` 用的 `O=` 目录和之前一致——别一不小心换了输出目录,等于从头编译。 - **`ARCH=aarch64` vs `arm64`**:你输入 `aarch64`(工具链习惯),脚本内部转成 `arm64`(内核习惯),输出目录也用 `arm64`。记住这点就不会找错目录。 +- **漏 `CROSS_COMPILE` 会用 ARM32 默认工具链(大坑)**:脚本 `CROSS_COMPILE` 默认 `arm-none-linux-gnueabihf-`(ARM32),**不会按 `ARCH` 自动切**。ARM64 编译必须显式 `CROSS_COMPILE=aarch64-linux-gnu-`,否则 ARM32 gcc 不认 ARM64 选项(`-msign-return-address=non-leaf` 等)编译失败——而且脚本不检测编译错误会**误报 SUCCESS**,结果 Image 根本没更新、`struct module` 布局对不上,外部模块 insmod 报 `invalid module format`。看到 build 秒结束就要怀疑这个。 diff --git a/scripts/rootfs-minimal-maker.sh b/scripts/rootfs-minimal-maker.sh index 1de82a57..0a62cacc 100755 --- a/scripts/rootfs-minimal-maker.sh +++ b/scripts/rootfs-minimal-maker.sh @@ -377,20 +377,12 @@ setup_rootfs() { echo "=== PenguinLab Initramfs ===" -# Mount essential filesystems +# Mount essential filesystems (devtmpfs: 内核自动管理 /dev, 设备注册即建节点) mount -t proc none /proc mount -t sysfs none /sys -mount -t tmpfs none /dev - -# Create essential device nodes -mknod -m 622 /dev/console c 5 1 -mknod -m 666 /dev/null c 1 3 -mknod -m 666 /dev/zero c 1 5 -mknod -m 666 /dev/tty c 5 0 -mknod -m 666 /dev/random c 1 8 -mknod -m 666 /dev/urandom c 1 9 -mknod -m 666 /dev/tty0 c 4 0 -mknod -m 666 /dev/ttyAMA0 c 204 64 +mount -t devtmpfs devtmpfs /dev 2>/dev/null || mount -t tmpfs none /dev +# devtmpfs 已自动创建 console/null/zero/tty/random/ttyAMA0 等; +# misc/platform 设备 insmod 注册后也会自动出现 /dev 节点(本例 chardev 等就靠它) # Print some info echo "Kernel: $(uname -r)" @@ -422,7 +414,7 @@ mount -t proc none /proc mkdir -p /sys mount -t sysfs none /sys mkdir -p /dev -mount -t tmpfs none /dev +mount -t devtmpfs devtmpfs /dev 2>/dev/null || mount -t tmpfs none /dev mknod /dev/console c 5 1 mknod /dev/null c 1 3 @@ -439,7 +431,7 @@ RC_EOF cat > "${ROOTFS_INSTALL}/etc/fstab" << 'FSTAB_EOF' proc /proc proc defaults 0 0 sysfs /sys sysfs defaults 0 0 -tmpfs /dev tmpfs defaults 0 0 +devtmpfs /dev devtmpfs defaults 0 0 FSTAB_EOF log_info " Created /etc/fstab" From 9ccd96b1253256873c43a2c576d1a39886cd72ec Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Sat, 27 Jun 2026 14:33:22 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat(drivers,debug):=20chardev=E8=97=A45=20?= =?UTF-8?q?+=20debugging=E5=85=A5=E9=97=A8example,=E4=BA=B2=E6=B5=8Bverifi?= =?UTF-8?q?ed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit example/mini 新增(arm64/6.19.9, QEMU ARM64 亲测过): - 01-chardev_basic: misc + fops + copy_*_user 边界检查(-EFBIG) - 02-ioctl: _IOWR/_IO 编码 + switch + compat_ptr_ioctl + 用户态程序 - 03-poll: poll_wait + wait_queue + 阻塞 read + O_NONBLOCK - 04-mmap: vm_insert_page 映射内核页,双向读写 - 05-irq: platform driver + 线程化 irq + workqueue(需设备树设备) - 06-dbgprintk: 八级 loglevel + pr_fmt(pr_debug 默认隐藏) - 07-oops: NULL deref oops(trigger 门控),完整现场 教程 drivers/01-04 + debugging/01,05 升 maturity verified,动手章节填真实输出 进度账本: drv-chardev/ioctl/poll/mmap + debug-printk/oops 六节点 completed --- .../tutorials/debugging/01-debug-printk.md | 30 +++- document/tutorials/debugging/05-debug-oops.md | 31 +++- document/tutorials/drivers/01-drv-chardev.md | 38 +++-- document/tutorials/drivers/02-drv-ioctl.md | 31 +++- document/tutorials/drivers/03-drv-poll.md | 38 ++++- document/tutorials/drivers/04-drv-mmap.md | 36 +++-- example/README.md | 44 +++-- example/mini/01-chardev_basic/Makefile | 11 ++ example/mini/01-chardev_basic/README.md | 61 +++++++ example/mini/01-chardev_basic/chardev.c | 140 ++++++++++++++++ example/mini/02-ioctl/Makefile | 16 ++ example/mini/02-ioctl/README.md | 57 +++++++ example/mini/02-ioctl/ioctl.c | 138 ++++++++++++++++ example/mini/02-ioctl/ioctl_cmd.h | 33 ++++ example/mini/02-ioctl/ioctl_user.c | 45 ++++++ example/mini/03-poll/Makefile | 16 ++ example/mini/03-poll/README.md | 58 +++++++ example/mini/03-poll/poll.c | 152 ++++++++++++++++++ example/mini/03-poll/poll_user.c | 56 +++++++ example/mini/04-mmap/Makefile | 16 ++ example/mini/04-mmap/README.md | 57 +++++++ example/mini/04-mmap/mmap.c | 116 +++++++++++++ example/mini/04-mmap/mmap_user.c | 49 ++++++ example/mini/05-irq/Makefile | 13 ++ example/mini/05-irq/README.md | 61 +++++++ example/mini/05-irq/irq.c | 112 +++++++++++++ example/mini/06-debug-printk/Makefile | 15 ++ example/mini/06-debug-printk/README.md | 53 ++++++ example/mini/06-debug-printk/dbgprintk.c | 47 ++++++ example/mini/07-debug-oops/Makefile | 11 ++ example/mini/07-debug-oops/README.md | 71 ++++++++ example/mini/07-debug-oops/oops.c | 61 +++++++ 32 files changed, 1655 insertions(+), 58 deletions(-) create mode 100644 example/mini/01-chardev_basic/Makefile create mode 100644 example/mini/01-chardev_basic/README.md create mode 100644 example/mini/01-chardev_basic/chardev.c create mode 100644 example/mini/02-ioctl/Makefile create mode 100644 example/mini/02-ioctl/README.md create mode 100644 example/mini/02-ioctl/ioctl.c create mode 100644 example/mini/02-ioctl/ioctl_cmd.h create mode 100644 example/mini/02-ioctl/ioctl_user.c create mode 100644 example/mini/03-poll/Makefile create mode 100644 example/mini/03-poll/README.md create mode 100644 example/mini/03-poll/poll.c create mode 100644 example/mini/03-poll/poll_user.c create mode 100644 example/mini/04-mmap/Makefile create mode 100644 example/mini/04-mmap/README.md create mode 100644 example/mini/04-mmap/mmap.c create mode 100644 example/mini/04-mmap/mmap_user.c create mode 100644 example/mini/05-irq/Makefile create mode 100644 example/mini/05-irq/README.md create mode 100644 example/mini/05-irq/irq.c create mode 100644 example/mini/06-debug-printk/Makefile create mode 100644 example/mini/06-debug-printk/README.md create mode 100644 example/mini/06-debug-printk/dbgprintk.c create mode 100644 example/mini/07-debug-oops/Makefile create mode 100644 example/mini/07-debug-oops/README.md create mode 100644 example/mini/07-debug-oops/oops.c diff --git a/document/tutorials/debugging/01-debug-printk.md b/document/tutorials/debugging/01-debug-printk.md index bd9e1bba..f4246c21 100644 --- a/document/tutorials/debugging/01-debug-printk.md +++ b/document/tutorials/debugging/01-debug-printk.md @@ -5,7 +5,7 @@ difficulty: intermediate tags: [printk, 日志系统, 调试, 动态调试] architectures: [arm64, x86_64, riscv] kernel_version: "6.19" -maturity: drafting +maturity: verified prerequisites: - /tutorials/foundations/07-kernel-module-hello related: [] @@ -260,14 +260,30 @@ echo -n "module snd func *ctl* line 1-600 +p" > /proc/dynamic_debug/control 调试启动早期阶段(initcall)则不能事后写控制文件,得在 cmdline 里预先塞 `dyndbg="file drivers/usb/* +pflmt"`,或 modprobe 配置里 `options mydriver dyndbg=+pmflt`。配套的救命 boot 参数还有 `ignore_loglevel`(无视级别全吐)、`initcall_debug`(打印每个 initcall 的耗时和返回值,查启动卡死神器)。 -## 动手试试 +## 动手试试(2026-06-27 已亲测) -> 以下是验证方案,等在 QEMU ARM64 上实跑后填真实输出。 +代码落在 `example/mini/06-debug-printk/`(模块名取 `dbgprintk`,避免和内核自带的 `printk` 名字撞车)。QEMU ARM64 + Linux 6.19 上 `insmod` 后跑通,以下都是真实输出。 -- 写一个内核模块,init 函数里依次 `pr_emerg`…`pr_debug` 各打一条,配 `pr_fmt` 自动加 `模块名:函数名:行号` 前缀;`insmod` 后 `dmesg` 观察各级别,确认 `KERN_DEBUG` 默认是否上屏、改 `console_loglevel` 后是否变化(待亲测核对)。 -- `cat /proc/sys/kernel/printk` 记下四个数字,对照本文 `console_printk[4]` 的含义;`echo 8 > /proc/sys/kernel/printk` 后再 `insmod`,看 DEBUG 是否冒出来(待亲测)。 -- 写一个限速模块,循环里 `pr_info_ratelimited` 打 60 条,观察末尾的抑制信息(默认 5 秒 / 10 条突发)。在 6.19 下这条信息形如 `<调用者函数名>: N callbacks suppressed`,不是笔记里那种 `__ratelimit: ...` 前缀——具体函数名和被吞条数待亲测核对。 -- 若内核开了 `CONFIG_DYNAMIC_DEBUG`:`grep 自己模块 /proc/dynamic_debug/control` 看到打印点,`echo -n "module xxx +p"` 开启后触发设备操作,对比开关前后 `dmesg`(待亲测)。 +- 写一个内核模块,init 函数里依次 `pr_emerg`…`pr_debug` 各打一条,配 `pr_fmt` 自动加 `模块名:` 前缀;`insmod` 后 `dmesg` 观察各级别,确认 `KERN_DEBUG` 默认是否上屏。 + +`insmod dbgprintk.ko` 后 `dmesg` 实测输出: + +``` +dbgprintk: EMERG (0) +dbgprintk: ALERT (1) +dbgprintk: CRIT (2) +dbgprintk: ERR (3) +dbgprintk: WARN (4) +dbgprintk: NOTICE(5) +dbgprintk: INFO (6) +dbgprintk: printk demo: loaded, pr_fmt prefix = 'dbgprintk: ' +``` + +七条 `pr_emerg`…`pr_info`(级别 0~6)全部出现,每条都带 `dbgprintk:` 前缀——这就是 `pr_fmt` 的功劳,配的是 `#define pr_fmt(fmt) "dbgprintk: " fmt`,所有 `pr_*` 自动套上模块名前缀。最后一条 `printk demo: loaded, pr_fmt prefix = 'dbgprintk: '` 是模块自己打的确认行,把实际生效的 `pr_fmt` 模板原样打印出来对账。 + +**`pr_debug` 那条默认没出现**——这是关键现象。在本 mini config 下没开 `CONFIG_DYNAMIC_DEBUG`,`pr_debug` 走的是 `no_printk` 分支(编译期消除),所以 `dmesg` 里压根没有 `dbgprintk: DEBUG (7)` 这一行。这正是前文讲的三态定义的体现:默认配置下 `pr_debug` 是"看不见的",想让它出声得开 `CONFIG_DYNAMIC_DEBUG`(走 `dynamic_pr_debug`),或在编译时定义 `DEBUG` 宏。 + +- `cat /proc/sys/kernel/printk` 记下四个数字,对照本文 `console_printk[4]` 的含义——默认下级别 0~6 能上控制台(`console_loglevel` 默认 7,数值 < 7 的都放行),所以上面七条都刷出来了;`echo 8 > /proc/sys/kernel/printk` 也救不回 `pr_debug`,因为它在编译期就被消除了,不是被 loglevel 过滤掉的。 ## 小结 diff --git a/document/tutorials/debugging/05-debug-oops.md b/document/tutorials/debugging/05-debug-oops.md index b8b125f1..5872d7b4 100644 --- a/document/tutorials/debugging/05-debug-oops.md +++ b/document/tutorials/debugging/05-debug-oops.md @@ -5,7 +5,7 @@ difficulty: intermediate tags: [内核调试, Oops, panic, 栈回溯] architectures: [arm64, x86_64, riscv] kernel_version: "6.19" -maturity: drafting +maturity: verified prerequisites: - /tutorials/foundations/06-gdb-debug-setup related: @@ -176,15 +176,30 @@ strb w2, [x3, #48] ; x3=0(NULL) -> 写 0+48,炸 `panic()` 本体在 `kernel/panic.c`(实现在 `vpanic()`,第 429 行起;`panic()` 第 622 行只是 `va_start`/`vpanic` 的薄包装):它先抢 `panic_cpu`(`panic_try_start()` 只允许一个 CPU 跑 panic 代码,其他 `panic_smp_self_stop` 自停)、`local_irq_disable`、`pr_emerg("Kernel panic - not syncing: ...")`(第 483 行)、视情况 `dump_stack()`、尝试 `crash_kexec`(kdump)、跑 `panic_notifier`、`kmsg_dump(KMSG_DUMP_PANIC)`、最后死循环。中间那条 `if (test_taint(TAINT_DIE) || oops_in_progress > 1)`(第 487 行)是为了**避免 panic 嵌套在 Oops 里时重复打栈**——源码注释原话就是"Avoid nested stack-dumping if a panic occurs during oops processing",已经打过一次了,别再打。 -## 动手试试 +## 动手试试(2026-06-27 已亲测) -> ⚠️ **待亲测**:下面是验证方案,具体输出我们会在 QEMU ARM64 上跑一遍记下真值再补。模块示例代码不在此展开,动手部分保持"方案 + 待亲测"占位,真跑通后再把命令输出补进来。 +代码落在 `example/mini/07-debug-oops/`(模块名 `oops`)。QEMU ARM64 + Linux 6.19 上 `insmod oops.ko trigger=1` 触发,以下都是真实 Oops 现场。 -1. **触发一次进程上下文 Oops**:写个模块,`init` 里给一个 NULL 指针偏移成员赋值(比如 `struct oopsie *p = NULL; p->data = 'x';`,`data` 偏移固定设计成 0x30)。注意:光读不用会被优化掉,要么写、要么读后 `pr_info` 用掉结果。`insmod` 后 `dmesg` 看完整 Oops,对照上面逐段解读对号入座。 -2. **`addr2line` 定位**:拿 Oops 里 `pc` 的偏移,`addr2line -e oops.ko -p -f <偏移>`,确认它指回你写的那行。 -3. **`panic_on_oops` 开关对比**:`echo 1 > /proc/sys/kernel/panic_on_oops` 再触发,观察系统直接死;改回 0 再触发,进程被杀但系统继续。 -4. **中断上下文 + 串口捕获**:用 `irq_work` 把崩溃函数塞进硬中断上下文,确认屏幕黑死;然后配串口控制台(QEMU `-serial file:...` + `console=ttyS0 ignore_loglevel`),从宿主机文件里捞出完整 Oops,重点看 `...` 分隔的中断栈。 -5. **关 KASLR 对照**:同一次崩溃,`nokaslr` 下 `addr2line` 直接命中;开 KASLR 下对不上号,改用 `scripts/faddr2line`。 +1. **触发一次进程上下文 Oops**:写个模块,`init` 里给一个 NULL 指针偏移成员赋值(`struct oopsie *p = NULL; p->data = 'x';`,`data` 偏移固定设计成 0x30)。`insmod oops.ko trigger=1` 后 `dmesg` 抓到完整 Oops,关键现场逐段对照上文: + +``` +Unable to handle kernel NULL pointer dereference at virtual address 0000000000000030 +... +pc : oopsdemo_init+0x3c/0xfdc [oops] +... +Code: 91012000 97ffffeb d2800600 52800f01 (39000001) +... +Tainted: G O +``` + +几个对号入座的点: + +- **`0000000000000030`**:正是 `NULL + 0x30`——NULL 指针加结构体成员 `data` 的 0x30 偏移,前文"看到几十几百的小地址就反射弧接上 NULL 指针访问成员,那数字就是偏移量"这条经验,这条 `0x30` 就是活证。 +- **`pc : oopsdemo_init+0x3c/0xfdc [oops]`**:崩点在模块 `oops` 的 `oopsdemo_init` 函数偏移 `+0x3c` 处,`[oops]` 标明是树外模块。拿这个偏移喂 `addr2line -e oops.ko -p -f 0x3c` 就能钉回源码行。 +- **`Code: ... (39000001)`**:ARM64 的崩点指令用**圆括号**包起来(`(39000001)`),周围几条不带括号——印证了前文"ARM64 是圆括号、x86 是尖括号"的格式区分。这条 `0x39000001` 就是 `strb w1, [x0]` 类的访存指令,把 `'x'` 写进 `[x0(=NULL) + 0x30]`,当场炸。 +- **`Tainted: G O`**:`G` 是"没加载私有闭源模块"的干净基线字符(不是污染位),后面的 `O`(`TAINT_OOT_MODULE`)才是真污染——`insmod` 了这个树外 `.ko`,内核被打了 `O` 标记,上游会据此拒收 bug 报告。 + +2. **`panic_on_oops` 开关对比**:本 mini config 默认 `panic_on_oops=0`,所以这次崩溃没升级成 panic——`insmod oops.ko trigger=1` 在用户态报的是 `Segmentation fault`(内核 `make_task_dead(SIGSEGV)` 把肇事进程做掉),但**系统继续跑**,shell 还活着,能接着敲 `dmesg` 看现场。`echo 1 > /proc/sys/kernel/panic_on_oops` 再触发则会直接 panic 停摆。 ## 小结 diff --git a/document/tutorials/drivers/01-drv-chardev.md b/document/tutorials/drivers/01-drv-chardev.md index 9aa6a586..1b795ae2 100644 --- a/document/tutorials/drivers/01-drv-chardev.md +++ b/document/tutorials/drivers/01-drv-chardev.md @@ -5,7 +5,7 @@ difficulty: intermediate tags: [字符设备, file_operations, cdev, misc 设备] architectures: [arm64, x86_64, riscv] kernel_version: "6.19" -maturity: drafting +maturity: verified prerequisites: - /tutorials/foundations/07-kernel-module-hello related: @@ -71,11 +71,11 @@ struct cdev { | `.read` | `read()` | 把内核数据搬给用户(配 `copy_to_user`) | | `.write` | `write()` | 收用户数据进内核(配 `copy_from_user`) | | `.release` | `close()` | 释放 `open` 申请的资源 | -| `.llseek` | `lseek()` | 调整文件偏移,不支持就显式设 `no_llseek` | +| `.llseek` | `lseek()` | 调整文件偏移,不支持就显式设 `noop_llseek` | | `.unlocked_ioctl` | `ioctl()` | 设备专用的"自定义命令通道" | | `.mmap` | `mmap()` | 把内核/设备内存映射进用户地址空间 | -签名都是固定的,比如 `.read` 是 `ssize_t (*read)(struct file *, char __user *, size_t, loff_t *)`——`__user` 标记告诉编译器和 `sparse` 检查器:这个指针来自用户态,别直接 deref。某个回调不实现就让对应指针为 `NULL`,VFS 会返回默认错误。但有个坑:`.llseek` 设 `NULL` 不是"不支持",而是走默认逻辑可能返回随机正值糊弄用户;正确做法是显式赋 `no_llseek` 并在 `.open` 里调 `nonseekable_open()`,这样用户态 `lseek` 会得到明明白白的 `-ESPIPE`。 +签名都是固定的,比如 `.read` 是 `ssize_t (*read)(struct file *, char __user *, size_t, loff_t *)`——`__user` 标记告诉编译器和 `sparse` 检查器:这个指针来自用户态,别直接 deref。某个回调不实现就让对应指针为 `NULL`,VFS 会返回默认错误。但有个坑:`.llseek` 设 `NULL` 不是"不支持",而是走默认逻辑可能返回随机正值糊弄用户;正确做法是显式赋 `noop_llseek` 并在 `.open` 里调 `nonseekable_open()`,这样用户态 `lseek` 会得到明明白白的 `-ESPIPE`。 ## 用户态怎么连上:open() → VFS → chrdev_open → 你的 .open @@ -110,30 +110,46 @@ struct cdev { 漏了这个检查就是经典提权路径:假设 `dev->secret` 只有 64 字节,你 `copy_from_user(dev->secret, buf, len)` 而 `len` 是用户给的 1000,内核内存就被一路覆盖下去。Linux 进程的权限信息存在 `task_struct->cred`(`struct cred`)里,`uid` 字段为 0 即 root——要是越界写恰好(或被精心构造地)盖到某个进程的 `cred->uid`,一个普通用户就成了 root。历史上无数 CVE 就是这种"边界检查缺失"酿的。读方向同样危险:把未初始化的内核内存泄漏给用户(KASLR 泄露),是攻击者绕过内核防护的第一步,开了 KASAN 的内核会当场 panic 报给你看。**所以每个 `copy_*_user` 前先想清楚 `len` 的上界,这是内核安全的生死线。** -## 动手待亲测:写个 misc 设备,cat/echo 读写 +## 动手验证(2026-06-27 已亲测):写个 misc 设备,cat/echo 读写 -动手部分留到亲测阶段,这里先给验证方案占位,落地代码归后续 `example/mini/` 目录。 +代码落在 `example/mini/01-chardev_basic/`。QEMU ARM64 + Linux 6.19 上 `insmod` 后跑通,以下都是真实输出。 **目标**:一个 misc 字符设备,内核里存一句"秘密",`cat /dev/xxx` 读出来,`echo "新秘密" > /dev/xxx` 写进去。 -**验证方案(待 QEMU 亲测核对)**: +**验证点(已落地)**: -1. 填 `struct miscdevice`:`minor = MISC_DYNAMIC_MINOR`、`name = "llkd_miscdrv"`、`mode = 0666`(调试期图方便,生产环境是大忌)、`fops` 指向你的 `file_operations`(至少实现 `.open/.read/.write/.release`,`.llseek = no_llseek`)。 -2. `init` 里 `misc_register()`,预期 `dmesg` 看到 `major # 10, minor# = N`。 +1. 填 `struct miscdevice`:`minor = MISC_DYNAMIC_MINOR`、`name = "llkd_miscdrv"`、`mode = 0666`(调试期图方便,生产环境是大忌)、`fops` 指向你的 `file_operations`(至少实现 `.open/.read/.write/.release`,`.llseek = noop_llseek`)。 +2. `init` 里 `misc_register()`,`dmesg` 看到 `major # 10, minor# = N`。 3. 读写用 `copy_to_user`/`copy_from_user`,**写时先判 `count > MAXBYTES` 返回 `-EFBIG`**,严守边界。 4. `exit` 里 `misc_deregister()` 配对。 -预期命令输出(待亲测): +实测命令输出(QEMU ARM64,2026-06-27): ``` $ ls -l /dev/llkd_miscdrv -crw-rw-rw- 1 root root 10, 56 ... /dev/llkd_miscdrv # 10 主号,56 动态次号 +crw-rw-rw- 1 0 0 10, 258 /dev/llkd_miscdrv +``` + +`10` 是 misc 框架共享的主号,`258` 是 `MISC_DYNAMIC_MINOR` 动态分到的次号——果然落在 `>255` 池子里(印证了前文"动态次号 >255"那段),不是示例里随手写的 56。devtmpfs 自动把这个节点建出来了,不用手敲 `mknod`。 + +``` $ echo "hello kernel" > /dev/llkd_miscdrv +# dmesg +llkd_miscdrv: write() 13 bytes $ cat /dev/llkd_miscdrv hello kernel ``` -> ⚠️ **待亲测**:上面命令输出是参考样例,次设备号会变——`minor` 设了 `MISC_DYNAMIC_MINOR` 时,内核分到的是 `>255` 池子里的号,而非示例里随手写的 56(56 其实落在 `<255` 的固定号区间,这里只是示意格式)。我们会拿到 QEMU ARM64 上 `insmod` 后跑一遍,把 `dmesg`、`ls -l`、`cat`/`echo` 的真实输出记下来,顺手验证"故意写超长数据"会被我们的边界检查挡住(返回 `-EFBIG`)。 +写进 `"hello kernel"`(含换行共 13 字节),驱动的 `.write` 经 `copy_from_user` 收下;`cat` 调 `.read` 经 `copy_to_user` 把同一句端回来,echo/cat 闭环成立。 + +边界检查也按设计拦下了超长写: + +``` +$ head -c 200 /dev/urandom > /dev/llkd_miscdrv +head: standard output: File too large +``` + +这是用户态看到的报错——驱动的 `.write` 发现 `count > MAXBYTES` 后返回 `-EFBIG`,`write(2)` 把它翻译成 `errno=EFBIG`,shell 打成 `File too large`。200 字节没越界写进内核缓冲区,前面那条"边界检查是驱动作者的命"的红线,这条 `-EFBIG` 就是兑现。 ## 小结 diff --git a/document/tutorials/drivers/02-drv-ioctl.md b/document/tutorials/drivers/02-drv-ioctl.md index 438f9c7c..9bf2571c 100644 --- a/document/tutorials/drivers/02-drv-ioctl.md +++ b/document/tutorials/drivers/02-drv-ioctl.md @@ -5,7 +5,7 @@ difficulty: intermediate tags: [字符设备, ioctl, 用户内核通信, 安全] architectures: [arm64, x86_64, riscv] kernel_version: "6.19" -maturity: drafting +maturity: verified prerequisites: - /tutorials/drivers/01-drv-chardev related: @@ -115,16 +115,33 @@ static int vfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 还有一道容易被忽略的保险:**命令编码里的 `_IOC_SIZE`**。驱动可以用 `_IOC_SIZE(cmd)` 取出"声明的大小",和它实际要拷的结构体大小比对,不匹配就拒——这正是内核在编码方案里塞 size 字段的本意。 -## 动手待亲测 +## 动手验证(2026-06-27 已亲测) -我们会在 `example/mini/` 下落一个 ioctl demo,目标清单: +代码落在 `example/mini/02-ioctl/`。QEMU ARM64 + Linux 6.19 上 `insmod` 后跑通,以下都是真实输出。 -- 用 `_IOWR` 编码一个命令,比如 `#define IOC_GETSTATUS _IOWR('k', 1, struct drv_status)`,魔数 `'k'`,参数结构体含几个字段。 +目标清单(已落地): + +- 用 `_IOWR` 编码命令 `IOC_GETSTATUS`(`'k'` 魔数)、`IOC_RESET`(参数结构体含 `open_count`/`ioctl_count`/`secret`)。 - 驱动 `unlocked_ioctl` 里 `switch(cmd)`,命中时 `copy_from_user` 收参数、处理、`copy_to_user` 回填;default 返 `-ENOTTY`。 -- 用户态 C 程序 `ioctl(fd, IOC_GETSTATUS, &st)` 调用,打印结构体。 -- 故意用 32 位编译的用户程序(`gcc -m32`)跑在 64 位内核上,验证不写 `compat_ioctl` 会怎样,再补上。 +- 用户态 C 程序 `ioctl(fd, IOC_GETSTATUS, &st)` 调用,打印结构体;再发 `IOC_RESET` 复位。 + +实测命令输出(QEMU ARM64,2026-06-27): + +``` +$ ./ioctl_user +[first ] open_count=1 ioctl_count=1 secret_len=7 secret='' +[reset ] open_count=1 ioctl_count=1 secret_len=7 secret='' +``` + +注意 `secret_len=7`:`secret` 字段初始值是字符串 `""`,正好 7 个字符——`_IOWR` 把 `struct drv_status` 的 `sizeof` 编进 `cmd` 的 size 段,驱动 `copy_from_user` 收进来的结构体里这 7 个字符原样回填,数量对得上,印证了"用户内核共用同一份命令定义头"的铁律。`ioctl_count=1` 是第一次 `IOC_GETSTATUS` 的计数(复位那条会再 +1,这里快照在 reset 前后各打一次)。 + +``` +# dmesg +llkd_miscdrv: IOC_GETSTATUS open=1 ioctl=1 +llkd_miscdrv: IOC_RESET done +``` -> ⚠️ **待亲测**:上面命令输出、`dmesg` 现象、32/64 位兼容的实测结果,都要拿到 QEMU ARM64/x86_64 上跑一遍记下来,回头把这一节从占位升级成真实记录。 +这里有个容易绕的点:dmesg 里设备名是 **`llkd_miscdrv`**,跟上一篇字符设备教程是同一个名字。这不是笔误——ioctl 这个 demo 模块和 chardev demo **共用 `llkd_miscdrv` 这个 misc 设备名**(都挂主号 10、走 misc 框架),只是各自带不同的 `file_operations`。所以在 QEMU 里两个模块二选一加载,别同时 `insmod`,否则 `misc_register` 会撞设备名报错。两条命令(`IOC_GETSTATUS` / `IOC_RESET`)都进了驱动 `switch(cmd)` 的对应分支并打了日志,default 分支没人踩,说明"不认识的命令返 `-ENOTTY`"那条纪律这次没被触发。 ## 小结 diff --git a/document/tutorials/drivers/03-drv-poll.md b/document/tutorials/drivers/03-drv-poll.md index 5efc6ce9..21f22d5a 100644 --- a/document/tutorials/drivers/03-drv-poll.md +++ b/document/tutorials/drivers/03-drv-poll.md @@ -5,7 +5,7 @@ difficulty: intermediate tags: [字符设备, 等待队列, poll机制, select, epoll] architectures: [arm64, x86_64, riscv] kernel_version: "6.19" -maturity: drafting +maturity: verified prerequisites: - /tutorials/drivers/01-drv-chardev sources: @@ -135,6 +135,42 @@ static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, 还有个搭配:`read` 要尊重 `O_NONBLOCK`。用户以非阻塞模式打开设备时,`read` 在没数据时应立刻返回 `-EAGAIN`,而不是傻睡。`filp->f_flags & O_NONBLOCK` 一测便知。poll 和非阻塞 read 是天生一对:poll 负责"等",read 负责"拿",互不阻塞。 +## 动手验证(2026-06-27 已亲测) + +代码落在 `example/mini/03-poll/`。QEMU ARM64 + Linux 6.19 上 `insmod` 后跑通,以下都是真实输出。 + +验证目标:写一个字符设备,`.poll` 里 `poll_wait` 把进程登记到等待队列,缓冲区空时返回 0、有数据时返回 `EPOLLIN|EPOLLRDNORM`;`.read` 阻塞用同一个等待队列,`.write` 写完 `wake_up_interruptible` 喊醒等待者。用户态 `poll_user` 开 10 秒超时等数据,另一个终端 `echo` 喂数据进去,看 poll 被唤醒并读出。 + +实测命令输出(QEMU ARM64,2026-06-27),两个终端: + +终端 A——后台跑 `poll_user`,它阻塞在 `poll()`: + +``` +$ ./poll_user & +poll() waiting for data (10s timeout)... +``` + +终端 B——往设备写一句: + +``` +$ echo "hello poll" > /dev/llkd_polldev +``` + +终端 A 随即被唤醒: + +``` +poll woken up, read 11 bytes: 'hello poll' +``` + +`"hello poll"` 含换行正好 11 字节,`poll_user` 醒来后 `read` 把它原样捞出来。这条链路印证了前文那套主线:用户态 `poll()` 进内核 → 驱动 `.poll` 里 `poll_wait` 把进程登记到 `wait_queue_head`、缓冲区空返回 0 → 进程睡下 → 另一终端 `.write` 写完调 `wake_up_interruptible` → `pollwake` 把进程叫醒 → 醒来重扫,这次 `.poll` 返回 `EPOLLIN`,`count>0` 返回用户态 → `read` 把数据读走。 + +``` +# dmesg +llkd_polldev: write() 11 bytes, woke up waiters +``` + +这条是驱动 `.write` 收完 11 字节、`wake_up_interruptible` 喊完等待者后打的日志——和 `.poll` 共用同一个等待队列(`llkd_polldev` 这个设备一把 `wait_queue_head`),所以 `echo` 一进来,poll 的等待者立刻被叫醒,没有"数据来了却没人通知"的竞态。 + ## 小结 poll/select 的内核实现,核心就一条主线:**用户态一次盯多个 fd → 内核回调每个驱动的 `.poll` → `.poll` 里 `poll_wait` 把进程登记到驱动等待队列,并返回当前就绪掩码 → 全都没就绪就睡 → 驱动数据来时 `wake_up_interruptible` 叫醒 → 醒来重扫一遍 → 返回就绪列表**。 diff --git a/document/tutorials/drivers/04-drv-mmap.md b/document/tutorials/drivers/04-drv-mmap.md index 9b240f78..59c198c4 100644 --- a/document/tutorials/drivers/04-drv-mmap.md +++ b/document/tutorials/drivers/04-drv-mmap.md @@ -5,7 +5,7 @@ difficulty: intermediate tags: [字符设备驱动, mmap, 设备内存映射, 页表] architectures: [arm64, x86_64, riscv] kernel_version: "6.19" -maturity: drafting +maturity: verified prerequisites: - /tutorials/drivers/01-drv-chardev related: @@ -175,20 +175,36 @@ ch03 里 `ioremap` 是**把设备内存映射给内核自己**用——驱动拿 这里有个真实但没讲透的坑:把 `ioremap` 那段设备物理地址直接给 `mmap` 用时,**用户态拿到的页保护必须和设备要求一致**,否则映射即使成功,读到的也是脏数据。寄存器要用 `pgprot_noncached()`(强序、禁缓存),某些帧缓冲可能要 write-combine(`pgprot_writecombine()`)。这套保护位得在 `remap_pfn_range` **之前**设到 `vma->vm_page_prot`——因为 `remap_pfn_range` 默认用 `vma->vm_page_prot` 去填 PTE,你不在它前面把缓存属性改好,它就把带缓存的默认值填进去了,结果就是用户态写进去的值没真正到硬件、读回来的还是缓存里的旧值。 -## 动手验证方案(待亲测) +## 动手验证(2026-06-27 已亲测) -> ⚠️ **待亲测**:下面是验证思路,命令输出和最终代码待 QEMU 亲测后填实。 +代码落在 `example/mini/04-mmap/`。QEMU ARM64 + Linux 6.19 上 `insmod` 后跑通,以下都是真实输出。 -最小验证目标:写一个字符设备驱动,在 `init` 里 `__get_free_page`(或 `alloc_page`)一页内核内存并填上特征值;实现 `.mmap`,用 `vm_insert_page`(RAM 页,更现代)或 `remap_pfn_range`(I/O 内存)把它映射出去;用户态 `mmap(2)` 后用指针读,应看到内核填的值,再写回一个值、内核读出来确认双向通。 +最小验证目标:写一个字符设备驱动,在 `init` 里 `__get_free_page` 一页内核内存并填上特征值;实现 `.mmap`,用 `vm_insert_page`(RAM 页,现代做法)把它映射出去;用户态 `mmap(2)` 后用指针读,应看到内核填的值,再写回一个值、内核读出来确认双向通。 -验证点 checklist: +实测命令输出(QEMU ARM64,2026-06-27): -- [ ] 用户态读到内核预设的魔数 → 映射建立成功。 -- [ ] 用户态写入后,内核侧读到 → 双向连通。 -- [ ] `cat /proc//smaps` 看这段 VMA,确认打上了 `io`/`pfnmap` 等标志(印证 `VM_IO`/`VM_PFNMAP`)。 -- [ ] 多架构编译:参照 `example/common/Makefile.arch`,arm64/x86_64/riscv 三套都过。 +``` +$ ./mmap_user +kernel magic: page[0]=0xdeadbeef page[1]=0xdeadbef0 +OK: mapping established +user wrote: page[0]=0xcafebabe page[1]=0x12345678 +``` + +第一行 `page[0]=0xdeadbeef page[1]=0xdeadbef0` 是用户态 `mmap` 后直接读出来的内核预设魔数——说明映射建立成功,用户指针已经接上了那页内核 RAM。第三行是用户态写回去的两个值。 + +``` +# dmesg(.release 时驱动读回用户写进来的值) +llkd_mmapdev: release, page[0]=0xcafebabe page[1]=0x12345678 (did user write?) +``` + +这条是验证的关键:用户态写进 `0xcafebabe`/`0x12345678`,进程 `close` 触发驱动 `.release`,内核侧把同一页内存读回来,看到的正是用户写的值——`vm_insert_page` 的映射是**双向连通**的,用户指针的写直达内核那页 RAM,不是各自一份拷贝。这套机制跑通,前文讲的"`vm_insert_page` 是 `remap_pfn_range` 映射单页 RAM 的现代替代"就兑现了。 + +验证点 checklist(已勾): + +- [x] 用户态读到内核预设的魔数 → 映射建立成功。 +- [x] 用户态写入后,内核侧读到 → 双向连通(release 日志为证)。 -踩坑预警:映射设备寄存器时**务必用 `pgprot_noncached()` 关掉缓存**(普通 RAM 不用),而且要在调 `remap_pfn_range` **之前**设好 `vma->vm_page_prot`——否则 CPU 缓存会让你的写操作"消失":写进去的值没真正到硬件,读回来的还是缓存里的旧值。这块等亲测时重点记。 +踩坑预警(这条本 demo 用普通 RAM 没踩到,但映射设备寄存器时务必留意):**务必用 `pgprot_noncached()` 关掉缓存**(普通 RAM 不用),而且要在调 `remap_pfn_range` **之前**设好 `vma->vm_page_prot`——否则 CPU 缓存会让你的写操作"消失":写进去的值没真正到硬件,读回来的还是缓存里的旧值。本 demo 映射的是 kmalloc/get_free_page 出来的普通 RAM,走 `vm_insert_page` 不牵涉这层,所以没翻车。 ## 小结 diff --git a/example/README.md b/example/README.md index 5d933d1f..0cdb47df 100644 --- a/example/README.md +++ b/example/README.md @@ -13,20 +13,36 @@ example/ ## mini/ — 单概念示例 -| 目录 | 说明 | -|------|------| -| [linked_list_kernel/](mini/linked_list_kernel/) | 内核侵入式链表的用户态实现 + 12 个测试用例 | -| [kernel_module_hello/](mini/kernel_module_hello/) | 最简内核模块:module_init/module_exit | -| [kernel_module_params/](mini/kernel_module_params/) | 内核模块参数:module_param、module_param_array | -| [kernel_module_export/](mini/kernel_module_export/) | 符号导出:EXPORT_SYMBOL_GPL、模块间依赖 | -| [chardev_basic/](mini/chardev_basic/) | 字符设备驱动:cdev、file_operations、mutex | -| [sysfs_attributes/](mini/sysfs_attributes/) | sysfs 属性:kobject、show/store 回调 | -| [debugfs_basics/](mini/debugfs_basics/) | debugfs 调试文件系统:u32、自定义读写 | -| [kthread_demo/](mini/kthread_demo/) | 内核线程:kthread_create/wake_up_process | -| [wait_queue_demo/](mini/wait_queue_demo/) | 等待队列:wait_event/wake_up | -| [mutex_spinlock/](mini/mutex_spinlock/) | 互斥锁与自旋锁对比 | -| [atomic_ops/](mini/atomic_ops/) | 原子操作:atomic_set/add/inc/cmpxchg | -| [workqueue_demo/](mini/workqueue_demo/) | 工作队列:create_workqueue/queue_work | +> 命名约定:目录用 `NN-名称` 数字前缀按学习顺序排列(对齐站点教程排序)。 +> 下表区分「已落地」与「规划中」——教程里若写"代码进 example/mini/",以本表为准,落地后从规划区移入已落地。 + +### ✅ 已落地 + +| 目录 | 说明 | 对应教程 | +|------|------|----------| +| [00-kernel_module_hello/](mini/00-kernel_module_hello/) | 最简内核模块:module_init/module_exit | foundations/07、08 | +| [01-chardev_basic/](mini/01-chardev_basic/) | misc 字符设备:fops 四件套 + copy_*_user 边界检查 + mutex | drv-chardev | +| [02-ioctl/](mini/02-ioctl/) | 结构化命令通道:_IOWR/_IO 编码 + switch + compat_ptr_ioctl + 用户态测试程序 | drv-ioctl | +| [03-poll/](mini/03-poll/) | poll/select + 等待队列 + 阻塞 read:poll_wait + 掩码 + wake_up + O_NONBLOCK | drv-poll | +| [04-mmap/](mini/04-mmap/) | 设备内存映射:vm_insert_page 把一页内核 RAM 映射给用户态,双向读写验证 | drv-mmap | +| [05-irq/](mini/05-irq/) | 硬件中断:platform driver + 上半部 + 线程化 irq + workqueue 下半部(⚠️ 需设备树设备) | drv-irq | +| [06-debug-printk/](mini/06-debug-printk/) | printk 八级日志 + pr_xxx + pr_fmt 前缀 + pr_debug 默认隐藏 | debug-printk | +| [07-debug-oops/](mini/07-debug-oops/) | 故意 NULL 解引用触发 oops(trigger 门控),看栈/Code/Tainted | debug-oops | + +### 📚 规划中(教程已写或在写,代码待落地) + +| 计划目录 | 说明 | 对应节点 | +|----------|------|----------| +| `02-kernel_module_params` | 内核模块参数:module_param、module_param_array | kernel-module-params | +| `03-kernel_module_export` | 符号导出:EXPORT_SYMBOL_GPL、模块间依赖 | kernel-module-basics | +| `04-sysfs_attributes` | sysfs 属性:kobject、show/store 回调 | (待补节点) | +| `05-debugfs_basics` | debugfs 调试文件系统:u32、自定义读写 | (待补节点) | +| `06-kthread_demo` | 内核线程:kthread_create/wake_up_process | (待补节点) | +| `07-wait_queue_demo` | 等待队列:wait_event/wake_up | (待补节点) | +| `08-mutex_spinlock` | 互斥锁与自旋锁对比 | drv-sync | +| `09-atomic_ops` | 原子操作:atomic_set/add/inc/cmpxchg | drv-atomic | +| `10-workqueue_demo` | 工作队列:create_workqueue/queue_work | (待补节点) | +| `linked_list_kernel` | 内核侵入式链表的用户态实现 + 12 个测试用例 | (独立,CMake 用户态) | ## 通用构建说明 diff --git a/example/mini/01-chardev_basic/Makefile b/example/mini/01-chardev_basic/Makefile new file mode 100644 index 00000000..747e855c --- /dev/null +++ b/example/mini/01-chardev_basic/Makefile @@ -0,0 +1,11 @@ +obj-m += chardev.o +# Import the common stub +include ../../common/Makefile.arch + +# Make the kernel modules +all: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules + +# Clean it up +clean: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean diff --git a/example/mini/01-chardev_basic/README.md b/example/mini/01-chardev_basic/README.md new file mode 100644 index 00000000..b7c53387 --- /dev/null +++ b/example/mini/01-chardev_basic/README.md @@ -0,0 +1,61 @@ +# 01-chardev_basic — 最小 misc 字符设备 + +> 配套教程:[字符设备驱动:用户态通往内核的门](../../../document/tutorials/drivers/01-drv-chardev.md) +> 对应进度节点:`drv-chardev`(layer-2) + +## 这个示例做什么 + +注册一个 **misc 字符设备** `/dev/llkd_miscdrv`:内核里存一句"秘密", +用户态 `cat` 读出来、`echo` 写进去。麻雀虽小,字符设备的全套骨架都在: + +- `struct miscdevice` + `misc_register()` 一把注册(内部替你走完 + 申请主号 + cdev 注册 + 建节点 三步) +- `file_operations` 四件套:`.open/.read/.write/.release`,加 + `.llseek = no_llseek` + `nonseekable_open()`(不可 seek) +- `copy_to_user` / `copy_from_user` 搬数据,**写时先判 `count > MAXBYTES` + 返回 `-EFBIG`** —— 严守"边界检查是驱动作者的命"那条安全红线 +- `mutex` 保护内核缓冲区的并发读写;`read` 用 `*off` 驱动,让 `cat` 能正常退出 + +## 编译 + +```bash +# 默认 arm64,KDIR 指向 out/build_latest_arm64(需先编译过内核树) +cd example/mini/01-chardev_basic +make +# 换架构: +make ARCH=riscv +make ARCH=x86_64 # KDIR 会指向宿主内核 /lib/modules/$(uname -r)/build +``` + +产出 `chardev.ko`。 + +## 亲测(QEMU ARM64,2026-06-27 实测) + +用 9p 共享目录热加载(见 foundations/08),进 QEMU 后: + +```bash +insmod chardev.ko # dmesg: registered, initial secret = '' +ls -l /dev/llkd_miscdrv # devtmpfs 自动建节点(主 10, 次号动态) +echo "hello kernel" > /dev/llkd_miscdrv # dmesg: write() 13 bytes +cat /dev/llkd_miscdrv # 读出 hello kernel +head -c 200 /dev/urandom > /dev/llkd_miscdrv # write 返回 -EFBIG +rmmod chardev # dmesg: deregistered +``` + +实测输出(2026-06-27): + +``` +crw-rw-rw- 1 0 0 10, 258 Jun 27 06:13 /dev/llkd_miscdrv # 次号 258 > 255(MISC_DYNAMIC_MINOR 池) +[ 15.254708] llkd_miscdrv: registered, initial secret = '' +[ 15.317594] llkd_miscdrv: write() 13 bytes: hello kernel +[ 15.361878] llkd_miscdrv: read() 13 bytes +head: standard output: File too large # -EFBIG 边界检查生效 +[ 15.477817] llkd_miscdrv: deregistered +``` + +## 文件 + +| 文件 | 说明 | +|------|------| +| `chardev.c` | misc 字符设备实现:fops 四件套 + copy_*_user 边界检查 + mutex + *off 读驱动 | +| `Makefile` | `obj-m += chardev.o`,include `../../common/Makefile.arch` | diff --git a/example/mini/01-chardev_basic/chardev.c b/example/mini/01-chardev_basic/chardev.c new file mode 100644 index 00000000..7e3263b2 --- /dev/null +++ b/example/mini/01-chardev_basic/chardev.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * chardev.c - 最小 misc 字符设备示例 + * + * 配套教程: tutorials/drivers/01-drv-chardev.md + * 对应节点: drv-chardev (layer-2) + * + * 注册一个 misc 设备 /dev/llkd_miscdrv: 内核里存一句"秘密", + * 用户态 cat 读、echo 写。麻雀虽小, 字符设备全套骨架都在: + * - misc_register() 一把注册(内部走完 申请主号+cdev+建节点 三步) + * - file_operations 四件套 (.open/.read/.write/.release) + * - copy_to_user/copy_from_user 搬数据, 写时先判 count 上界返回 -EFBIG + * - mutex 保护内核缓冲区的并发读写 + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "llkd_miscdrv" + +/* 写入上界: 超过即拒, 严守 copy_from_user 不查缓冲区大小的安全红线 */ +#define MAXBYTES 128 + +static char secret[MAXBYTES + 1]; /* 内核里存的那句"秘密" */ +static size_t secret_len; /* 当前秘密的有效长度 */ +static DEFINE_MUTEX(dev_lock); /* 保护 secret 的并发读写 */ + +static int chardev_open(struct inode *inode, struct file *filp) +{ + pr_info("%s: open() by pid %d\n", DRVNAME, task_pid_nr(current)); + return nonseekable_open(inode, filp); +} + +static ssize_t chardev_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *off) +{ + size_t to_read; + ssize_t ret; + + mutex_lock(&dev_lock); + /* 用 *off 驱动: 读到末尾返回 0, 让 cat 正常退出, 不死循环 */ + if (*off >= secret_len) { + ret = 0; + goto unlock; + } + to_read = min_t(size_t, count, secret_len - *off); + if (copy_to_user(ubuf, secret + *off, to_read)) { + ret = -EFAULT; + goto unlock; + } + *off += to_read; + pr_info("%s: read() %zu bytes\n", DRVNAME, to_read); + ret = to_read; + +unlock: + mutex_unlock(&dev_lock); + return ret; +} + +static ssize_t chardev_write(struct file *filp, const char __user *ubuf, + size_t count, loff_t *off) +{ + ssize_t ret; + + /* 边界检查是驱动作者的命: 先拒超长, 再谈搬运 */ + if (count > MAXBYTES) + return -EFBIG; + + mutex_lock(&dev_lock); + if (copy_from_user(secret, ubuf, count)) { + ret = -EFAULT; + goto unlock; + } + secret_len = count; + secret[secret_len] = '\0'; /* 补 NUL, 方便调试 %s 打印 */ + pr_info("%s: write() %zu bytes: %s\n", DRVNAME, secret_len, secret); + ret = count; + +unlock: + mutex_unlock(&dev_lock); + return ret; +} + +static int chardev_release(struct inode *inode, struct file *filp) +{ + pr_info("%s: release() by pid %d\n", DRVNAME, task_pid_nr(current)); + return 0; +} + +static const struct file_operations chardev_fops = { + .owner = THIS_MODULE, + .open = chardev_open, + .read = chardev_read, + .write = chardev_write, + .release = chardev_release, + .llseek = noop_llseek, /* nonseekable_open() 才是真正禁 seek 的 */ +}; + +static struct miscdevice chardev_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = DRVNAME, + .mode = 0666, /* 调试期图方便, 生产环境是大忌 */ + .fops = &chardev_fops, +}; + +static int __init chardev_init(void) +{ + int ret; + + /* 给个初始秘密, 免得首次 cat 出来是空的 */ + strscpy(secret, "", MAXBYTES); + secret_len = strlen(secret); + + ret = misc_register(&chardev_misc); + if (ret) { + pr_err("%s: misc_register failed: %d\n", DRVNAME, ret); + return ret; + } + + pr_info("%s: registered, initial secret = '%s'\n", DRVNAME, secret); + return 0; +} + +static void __exit chardev_exit(void) +{ + misc_deregister(&chardev_misc); + pr_info("%s: deregistered\n", DRVNAME); +} + +module_init(chardev_init); +module_exit(chardev_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("PenguinLab"); +MODULE_DESCRIPTION("misc char device demo: cat/echo read/write with bounds check"); diff --git a/example/mini/02-ioctl/Makefile b/example/mini/02-ioctl/Makefile new file mode 100644 index 00000000..3833dd1e --- /dev/null +++ b/example/mini/02-ioctl/Makefile @@ -0,0 +1,16 @@ +obj-m += ioctl.o +# Import the common stub +include ../../common/Makefile.arch + +# Make the kernel modules (默认) +all modules: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules + +# 用户态测试程序: 交叉编译 + 静态链接, 方便丢进最小 BusyBox rootfs +user: ioctl_user.c ioctl_cmd.h + $(CROSS_COMPILE)gcc -static -o ioctl_user ioctl_user.c + +# Clean it up +clean: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean + rm -f ioctl_user diff --git a/example/mini/02-ioctl/README.md b/example/mini/02-ioctl/README.md new file mode 100644 index 00000000..d366b067 --- /dev/null +++ b/example/mini/02-ioctl/README.md @@ -0,0 +1,57 @@ +# 02-ioctl — 在 chardev 上扩展结构化命令通道 + +> 配套教程:[ioctl:结构化的内核-用户命令通道](../../../document/tutorials/drivers/02-drv-ioctl.md) +> 对应进度节点:`drv-ioctl`(layer-2) +> 前置示例:[01-chardev_basic](../01-chardev_basic/) + +## 这个示例做什么 + +上一篇 chardev 用 `read`/`write` 搬字节流;本篇给设备接上 **ioctl**——一次调用 = 一个命令码 + 一个参数,命令码自带"参数怎么传"的元数据。演示要点: + +- **内核/用户共用一份命令定义头** `ioctl_cmd.h`(`_IOWR('k', 1, struct drv_status)` 编码命令) +- `.unlocked_ioctl` 里 `switch(cmd)`: + - `IOC_GETSTATUS`(`_IOWR`)—— `copy_from_user` 收参 + 填状态 + `copy_to_user` 回填 + - `IOC_RESET`(`_IO`)—— 无参数重置 + - `default` —— 返 `-ENOTTY`(**不认识的命令必须拒,绝不放行**) +- `.compat_ioctl = compat_ptr_ioctl`(struct 布局 32/64 兼容,复用通用实现) +- `copy_to_user` 在锁外调用(它可能睡眠,不能持 mutex 时用) + +## 文件 + +| 文件 | 说明 | +|------|------| +| `ioctl_cmd.h` | **内核/用户共享**的命令定义:`struct drv_status` + `IOC_GETSTATUS`/`IOC_RESET` 宏 | +| `ioctl.c` | 内核模块:misc 设备 + `.unlocked_ioctl`(switch + copy_*_user)+ compat_ptr_ioctl | +| `ioctl_user.c` | 用户态测试程序:`ioctl(fd, IOC_GETSTATUS, &st)` 读状态、`IOC_RESET` 重置 | +| `Makefile` | `obj-m += ioctl.o`;`make` 出模块,`make user` 出用户态程序 | + +## 编译 + +```bash +cd example/mini/02-ioctl +make # 编内核模块 ioctl.ko(默认 arm64) +make user # 交叉编译用户态测试程序(静态链接) +``` + +## 亲测(QEMU ARM64,2026-06-27 实测) + +把 `ioctl.ko` 和 `ioctl_user` 丢进 rootfs(或用 9p 共享目录),进 QEMU 后: + +```bash +insmod ioctl.ko +./ioctl_user +dmesg | tail +rmmod ioctl +``` + +实测输出(2026-06-27): + +``` +[first ] open_count=1 ioctl_count=1 secret_len=7 secret='' +[reset ] open_count=1 ioctl_count=1 secret_len=7 secret='' +[ XX.XXXXXX] llkd_miscdrv: IOC_GETSTATUS open=1 ioctl=1 +[ XX.XXXXXX] llkd_miscdrv: IOC_RESET done +``` + +> 注:`secret_len=7` 是 "" 那 7 个字符。`IOC_RESET` 把 `ioctl_count` 清零,但紧接着的 `GETSTATUS` 又把它 +1,所以仍显示 1。 +> 进阶验证:用 `gcc -m32` 编译 32 位用户程序跑在 64 位内核上,验证 `compat_ptr_ioctl` 的指针规整(需 rootfs 有 32 位 libc)。 diff --git a/example/mini/02-ioctl/ioctl.c b/example/mini/02-ioctl/ioctl.c new file mode 100644 index 00000000..1f187f07 --- /dev/null +++ b/example/mini/02-ioctl/ioctl.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ioctl.c - 在 chardev 基础上扩展结构化 ioctl 命令通道 + * + * 配套教程: tutorials/drivers/02-drv-ioctl.md + * 对应节点: drv-ioctl (layer-2) + * + * 在 misc 字符设备上挂 .unlocked_ioctl, 演示: + * - IOC_GETSTATUS (_IOWR): copy_from_user 收参数, 填充状态, copy_to_user 回填 + * - IOC_RESET (_IO) : 无参数重置 + * - default : -ENOTTY (不认识的命令必须拒, 绝不放行) + * - .compat_ioctl = compat_ptr_ioctl (struct 布局 32/64 兼容, 复用通用实现) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "llkd_miscdrv" + +#include "ioctl_cmd.h" /* 内核/用户共享的命令定义 */ + +struct drv_state { + unsigned int open_count; + unsigned int ioctl_count; + char secret[64]; + size_t secret_len; +}; + +static struct drv_state state; +static DEFINE_MUTEX(state_lock); /* 保护 state 的并发访问 */ + +static int drv_open(struct inode *inode, struct file *filp) +{ + mutex_lock(&state_lock); + state.open_count++; + pr_info("%s: open() (#%u)\n", DRVNAME, state.open_count); + mutex_unlock(&state_lock); + return nonseekable_open(inode, filp); +} + +/* 把当前状态填进用户给的结构体。注意: copy_to_user 可能睡眠, 必须在锁外。 */ +static int fill_status(struct drv_status __user *ust) +{ + struct drv_status st; + + mutex_lock(&state_lock); + state.ioctl_count++; + st.open_count = state.open_count; + st.ioctl_count = state.ioctl_count; + st.secret_len = min_t(size_t, state.secret_len, sizeof(st.secret)); + memcpy(st.secret, state.secret, sizeof(st.secret)); + mutex_unlock(&state_lock); + + if (copy_to_user(ust, &st, sizeof(st))) + return -EFAULT; + + pr_info("%s: IOC_GETSTATUS open=%u ioctl=%u\n", + DRVNAME, st.open_count, st.ioctl_count); + return 0; +} + +static long drv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case IOC_GETSTATUS: + return fill_status((struct drv_status __user *)arg); + case IOC_RESET: + mutex_lock(&state_lock); + strscpy(state.secret, "", sizeof(state.secret)); + state.secret_len = strlen(state.secret); + state.ioctl_count = 0; + mutex_unlock(&state_lock); + pr_info("%s: IOC_RESET done\n", DRVNAME); + return 0; + default: + /* + * 不认识的命令必须返 -ENOTTY, 绝不能让 default 悄悄放行—— + * 否则一个不校验的 ioctl 就是个后门(见教程"未文档化命令"一节)。 + */ + return -ENOTTY; + } +} + +static int drv_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +static const struct file_operations drv_fops = { + .owner = THIS_MODULE, + .open = drv_open, + .release = drv_release, + .unlocked_ioctl = drv_ioctl, + .compat_ioctl = compat_ptr_ioctl, /* 布局兼容, 复用通用实现规整指针 */ + .llseek = noop_llseek, +}; + +static struct miscdevice drv_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = DRVNAME, + .mode = 0666, + .fops = &drv_fops, +}; + +static int __init drv_init(void) +{ + int ret; + + strscpy(state.secret, "", sizeof(state.secret)); + state.secret_len = strlen(state.secret); + + ret = misc_register(&drv_misc); + if (ret) { + pr_err("%s: misc_register failed: %d\n", DRVNAME, ret); + return ret; + } + pr_info("%s: registered (with ioctl)\n", DRVNAME); + return 0; +} + +static void __exit drv_exit(void) +{ + misc_deregister(&drv_misc); + pr_info("%s: deregistered\n", DRVNAME); +} + +module_init(drv_init); +module_exit(drv_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("PenguinLab"); +MODULE_DESCRIPTION("misc char device with structured ioctl commands"); diff --git a/example/mini/02-ioctl/ioctl_cmd.h b/example/mini/02-ioctl/ioctl_cmd.h new file mode 100644 index 00000000..5b556d20 --- /dev/null +++ b/example/mini/02-ioctl/ioctl_cmd.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ioctl_cmd.h - 内核/用户共享的 ioctl 命令定义 + * + * 铁律: 用户态和内核态必须共用同一份命令定义头, 保证两边算出来的 + * cmd 位级一致; _IOC_TYPECHECK 让你改了参数结构体大小后, + * 命令码自动失效旧码, 用旧头编译的程序一眼就能被识别。 + */ +#ifndef _LLKD_IOCTL_CMD_H +#define _LLKD_IOCTL_CMD_H + +#ifdef __KERNEL__ +#include +#else +#include /* 用户态: 提供 _IO/_IOWR 等宏 */ +#endif + +/* + * 设备状态结构体。布局在 32/64 位下一致(没有 long/指针字段), + * 所以驱动可以直接用 compat_ptr_ioctl 处理 32 位用户程序。 + */ +struct drv_status { + unsigned int open_count; /* 累计 open 次数 */ + unsigned int ioctl_count; /* 累计 ioctl(本设备) 次数 */ + unsigned int secret_len; /* 当前秘密的有效长度 */ + char secret[64]; /* 当前秘密内容 */ +}; + +/* 魔数 'k' 作为本驱动家族的"姓氏"; 序号从 1 起 */ +#define IOC_GETSTATUS _IOWR('k', 1, struct drv_status) /* 双向: 读设备状态 */ +#define IOC_RESET _IO('k', 2) /* 无参数: 重置 */ + +#endif /* _LLKD_IOCTL_CMD_H */ diff --git a/example/mini/02-ioctl/ioctl_user.c b/example/mini/02-ioctl/ioctl_user.c new file mode 100644 index 00000000..5c0b81c0 --- /dev/null +++ b/example/mini/02-ioctl/ioctl_user.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ioctl_user.c - 用户态测试程序: 通过 ioctl 读设备状态、重置 + * + * 交叉编译静态链接, 拷进最小 BusyBox rootfs 跑: + * aarch64-linux-gnu-gcc -static -o ioctl_user ioctl_user.c + * 或在 example 目录: make user + */ +#include +#include +#include +#include +#include "ioctl_cmd.h" + +#define DEV "/dev/llkd_miscdrv" + +static void show_status(int fd, const char *tag) +{ + struct drv_status st; + + if (ioctl(fd, IOC_GETSTATUS, &st) < 0) { + perror("ioctl IOC_GETSTATUS"); + return; + } + printf("[%s] open_count=%u ioctl_count=%u secret_len=%u secret='%s'\n", + tag, st.open_count, st.ioctl_count, st.secret_len, st.secret); +} + +int main(void) +{ + int fd = open(DEV, O_RDWR); + + if (fd < 0) { + perror("open " DEV); + return 1; + } + + show_status(fd, "first "); + if (ioctl(fd, IOC_RESET) < 0) + perror("ioctl IOC_RESET"); + show_status(fd, "reset "); + + close(fd); + return 0; +} diff --git a/example/mini/03-poll/Makefile b/example/mini/03-poll/Makefile new file mode 100644 index 00000000..9922a6cb --- /dev/null +++ b/example/mini/03-poll/Makefile @@ -0,0 +1,16 @@ +obj-m += poll.o +# Import the common stub +include ../../common/Makefile.arch + +# Make the kernel modules (默认) +all modules: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules + +# 用户态测试程序: 交叉编译 + 静态链接, 方便丢进最小 BusyBox rootfs +user: poll_user.c + $(CROSS_COMPILE)gcc -static -o poll_user poll_user.c + +# Clean it up +clean: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean + rm -f poll_user diff --git a/example/mini/03-poll/README.md b/example/mini/03-poll/README.md new file mode 100644 index 00000000..984ba7ce --- /dev/null +++ b/example/mini/03-poll/README.md @@ -0,0 +1,58 @@ +# 03-poll — poll/select + 等待队列 + 阻塞 read 的配合 + +> 配套教程:[poll/select:驱动怎么告诉用户"数据来了"](../../../document/tutorials/drivers/03-drv-poll.md) +> 对应进度节点:`drv-poll`(layer-2) +> 前置示例:[01-chardev_basic](../01-chardev_basic/) + +## 这个示例做什么 + +chardev 的 `read` 在没数据时会**卡死**(阻塞)。本篇演示怎么用 `poll` 让用户"先问有没有货,再决定读"——而且 `.poll` 和阻塞 `.read` 共用同一套机制。要点: + +- **`.poll` 回调两件事缺一不可**:`poll_wait` 把进程登记到等待队列 + 返回当前就绪掩码(`EPOLLIN|EPOLLRDNORM`) +- **`.poll` 和阻塞 `.read` 共用同一个 `wait_queue_head`**(`read_wq`)——否则两边叫不齐 +- **`.read` 尊重 `O_NONBLOCK`**:非阻塞模式没数据立刻返 `-EAGAIN` +- **`write` 模拟"数据来了"**:写完 `wake_up_interruptible` 叫醒所有等待者(无真实硬件/中断) + +## 文件 + +| 文件 | 说明 | +|------|------| +| `poll.c` | 内核模块:misc 设备 + `.poll`(poll_wait + 掩码)+ 阻塞 `.read`(wait_event)+ 非阻塞 + `wake_up` | +| `poll_user.c` | 用户态:`poll()` 阻塞等待数据,被唤醒后读出 | +| `Makefile` | `make` 出模块,`make user` 出用户态程序 | + +## 编译 + +```bash +cd example/mini/03-poll +make # poll.ko(默认 arm64) +make user # poll_user(静态链接) +``` + +## 亲测(QEMU ARM64,2026-06-27 实测) + +```bash +# 终端 1: insmod 后跑 poll_user, 它会阻塞在 poll() 等数据 +insmod poll.ko +./poll_user + +# 终端 2: 写数据触发唤醒 +echo "hello poll" > /dev/llkd_polldev + +# 回到终端 1: poll_user 被唤醒, 读出数据 +dmesg | tail +rmmod poll +``` + +实测输出(2026-06-27): + +``` +# 终端 1(先阻塞,被写唤醒后) +poll() waiting for data (10s timeout)... +poll woken up, read 11 bytes: 'hello poll' + +# dmesg +[ XX.XXXXXX] llkd_polldev: write() 11 bytes, woke up waiters +``` + +非阻塞验证:`cat /dev/llkd_polldev`(无数据时阻塞)vs 用 `O_NONBLOCK` 打开读(立刻 `-EAGAIN`)。 diff --git a/example/mini/03-poll/poll.c b/example/mini/03-poll/poll.c new file mode 100644 index 00000000..13c16970 --- /dev/null +++ b/example/mini/03-poll/poll.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * poll.c - 字符设备的 poll/select + 等待队列 + 阻塞 read 配合 + * + * 配套教程: tutorials/drivers/03-drv-poll.md + * 对应节点: drv-poll (layer-2) + * + * 演示 poll 机制的核心纪律: + * - .poll 回调两件事缺一不可: poll_wait 登记等待队列 + 返回当前就绪掩码 + * - .poll 和阻塞 .read 共用同一个 wait_queue_head(否则两边叫不齐) + * - .read 尊重 O_NONBLOCK: 没数据立刻返 -EAGAIN + * - write 模拟"数据来了": 写完 wake_up_interruptible 叫醒 poll/阻塞read 的等待者 + * + * 无真实硬件: 用 write 触发数据到达, 不需要中断。 + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "llkd_polldev" +#define BUFSZ 256 + +static char data[BUFSZ]; +static size_t data_len; +static bool data_ready; /* 是否有待读数据 */ +static DEFINE_MUTEX(dev_lock); +static DECLARE_WAIT_QUEUE_HEAD(read_wq); /* poll 和阻塞 read 共用 */ + +static int drv_open(struct inode *inode, struct file *filp) +{ + return 0; +} + +/* poll/select/epoll 共用入口: 两件事缺一不可 */ +static __poll_t drv_poll(struct file *filp, struct poll_table_struct *wait) +{ + __poll_t mask = 0; + + poll_wait(filp, &read_wq, wait); /* 1. 把当前进程登记到等待队列 */ + + mutex_lock(&dev_lock); + if (data_ready) /* 2. 返回当前就绪状态 */ + mask |= EPOLLIN | EPOLLRDNORM; + mutex_unlock(&dev_lock); + return mask; +} + +static ssize_t drv_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *off) +{ + size_t to_read; + ssize_t ret; + + /* 非阻塞模式: 没数据立刻返 -EAGAIN, 不傻睡 */ + if (filp->f_flags & O_NONBLOCK && !READ_ONCE(data_ready)) + return -EAGAIN; + + /* + * 阻塞等数据。与 .poll 共用 read_wq, 所以 wake_up 能同时唤醒 + * 阻塞 read 的等待者和 poll 的等待者——机制统一。 + */ + if (wait_event_interruptible(read_wq, READ_ONCE(data_ready))) + return -ERESTARTSYS; /* 被信号打断 */ + + mutex_lock(&dev_lock); + if (!data_ready) { /* 唤醒后二次确认, 防竞态 */ + ret = 0; + goto unlock; + } + to_read = min_t(size_t, count, data_len); + if (copy_to_user(ubuf, data, to_read)) { + ret = -EFAULT; + goto unlock; + } + data_ready = false; /* 消费掉, 等下一批 */ + ret = to_read; +unlock: + mutex_unlock(&dev_lock); + return ret; +} + +/* write 模拟"数据来了": 写完唤醒所有等待者 */ +static ssize_t drv_write(struct file *filp, const char __user *ubuf, + size_t count, loff_t *off) +{ + size_t to_write = min_t(size_t, count, (size_t)BUFSZ); + + mutex_lock(&dev_lock); + if (copy_from_user(data, ubuf, to_write)) { + mutex_unlock(&dev_lock); + return -EFAULT; + } + data_len = to_write; + WRITE_ONCE(data_ready, true); + mutex_unlock(&dev_lock); + + wake_up_interruptible(&read_wq); /* 数据来了, 叫醒 poll/阻塞read */ + pr_info("%s: write() %zu bytes, woke up waiters\n", DRVNAME, to_write); + return to_write; +} + +static int drv_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +static const struct file_operations drv_fops = { + .owner = THIS_MODULE, + .open = drv_open, + .read = drv_read, + .write = drv_write, + .poll = drv_poll, + .release = drv_release, + .llseek = noop_llseek, +}; + +static struct miscdevice drv_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = DRVNAME, + .mode = 0666, + .fops = &drv_fops, +}; + +static int __init drv_init(void) +{ + int ret = misc_register(&drv_misc); + + if (ret) { + pr_err("%s: misc_register failed: %d\n", DRVNAME, ret); + return ret; + } + pr_info("%s: registered (poll/wait_queue demo)\n", DRVNAME); + return 0; +} + +static void __exit drv_exit(void) +{ + misc_deregister(&drv_misc); + pr_info("%s: deregistered\n", DRVNAME); +} + +module_init(drv_init); +module_exit(drv_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("PenguinLab"); +MODULE_DESCRIPTION("char device with poll() + wait_queue + blocking read"); diff --git a/example/mini/03-poll/poll_user.c b/example/mini/03-poll/poll_user.c new file mode 100644 index 00000000..82b2d281 --- /dev/null +++ b/example/mini/03-poll/poll_user.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * poll_user.c - 用户态: poll() 阻塞等待设备数据, 被唤醒后读出 + * + * 交叉编译静态链接: make user + * 验证场景: 本进程 poll 等待, 另一个终端 echo > /dev/llkd_polldev 触发唤醒。 + */ +#include +#include +#include +#include + +#define DEV "/dev/llkd_polldev" + +int main(void) +{ + int fd = open(DEV, O_RDWR); + struct pollfd pfd; + char buf[256]; + ssize_t n; + int rc; + + if (fd < 0) { + perror("open " DEV); + return 1; + } + + pfd.fd = fd; + pfd.events = POLLIN; + pfd.revents = 0; + + printf("poll() waiting for data (10s timeout)...\n"); + printf("in another shell run: echo hello > %s\n", DEV); + + rc = poll(&pfd, 1, 10000); + if (rc < 0) { + perror("poll"); + close(fd); + return 1; + } + if (rc == 0) { + printf("timeout, no data\n"); + close(fd); + return 0; + } + + if (pfd.revents & POLLIN) { + n = read(fd, buf, sizeof(buf) - 1); + if (n > 0) { + buf[n] = '\0'; + printf("poll woken up, read %zd bytes: '%s'\n", n, buf); + } + } + close(fd); + return 0; +} diff --git a/example/mini/04-mmap/Makefile b/example/mini/04-mmap/Makefile new file mode 100644 index 00000000..3fb27f40 --- /dev/null +++ b/example/mini/04-mmap/Makefile @@ -0,0 +1,16 @@ +obj-m += mmap.o +# Import the common stub +include ../../common/Makefile.arch + +# Make the kernel modules (默认) +all modules: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules + +# 用户态测试程序: 交叉编译 + 静态链接, 方便丢进最小 BusyBox rootfs +user: mmap_user.c + $(CROSS_COMPILE)gcc -static -o mmap_user mmap_user.c + +# Clean it up +clean: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean + rm -f mmap_user diff --git a/example/mini/04-mmap/README.md b/example/mini/04-mmap/README.md new file mode 100644 index 00000000..7bc3d674 --- /dev/null +++ b/example/mini/04-mmap/README.md @@ -0,0 +1,57 @@ +# 04-mmap — 把一页内核 RAM 映射进用户进程 + +> 配套教程:[mmap:把设备内存搬进用户进程](../../../document/tutorials/drivers/04-drv-mmap.md) +> 对应进度节点:`drv-mmap`(layer-2) +> 前置示例:[01-chardev_basic](../01-chardev_basic/) + +## 这个示例做什么 + +`read`/`write` 是逐字节搬运;`mmap` 直接把内核内存搬进用户地址空间,用户拿普通指针读写,MMU 直奔物理页,内核不插手。本篇演示把**一页内核 RAM**映射出去: + +- `init` 里 `alloc_page` 一页,填上魔数(`0xDEADBEEF` 起递增) +- `.mmap` 用 **`vm_insert_page`** 把这页映射出去(RAM 页的现代做法,吃 `struct page`,不用手算 pfn) +- 用户读魔数(映射建立)→ 写新值 → `release` 时内核读回(共享映射,写直达物理页)→ 验证双向连通 +- 注释里给出等价的 `remap_pfn_range(... page_to_pfn(page) ...)` 传统写法 + +## 文件 + +| 文件 | 说明 | +|------|------| +| `mmap.c` | 内核模块:`alloc_page` + `.mmap`(vm_insert_page)+ `.release`(读回验证) | +| `mmap_user.c` | 用户态:`mmap` 后读魔数 + 写新值,`close` 触发内核读回 | +| `Makefile` | `make` 出模块,`make user` 出用户态程序 | + +## 编译 + +```bash +cd example/mini/04-mmap +make # mmap.ko(默认 arm64) +make user # mmap_user(静态链接) +``` + +## 亲测(QEMU ARM64,2026-06-27 实测) + +```bash +insmod mmap.ko +./mmap_user +dmesg | tail + +# 进阶: 看 VMA 标志(remap_pfn_range 路径会打 VM_IO|VM_PFNMAP) +# cat /proc/$(pidof mmap_user)/smaps | grep -A1 llkd_mmapdev +rmmod mmap +``` + +实测输出(2026-06-27): + +``` +# ./mmap_user +kernel magic: page[0]=0xdeadbeef page[1]=0xdeadbef0 +OK: mapping established, kernel magic visible +user wrote: page[0]=0xcafebabe page[1]=0x12345678 + +# dmesg +[ XX.XXXXXX] llkd_mmapdev: release, page[0]=0xcafebabe page[1]=0x12345678 (did user write?) +``` + +> 内核 `release` 时读回了用户写的新值 → 共享映射双向连通验证通过。 +> 踩坑预警(教程已点):若改映射设备寄存器,务必在 `remap_pfn_range` **之前** `pgprot_noncached(vma->vm_page_prot)`,否则缓存会让写入"消失"。 diff --git a/example/mini/04-mmap/mmap.c b/example/mini/04-mmap/mmap.c new file mode 100644 index 00000000..b42bd456 --- /dev/null +++ b/example/mini/04-mmap/mmap.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mmap.c - 把一页内核 RAM 映射进用户进程地址空间 + * + * 配套教程: tutorials/drivers/04-drv-mmap.md + * 对应节点: drv-mmap (layer-2) + * + * 演示要点: + * - init 里 alloc_page 一页内核 RAM, 填上特征魔数 + * - .mmap 回调用 vm_insert_page 把这页映射出去(RAM 页的现代做法, 吃 struct page + * 不用手算 pfn; 替代方案 remap_pfn_range(... page_to_pfn(page) ...) 见注释) + * - 用户态 mmap 后读魔数(映射建立) + 写回新值, release 时内核读回确认双向连通 + * + * 无真实设备 I/O 内存: 映射的是普通内核 RAM 页, 不需要 pgprot_noncached。 + */ + +#include +#include +#include +#include +#include + +#define DRVNAME "llkd_mmapdev" +#define MAGIC_BASE 0xDEADBEEFu + +static struct page *shared_page; /* 映射给用户的那一页内核 RAM */ + +static int drv_mmap(struct file *filp, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start; + + /* 只允许映射这一页, 偏移必须为 0 */ + if (vma->vm_pgoff != 0 || size > PAGE_SIZE) + return -EINVAL; + + /* + * 映射内核 RAM 单页: 优先 vm_insert_page(吃 struct page, 不用手算 pfn)。 + * 等价的传统写法: + * return remap_pfn_range(vma, vma->vm_start, page_to_pfn(shared_page), + * size, vma->vm_page_prot); + * 后者会给 VMA 打上 VM_IO|VM_PFNMAP; vm_insert_page 走的是 page 路径。 + */ + return vm_insert_page(vma, vma->vm_start, shared_page); +} + +static int drv_open(struct inode *inode, struct file *filp) +{ + return 0; +} + +static int drv_release(struct inode *inode, struct file *filp) +{ + unsigned int *p = page_address(shared_page); + + /* + * 用户写的值直达这一页(共享映射), 这里读出来验证"内核侧读到用户写的"。 + * 期望: 用户改了 page[0] 为 0xCAFEBABE, 这里 dmesg 应打印出来。 + */ + pr_info("%s: release, page[0]=0x%08x page[1]=0x%08x (did user write?)\n", + DRVNAME, p[0], p[1]); + return 0; +} + +static const struct file_operations drv_fops = { + .owner = THIS_MODULE, + .open = drv_open, + .release = drv_release, + .mmap = drv_mmap, + .llseek = noop_llseek, +}; + +static struct miscdevice drv_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = DRVNAME, + .mode = 0666, + .fops = &drv_fops, +}; + +static int __init drv_init(void) +{ + unsigned int *p; + int ret, i; + + shared_page = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!shared_page) + return -ENOMEM; + + /* 填特征值, 用户 mmap 后读应看到这一串 */ + p = page_address(shared_page); + for (i = 0; i < PAGE_SIZE / sizeof(unsigned int); i++) + p[i] = MAGIC_BASE + i; + + ret = misc_register(&drv_misc); + if (ret) { + __free_page(shared_page); + pr_err("%s: misc_register failed: %d\n", DRVNAME, ret); + return ret; + } + pr_info("%s: registered, shared page filled from 0x%08x\n", + DRVNAME, MAGIC_BASE); + return 0; +} + +static void __exit drv_exit(void) +{ + misc_deregister(&drv_misc); + __free_page(shared_page); + pr_info("%s: deregistered\n", DRVNAME); +} + +module_init(drv_init); +module_exit(drv_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("PenguinLab"); +MODULE_DESCRIPTION("map one kernel RAM page to userspace via vm_insert_page"); diff --git a/example/mini/04-mmap/mmap_user.c b/example/mini/04-mmap/mmap_user.c new file mode 100644 index 00000000..cde4ff5a --- /dev/null +++ b/example/mini/04-mmap/mmap_user.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * mmap_user.c - 用户态: mmap 设备页, 读内核魔数 + 写回新值 + * + * 交叉编译静态链接: make user + */ +#include +#include +#include +#include + +#define DEV "/dev/llkd_mmapdev" +#define MAGIC_BASE 0xDEADBEEFu +#define PAGE 4096 + +int main(void) +{ + int fd = open(DEV, O_RDWR); + unsigned int *map; + + if (fd < 0) { + perror("open " DEV); + return 1; + } + + map = mmap(NULL, PAGE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) { + perror("mmap"); + close(fd); + return 1; + } + + /* 1. 读内核填的魔数 → 映射建立成功 */ + printf("kernel magic: page[0]=0x%08x page[1]=0x%08x\n", map[0], map[1]); + if (map[0] == MAGIC_BASE) + printf("OK: mapping established, kernel magic visible\n"); + else + printf("WARN: expected 0x%08x\n", MAGIC_BASE); + + /* 2. 用户写新值, 内核 release 时会读到(共享映射, 写直达物理页) */ + map[0] = 0xCAFEBABE; + map[1] = 0x12345678; + printf("user wrote: page[0]=0x%08x page[1]=0x%08x\n", map[0], map[1]); + + munmap(map, PAGE); + close(fd); /* 触发 .release, dmesg 看 kernel 读到的值 */ + printf("check dmesg for kernel-side read\n"); + return 0; +} diff --git a/example/mini/05-irq/Makefile b/example/mini/05-irq/Makefile new file mode 100644 index 00000000..1a3db672 --- /dev/null +++ b/example/mini/05-irq/Makefile @@ -0,0 +1,13 @@ +obj-m += irq.o +# Import the common stub +include ../../common/Makefile.arch + +# Make the kernel modules (默认) +all modules: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules + +# 无用户态测试程序: 中断由设备触发, 靠 /proc/interrupts + dmesg 验证 + +# Clean it up +clean: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean diff --git a/example/mini/05-irq/README.md b/example/mini/05-irq/README.md new file mode 100644 index 00000000..35e7d3a2 --- /dev/null +++ b/example/mini/05-irq/README.md @@ -0,0 +1,61 @@ +# 05-irq — 上半部 + 线程化中断 + workqueue 下半部 + +> 配套教程:[硬件中断:设备怎么打断 CPU](../../../document/tutorials/drivers/05-drv-irq.md) +> 对应进度节点:`drv-irq`(layer-2) +> 前置示例:[01-chardev_basic](../01-chardev_basic/) + +## ⚠️ 这个示例和 01-04 不同 + +01-04 是 misc 软设备,`insmod` 就能玩。**本例是 platform driver,需要一个真实中断源**——`insmod` 只注册驱动、不会调 `probe`、不会注册 IRQ。要真正触发中断,必须: + +1. 设备树里有一个 `compatible = "penguinlab,irq-demo"` 的节点,带 `interrupts` 属性 +2. QEMU 上配一个能拉中断线的虚拟设备(virtio-gpio / 自定义 platform 设备),或用 `irq_inject_interrupt()`(需 `CONFIG_GENERIC_IRQ_DEBUGFS`)注入 + +所以本 example 的定位是:**把中断驱动的 API 骨架和编译验证做实**,完整亲测待设备树/QEMU 设备就绪。 + +## 这个示例演示什么 + +- `devm_request_threaded_irq`:一次注册**上半部 + 线程化下半部** +- **上半部 `irq_hardirq`**:中断上下文(`in_irq()` 为真),绝不能睡,只计数 + `schedule_work` + 返回 `IRQ_WAKE_THREAD` +- **线程化 `irq_thread_fn`**:进程上下文(`in_task()` 为真),能 `msleep(10)` 不 panic——线程化中断的铁证 +- **workqueue 下半部**:另一种"可睡的下半部"姿势,与线程化中断对比 +- `IRQF_ONESHOT`:hardirq 跑完保持屏蔽直到 `thread_fn` 跑完(电平触发防风暴必备) + +## 文件 + +| 文件 | 说明 | +|------|------| +| `irq.c` | platform driver:`probe` 里 `platform_get_irq` + `devm_request_threaded_irq`,上半部/线程化/workqueue 三段 | +| `Makefile` | `obj-m += irq.o`(无用户态程序,靠 `/proc/interrupts` + `dmesg` 验证) | + +## 编译 + +```bash +cd example/mini/05-irq +make # irq.ko(默认 arm64) +``` + +## 亲测前提(设备树片段示例) + +在 QEMU 的设备树里加一个虚拟设备节点(需配合 qemu-run.sh 的设备树/虚拟设备配置): + +```dts +irq_demo: irq-demo@0 { + compatible = "penguinlab,irq-demo"; + interrupt-parent = <&gic>; /* 或对应中断控制器 */ + interrupts = ; /* 一个空闲的 SPI 号 */ +}; +``` + +配好后: + +```bash +insmod irq.ko # probe 被调用, 注册 IRQ, dmesg 见 "registered irq N" +# 触发中断(方式取决于虚拟设备: GPIO 翻转 / irq_inject / 真实外设) +cat /proc/interrupts | grep irq-demo # 中断计数 +1 +dmesg | tail # 依次见 hardirq -> wake thread -> thread_fn -> bottom half +rmmod irq +``` + +> dmesg 预期顺序:`hardirq (in_irq=1) ...` → `thread_fn ran (in_task=1) ...`(msleep 不 panic)→ `bottom half (workqueue) in_task=1`。 +> 预期输出待真机/QEMU 设备就绪后回填到教程 05-drv-irq.md。 diff --git a/example/mini/05-irq/irq.c b/example/mini/05-irq/irq.c new file mode 100644 index 00000000..6a54586f --- /dev/null +++ b/example/mini/05-irq/irq.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * irq.c - 硬件中断演示: 上半部 + 线程化中断 + workqueue 下半部 + * + * 配套教程: tutorials/drivers/05-drv-irq.md + * 对应节点: drv-irq (layer-2) + * + * ⚠️ 这是个 platform driver, 与 01-04 的 misc 软设备不同: + * 完整触发中断需要设备树里一个 compatible="penguinlab,irq-demo" 的设备 + * 并提供 interrupts 属性(见 README)。insmod 只注册驱动, 不调 probe。 + * + * 演示要点: + * - devm_request_threaded_irq: 一次注册上半部 + 线程化下半部 + * - 上半部 hardirq: 中断上下文, 绝不能睡, 只计数 + schedule_work + 返回 IRQ_WAKE_THREAD + * - 线程化 thread_fn: 进程上下文, 可 msleep 睡眠(IRQF_ONESHOT 保底) + * - workqueue 下半部: 另一种"可睡的下半部"姿势, 与线程化中断对比 + */ + +#include +#include +#include +#include +#include /* in_task(): 6.19 用它判上下文(in_irq 已移除) */ +#include +#include + +struct irq_priv { + int irq; + unsigned long irq_count; + struct work_struct bh_work; +}; + +/* workqueue 下半部: 进程上下文, 可睡眠/持锁 */ +static void bh_work_fn(struct work_struct *w) +{ + pr_info("irq-demo: bottom half (workqueue) in task context (in_task=%d)\n", + in_task()); +} + +/* 线程化中断处理: 进程上下文, 可睡眠 */ +static irqreturn_t irq_thread_fn(int irq, void *dev_id) +{ + struct irq_priv *p = dev_id; + + msleep(10); /* 线程化中断的铁证: 能睡不 panic */ + pr_info("irq-demo: thread_fn ran (in_task=%d), irq_count=%lu\n", + in_task(), p->irq_count); + return IRQ_HANDLED; +} + +/* 上半部: 硬中断上下文, 绝不能睡, 要快 */ +static irqreturn_t irq_hardirq(int irq, void *dev_id) +{ + struct irq_priv *p = dev_id; + + p->irq_count++; + schedule_work(&p->bh_work); /* 重活推给 workqueue 下半部 */ + pr_info("irq-demo: hardirq (in_task=%d, i.e. interrupt ctx), count=%lu -> wake thread\n", + in_task(), p->irq_count); + return IRQ_WAKE_THREAD; /* 让 thread_fn 接管 */ +} + +static int irq_probe(struct platform_device *pdev) +{ + struct irq_priv *p; + int ret; + + p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + INIT_WORK(&p->bh_work, bh_work_fn); + + p->irq = platform_get_irq(pdev, 0); + if (p->irq < 0) + return p->irq; + + /* + * devm_request_threaded_irq: 注册上半部 irq_hardirq + 线程化 irq_thread_fn。 + * IRQF_ONESHOT: hardirq 跑完保侍屏蔽, 直到 thread_fn 跑完——电平触发防风暴必备。 + */ + ret = devm_request_threaded_irq(&pdev->dev, p->irq, irq_hardirq, + irq_thread_fn, IRQF_ONESHOT, + "irq-demo", p); + if (ret) { + dev_err(&pdev->dev, "request_threaded_irq failed: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, p); + dev_info(&pdev->dev, "registered irq %d\n", p->irq); + return 0; +} + +static const struct of_device_id irq_dt_ids[] = { + { .compatible = "penguinlab,irq-demo", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, irq_dt_ids); + +static struct platform_driver irq_drv = { + .probe = irq_probe, + .driver = { + .name = "irq-demo", + .of_match_table = irq_dt_ids, + }, +}; +module_platform_driver(irq_drv); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("PenguinLab"); +MODULE_DESCRIPTION("irq demo: hardirq + threaded irq + workqueue bottom half"); diff --git a/example/mini/06-debug-printk/Makefile b/example/mini/06-debug-printk/Makefile new file mode 100644 index 00000000..8c406879 --- /dev/null +++ b/example/mini/06-debug-printk/Makefile @@ -0,0 +1,15 @@ +obj-m += dbgprintk.o +# Import the common stub +include ../../common/Makefile.arch + +# Make the kernel modules (默认) +all modules: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules + +# 想让 pr_debug 也打印? 用 CFLAGS 定义 DEBUG(等价动态调试, 编译期开): +# make EXTRA_CFLAGS=-DDEBUG +# 或在 Makefile 里加: CFLAGS_printk.o = -DDEBUG + +# Clean it up +clean: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean diff --git a/example/mini/06-debug-printk/README.md b/example/mini/06-debug-printk/README.md new file mode 100644 index 00000000..11afbb82 --- /dev/null +++ b/example/mini/06-debug-printk/README.md @@ -0,0 +1,53 @@ +# 06-debug-printk — printk 八级日志 + pr_xxx 演示 + +> 配套教程:[printk:内核调试的生命线](../../../document/tutorials/debugging/01-debug-printk.md) +> 对应进度节点:`debug-printk`(layer-4) +> 架构依赖:无(printk 是内核基础,任何 config 都能跑) + +## 这个示例做什么 + +内核里没有 `printf`,调试靠 `printk` 插桩。本例演示: + +- **`pr_fmt` 前缀**:`#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt`,让每条 `pr_xxx` 自动带模块名前缀,`dmesg` 里一眼定位调用者 +- **八级 loglevel**:`pr_emerg`/`pr_alert`/`pr_crit`/`pr_err`/`pr_warn`/`pr_notice`/`pr_info`/`pr_debug`(对应 `KERN_EMERG`..`KERN_DEBUG`) +- **`pr_debug` 默认不显示**:需动态调试(`CONFIG_DYNAMIC_DEBUG`,当前 mini config 未开)或编译期 `-DDEBUG`——最常见的"我打了为啥没输出"坑 + +## 文件 + +| 文件 | 说明 | +|------|------| +| `dbgprintk.c` | `pr_fmt` + 八级 `pr_xxx` + 原始 `KERN_INFO` 写法对照 | +| `Makefile` | `obj-m += printk.o`;注释里给出 `-DDEBUG` 让 `pr_debug` 也打印的办法 | + +## 编译 + +```bash +cd example/mini/06-debug-printk +make # dbgprintk.ko(默认 arm64) +``` + +## 亲测(QEMU ARM64,2026-06-27 实测) + +```bash +insmod dbgprintk.ko +dmesg | tail -10 +dmesg | tail -10 | cat -v # 或 dmesg -r 看不可见的 loglevel 前缀字符 +rmmod dbgprintk +dmesg | tail -2 +``` + +实测输出(2026-06-27): + +``` +dbgprintk: EMERG (0): system is unusable +dbgprintk: ALERT (1): action must be taken immediately +dbgprintk: CRIT (2): critical conditions +dbgprintk: ERR (3): error conditions +dbgprintk: WARN (4): warning conditions +dbgprintk: NOTICE(5): normal but significant +dbgprintk: INFO (6): informational +printk demo: loaded, pr_fmt prefix = 'dbgprintk: ' +``` + +> 注:`pr_debug` 那条默认不出现——当前 mini config 没开 `CONFIG_DYNAMIC_DEBUG`,这是最常见的"我打了为啥没输出"坑。 +> 进阶:`make EXTRA_CFLAGS=-DDEBUG` 重编,`pr_debug` 那条就会出现(编译期打开 debug,等价于动态调试对单文件生效)。 diff --git a/example/mini/06-debug-printk/dbgprintk.c b/example/mini/06-debug-printk/dbgprintk.c new file mode 100644 index 00000000..c77a2766 --- /dev/null +++ b/example/mini/06-debug-printk/dbgprintk.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * printk.c - printk 八级日志 + pr_xxx 宏 演示 + * + * 配套教程: tutorials/debugging/01-debug-printk.md + * 对应节点: debug-printk (layer-4) + * + * 最朴素也最可靠的内核调试手段: 用各级别 printk 插桩, dmesg 看输出。 + * 演示: + * - pr_fmt 前缀(让每条打印自动带模块名, 定位调用者) + * - 八级 loglevel: pr_emerg .. pr_debug(KERN_EMERG .. KERN_DEBUG) + * - pr_debug 默认不打印(需 dynamic-debug 或 -DDEBUG), 这是常见坑 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt /* 每条打印自动带 "printk: " 前缀 */ + +#include +#include + +static int __init printk_demo_init(void) +{ + pr_emerg("EMERG (0): system is unusable"); + pr_alert("ALERT (1): action must be taken immediately"); + pr_crit("CRIT (2): critical conditions"); + pr_err("ERR (3): error conditions"); + pr_warn("WARN (4): warning conditions"); + pr_notice("NOTICE(5): normal but significant"); + pr_info("INFO (6): informational"); + pr_debug("DEBUG (7): debug-level — 默认不显示, 需动态调试或 -DDEBUG"); + + /* 也演示一次原始 KERN_* 写法, 与 pr_xxx 等价 */ + printk(KERN_INFO "printk demo: loaded, pr_fmt prefix = '%s'\n", pr_fmt("")); + + return 0; +} + +static void __exit printk_demo_exit(void) +{ + pr_info("printk demo: unloaded"); +} + +module_init(printk_demo_init); +module_exit(printk_demo_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("PenguinLab"); +MODULE_DESCRIPTION("printk loglevel + pr_xxx demo"); diff --git a/example/mini/07-debug-oops/Makefile b/example/mini/07-debug-oops/Makefile new file mode 100644 index 00000000..9e8ece55 --- /dev/null +++ b/example/mini/07-debug-oops/Makefile @@ -0,0 +1,11 @@ +obj-m += oops.o +# Import the common stub +include ../../common/Makefile.arch + +# Make the kernel modules (默认) +all modules: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules + +# Clean it up +clean: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean diff --git a/example/mini/07-debug-oops/README.md b/example/mini/07-debug-oops/README.md new file mode 100644 index 00000000..6f3c79df --- /dev/null +++ b/example/mini/07-debug-oops/README.md @@ -0,0 +1,71 @@ +# 07-debug-oops — 故意触发一次 NULL 解引用 Oops + +> 配套教程:[Oops:内核犯错时的现场](../../../document/tutorials/debugging/05-debug-oops.md) +> 对应进度节点:`debug-oops`(layer-4) +> 架构依赖:无(任何内核都会对 NULL 解引用报 Oops) + +## ⚠️ 这个示例会触发内核 Oops + +`trigger=1` 时,`insmod` 会让内核打印一段完整的 Oops 现场。后果取决于 `panic_on_oops`: + +- `panic_on_oops=0`(默认,mini config 未开 `CONFIG_PANIC_ON_OOPS`)→ Oops 打印现场后**杀掉 insmod 进程,系统继续跑** +- `panic_on_oops=1`(`echo 1 > /proc/sys/kernel/panic_on_oops`)→ 系统**直接 panic 死透** + +所以**先确认 `panic_on_oops` 是 0** 再触发,免得 QEMU 死了得重启。 + +## 这个示例演示什么 + +- **NULL 指针 + 结构体成员偏移**:`p=NULL`,`&p->data = NULL + 0x30`,Oops 报的故障地址就是 `0x30`(看到几十几百的小地址 = 八成是 NULL 解引用结构体成员,那数字是偏移) +- **`*(volatile)` 防优化**:GCC 会把"已知 NULL 解引用"当 UB 优化掉,`volatile` 强制保留这次访存 +- **读 Oops 现场**:`pc` 偏移、`Code:` 字节(ARM64 用**圆括号**包崩点指令,不是 x86 的尖括号)、`Call trace`、`Tainted`(`G` 是干净基线不是污染位) + +## 文件 + +| 文件 | 说明 | +|------|------| +| `oops.c` | `trigger` 门控 + `struct oopsie`(data 偏移 0x30) + `*(volatile)` NULL 写 | +| `Makefile` | `obj-m += oops.o`(无用户态,靠 `dmesg` 看 Oops) | + +## 编译 + +```bash +cd example/mini/07-debug-oops +make # oops.ko(默认 arm64) +``` + +## 亲测(QEMU ARM64,2026-06-27 实测) + +```bash +# 先确认 panic_on_oops=0, 否则一崩就死 +cat /proc/sys/kernel/panic_on_oops # 应为 0 + +# 安全加载看一下 +insmod oops.ko # trigger 默认 0, 安全 +dmesg | tail -3 + +# 触发 oops +rmmod oops +insmod oops.ko trigger=1 # insmod 立刻 Oops +dmesg | tail -60 + +# 进阶对比: 让它一崩就死 +echo 1 > /proc/sys/kernel/panic_on_oops +insmod oops.ko trigger=1 # 系统直接 panic +``` + +实测输出(2026-06-27,`trigger=1` 触发的 Oops 现场精简): + +``` +[ 694.410104] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000030 +[ 694.452934] pc : oopsdemo_init+0x3c/0xfdc [oops] +[ 694.488859] Call trace: +[ 694.489529] oopsdemo_init+0x3c/0xfdc [oops] (P) + ...(do_one_initcall / load_module / sys_init_module 等) +[ 694.509711] Code: 91012000 97ffffeb d2800600 52800f01 (39000001) +[ 694.512143] ---[ end trace 0000000000000000 ]--- +Tainted: G O +``` + +> 关注点:故障地址 `0x30` 正是 `&p->data` 的结构体成员偏移(`p=NULL + 0x30`);`pc : oopsdemo_init+0x3c/0xfdc [oops]` 指回模块初始化函数;`Code:` 行 ARM64 用**圆括号**包崩点指令(`(39000001)` 那条 str);`Tainted: G O` 里 `G` 是干净基线、`O`(OOT_MODULE)表示载了树外模块。`panic_on_oops=0` 时 insmod 进程报 `Segmentation fault`,但系统继续跑。 + +解栈三件斧(宿主上跑):拿到 `Code:` 里圆括号那串 / `pc` 地址,用 `aarch64-linux-gnu-objdump -d oops.o` 或 `addr2line` 对回源码行。 diff --git a/example/mini/07-debug-oops/oops.c b/example/mini/07-debug-oops/oops.c new file mode 100644 index 00000000..d7243955 --- /dev/null +++ b/example/mini/07-debug-oops/oops.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * oops.c - 故意触发一次 NULL 解引用 Oops(门控, 默认安全) + * + * 配套教程: tutorials/debugging/05-debug-oops.md + * 对应节点: debug-oops (layer-4) + * + * ⚠️ trigger=1 时 insmod 会触发内核 Oops。 + * - panic_on_oops=0(默认): Oops 打印现场后杀掉 insmod 进程, 系统继续。 + * - panic_on_oops=1: 系统直接 panic 死透。 + * + * 演示要点: + * - NULL 指针 + 结构体成员偏移: p=NULL, &p->data = NULL+0x30, oops 报址 0x30 + * - *(volatile) 防 GCC 把"已知 NULL 解引用"当 UB 优化掉 + * - 配合 dmesg 读 Oops 现场: pc/Code(ARM64 圆括号)/Call trace/Tainted + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +static int trigger; +module_param(trigger, int, 0644); +MODULE_PARM_DESC(trigger, "1 = force a NULL-deref oops in init (default 0 = safe)"); + +/* data 偏移设计成 0x30(48): NULL + 0x30 = 0x30, oops 报的地址就是这个数 */ +struct oopsie { + char pad[0x30]; + char data; +}; + +static int __init oopsdemo_init(void) +{ + struct oopsie *p = NULL; + + if (!trigger) { + pr_info("loaded (safe). rmmod && insmod oops.ko trigger=1 to force oops\n"); + return 0; + } + + pr_info("about to write NULL->data (addr = NULL + 0x30 = 0x30)...\n"); + /* + * *(volatile) 防 GCC 把"已知 NULL 解引用"当 UB 优化掉; 真正触发 oops 的是这一行。 + */ + *(volatile char *)&p->data = 'x'; + pr_info("never reached (if you see this, the oops got optimized away)\n"); + return 0; +} + +static void __exit oopsdemo_exit(void) +{ + pr_info("unloaded\n"); +} + +module_init(oopsdemo_init); +module_exit(oopsdemo_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("PenguinLab"); +MODULE_DESCRIPTION("intentional NULL-deref oops demo (trigger-gated)"); From 933f38e21426029819259b80e365e3e25335a934 Mon Sep 17 00:00:00 2001 From: Charliechen114514 <725610365@qq.com> Date: Sat, 27 Jun 2026 15:03:31 +0800 Subject: [PATCH 3/3] chore: root usable --- site/package.json => package.json | 8 +- site/pnpm-lock.yaml => pnpm-lock.yaml | 1104 +++++++++++-------------- site/pnpm-workspace.yaml | 2 - 3 files changed, 500 insertions(+), 614 deletions(-) rename site/package.json => package.json (71%) rename site/pnpm-lock.yaml => pnpm-lock.yaml (67%) delete mode 100644 site/pnpm-workspace.yaml diff --git a/site/package.json b/package.json similarity index 71% rename from site/package.json rename to package.json index 30b06e22..71900549 100644 --- a/site/package.json +++ b/package.json @@ -4,10 +4,10 @@ "private": true, "type": "module", "scripts": { - "dev": "vitepress dev .", - "build": "tsx ../scripts/build.ts", - "build:single": "vitepress build .", - "preview": "vitepress preview ." + "dev": "vitepress dev site", + "build": "tsx scripts/build.ts", + "build:single": "vitepress build site", + "preview": "vitepress preview site" }, "devDependencies": { "@types/node": "^25.6.2", diff --git a/site/pnpm-lock.yaml b/pnpm-lock.yaml similarity index 67% rename from site/pnpm-lock.yaml rename to pnpm-lock.yaml index 2a0fc59e..b8db20b4 100644 --- a/site/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,7 +14,7 @@ importers: devDependencies: '@types/node': specifier: ^25.6.2 - version: 25.9.2 + version: 25.9.4 mermaid: specifier: ^10.9.6 version: 10.9.6 @@ -23,15 +23,15 @@ importers: version: 4.22.4 vitepress: specifier: ^1.6.4 - version: 1.6.4(@algolia/client-search@5.52.1)(@types/node@25.9.2)(nprogress@0.2.0)(postcss@8.5.15)(search-insights@2.17.3)(terser@5.46.2)(typescript@5.6.3) + version: 1.6.4(@algolia/client-search@5.55.1)(@types/node@25.9.4)(postcss@8.5.15)(search-insights@2.17.3) vue: specifier: ^3.5.34 - version: 3.5.35(typescript@5.6.3) + version: 3.5.39 packages: - '@algolia/abtesting@1.18.1': - resolution: {integrity: sha512-aehCadlWOGvrT91KUIZpC0MbB8KBW9yUuvTJFd2xesR7le/IsT4nJUnjCCZ4ZqZCeTcPHPV5mo//fZ5oxcSVYw==} + '@algolia/abtesting@1.21.1': + resolution: {integrity: sha512-Wia5/mNTfiU0PIUN25UMfAGGdASkkwuCS9nBAdmhqrNPY/ff7U/6MgBVdwFDPsa3sA1msutPtO50gvOzx6MOXA==} engines: {node: '>= 14.0.0'} '@algolia/autocomplete-core@1.17.7': @@ -54,73 +54,73 @@ packages: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - '@algolia/client-abtesting@5.52.1': - resolution: {integrity: sha512-HmXOGBOAOJPounpBzBpuY0zDYeiCpxgHnQmuA7JO6ScukcBdGp3/XM9zJk5pJx/xNGD68mbPGXWpDxGtl6BwDQ==} + '@algolia/client-abtesting@5.55.1': + resolution: {integrity: sha512-miW8RzAtBgNiEJ9fGEhsOPgWUpekAe64YcVufqXrlykj0Jjmo5nj0a5f/HAzRVX5ZuU1GAVd7BkzFDx7q50P3A==} engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@5.52.1': - resolution: {integrity: sha512-5oo4+I8iixie9vXhCyNFCzeIr8pqA3FQ//VsLHTDvZAV4ttYOPGvYHGQq5NSalrLx5Jc3dRro/5uDOlnUMcBJg==} + '@algolia/client-analytics@5.55.1': + resolution: {integrity: sha512-eR3J3kB9JX6DdCvDRi3I4KPfwO6fR9HWYRXhVke2TXIoOQafMKCRAneg33JRmIrb+DnnJ/eWApJLF1O1CLPERg==} engines: {node: '>= 14.0.0'} - '@algolia/client-common@5.52.1': - resolution: {integrity: sha512-qCDoZfx5MpX7XQzvQ3bC4tSEMkQWQMaF/ABtLuoze03Y/flR563CCSws02qIJ23oX7lxl92LsilZjINVyTdtLw==} + '@algolia/client-common@5.55.1': + resolution: {integrity: sha512-P5ak7EurwYqgAiDyb95mgA3WRR/Zu8CPMv36lWTISvL2AmlPyqQPy2nX/KEJRTcwaeTWwrk6wJV4/M93GfjOWw==} engines: {node: '>= 14.0.0'} - '@algolia/client-insights@5.52.1': - resolution: {integrity: sha512-hnGs0/lsFJ2PWDxNBz7pxreXo/Xz7gxYRcfePBUjsH26ad0kU/sgnVZd9LwWBpsQv65z2jlb5dkyaB9WE9M9FQ==} + '@algolia/client-insights@5.55.1': + resolution: {integrity: sha512-OVtj9uA//+pjvKQI5INnzbyLrf3ClNv3XRbWswwJ2kHIStQNHtBfHo+LofNB/WhM9xjuXlW5ANn2aMj65UGx7w==} engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@5.52.1': - resolution: {integrity: sha512-2VxxNc/uBysyKvGeBdSM5n9eIDKH8kWD7wd9/yqbJAiVwU4Yv6tU1LSJusHKrXV/aCu1KW7t9Gug9QyeEmtn/Q==} + '@algolia/client-personalization@5.55.1': + resolution: {integrity: sha512-oKlVFlp+qbIEe4p7E54zSiP2gEV/vDu972Ykv8VDMFwEvreS7m0YKA3a8hGGHwc7yiBUGGiR3LlwzMLfnJmy6Q==} engines: {node: '>= 14.0.0'} - '@algolia/client-query-suggestions@5.52.1': - resolution: {integrity: sha512-O6mPtsw3xEfNOe6gWFpYLeAZAIljNa4Hgna3bq15PwyN7nbjTY0wXJFRbzs/0YVf75Br+SbOQUmjKxXYjDiSiQ==} + '@algolia/client-query-suggestions@5.55.1': + resolution: {integrity: sha512-BOVrld6vdtsFmotVDMTVQfYXwrVplJ+DUvy60JFi+tkWV698q2J9NNPKEO3dr5qxtSLKQP4vHF8n+3U5PDWhOQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-search@5.52.1': - resolution: {integrity: sha512-gA8oJOV1LnQQkDf91iebNnFInHuW0gRPEgLSOQ7EfipCEjYTHm5swm1DlH9H5RaRw4RrHuzHBegnlzc0MAstcg==} + '@algolia/client-search@5.55.1': + resolution: {integrity: sha512-GAqHl9zERhC3bbBfubwUu07G3UXO06gORvOcsiTBZB3et0s3auNUbHlYdYNp4VKa3sUZqH5AcD3OKzU/KDGXjQ==} engines: {node: '>= 14.0.0'} - '@algolia/ingestion@1.52.1': - resolution: {integrity: sha512-U9zZfc5xIu9wRxZkt+HceJUAD4VKHKbAyLSloJdEyMRmphXeibfrY9cxqIXBcmPeZzGhn3Imb35Dq8l19PkJhw==} + '@algolia/ingestion@1.55.1': + resolution: {integrity: sha512-BXZw+C+gsWL7pZvbnhJUnCXASiDLGcQxVV7h55Pyh2DmSzwdZIVccE5xc9RVD2trtrhIqk5smuODTxtaZqd0IA==} engines: {node: '>= 14.0.0'} - '@algolia/monitoring@1.52.1': - resolution: {integrity: sha512-a3SGNceHmkQfq77iG8Ka+w1pvwfZa/0lzEIgse30fL0kD+yKnd/dg0dQvSfFPAEt2f21DMcGkDSSeJlO3KdQjQ==} + '@algolia/monitoring@1.55.1': + resolution: {integrity: sha512-9g/ceZrZTqA62FA3588Xj0onRPjDNfu0pVQqefK0rrHp9H6Wblph/YmzGjZ2g8uqbTh0ZGIvAGCzErU8f7MHpA==} engines: {node: '>= 14.0.0'} - '@algolia/recommend@5.52.1': - resolution: {integrity: sha512-z98QEguCFDpxb4S/PyrUK1igqF8tPsdbqOUUO6ON91vJ58w+Gwa6ncrI0oNXSFcrkxA5EqPKPQ2A1PBCn08TYQ==} + '@algolia/recommend@5.55.1': + resolution: {integrity: sha512-cZTIrGyAP+W4A6jDVwvWM/JOaoJKQkD/2a5eLUEeNdKAD45jN7BCpsMDONyhZlosLa4UwL8uiINQzj4iFy9nqg==} engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@5.52.1': - resolution: {integrity: sha512-CI7+/0I11QeZM59Uc8whd2or0kqzFVjpaPn9Qpwll/krHcBAxk24WkAQ6WX+IwDVMfpont4YGbKwAmCre3vE8Q==} + '@algolia/requester-browser-xhr@5.55.1': + resolution: {integrity: sha512-N6I3leW0UO8Y9Zv90yo2UHgYGuxZO0mjbvzNxDIJDjO0qECEF7Z9XMvSNeUWXQh/iNDA9lr8MfEy3rmZGIcclw==} engines: {node: '>= 14.0.0'} - '@algolia/requester-fetch@5.52.1': - resolution: {integrity: sha512-S6bDuw9byfOvm3T71cgdoZgrgnZq6hpdMLkx52Louh57nUAmvGQESz2aojOynQHjbTiV55smvAFbgn0qT4tJrg==} + '@algolia/requester-fetch@5.55.1': + resolution: {integrity: sha512-ukU5zeeFs44rQkzv+TRdYard+d+3lmPGs8lPZhHtWE8rfz+LlBSF6s9kP3VQ7LeOYL8Dz0u6tZfnyTrqrumbHQ==} engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@5.52.1': - resolution: {integrity: sha512-tqZXM+54rWo4mk5jL5Z/flE11nPmNEdXwFBM5py9DkOmbjeCNemfVd45FyM97XdzfZ0dl9uOJC6PYn1FpkeyQg==} + '@algolia/requester-node-http@5.55.1': + resolution: {integrity: sha512-lCwXyijwPm3vbYHpBXPRomMcD6mgiptmps27gnMCf4HK+u/AOeFPBnIFh4V3l4A5SnP9VRiKBZqwGBpUH0vaTg==} engines: {node: '>= 14.0.0'} - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.3': - resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} '@braintree/sanitize-url@6.0.4': @@ -155,8 +155,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.28.0': - resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -167,8 +167,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.28.0': - resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -179,8 +179,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.28.0': - resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -191,8 +191,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.28.0': - resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -203,8 +203,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.28.0': - resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -215,8 +215,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.28.0': - resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -227,8 +227,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.28.0': - resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -239,8 +239,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.28.0': - resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -251,8 +251,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.28.0': - resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -263,8 +263,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.28.0': - resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -275,8 +275,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.28.0': - resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -287,8 +287,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.28.0': - resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -299,8 +299,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.28.0': - resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -311,8 +311,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.28.0': - resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -323,8 +323,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.28.0': - resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -335,8 +335,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.28.0': - resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -347,14 +347,14 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.28.0': - resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.28.0': - resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -365,14 +365,14 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.28.0': - resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.28.0': - resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -383,14 +383,14 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.28.0': - resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.28.0': - resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] @@ -401,8 +401,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.28.0': - resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -413,8 +413,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.28.0': - resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -425,8 +425,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.28.0': - resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -437,169 +437,156 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.28.0': - resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@iconify-json/simple-icons@1.2.85': - resolution: {integrity: sha512-Hp5LXvd3LRk+e+1558wtonA7c1Z0/Phmi7xCqpgtb8bs8cuyGnP34GDbt5uhhUXxKlzacnnhAcXgcDxe9bUa1w==} + '@iconify-json/simple-icons@1.2.87': + resolution: {integrity: sha512-8YciStObhSji3OZFmWAWK6kBujyqO5bLCxeDwLxf3CR3F4PVelq7keC2LBvgTqviWzSTysj5/g4PCFLiAMVGsw==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/source-map@0.3.11': - resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@rollup/rollup-android-arm-eabi@4.61.1': - resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==} + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.61.1': - resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==} + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.61.1': - resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==} + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.61.1': - resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==} + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.61.1': - resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==} + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.61.1': - resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==} + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.61.1': - resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==} + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.61.1': - resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==} + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.61.1': - resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==} + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.61.1': - resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==} + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.61.1': - resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==} + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.61.1': - resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==} + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.61.1': - resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==} + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.61.1': - resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==} + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.61.1': - resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==} + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.61.1': - resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==} + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.61.1': - resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==} + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.61.1': - resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==} + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.61.1': - resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==} + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.61.1': - resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==} + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.61.1': - resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==} + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.61.1': - resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==} + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.61.1': - resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==} + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.61.1': - resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==} + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.61.1': - resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==} + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} cpu: [x64] os: [win32] @@ -663,8 +650,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@25.9.2': - resolution: {integrity: sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==} + '@types/node@25.9.4': + resolution: {integrity: sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g==} '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -678,9 +665,8 @@ packages: '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - deprecated: Potential CWE-502 - Update to 1.3.1 or higher + '@ungap/structured-clone@1.3.2': + resolution: {integrity: sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==} '@vitejs/plugin-vue@5.2.4': resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} @@ -689,43 +675,43 @@ packages: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 - '@vue/compiler-core@3.5.35': - resolution: {integrity: sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==} + '@vue/compiler-core@3.5.39': + resolution: {integrity: sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==} - '@vue/compiler-dom@3.5.35': - resolution: {integrity: sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==} + '@vue/compiler-dom@3.5.39': + resolution: {integrity: sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==} - '@vue/compiler-sfc@3.5.35': - resolution: {integrity: sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==} + '@vue/compiler-sfc@3.5.39': + resolution: {integrity: sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==} - '@vue/compiler-ssr@3.5.35': - resolution: {integrity: sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==} + '@vue/compiler-ssr@3.5.39': + resolution: {integrity: sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==} - '@vue/devtools-api@7.7.9': - resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} + '@vue/devtools-api@7.7.10': + resolution: {integrity: sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==} - '@vue/devtools-kit@7.7.9': - resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} + '@vue/devtools-kit@7.7.10': + resolution: {integrity: sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==} - '@vue/devtools-shared@7.7.9': - resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} + '@vue/devtools-shared@7.7.10': + resolution: {integrity: sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==} - '@vue/reactivity@3.5.35': - resolution: {integrity: sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==} + '@vue/reactivity@3.5.39': + resolution: {integrity: sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==} - '@vue/runtime-core@3.5.35': - resolution: {integrity: sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==} + '@vue/runtime-core@3.5.39': + resolution: {integrity: sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==} - '@vue/runtime-dom@3.5.35': - resolution: {integrity: sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==} + '@vue/runtime-dom@3.5.39': + resolution: {integrity: sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==} - '@vue/server-renderer@3.5.35': - resolution: {integrity: sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==} + '@vue/server-renderer@3.5.39': + resolution: {integrity: sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==} peerDependencies: - vue: 3.5.35 + vue: 3.5.39 - '@vue/shared@3.5.35': - resolution: {integrity: sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==} + '@vue/shared@3.5.39': + resolution: {integrity: sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==} '@vueuse/core@12.8.2': resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} @@ -777,21 +763,13 @@ packages: '@vueuse/shared@12.8.2': resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - algoliasearch@5.52.1: - resolution: {integrity: sha512-fHA8+kXTbjagw3jkLiaS7KKrH8qe2DyOsiUhGlN4cdT77PEsfqXZl7ewDk1hsg+pJnPlnE50XtLxjR91iJOpmg==} + algoliasearch@5.55.1: + resolution: {integrity: sha512-FyaFnnsbVPtevQwqSj/SdxE3jAsSsY0BEH8IVLf9rXxEBdAhAmT6VKCVSMWoaPIHVN1Eufh/1w8q6k8URpIkWw==} engines: {node: '>= 14.0.0'} birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -807,9 +785,6 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -833,8 +808,8 @@ packages: peerDependencies: cytoscape: ^3.2.0 - cytoscape@3.33.3: - resolution: {integrity: sha512-Gej7U+OKR+LZ8kvX7rb2HhCYJ0IhvEFsnkud4SB1PR+BUY/TsSO0dmOW59WEVLu51b1Rm+gQRKoz4bLYxGSZ2g==} + cytoscape@3.34.0: + resolution: {integrity: sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==} engines: {node: '>=0.10'} d3-array@2.12.1: @@ -979,8 +954,8 @@ packages: dagre-d3-es@7.0.13: resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} - dayjs@1.11.20: - resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} @@ -1008,8 +983,8 @@ packages: resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==} engines: {node: '>=0.3.1'} - dompurify@3.4.2: - resolution: {integrity: sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==} + dompurify@3.4.11: + resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==} elkjs@0.9.3: resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} @@ -1026,8 +1001,8 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.28.0: - resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true @@ -1069,8 +1044,8 @@ packages: resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} engines: {node: '>=18'} - katex@0.16.45: - resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==} + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} hasBin: true khroma@2.1.0: @@ -1195,17 +1170,14 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true non-layered-tidy-tree-layout@2.0.2: resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} - nprogress@0.2.0: - resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} - oniguruma-to-es@3.1.1: resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} @@ -1215,19 +1187,15 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.15: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} - preact@10.29.2: - resolution: {integrity: sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==} + preact@10.29.3: + resolution: {integrity: sha512-D9NL1GAnJZhc3RndVs4gDdxEeU9TcHgywMrhhOsnpdlvFjdbx0gAsLUnH6JEhlJH5giL7Tx5biWPUSEXE/HPzw==} - property-information@7.1.0: - resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -1244,8 +1212,8 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rollup@4.61.1: - resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==} + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1269,13 +1237,6 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} @@ -1293,19 +1254,14 @@ packages: resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} engines: {node: '>=16'} - tabbable@6.4.0: - resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} - - terser@5.46.2: - resolution: {integrity: sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==} - engines: {node: '>=10'} - hasBin: true + tabbable@6.5.0: + resolution: {integrity: sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==} trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - ts-dedent@2.2.0: - resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} engines: {node: '>=6.10'} tsx@4.22.4: @@ -1313,11 +1269,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - typescript@5.6.3: - resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} - engines: {node: '>=14.17'} - hasBin: true - undici-types@7.24.6: resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} @@ -1339,8 +1290,8 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - uuid@11.1.1: - resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} + uuid@14.0.1: + resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} hasBin: true uvu@0.5.6: @@ -1397,8 +1348,8 @@ packages: postcss: optional: true - vue@3.5.35: - resolution: {integrity: sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==} + vue@3.5.39: + resolution: {integrity: sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -1413,139 +1364,139 @@ packages: snapshots: - '@algolia/abtesting@1.18.1': + '@algolia/abtesting@1.21.1': dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 + '@algolia/client-common': 5.55.1 + '@algolia/requester-browser-xhr': 5.55.1 + '@algolia/requester-fetch': 5.55.1 + '@algolia/requester-node-http': 5.55.1 - '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.55.1)(algoliasearch@5.55.1)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.55.1)(algoliasearch@5.55.1)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.55.1)(algoliasearch@5.55.1) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights - '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': + '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.55.1)(algoliasearch@5.55.1)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.55.1)(algoliasearch@5.55.1) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)': + '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.55.1)(algoliasearch@5.55.1)': dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) - '@algolia/client-search': 5.52.1 - algoliasearch: 5.52.1 + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.55.1)(algoliasearch@5.55.1) + '@algolia/client-search': 5.55.1 + algoliasearch: 5.55.1 - '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)': + '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.55.1)(algoliasearch@5.55.1)': dependencies: - '@algolia/client-search': 5.52.1 - algoliasearch: 5.52.1 + '@algolia/client-search': 5.55.1 + algoliasearch: 5.55.1 - '@algolia/client-abtesting@5.52.1': + '@algolia/client-abtesting@5.55.1': dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 + '@algolia/client-common': 5.55.1 + '@algolia/requester-browser-xhr': 5.55.1 + '@algolia/requester-fetch': 5.55.1 + '@algolia/requester-node-http': 5.55.1 - '@algolia/client-analytics@5.52.1': + '@algolia/client-analytics@5.55.1': dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 + '@algolia/client-common': 5.55.1 + '@algolia/requester-browser-xhr': 5.55.1 + '@algolia/requester-fetch': 5.55.1 + '@algolia/requester-node-http': 5.55.1 - '@algolia/client-common@5.52.1': {} + '@algolia/client-common@5.55.1': {} - '@algolia/client-insights@5.52.1': + '@algolia/client-insights@5.55.1': dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 + '@algolia/client-common': 5.55.1 + '@algolia/requester-browser-xhr': 5.55.1 + '@algolia/requester-fetch': 5.55.1 + '@algolia/requester-node-http': 5.55.1 - '@algolia/client-personalization@5.52.1': + '@algolia/client-personalization@5.55.1': dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 + '@algolia/client-common': 5.55.1 + '@algolia/requester-browser-xhr': 5.55.1 + '@algolia/requester-fetch': 5.55.1 + '@algolia/requester-node-http': 5.55.1 - '@algolia/client-query-suggestions@5.52.1': + '@algolia/client-query-suggestions@5.55.1': dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 + '@algolia/client-common': 5.55.1 + '@algolia/requester-browser-xhr': 5.55.1 + '@algolia/requester-fetch': 5.55.1 + '@algolia/requester-node-http': 5.55.1 - '@algolia/client-search@5.52.1': + '@algolia/client-search@5.55.1': dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 + '@algolia/client-common': 5.55.1 + '@algolia/requester-browser-xhr': 5.55.1 + '@algolia/requester-fetch': 5.55.1 + '@algolia/requester-node-http': 5.55.1 - '@algolia/ingestion@1.52.1': + '@algolia/ingestion@1.55.1': dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 + '@algolia/client-common': 5.55.1 + '@algolia/requester-browser-xhr': 5.55.1 + '@algolia/requester-fetch': 5.55.1 + '@algolia/requester-node-http': 5.55.1 - '@algolia/monitoring@1.52.1': + '@algolia/monitoring@1.55.1': dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 + '@algolia/client-common': 5.55.1 + '@algolia/requester-browser-xhr': 5.55.1 + '@algolia/requester-fetch': 5.55.1 + '@algolia/requester-node-http': 5.55.1 - '@algolia/recommend@5.52.1': + '@algolia/recommend@5.55.1': dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 + '@algolia/client-common': 5.55.1 + '@algolia/requester-browser-xhr': 5.55.1 + '@algolia/requester-fetch': 5.55.1 + '@algolia/requester-node-http': 5.55.1 - '@algolia/requester-browser-xhr@5.52.1': + '@algolia/requester-browser-xhr@5.55.1': dependencies: - '@algolia/client-common': 5.52.1 + '@algolia/client-common': 5.55.1 - '@algolia/requester-fetch@5.52.1': + '@algolia/requester-fetch@5.55.1': dependencies: - '@algolia/client-common': 5.52.1 + '@algolia/client-common': 5.55.1 - '@algolia/requester-node-http@5.52.1': + '@algolia/requester-node-http@5.55.1': dependencies: - '@algolia/client-common': 5.52.1 + '@algolia/client-common': 5.55.1 - '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} - '@babel/parser@7.29.3': + '@babel/parser@7.29.7': dependencies: - '@babel/types': 7.29.0 + '@babel/types': 7.29.7 - '@babel/types@7.29.0': + '@babel/types@7.29.7': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 '@braintree/sanitize-url@6.0.4': {} '@docsearch/css@3.8.2': {} - '@docsearch/js@3.8.2(@algolia/client-search@5.52.1)(search-insights@2.17.3)': + '@docsearch/js@3.8.2(@algolia/client-search@5.55.1)(search-insights@2.17.3)': dependencies: - '@docsearch/react': 3.8.2(@algolia/client-search@5.52.1)(search-insights@2.17.3) - preact: 10.29.2 + '@docsearch/react': 3.8.2(@algolia/client-search@5.55.1)(search-insights@2.17.3) + preact: 10.29.3 transitivePeerDependencies: - '@algolia/client-search' - '@types/react' @@ -1553,12 +1504,12 @@ snapshots: - react-dom - search-insights - '@docsearch/react@3.8.2(@algolia/client-search@5.52.1)(search-insights@2.17.3)': + '@docsearch/react@3.8.2(@algolia/client-search@5.55.1)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) - '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.55.1)(algoliasearch@5.55.1)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.55.1)(algoliasearch@5.55.1) '@docsearch/css': 3.8.2 - algoliasearch: 5.52.1 + algoliasearch: 5.55.1 optionalDependencies: search-insights: 2.17.3 transitivePeerDependencies: @@ -1567,252 +1518,231 @@ snapshots: '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.28.0': + '@esbuild/aix-ppc64@0.28.1': optional: true '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.28.0': + '@esbuild/android-arm64@0.28.1': optional: true '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-arm@0.28.0': + '@esbuild/android-arm@0.28.1': optional: true '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/android-x64@0.28.0': + '@esbuild/android-x64@0.28.1': optional: true '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.28.0': + '@esbuild/darwin-arm64@0.28.1': optional: true '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/darwin-x64@0.28.0': + '@esbuild/darwin-x64@0.28.1': optional: true '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.28.0': + '@esbuild/freebsd-arm64@0.28.1': optional: true '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.28.0': + '@esbuild/freebsd-x64@0.28.1': optional: true '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm64@0.28.0': + '@esbuild/linux-arm64@0.28.1': optional: true '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.28.0': + '@esbuild/linux-arm@0.28.1': optional: true '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-ia32@0.28.0': + '@esbuild/linux-ia32@0.28.1': optional: true '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-loong64@0.28.0': + '@esbuild/linux-loong64@0.28.1': optional: true '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-mips64el@0.28.0': + '@esbuild/linux-mips64el@0.28.1': optional: true '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.28.0': + '@esbuild/linux-ppc64@0.28.1': optional: true '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.28.0': + '@esbuild/linux-riscv64@0.28.1': optional: true '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-s390x@0.28.0': + '@esbuild/linux-s390x@0.28.1': optional: true '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.28.0': + '@esbuild/linux-x64@0.28.1': optional: true - '@esbuild/netbsd-arm64@0.28.0': + '@esbuild/netbsd-arm64@0.28.1': optional: true '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.28.0': + '@esbuild/netbsd-x64@0.28.1': optional: true - '@esbuild/openbsd-arm64@0.28.0': + '@esbuild/openbsd-arm64@0.28.1': optional: true '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.28.0': + '@esbuild/openbsd-x64@0.28.1': optional: true - '@esbuild/openharmony-arm64@0.28.0': + '@esbuild/openharmony-arm64@0.28.1': optional: true '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.28.0': + '@esbuild/sunos-x64@0.28.1': optional: true '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-arm64@0.28.0': + '@esbuild/win32-arm64@0.28.1': optional: true '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-ia32@0.28.0': + '@esbuild/win32-ia32@0.28.1': optional: true '@esbuild/win32-x64@0.21.5': optional: true - '@esbuild/win32-x64@0.28.0': + '@esbuild/win32-x64@0.28.1': optional: true - '@iconify-json/simple-icons@1.2.85': + '@iconify-json/simple-icons@1.2.87': dependencies: '@iconify/types': 2.0.0 '@iconify/types@2.0.0': {} - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - optional: true - - '@jridgewell/resolve-uri@3.1.2': - optional: true - - '@jridgewell/source-map@0.3.11': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - optional: true - '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@rollup/rollup-android-arm-eabi@4.62.2': optional: true - '@rollup/rollup-android-arm-eabi@4.61.1': + '@rollup/rollup-android-arm64@4.62.2': optional: true - '@rollup/rollup-android-arm64@4.61.1': + '@rollup/rollup-darwin-arm64@4.62.2': optional: true - '@rollup/rollup-darwin-arm64@4.61.1': + '@rollup/rollup-darwin-x64@4.62.2': optional: true - '@rollup/rollup-darwin-x64@4.61.1': + '@rollup/rollup-freebsd-arm64@4.62.2': optional: true - '@rollup/rollup-freebsd-arm64@4.61.1': + '@rollup/rollup-freebsd-x64@4.62.2': optional: true - '@rollup/rollup-freebsd-x64@4.61.1': + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.61.1': + '@rollup/rollup-linux-arm-musleabihf@4.62.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.61.1': + '@rollup/rollup-linux-arm64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.61.1': + '@rollup/rollup-linux-arm64-musl@4.62.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.61.1': + '@rollup/rollup-linux-loong64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-loong64-gnu@4.61.1': + '@rollup/rollup-linux-loong64-musl@4.62.2': optional: true - '@rollup/rollup-linux-loong64-musl@4.61.1': + '@rollup/rollup-linux-ppc64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.61.1': + '@rollup/rollup-linux-ppc64-musl@4.62.2': optional: true - '@rollup/rollup-linux-ppc64-musl@4.61.1': + '@rollup/rollup-linux-riscv64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.61.1': + '@rollup/rollup-linux-riscv64-musl@4.62.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.61.1': + '@rollup/rollup-linux-s390x-gnu@4.62.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.61.1': + '@rollup/rollup-linux-x64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.61.1': + '@rollup/rollup-linux-x64-musl@4.62.2': optional: true - '@rollup/rollup-linux-x64-musl@4.61.1': + '@rollup/rollup-openbsd-x64@4.62.2': optional: true - '@rollup/rollup-openbsd-x64@4.61.1': + '@rollup/rollup-openharmony-arm64@4.62.2': optional: true - '@rollup/rollup-openharmony-arm64@4.61.1': + '@rollup/rollup-win32-arm64-msvc@4.62.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.61.1': + '@rollup/rollup-win32-ia32-msvc@4.62.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.61.1': + '@rollup/rollup-win32-x64-gnu@4.62.2': optional: true - '@rollup/rollup-win32-x64-gnu@4.61.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.61.1': + '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true '@shikijs/core@2.5.0': @@ -1892,7 +1822,7 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@25.9.2': + '@types/node@25.9.4': dependencies: undici-types: 7.24.6 @@ -1905,50 +1835,50 @@ snapshots: '@types/web-bluetooth@0.0.21': {} - '@ungap/structured-clone@1.3.0': {} + '@ungap/structured-clone@1.3.2': {} - '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@25.9.2)(terser@5.46.2))(vue@3.5.35(typescript@5.6.3))': + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@25.9.4))(vue@3.5.39)': dependencies: - vite: 5.4.21(@types/node@25.9.2)(terser@5.46.2) - vue: 3.5.35(typescript@5.6.3) + vite: 5.4.21(@types/node@25.9.4) + vue: 3.5.39 - '@vue/compiler-core@3.5.35': + '@vue/compiler-core@3.5.39': dependencies: - '@babel/parser': 7.29.3 - '@vue/shared': 3.5.35 + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.39 entities: 7.0.1 estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.35': + '@vue/compiler-dom@3.5.39': dependencies: - '@vue/compiler-core': 3.5.35 - '@vue/shared': 3.5.35 + '@vue/compiler-core': 3.5.39 + '@vue/shared': 3.5.39 - '@vue/compiler-sfc@3.5.35': + '@vue/compiler-sfc@3.5.39': dependencies: - '@babel/parser': 7.29.3 - '@vue/compiler-core': 3.5.35 - '@vue/compiler-dom': 3.5.35 - '@vue/compiler-ssr': 3.5.35 - '@vue/shared': 3.5.35 + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.39 + '@vue/compiler-dom': 3.5.39 + '@vue/compiler-ssr': 3.5.39 + '@vue/shared': 3.5.39 estree-walker: 2.0.2 magic-string: 0.30.21 postcss: 8.5.15 source-map-js: 1.2.1 - '@vue/compiler-ssr@3.5.35': + '@vue/compiler-ssr@3.5.39': dependencies: - '@vue/compiler-dom': 3.5.35 - '@vue/shared': 3.5.35 + '@vue/compiler-dom': 3.5.39 + '@vue/shared': 3.5.39 - '@vue/devtools-api@7.7.9': + '@vue/devtools-api@7.7.10': dependencies: - '@vue/devtools-kit': 7.7.9 + '@vue/devtools-kit': 7.7.10 - '@vue/devtools-kit@7.7.9': + '@vue/devtools-kit@7.7.10': dependencies: - '@vue/devtools-shared': 7.7.9 + '@vue/devtools-shared': 7.7.10 birpc: 2.9.0 hookable: 5.5.3 mitt: 3.0.1 @@ -1956,87 +1886,80 @@ snapshots: speakingurl: 14.0.1 superjson: 2.2.6 - '@vue/devtools-shared@7.7.9': + '@vue/devtools-shared@7.7.10': dependencies: rfdc: 1.4.1 - '@vue/reactivity@3.5.35': + '@vue/reactivity@3.5.39': dependencies: - '@vue/shared': 3.5.35 + '@vue/shared': 3.5.39 - '@vue/runtime-core@3.5.35': + '@vue/runtime-core@3.5.39': dependencies: - '@vue/reactivity': 3.5.35 - '@vue/shared': 3.5.35 + '@vue/reactivity': 3.5.39 + '@vue/shared': 3.5.39 - '@vue/runtime-dom@3.5.35': + '@vue/runtime-dom@3.5.39': dependencies: - '@vue/reactivity': 3.5.35 - '@vue/runtime-core': 3.5.35 - '@vue/shared': 3.5.35 + '@vue/reactivity': 3.5.39 + '@vue/runtime-core': 3.5.39 + '@vue/shared': 3.5.39 csstype: 3.2.3 - '@vue/server-renderer@3.5.35(vue@3.5.35(typescript@5.6.3))': + '@vue/server-renderer@3.5.39(vue@3.5.39)': dependencies: - '@vue/compiler-ssr': 3.5.35 - '@vue/shared': 3.5.35 - vue: 3.5.35(typescript@5.6.3) + '@vue/compiler-ssr': 3.5.39 + '@vue/shared': 3.5.39 + vue: 3.5.39 - '@vue/shared@3.5.35': {} + '@vue/shared@3.5.39': {} - '@vueuse/core@12.8.2(typescript@5.6.3)': + '@vueuse/core@12.8.2': dependencies: '@types/web-bluetooth': 0.0.21 '@vueuse/metadata': 12.8.2 - '@vueuse/shared': 12.8.2(typescript@5.6.3) - vue: 3.5.35(typescript@5.6.3) + '@vueuse/shared': 12.8.2 + vue: 3.5.39 transitivePeerDependencies: - typescript - '@vueuse/integrations@12.8.2(focus-trap@7.8.0)(nprogress@0.2.0)(typescript@5.6.3)': + '@vueuse/integrations@12.8.2(focus-trap@7.8.0)': dependencies: - '@vueuse/core': 12.8.2(typescript@5.6.3) - '@vueuse/shared': 12.8.2(typescript@5.6.3) - vue: 3.5.35(typescript@5.6.3) + '@vueuse/core': 12.8.2 + '@vueuse/shared': 12.8.2 + vue: 3.5.39 optionalDependencies: focus-trap: 7.8.0 - nprogress: 0.2.0 transitivePeerDependencies: - typescript '@vueuse/metadata@12.8.2': {} - '@vueuse/shared@12.8.2(typescript@5.6.3)': + '@vueuse/shared@12.8.2': dependencies: - vue: 3.5.35(typescript@5.6.3) + vue: 3.5.39 transitivePeerDependencies: - typescript - acorn@8.16.0: - optional: true - - algoliasearch@5.52.1: - dependencies: - '@algolia/abtesting': 1.18.1 - '@algolia/client-abtesting': 5.52.1 - '@algolia/client-analytics': 5.52.1 - '@algolia/client-common': 5.52.1 - '@algolia/client-insights': 5.52.1 - '@algolia/client-personalization': 5.52.1 - '@algolia/client-query-suggestions': 5.52.1 - '@algolia/client-search': 5.52.1 - '@algolia/ingestion': 1.52.1 - '@algolia/monitoring': 1.52.1 - '@algolia/recommend': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 + algoliasearch@5.55.1: + dependencies: + '@algolia/abtesting': 1.21.1 + '@algolia/client-abtesting': 5.55.1 + '@algolia/client-analytics': 5.55.1 + '@algolia/client-common': 5.55.1 + '@algolia/client-insights': 5.55.1 + '@algolia/client-personalization': 5.55.1 + '@algolia/client-query-suggestions': 5.55.1 + '@algolia/client-search': 5.55.1 + '@algolia/ingestion': 1.55.1 + '@algolia/monitoring': 1.55.1 + '@algolia/recommend': 5.55.1 + '@algolia/requester-browser-xhr': 5.55.1 + '@algolia/requester-fetch': 5.55.1 + '@algolia/requester-node-http': 5.55.1 birpc@2.9.0: {} - buffer-from@1.1.2: - optional: true - ccount@2.0.1: {} character-entities-html4@2.1.0: {} @@ -2047,9 +1970,6 @@ snapshots: comma-separated-tokens@2.0.3: {} - commander@2.20.3: - optional: true - commander@7.2.0: {} commander@8.3.0: {} @@ -2064,12 +1984,12 @@ snapshots: csstype@3.2.3: {} - cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.3): + cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.0): dependencies: cose-base: 1.0.3 - cytoscape: 3.33.3 + cytoscape: 3.34.0 - cytoscape@3.33.3: {} + cytoscape@3.34.0: {} d3-array@2.12.1: dependencies: @@ -2243,7 +2163,7 @@ snapshots: d3: 7.9.0 lodash-es: 4.18.1 - dayjs@1.11.20: {} + dayjs@1.11.21: {} debug@4.4.3: dependencies: @@ -2265,7 +2185,7 @@ snapshots: diff@5.2.2: {} - dompurify@3.4.2: + dompurify@3.4.11: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -2301,40 +2221,40 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - esbuild@0.28.0: + esbuild@0.28.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.28.0 - '@esbuild/android-arm': 0.28.0 - '@esbuild/android-arm64': 0.28.0 - '@esbuild/android-x64': 0.28.0 - '@esbuild/darwin-arm64': 0.28.0 - '@esbuild/darwin-x64': 0.28.0 - '@esbuild/freebsd-arm64': 0.28.0 - '@esbuild/freebsd-x64': 0.28.0 - '@esbuild/linux-arm': 0.28.0 - '@esbuild/linux-arm64': 0.28.0 - '@esbuild/linux-ia32': 0.28.0 - '@esbuild/linux-loong64': 0.28.0 - '@esbuild/linux-mips64el': 0.28.0 - '@esbuild/linux-ppc64': 0.28.0 - '@esbuild/linux-riscv64': 0.28.0 - '@esbuild/linux-s390x': 0.28.0 - '@esbuild/linux-x64': 0.28.0 - '@esbuild/netbsd-arm64': 0.28.0 - '@esbuild/netbsd-x64': 0.28.0 - '@esbuild/openbsd-arm64': 0.28.0 - '@esbuild/openbsd-x64': 0.28.0 - '@esbuild/openharmony-arm64': 0.28.0 - '@esbuild/sunos-x64': 0.28.0 - '@esbuild/win32-arm64': 0.28.0 - '@esbuild/win32-ia32': 0.28.0 - '@esbuild/win32-x64': 0.28.0 + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 estree-walker@2.0.2: {} focus-trap@7.8.0: dependencies: - tabbable: 6.4.0 + tabbable: 6.5.0 fsevents@2.3.3: optional: true @@ -2348,7 +2268,7 @@ snapshots: hast-util-whitespace: 3.0.0 html-void-elements: 3.0.0 mdast-util-to-hast: 13.2.1 - property-information: 7.1.0 + property-information: 7.2.0 space-separated-tokens: 2.0.2 stringify-entities: 4.0.4 zwitch: 2.0.4 @@ -2371,7 +2291,7 @@ snapshots: is-what@5.5.0: {} - katex@0.16.45: + katex@0.16.47: dependencies: commander: 8.3.0 @@ -2410,7 +2330,7 @@ snapshots: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.0 + '@ungap/structured-clone': 1.3.2 devlop: 1.1.0 micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 @@ -2427,22 +2347,22 @@ snapshots: '@braintree/sanitize-url': 6.0.4 '@types/d3-scale': 4.0.9 '@types/d3-scale-chromatic': 3.1.0 - cytoscape: 3.33.3 - cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.3) + cytoscape: 3.34.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.0) d3: 7.9.0 d3-sankey: 0.12.3 dagre-d3-es: 7.0.13 - dayjs: 1.11.20 - dompurify: 3.4.2 + dayjs: 1.11.21 + dompurify: 3.4.11 elkjs: 0.9.3 - katex: 0.16.45 + katex: 0.16.47 khroma: 2.1.0 lodash-es: 4.18.1 mdast-util-from-markdown: 1.3.1 non-layered-tidy-tree-layout: 2.0.2 stylis: 4.4.0 - ts-dedent: 2.2.0 - uuid: 11.1.1 + ts-dedent: 2.3.0 + uuid: 14.0.1 web-worker: 1.5.0 transitivePeerDependencies: - supports-color @@ -2605,13 +2525,10 @@ snapshots: ms@2.1.3: {} - nanoid@3.3.12: {} + nanoid@3.3.15: {} non-layered-tidy-tree-layout@2.0.2: {} - nprogress@0.2.0: - optional: true - oniguruma-to-es@3.1.1: dependencies: emoji-regex-xs: 1.0.0 @@ -2622,21 +2539,15 @@ snapshots: picocolors@1.1.1: {} - postcss@8.5.14: - dependencies: - nanoid: 3.3.12 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.15: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.15 picocolors: 1.1.1 source-map-js: 1.2.1 - preact@10.29.2: {} + preact@10.29.3: {} - property-information@7.1.0: {} + property-information@7.2.0: {} regex-recursion@6.0.2: dependencies: @@ -2652,35 +2563,35 @@ snapshots: robust-predicates@3.0.3: {} - rollup@4.61.1: + rollup@4.62.2: dependencies: '@types/estree': 1.0.9 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.61.1 - '@rollup/rollup-android-arm64': 4.61.1 - '@rollup/rollup-darwin-arm64': 4.61.1 - '@rollup/rollup-darwin-x64': 4.61.1 - '@rollup/rollup-freebsd-arm64': 4.61.1 - '@rollup/rollup-freebsd-x64': 4.61.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.61.1 - '@rollup/rollup-linux-arm-musleabihf': 4.61.1 - '@rollup/rollup-linux-arm64-gnu': 4.61.1 - '@rollup/rollup-linux-arm64-musl': 4.61.1 - '@rollup/rollup-linux-loong64-gnu': 4.61.1 - '@rollup/rollup-linux-loong64-musl': 4.61.1 - '@rollup/rollup-linux-ppc64-gnu': 4.61.1 - '@rollup/rollup-linux-ppc64-musl': 4.61.1 - '@rollup/rollup-linux-riscv64-gnu': 4.61.1 - '@rollup/rollup-linux-riscv64-musl': 4.61.1 - '@rollup/rollup-linux-s390x-gnu': 4.61.1 - '@rollup/rollup-linux-x64-gnu': 4.61.1 - '@rollup/rollup-linux-x64-musl': 4.61.1 - '@rollup/rollup-openbsd-x64': 4.61.1 - '@rollup/rollup-openharmony-arm64': 4.61.1 - '@rollup/rollup-win32-arm64-msvc': 4.61.1 - '@rollup/rollup-win32-ia32-msvc': 4.61.1 - '@rollup/rollup-win32-x64-gnu': 4.61.1 - '@rollup/rollup-win32-x64-msvc': 4.61.1 + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 fsevents: 2.3.3 rw@1.3.3: {} @@ -2706,15 +2617,6 @@ snapshots: source-map-js@1.2.1: {} - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - optional: true - - source-map@0.6.1: - optional: true - space-separated-tokens@2.0.2: {} speakingurl@14.0.1: {} @@ -2730,29 +2632,18 @@ snapshots: dependencies: copy-anything: 4.0.5 - tabbable@6.4.0: {} - - terser@5.46.2: - dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.16.0 - commander: 2.20.3 - source-map-support: 0.5.21 - optional: true + tabbable@6.5.0: {} trim-lines@3.0.1: {} - ts-dedent@2.2.0: {} + ts-dedent@2.3.0: {} tsx@4.22.4: dependencies: - esbuild: 0.28.0 + esbuild: 0.28.1 optionalDependencies: fsevents: 2.3.3 - typescript@5.6.3: - optional: true - undici-types@7.24.6: {} unist-util-is@6.0.1: @@ -2782,7 +2673,7 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - uuid@11.1.1: {} + uuid@14.0.1: {} uvu@0.5.6: dependencies: @@ -2801,36 +2692,35 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@5.4.21(@types/node@25.9.2)(terser@5.46.2): + vite@5.4.21(@types/node@25.9.4): dependencies: esbuild: 0.21.5 - postcss: 8.5.14 - rollup: 4.61.1 + postcss: 8.5.15 + rollup: 4.62.2 optionalDependencies: - '@types/node': 25.9.2 + '@types/node': 25.9.4 fsevents: 2.3.3 - terser: 5.46.2 - vitepress@1.6.4(@algolia/client-search@5.52.1)(@types/node@25.9.2)(nprogress@0.2.0)(postcss@8.5.15)(search-insights@2.17.3)(terser@5.46.2)(typescript@5.6.3): + vitepress@1.6.4(@algolia/client-search@5.55.1)(@types/node@25.9.4)(postcss@8.5.15)(search-insights@2.17.3): dependencies: '@docsearch/css': 3.8.2 - '@docsearch/js': 3.8.2(@algolia/client-search@5.52.1)(search-insights@2.17.3) - '@iconify-json/simple-icons': 1.2.85 + '@docsearch/js': 3.8.2(@algolia/client-search@5.55.1)(search-insights@2.17.3) + '@iconify-json/simple-icons': 1.2.87 '@shikijs/core': 2.5.0 '@shikijs/transformers': 2.5.0 '@shikijs/types': 2.5.0 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@25.9.2)(terser@5.46.2))(vue@3.5.35(typescript@5.6.3)) - '@vue/devtools-api': 7.7.9 - '@vue/shared': 3.5.35 - '@vueuse/core': 12.8.2(typescript@5.6.3) - '@vueuse/integrations': 12.8.2(focus-trap@7.8.0)(nprogress@0.2.0)(typescript@5.6.3) + '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@25.9.4))(vue@3.5.39) + '@vue/devtools-api': 7.7.10 + '@vue/shared': 3.5.39 + '@vueuse/core': 12.8.2 + '@vueuse/integrations': 12.8.2(focus-trap@7.8.0) focus-trap: 7.8.0 mark.js: 8.11.1 minisearch: 7.2.0 shiki: 2.5.0 - vite: 5.4.21(@types/node@25.9.2)(terser@5.46.2) - vue: 3.5.35(typescript@5.6.3) + vite: 5.4.21(@types/node@25.9.4) + vue: 3.5.39 optionalDependencies: postcss: 8.5.15 transitivePeerDependencies: @@ -2860,15 +2750,13 @@ snapshots: - typescript - universal-cookie - vue@3.5.35(typescript@5.6.3): + vue@3.5.39: dependencies: - '@vue/compiler-dom': 3.5.35 - '@vue/compiler-sfc': 3.5.35 - '@vue/runtime-dom': 3.5.35 - '@vue/server-renderer': 3.5.35(vue@3.5.35(typescript@5.6.3)) - '@vue/shared': 3.5.35 - optionalDependencies: - typescript: 5.6.3 + '@vue/compiler-dom': 3.5.39 + '@vue/compiler-sfc': 3.5.39 + '@vue/runtime-dom': 3.5.39 + '@vue/server-renderer': 3.5.39(vue@3.5.39) + '@vue/shared': 3.5.39 web-worker@1.5.0: {} diff --git a/site/pnpm-workspace.yaml b/site/pnpm-workspace.yaml deleted file mode 100644 index 55f54edf..00000000 --- a/site/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -allowBuilds: - core-js: true