-
Notifications
You must be signed in to change notification settings - Fork 126
Feat/x86 64 ptrace #1060
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Feat/x86 64 ptrace #1060
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
396463a
feat(starry-kernel): x86_64 ptrace register r/w and PTRACE_SINGLESTEP…
cc1d287
feat(starry): add gdb smoke test app and update mount-umount2 compat …
adc0001
fix(starry-kernel): gate #DB handler behind #[cfg(target_arch = "x86_…
ee91b81
test(starryos): extend gdb batch timeout
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| target = "x86_64-unknown-none" | ||
| env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } | ||
| log = "Warn" | ||
| features = ["qemu"] | ||
| plat_dyn = false |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| app_dir="${STARRY_APP_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" | ||
| base_rootfs="${STARRY_BASE_ROOTFS:-}" | ||
| staging_root="${STARRY_STAGING_ROOT:-}" | ||
| overlay_dir="${STARRY_OVERLAY_DIR:-}" | ||
|
|
||
| READELF="${READELF:-/opt/homebrew/opt/binutils/bin/readelf}" | ||
|
|
||
| require_env() { | ||
| local name="$1" | ||
| local value="$2" | ||
| if [[ -z "$value" ]]; then | ||
| echo "error: $name is required" >&2 | ||
| exit 1 | ||
| fi | ||
| } | ||
|
|
||
| ensure_host_tools() { | ||
| local missing=() | ||
|
|
||
| command -v debugfs >/dev/null 2>&1 || missing+=(e2fsprogs) | ||
| command -v install >/dev/null 2>&1 || missing+=(coreutils) | ||
| command -v docker >/dev/null 2>&1 || missing+=(docker) | ||
| command -v "$READELF" >/dev/null 2>&1 || missing+=("readelf (binutils)") | ||
|
|
||
| if [[ ${#missing[@]} -eq 0 ]]; then | ||
| return | ||
| fi | ||
|
|
||
| echo "error: missing required host tools: ${missing[*]}" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| extract_base_rootfs() { | ||
| debugfs -R "rdump / $staging_root" "$base_rootfs" | ||
| } | ||
|
|
||
| install_gdb_package() { | ||
| echo "Installing gdb via Docker (Alpine x86_64)..." | ||
| docker run --rm --platform linux/amd64 \ | ||
| -v "$staging_root:/staging" \ | ||
| alpine:edge apk --root /staging --no-scripts add gdb | ||
| } | ||
|
|
||
| copy_file_to_overlay() { | ||
| local guest_path="$1" | ||
| local mode="$2" | ||
| local source="$staging_root${guest_path}" | ||
| local target="$overlay_dir${guest_path}" | ||
|
|
||
| if [[ ! -e "$source" ]]; then | ||
| echo "error: missing guest file after gdb package install: $guest_path" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [[ -L "$source" ]]; then | ||
| source="$(readlink -f "$source")" | ||
| fi | ||
|
|
||
| install -Dm"$mode" "$source" "$target" | ||
| } | ||
|
|
||
| find_library_path() { | ||
| local library="$1" | ||
| local dir | ||
|
|
||
| for dir in lib usr/lib usr/local/lib; do | ||
| if [[ -e "$staging_root/$dir/$library" ]]; then | ||
| printf '/%s/%s\n' "$dir" "$library" | ||
| return 0 | ||
| fi | ||
| done | ||
|
|
||
| return 1 | ||
| } | ||
|
|
||
| copy_runtime_dependencies() { | ||
| local pending=("$@") | ||
| local seen=" " | ||
| local guest_path library | ||
|
|
||
| while [[ ${#pending[@]} -gt 0 ]]; do | ||
| guest_path="${pending[0]}" | ||
| pending=("${pending[@]:1}") | ||
|
|
||
| if [[ "$seen" == *" $guest_path "* ]]; then | ||
| continue | ||
| fi | ||
| seen+="$guest_path " | ||
|
|
||
| while IFS= read -r library; do | ||
| local library_path | ||
| if ! library_path="$(find_library_path "$library")"; then | ||
| continue | ||
| fi | ||
| copy_file_to_overlay "$library_path" 0644 | ||
| pending+=("$library_path") | ||
| done < <( | ||
| "$READELF" -d "$staging_root$guest_path" 2>/dev/null | | ||
| sed -n 's/.*Shared library: \[\(.*\)\].*/\1/p' | ||
| ) | ||
| done | ||
| } | ||
|
|
||
| populate_overlay() { | ||
| copy_file_to_overlay /usr/bin/gdb 0755 | ||
| copy_runtime_dependencies /usr/bin/gdb | ||
| } | ||
|
|
||
| require_env STARRY_BASE_ROOTFS "$base_rootfs" | ||
| require_env STARRY_STAGING_ROOT "$staging_root" | ||
| require_env STARRY_OVERLAY_DIR "$overlay_dir" | ||
|
|
||
| ensure_host_tools | ||
| extract_base_rootfs | ||
| install_gdb_package | ||
| populate_overlay | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| args = [ | ||
| "-nographic", | ||
| "-m", | ||
| "512M", | ||
| "-device", | ||
| "virtio-blk-pci,drive=disk0", | ||
| "-drive", | ||
| "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", | ||
| "-device", | ||
| "virtio-net-pci,netdev=net0", | ||
| "-netdev", | ||
| "user,id=net0", | ||
| ] | ||
| uefi = false | ||
| to_bin = false | ||
| shell_prefix = "root@starry:" | ||
| shell_init_cmd = "gdb --version && echo GDB_SMOKE_PASSED" | ||
| success_regex = ["(?m)^GDB_SMOKE_PASSED\\s*$"] | ||
| fail_regex = ['(?i)\bpanic(?:ked)?\b'] | ||
| timeout = 120 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,10 +3,14 @@ | |
| 本文档说明本次在 StarryOS 中围绕 `sys_mount()` / `sys_umount2()` 做的 Linux 兼容性补齐,重点覆盖: | ||
|
|
||
| - `mount(2)` 中的 propagation flags 组合校验 | ||
| - `MS_SHARED` / `MS_PRIVATE` / `MS_SLAVE` / `MS_UNBINDABLE` | ||
| - `MS_BIND` / `MS_BIND|MS_REC` | ||
| - bind 子目录 | ||
| - `MS_MOVE` | ||
| - `MS_MOVE` 对 descendant target 的 `ELOOP` | ||
| - `MS_REMOUNT` | ||
| - `MS_RDONLY` | ||
| - `MS_REMOUNT|MS_BIND|MS_RDONLY` | ||
| - `umount2(2)` 中的 `MNT_EXPIRE` | ||
| - `UMOUNT_NOFOLLOW` | ||
| - `MNT_DETACH` | ||
|
|
@@ -84,19 +88,20 @@ Linux 中: | |
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 本 PR 核心是 x86_64 ptrace 支持,但对此文档做了大量更新(+84/-20),描述 shared/private/slave/unbindable 传播语义实现状态。经验证这些内核实现已在 dev 基线中,文档更新准确。但此变更与 ptrace 功能无关,建议拆分到 PR #1059(同一作者的 mount 兼容 PR)中,保持每个 PR 职责单一。 |
||
| #### 本次 StarryOS 的实现边界 | ||
|
|
||
| 这次 StarryOS 只实现了: | ||
| 这次 StarryOS 已经实现到“可被用户态测试验证”的 shared subtree 语义: | ||
|
|
||
| - 非法组合检查 | ||
| - propagation-only 调用不再错误地新建一个 fresh mount | ||
| - 对已有 mount 返回成功,并保持已有内容不变 | ||
| - propagation flags 非法组合检查 | ||
| - `MS_SHARED`:shared mount 的 bind peer 进入同一传播组 | ||
| - `MS_PRIVATE`:从 shared/slave 关系中脱离,停止后续传播 | ||
| - `MS_SLAVE`:接收 master 的传播,但不向 master 反向传播 | ||
| - `MS_UNBINDABLE`:禁止直接 bind,并在 recursive bind 时剪掉 unbindable child mount | ||
| - shared mount 下新增 child mount 时,对 peer / slave 做传播 | ||
|
|
||
| 这次 **没有** 实现真实的 shared subtree 传播机制,也就是: | ||
| 仍未完全实现的,是 Linux mount namespace 更细的传播细节,例如: | ||
|
|
||
| - 新 mount 在 shared peer 之间自动传播 | ||
| - slave 接收而不反向传播 | ||
| - unbindable 禁止被 bind | ||
|
|
||
| 这些属于后续更深层的 mount namespace / propagation tree 语义。 | ||
| - 更完整的 peer group 生命周期 | ||
| - 更复杂的递归 propagation corner case | ||
| - namespace 之间更深层的传播隔离规则 | ||
|
|
||
| ### 3.2 `MS_BIND` | ||
|
|
||
|
|
@@ -114,6 +119,7 @@ Linux 中: | |
|
|
||
| - 顶层目录内容应该一致 | ||
| - 但 source 树下面如果有更深一层的子挂载点,默认不应该自动出现在 bind 目标里 | ||
| - source 不必是 mount root,也可以是 mount 内部的普通子目录 | ||
|
|
||
| #### `MS_BIND|MS_REC` | ||
|
|
||
|
|
@@ -130,6 +136,7 @@ Linux 中: | |
| | `MS_BIND|MS_REC` | 递归 bind,nested submount 也可见 | | ||
| | `MS_BIND|MS_RDONLY` | 新的 bind mount 视图只读 | | ||
| | `MS_REMOUNT|MS_BIND|MS_RDONLY` | 把已有 bind mount remount 成只读 | | ||
| | `MS_BIND` on unbindable source | `EINVAL` | | ||
|
|
||
| ### 3.3 `MS_MOVE` | ||
|
|
||
|
|
@@ -142,6 +149,7 @@ Linux 中: | |
| | 新路径 | 应看到原 mount 的内容 | | ||
| | 旧路径 | 不再是那个 mount 的可见入口 | | ||
| | mount 本身 | 不是复制,而是移动 | | ||
| | target 是 source 后代 | `ELOOP` | | ||
|
|
||
| 这类操作的关键不是“新建一个 mount”,而是“修改 mount tree 中 parent-child 关系”。 | ||
|
|
||
|
|
@@ -177,6 +185,14 @@ Linux 中: | |
| | `mount(..., MS_REMOUNT|MS_RDONLY, ...)` | remount 成功,已有内容保留,后续写入 `EROFS` | | ||
| | `mount(..., MS_REMOUNT|MS_BIND|MS_RDONLY, ...)` | bind mount 视图只读,但源 mount 仍可写 | | ||
|
|
||
| 除了普通 `write()`,Linux 风格的只读 mount 还应拒绝: | ||
|
|
||
| - `chmod` | ||
| - `rename` | ||
| - `unlink` | ||
| - `mkdir` | ||
| - 已打开 fd 的后续 `write` / `append` | ||
|
|
||
| ## 4. Linux `umount2(2)` 语义矩阵 | ||
|
|
||
| Linux `umount2()` 原型: | ||
|
|
@@ -284,7 +300,7 @@ match fs_type.as_str() { | |
| - `MS_MOVE` / `MS_BIND` 都属于 mount tree 操作,不应该落进 `tmpfs` / `ext4` 分支 | ||
| - 只有当这些“特殊 mount 操作”都不命中时,才说明这是普通挂载 | ||
|
|
||
| ## 5.2 propagation flags:先校验合法性,再对已有 mount 返回成功 | ||
| ## 5.2 propagation flags:先校验合法性,再按 mountpoint 状态更新传播关系 | ||
|
|
||
| 本次实现: | ||
|
|
||
|
|
@@ -305,6 +321,16 @@ if propagation != 0 { | |
| if !target.is_root_of_mount() { | ||
| return Err(AxError::InvalidInput); | ||
| } | ||
| let mountpoint = target.mountpoint(); | ||
| if (propagation & MS_PRIVATE) != 0 { | ||
| mountpoint.set_private(); | ||
| } else if (propagation & MS_SHARED) != 0 { | ||
| mountpoint.set_shared(); | ||
| } else if (propagation & MS_SLAVE) != 0 { | ||
| mountpoint.set_slave(); | ||
| } else if (propagation & MS_UNBINDABLE) != 0 { | ||
| mountpoint.set_unbindable(); | ||
| } | ||
| return Ok(0); | ||
| } | ||
| ``` | ||
|
|
@@ -313,7 +339,8 @@ if propagation != 0 { | |
|
|
||
| - `count_ones() > 1` 对应 Linux 的“多个 propagation type flags 同时出现是 `EINVAL`” | ||
| - `flags & !allowed != 0` 对应 Linux 的“propagation type flags 只能和 `MS_REC` / `MS_SILENT` 共存” | ||
| - 这里当前只是让 StarryOS 具备“不要误挂新 fs”的语义边界,真实传播机制仍是后续工作 | ||
| - propagation-only 调用仍然不新建 fs,而是修改已有 mountpoint 的传播属性 | ||
| - 传播状态现在是 mountpoint 级别的,而不是底层 inode 级别的 | ||
|
|
||
| ## 5.3 `axfs-ng-vfs::Mountpoint`:把 mountpoint 当成 mount 语义状态承载体 | ||
|
|
||
|
|
@@ -327,6 +354,7 @@ pub struct Mountpoint { | |
| device: u64, | ||
| readonly: AtomicBool, | ||
| expired: AtomicBool, | ||
| propagation: Mutex<PropagationState>, | ||
| } | ||
| ``` | ||
|
|
||
|
|
@@ -341,6 +369,7 @@ Linux 里的这些语义,本质上都是“对 mount 实例”的属性,而 | |
| - bind mount 视图只读,不应必然把 source mount 一起变只读 | ||
| - `MNT_EXPIRE` 的 expired 标记属于 mount,不属于底层文件 | ||
| - lazy detach 也是从 mount tree 摘除,而不是销毁底层文件对象 | ||
| - shared/private/slave/unbindable 也属于 mount 之间的关系,而不是目录项自己的关系 | ||
|
|
||
| 因此在 StarryOS 里,把这类状态绑定到 `Mountpoint` 是合理且稳定的。 | ||
|
|
||
|
|
@@ -371,11 +400,15 @@ fn bind(source: &Location, location_in_parent: Location, recursive: bool) -> Arc | |
|
|
||
| ### 讲解 | ||
|
|
||
| - 顶层 bind 的本质是:目标 mountpoint 的 root 指向与 source 相同的目录入口 | ||
| - 顶层 bind 的本质是:目标 mountpoint 的 root 指向与 source 当前可见的目录入口 | ||
| - source 可以是 mount 内部的普通子目录,不必是 mount root | ||
| - 普通 bind 时,不拷贝 `children` | ||
| - 所以 nested submount 在目标树下不可见 | ||
| - recursive bind 时,把 `children` 也带过去 | ||
| - 所以 nested submount 在目标树下继续可见 | ||
| - 如果 source mount 被标记为 unbindable: | ||
| - 直接 bind 返回 `EINVAL` | ||
| - recursive bind 时会跳过 unbindable child mount | ||
|
|
||
| 这正对应 Linux 中: | ||
|
|
||
|
|
@@ -415,6 +448,15 @@ fn resolve_mountpoint(self) -> Self { | |
|
|
||
| 这让“是否递归”真正体现在 mount tree 本身,而不是体现在共享的底层目录节点上。 | ||
|
|
||
| 同样的机制也被用在 shared/slave propagation 上: | ||
|
|
||
| - shared mount 新增 child mount 时 | ||
| - 先计算 child 相对 mount root 的路径 | ||
| - 再在 peer / slave 那边找到同一路径位置 | ||
| - 最后把 child mount 暴露到对端对应的 `children` map 中 | ||
|
|
||
| 这样 peer 看到的是“对端树中相同相对路径上的 child mount”,而不是错误地复用源侧目录项位置。 | ||
|
|
||
| ## 5.6 `MS_MOVE`:修改 mount tree 的 parent-child 关系 | ||
|
|
||
| 实现片段: | ||
|
|
@@ -446,6 +488,12 @@ pub fn move_to(self: &Arc<Self>, new_location: &Location) -> VfsResult<()> { | |
|
|
||
| 这正是 Linux `MS_MOVE` 的语义:移动现有 mount,而不是复制。 | ||
|
|
||
| 另外本次还补了一个 Linux 风格错误条件: | ||
|
|
||
| - 如果 target 是 source 后代,返回 `ELOOP` | ||
|
|
||
| 这个检查的本质是:沿着 target 的 parent 链向上走,若遇到当前被移动的 mount root,则说明会形成循环。 | ||
|
|
||
| ## 5.7 `MS_RDONLY` / `MS_REMOUNT|MS_RDONLY`:按 mountpoint 拒绝写入 | ||
|
|
||
| ### 在 `sys_mount()` 中设置只读位 | ||
|
|
@@ -500,6 +548,13 @@ if self.inner.location().is_readonly() | |
| - `MS_REMOUNT|MS_RDONLY` 后已有内容保留,但新增写入 `EROFS` | ||
| - `MS_REMOUNT|MS_BIND|MS_RDONLY` 只让 bind 视图只读,而源 mount 保持可写 | ||
|
|
||
| 此外,本次还把只读拦截扩展到了元数据/目录修改路径: | ||
|
|
||
| - `Location::update_metadata()` 上拒绝 `chmod` | ||
| - 目录创建/删除/重命名路径拒绝 `mkdir` / `unlink` / `rename` | ||
|
|
||
| 这样用户态就能观察到更完整的 Linux 风格 `EROFS` 行为,而不只是普通文件写入失败。 | ||
|
|
||
| ## 5.8 `MNT_EXPIRE`:两阶段过期标记 | ||
|
|
||
| 实现片段: | ||
|
|
@@ -607,13 +662,16 @@ pub fn detach(self: &Arc<Self>) -> VfsResult<()> { | |
| |---|---|---| | ||
| | `MS_SHARED | MS_PRIVATE` | `EINVAL` | 已支持 | | ||
| | `MS_SHARED | MS_BIND` | `EINVAL` | 已支持 | | ||
| | `MS_PRIVATE` | 修改已有 mount 的传播属性 | 当前返回成功并保持内容,不实现真实传播 | | ||
| | `MS_SHARED` | 修改已有 mount 的传播属性 | 当前返回成功并保持内容,不实现真实传播 | | ||
| | `MS_SLAVE` | 修改已有 mount 的传播属性 | 当前返回成功并保持内容,不实现真实传播 | | ||
| | `MS_UNBINDABLE` | 修改已有 mount 的传播属性 | 当前返回成功并保持内容,不实现真实传播 | | ||
| | `MS_PRIVATE` | 修改已有 mount 的传播属性 | 已支持当前测试覆盖的 private 语义 | | ||
| | `MS_SHARED` | 修改已有 mount 的传播属性 | 已支持当前测试覆盖的 shared peer 传播语义 | | ||
| | `MS_SLAVE` | 修改已有 mount 的传播属性 | 已支持当前测试覆盖的 slave 单向传播语义 | | ||
| | `MS_UNBINDABLE` | 修改已有 mount 的传播属性 | 已支持 bind 禁止与 recursive bind prune | | ||
| | `MS_BIND` | 普通 bind,不带 nested submount | 已支持 | | ||
| | `MS_BIND` on subdirectory | bind mount 内部子目录 | 已支持 | | ||
| | `MS_BIND|MS_REC` | recursive bind,带 nested submount | 已支持 | | ||
| | `MS_BIND|MS_REC` with unbindable child | 剪掉 unbindable child mount | 已支持 | | ||
| | `MS_MOVE` | 移动已有 mount | 已支持 | | ||
| | `MS_MOVE` to descendant target | `ELOOP` | 已支持 | | ||
| | `MS_REMOUNT` | remount 现有 mount,内容保留 | 已支持 | | ||
| | `MS_RDONLY` | 挂成只读,写入 `EROFS` | 已支持 | | ||
| | `MS_REMOUNT|MS_RDONLY` | remount 成只读,写入 `EROFS` | 已支持 | | ||
|
|
@@ -641,8 +699,13 @@ pub fn detach(self: &Arc<Self>) -> VfsResult<()> { | |
| 它覆盖的核心点是: | ||
|
|
||
| - propagation flags 的非法组合和无副作用保证 | ||
| - shared/private/slave/unbindable 的真实传播或隔离效果 | ||
| - propagation-only 调用不应替换现有 mount 内容 | ||
| - bind / recursive bind / move / remount / readonly | ||
| - bind 子目录 | ||
| - recursive bind 对 unbindable child 的 prune | ||
| - move descendant target 的 `ELOOP` | ||
| - readonly mount 上的 `chmod` / `rename` / `unlink` / `mkdir` | ||
| - `umount2` 的 invalid flags / invalid combo / expire / nofollow / detach | ||
|
|
||
| 这也是为什么这次修复能够比较扎实: | ||
|
|
@@ -654,15 +717,16 @@ pub fn detach(self: &Arc<Self>) -> VfsResult<()> { | |
|
|
||
| 虽然这次 `util-linux` 测试已经全绿,但仍有一些 Linux mount/namespace 语义没有在本轮实现: | ||
|
|
||
| - 真实 shared subtree propagation | ||
| - `MS_UNBINDABLE` 对后续 bind 的禁止效果 | ||
| - 更完整的 mount namespace 传播行为 | ||
| - 更完整的 shared subtree propagation corner case | ||
| - 更复杂 namespace 拓扑下的传播行为 | ||
| - 真实 peer group 生命周期管理 | ||
| - 其他普通 mount flags,如 `MS_NODEV`、`MS_NOSUID`、`MS_NOEXEC` 等 | ||
| - `MNT_FORCE` 的真实强制卸载语义 | ||
|
|
||
| 因此更准确地说: | ||
|
|
||
| - 这次已经把本轮补的 `mount/umount2` Linux 兼容测试全部打绿 | ||
| - 而且 shared/private/slave/unbindable / bind / move / readonly / umount2 核心路径已经具备稳定可验证的 Linux 风格语义 | ||
| - 但并不意味着 Linux 整体 mount namespace 语义已经“全部实现” | ||
|
|
||
| ## 9. 参考 | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
READELF 默认路径是 macOS(Apple Silicon Homebrew)专用路径。在 Linux 开发环境或 CI 中,如果不设置 READELF 环境变量,ensure_host_tools() 会将 readelf 报为缺失并退出。
建议改为使用系统 PATH 查找:
READELF="${READELF:-readelf}"