diff --git a/apps/starry/gdb-smoke/README.md b/apps/starry/gdb-smoke/README.md index edae7807fb..5056f3c194 100644 --- a/apps/starry/gdb-smoke/README.md +++ b/apps/starry/gdb-smoke/README.md @@ -1,7 +1,9 @@ # StarryOS GDB Smoke -This app prepares a RISC-V Alpine rootfs overlay with guest `gdb`, `gdbserver`, -and tiny target programs for StarryOS user-space debugger smoke testing. +This app prepares an Alpine rootfs overlay with guest `gdb`, `gdbserver`, and +tiny target programs for StarryOS user-space debugger smoke testing. The native +GDB smoke and the single-process gdbserver smoke are available on riscv64, +aarch64, and loongarch64. ## Batch Native GDB Smoke @@ -11,6 +13,18 @@ Use this command for the automated native GDB batch smoke: cargo xtask starry app qemu -t gdb-smoke --arch riscv64 ``` +For the current aarch64 native GDB baseline: + +```bash +cargo xtask starry app qemu -t gdb-smoke --arch aarch64 +``` + +For the current LoongArch native GDB baseline: + +```bash +cargo xtask starry app qemu -t gdb-smoke --arch loongarch64 +``` + The batch script runs: ```gdb @@ -101,10 +115,28 @@ cargo xtask starry app qemu -t gdb-smoke --arch riscv64 \ --qemu-config qemu-riscv64-gdbserver.toml ``` +For aarch64: + +```bash +cargo xtask starry app qemu -t gdb-smoke --arch aarch64 \ + --qemu-config qemu-aarch64-gdbserver.toml +``` + +For LoongArch: + +```bash +cargo xtask starry app qemu -t gdb-smoke --arch loongarch64 \ + --qemu-config qemu-loongarch64-gdbserver.toml +``` + The default gdbserver script connects to `127.0.0.1:1234`, breaks on `compute_value`, prints a backtrace, deletes the breakpoint, and continues the remote target. +On LoongArch, gdbserver can print legacy regset warnings while probing +unsupported optional register paths. The single-process smoke still passes when +breakpoint, backtrace, continue, and target exit markers complete. + Remote pthread gdbserver coverage is opt-in because it is slower and exercises the heavier clone/thread event path: @@ -140,6 +172,20 @@ cargo xtask starry app qemu -t gdb-smoke --arch riscv64 \ --qemu-config qemu-riscv64-gdbserver-manual.toml ``` +For aarch64: + +```bash +cargo xtask starry app qemu -t gdb-smoke --arch aarch64 \ + --qemu-config qemu-aarch64-gdbserver-manual.toml +``` + +For LoongArch: + +```bash +cargo xtask starry app qemu -t gdb-smoke --arch loongarch64 \ + --qemu-config qemu-loongarch64-gdbserver-manual.toml +``` + When running through the long-lived Docker container, keep stdin and a TTY attached: @@ -168,8 +214,24 @@ gdb-multiarch -q -x apps/starry/gdb-smoke/gdbserver/host-manual.gdb \ target/gdb-smoke-host/gdbserver-smoke-target ``` +For aarch64, use the architecture-specific script and symbol copy: + +```bash +gdb-multiarch -q -x apps/starry/gdb-smoke/gdbserver/host-manual-aarch64.gdb \ + target/gdb-smoke-host/aarch64/gdbserver-smoke-target +``` + +For LoongArch: + +```bash +gdb-multiarch -q -x apps/starry/gdb-smoke/gdbserver/host-manual-loongarch64.gdb \ + target/gdb-smoke-host/loongarch64/gdbserver-smoke-target +``` + `host-manual.gdb` sets the riscv64 remote debugging defaults and connects to -`:1234`, but leaves you at the GDB prompt for manual commands. +`:1234`; `host-manual-aarch64.gdb` and `host-manual-loongarch64.gdb` do the +same for aarch64 and LoongArch. All scripts leave you at the GDB prompt for +manual commands. Inside host GDB: @@ -194,6 +256,20 @@ cargo xtask starry app qemu -t gdb-smoke --arch riscv64 \ --qemu-config qemu-riscv64-gdbserver-host.toml ``` +For aarch64: + +```bash +cargo xtask starry app qemu -t gdb-smoke --arch aarch64 \ + --qemu-config qemu-aarch64-gdbserver-host.toml +``` + +For LoongArch: + +```bash +cargo xtask starry app qemu -t gdb-smoke --arch loongarch64 \ + --qemu-config qemu-loongarch64-gdbserver-host.toml +``` + This automatic config starts guest `gdbserver` for you and is intended for repeatable logs rather than manual interaction. @@ -204,4 +280,18 @@ gdb-multiarch -q -batch -x apps/starry/gdb-smoke/gdbserver/host-remote.gdb \ target/gdb-smoke-host/gdbserver-smoke-target ``` +For aarch64: + +```bash +gdb-multiarch -q -batch -x apps/starry/gdb-smoke/gdbserver/host-remote-aarch64.gdb \ + target/gdb-smoke-host/aarch64/gdbserver-smoke-target +``` + +For LoongArch: + +```bash +gdb-multiarch -q -batch -x apps/starry/gdb-smoke/gdbserver/host-remote-loongarch64.gdb \ + target/gdb-smoke-host/loongarch64/gdbserver-smoke-target +``` + `-batch` runs the scripted host GDB flow and exits after the marker output. diff --git a/apps/starry/gdb-smoke/build-aarch64-unknown-none-softfloat.toml b/apps/starry/gdb-smoke/build-aarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..261b93cef9 --- /dev/null +++ b/apps/starry/gdb-smoke/build-aarch64-unknown-none-softfloat.toml @@ -0,0 +1,12 @@ +features = [ + "qemu", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "starry-kernel/input", + "starry-kernel/vsock", +] +log = "Warn" +target = "aarch64-unknown-none-softfloat" diff --git a/apps/starry/gdb-smoke/build-loongarch64-unknown-none-softfloat.toml b/apps/starry/gdb-smoke/build-loongarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..4270b5c2b7 --- /dev/null +++ b/apps/starry/gdb-smoke/build-loongarch64-unknown-none-softfloat.toml @@ -0,0 +1,15 @@ +target = "loongarch64-unknown-none-softfloat" +log = "Warn" +features = [ + "ax-hal/loongarch64-qemu-virt", + "qemu", + "ax-driver/plat-static", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "starry-kernel/input", + "starry-kernel/vsock", +] +plat_dyn = false diff --git a/apps/starry/gdb-smoke/gdbserver/host-manual-aarch64.gdb b/apps/starry/gdb-smoke/gdbserver/host-manual-aarch64.gdb new file mode 100644 index 0000000000..d9bf04d437 --- /dev/null +++ b/apps/starry/gdb-smoke/gdbserver/host-manual-aarch64.gdb @@ -0,0 +1,10 @@ +set pagination off +set confirm off +set debuginfod enabled off +set architecture aarch64 +set sysroot / +set solib-search-path /lib:/usr/lib +set remotetimeout 10 +set remote hostio-open-packet off +set remote hostio-pread-packet off +target remote :1234 diff --git a/apps/starry/gdb-smoke/gdbserver/host-manual-loongarch64.gdb b/apps/starry/gdb-smoke/gdbserver/host-manual-loongarch64.gdb new file mode 100644 index 0000000000..4d31d248a5 --- /dev/null +++ b/apps/starry/gdb-smoke/gdbserver/host-manual-loongarch64.gdb @@ -0,0 +1,10 @@ +set pagination off +set confirm off +set debuginfod enabled off +set architecture Loongarch64 +set sysroot / +set solib-search-path /lib:/usr/lib +set remotetimeout 10 +set remote hostio-open-packet off +set remote hostio-pread-packet off +target remote :1234 diff --git a/apps/starry/gdb-smoke/gdbserver/host-remote-aarch64.gdb b/apps/starry/gdb-smoke/gdbserver/host-remote-aarch64.gdb new file mode 100644 index 0000000000..2521d5fe02 --- /dev/null +++ b/apps/starry/gdb-smoke/gdbserver/host-remote-aarch64.gdb @@ -0,0 +1,18 @@ +set pagination off +set confirm off +set debuginfod enabled off +set architecture aarch64 +set sysroot / +set solib-search-path /lib:/usr/lib +set remotetimeout 10 +set remote hostio-open-packet off +set remote hostio-pread-packet off +target remote :1234 +echo HOST_GDB_REMOTE_CONNECTED\n +break compute_value +continue +bt +echo HOST_GDB_REMOTE_BT_DONE\n +detach +echo HOST_GDB_REMOTE_DETACH_DONE\n +quit diff --git a/apps/starry/gdb-smoke/gdbserver/host-remote-loongarch64.gdb b/apps/starry/gdb-smoke/gdbserver/host-remote-loongarch64.gdb new file mode 100644 index 0000000000..2312642572 --- /dev/null +++ b/apps/starry/gdb-smoke/gdbserver/host-remote-loongarch64.gdb @@ -0,0 +1,18 @@ +set pagination off +set confirm off +set debuginfod enabled off +set architecture Loongarch64 +set sysroot / +set solib-search-path /lib:/usr/lib +set remotetimeout 10 +set remote hostio-open-packet off +set remote hostio-pread-packet off +target remote :1234 +echo HOST_GDB_REMOTE_CONNECTED\n +break compute_value +continue +bt +echo HOST_GDB_REMOTE_BT_DONE\n +detach +echo HOST_GDB_REMOTE_DETACH_DONE\n +quit diff --git a/apps/starry/gdb-smoke/gdbserver/src/main.c b/apps/starry/gdb-smoke/gdbserver/src/main.c index 39b445309c..18feca240c 100644 --- a/apps/starry/gdb-smoke/gdbserver/src/main.c +++ b/apps/starry/gdb-smoke/gdbserver/src/main.c @@ -1,7 +1,9 @@ #include #include +#if defined(__riscv) #define RISCV_HWCAP_ISA_D (1UL << ('D' - 'A')) +#endif __attribute__((noinline)) static int compute_value(void) { @@ -11,10 +13,12 @@ __attribute__((noinline)) static int compute_value(void) int main(void) { unsigned long hwcap = getauxval(AT_HWCAP); +#if defined(__riscv) if ((hwcap & RISCV_HWCAP_ISA_D) == 0) { printf("gdbserver-smoke-target missing riscv D hwcap: %#lx\n", hwcap); return 1; } +#endif int value = compute_value(); printf("gdbserver-smoke-target value=%d hwcap=%#lx\n", value, hwcap); diff --git a/apps/starry/gdb-smoke/prebuild.sh b/apps/starry/gdb-smoke/prebuild.sh index a49ba5141a..9047d7b1ec 100755 --- a/apps/starry/gdb-smoke/prebuild.sh +++ b/apps/starry/gdb-smoke/prebuild.sh @@ -5,9 +5,12 @@ app_dir="${STARRY_APP_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" base_rootfs="${STARRY_ROOTFS:-}" staging_root="${STARRY_STAGING_ROOT:-}" overlay_dir="${STARRY_OVERLAY_DIR:-}" -apk_cache="${STARRY_WORKSPACE:-$(cd "$app_dir/../../.." && pwd)}/target/gdb-smoke-apk-cache" -host_artifact_dir="${STARRY_WORKSPACE:-$(cd "$app_dir/../../.." && pwd)}/target/gdb-smoke-host" +arch="${STARRY_ARCH:-}" +workspace="${STARRY_WORKSPACE:-$(cd "$app_dir/../../.." && pwd)}" +apk_cache="$workspace/target/gdb-smoke-apk-cache/${arch:-unknown}" +host_artifact_dir="$workspace/target/gdb-smoke-host" qemu_runner="" +linux_target="" lld_linker="" lld_linker_dir="" @@ -58,12 +61,33 @@ extract_base_rootfs() { } find_qemu_runner() { - if command -v qemu-riscv64-static >/dev/null 2>&1; then - qemu_runner="$(command -v qemu-riscv64-static)" - elif command -v qemu-riscv64 >/dev/null 2>&1; then - qemu_runner="$(command -v qemu-riscv64)" + local qemu_name + + case "$arch" in + riscv64) + qemu_name=qemu-riscv64 + linux_target=riscv64-linux-musl + ;; + aarch64) + qemu_name=qemu-aarch64 + linux_target=aarch64-linux-musl + ;; + loongarch64) + qemu_name=qemu-loongarch64 + linux_target=loongarch64-linux-musl + ;; + *) + echo "error: unsupported gdb-smoke arch: $arch" >&2 + exit 1 + ;; + esac + + if command -v "${qemu_name}-static" >/dev/null 2>&1; then + qemu_runner="$(command -v "${qemu_name}-static")" + elif command -v "$qemu_name" >/dev/null 2>&1; then + qemu_runner="$(command -v "$qemu_name")" else - echo "error: qemu-riscv64-static or qemu-riscv64 is required" >&2 + echo "error: ${qemu_name}-static or ${qemu_name} is required" >&2 exit 1 fi } @@ -122,7 +146,7 @@ compile_target() { install -d "$(dirname "$overlay_dir$output")" clang \ - --target=riscv64-linux-musl \ + --target="$linux_target" \ --sysroot="$staging_root" \ --gcc-toolchain="$staging_root/usr" \ --ld-path="$lld_linker" \ @@ -211,6 +235,8 @@ populate_overlay() { -Wall -Wextra -Werror -O0 -g -pthread install -Dm0755 "$overlay_dir/usr/bin/gdbserver-smoke-target" \ "$host_artifact_dir/gdbserver-smoke-target" + install -Dm0755 "$overlay_dir/usr/bin/gdbserver-smoke-target" \ + "$host_artifact_dir/$arch/gdbserver-smoke-target" copy_file_to_overlay /usr/bin/gdb 0755 copy_file_to_overlay /usr/bin/gdbserver 0755 @@ -240,6 +266,7 @@ populate_overlay() { require_env STARRY_ROOTFS "$base_rootfs" require_env STARRY_STAGING_ROOT "$staging_root" require_env STARRY_OVERLAY_DIR "$overlay_dir" +require_env STARRY_ARCH "$arch" ensure_host_packages extract_base_rootfs diff --git a/apps/starry/gdb-smoke/qemu-aarch64-gdbserver-host.toml b/apps/starry/gdb-smoke/qemu-aarch64-gdbserver-host.toml new file mode 100644 index 0000000000..cc7783773a --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-aarch64-gdbserver-host.toml @@ -0,0 +1,33 @@ +args = [ + "-nographic", + "-m", + "512M", + "-cpu", + "cortex-a53", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-aarch64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0,hostfwd=tcp::1234-:1234", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "gdbserver 0.0.0.0:1234 /usr/bin/gdbserver-smoke-target; echo __HOST_REMOTE_GDBSERVER_DONE__" +success_regex = [ + "(?m)^__HOST_REMOTE_GDBSERVER_DONE__$", +] +fail_regex = [ + '(?i)\bpanic(?:ked)?\b', + '(?m)FAIL', + 'gdbserver: not found', + 'Cannot insert breakpoint', + 'Remote communication error', + 'ptrace: Function not implemented', + 'Program received signal SIGSEGV', + '(?i)Segmentation fault', +] +timeout = 240 diff --git a/apps/starry/gdb-smoke/qemu-aarch64-gdbserver-manual.toml b/apps/starry/gdb-smoke/qemu-aarch64-gdbserver-manual.toml new file mode 100644 index 0000000000..c5a027c639 --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-aarch64-gdbserver-manual.toml @@ -0,0 +1,25 @@ +args = [ + "-nographic", + "-m", + "512M", + "-cpu", + "cortex-a53", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-aarch64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0,hostfwd=tcp::1234-:1234", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +success_regex = [] +fail_regex = [ + '(?i)\bpanic(?:ked)?\b', + '(?m)FAIL', + 'gdbserver: not found', +] +timeout = 0 diff --git a/apps/starry/gdb-smoke/qemu-aarch64-gdbserver.toml b/apps/starry/gdb-smoke/qemu-aarch64-gdbserver.toml new file mode 100644 index 0000000000..190e01ea33 --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-aarch64-gdbserver.toml @@ -0,0 +1,34 @@ +args = [ + "-nographic", + "-m", + "512M", + "-cpu", + "cortex-a53", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-aarch64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/gdbserver-smoke.sh" +success_regex = ["GDBSERVER_SMOKE_DONE"] +fail_regex = [ + '(?i)\bpanic(?:ked)?\b', + '(?m)FAIL', + 'gdb: not found', + 'Python Exception', + 'Python initialization failed', + 'Cannot insert breakpoint', + 'Remote communication error', + 'ptrace: Function not implemented', + 'No stack', + 'Program received signal SIGSEGV', + '(?i)Segmentation fault', +] +timeout = 180 diff --git a/apps/starry/gdb-smoke/qemu-aarch64.toml b/apps/starry/gdb-smoke/qemu-aarch64.toml new file mode 100644 index 0000000000..4794c303af --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-aarch64.toml @@ -0,0 +1,31 @@ +args = [ + "-nographic", + "-m", + "512M", + "-cpu", + "cortex-a53", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-aarch64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/gdb -q -batch -x /usr/bin/gdb-native-smoke.gdb /usr/bin/gdb-native-smoke-target" +success_regex = ["GDB_NATIVE_SMOKE_DONE"] +fail_regex = [ + '(?i)\bpanic(?:ked)?\b', + '(?m)FAIL', + 'gdb: not found', + 'Python Exception', + 'Python initialization failed', + 'Cannot insert breakpoint', + 'ptrace: Function not implemented', + 'No stack', +] +timeout = 120 diff --git a/apps/starry/gdb-smoke/qemu-loongarch64-gdbserver-host.toml b/apps/starry/gdb-smoke/qemu-loongarch64-gdbserver-host.toml new file mode 100644 index 0000000000..4e95564e6a --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-loongarch64-gdbserver-host.toml @@ -0,0 +1,28 @@ +args = [ + "-machine", "virt", + "-cpu", "la464", + "-nographic", + "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-loongarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0,hostfwd=tcp::1234-:1234", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "gdbserver 0.0.0.0:1234 /usr/bin/gdbserver-smoke-target; echo __HOST_REMOTE_GDBSERVER_DONE__" +success_regex = [ + "(?m)^__HOST_REMOTE_GDBSERVER_DONE__$", +] +fail_regex = [ + '(?i)\bpanic(?:ked)?\b', + '(?m)FAIL', + 'gdbserver: not found', + 'Cannot insert breakpoint', + 'Remote communication error', + 'ptrace: Function not implemented', + 'Program received signal SIGSEGV', + '(?i)Segmentation fault', +] +timeout = 300 diff --git a/apps/starry/gdb-smoke/qemu-loongarch64-gdbserver-manual.toml b/apps/starry/gdb-smoke/qemu-loongarch64-gdbserver-manual.toml new file mode 100644 index 0000000000..033cd33098 --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-loongarch64-gdbserver-manual.toml @@ -0,0 +1,14 @@ +args = [ + "-machine", "virt", + "-cpu", "la464", + "-nographic", + "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-loongarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0,hostfwd=tcp::1234-:1234", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +timeout = 0 diff --git a/apps/starry/gdb-smoke/qemu-loongarch64-gdbserver.toml b/apps/starry/gdb-smoke/qemu-loongarch64-gdbserver.toml new file mode 100644 index 0000000000..36f0be97b7 --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-loongarch64-gdbserver.toml @@ -0,0 +1,29 @@ +args = [ + "-machine", "virt", + "-cpu", "la464", + "-nographic", + "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-loongarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/gdbserver-smoke.sh" +success_regex = ["GDBSERVER_SMOKE_DONE"] +fail_regex = [ + '(?i)\bpanic(?:ked)?\b', + '(?m)FAIL', + 'gdb: not found', + 'Python Exception', + 'Python initialization failed', + 'Cannot insert breakpoint', + 'Remote communication error', + 'ptrace: Function not implemented', + 'No stack', + 'Program received signal SIGSEGV', + '(?i)Segmentation fault', +] +timeout = 240 diff --git a/apps/starry/gdb-smoke/qemu-loongarch64-manual.toml b/apps/starry/gdb-smoke/qemu-loongarch64-manual.toml new file mode 100644 index 0000000000..5b4cf666b0 --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-loongarch64-manual.toml @@ -0,0 +1,13 @@ +args = [ + "-machine", "virt", + "-cpu", "la464", + "-nographic", + "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-loongarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +timeout = 0 diff --git a/apps/starry/gdb-smoke/qemu-loongarch64.toml b/apps/starry/gdb-smoke/qemu-loongarch64.toml new file mode 100644 index 0000000000..b41157a804 --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-loongarch64.toml @@ -0,0 +1,26 @@ +args = [ + "-machine", "virt", + "-cpu", "la464", + "-nographic", + "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-loongarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/gdb -q -batch -x /usr/bin/gdb-native-smoke.gdb /usr/bin/gdb-native-smoke-target" +success_regex = ["GDB_NATIVE_SMOKE_DONE"] +fail_regex = [ + '(?i)\bpanic(?:ked)?\b', + '(?m)FAIL', + 'gdb: not found', + 'Python Exception', + 'Python initialization failed', + 'Cannot insert breakpoint', + 'ptrace: Function not implemented', + 'No stack', +] +timeout = 180 diff --git a/os/StarryOS/kernel/src/syscall/task/clone.rs b/os/StarryOS/kernel/src/syscall/task/clone.rs index 3995260971..7ef5ed33e7 100644 --- a/os/StarryOS/kernel/src/syscall/task/clone.rs +++ b/os/StarryOS/kernel/src/syscall/task/clone.rs @@ -409,7 +409,7 @@ impl CloneArgs { // Block the parent until the child exec's or exits. if needs_vfork_block { new_proc_data.wait_vfork_done(); - let _ = super::ptrace::ptrace_notify_vfork_done(parent_pid, tid as Pid); + let _ = super::ptrace::ptrace_notify_vfork_done(parent_pid, parent_tid, tid as Pid); } Ok(tid as _) diff --git a/os/StarryOS/kernel/src/syscall/task/ptrace.rs b/os/StarryOS/kernel/src/syscall/task/ptrace.rs index 6c13cc3756..ea77dce54c 100644 --- a/os/StarryOS/kernel/src/syscall/task/ptrace.rs +++ b/os/StarryOS/kernel/src/syscall/task/ptrace.rs @@ -1,6 +1,10 @@ use alloc::{sync::Arc, vec, vec::Vec}; use core::mem::{MaybeUninit, size_of}; -#[cfg(target_arch = "riscv64")] +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] use core::slice; use ax_errno::{AxError, AxResult, LinuxError}; @@ -11,6 +15,12 @@ use starry_process::Pid; use starry_signal::Signo; use starry_vm::{VmMutPtr, VmPtr, vm_read_slice, vm_write_slice}; +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] +use crate::task::PtraceStopFpData; use crate::{ mm::{AddrSpace, IoVec}, task::{AsThread, ProcessData, get_process_data, get_task}, @@ -41,7 +51,11 @@ const PTRACE_SEIZE: u32 = 0x4206; const PTRACE_INTERRUPT: u32 = 0x4207; const NT_PRSTATUS: usize = 1; -#[cfg(target_arch = "riscv64")] +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] const NT_FPREGSET: usize = 2; const PTRACE_O_TRACESYSGOOD: usize = 1; @@ -61,6 +75,23 @@ const PTRACE_EVENT_EXIT: u32 = 6; #[cfg(target_arch = "riscv64")] const EBREAK_INSN: u16 = 0x9002; +#[cfg(target_arch = "aarch64")] +const AARCH64_BRK_INSN: u32 = 0xd4200000; +#[cfg(target_arch = "loongarch64")] +const LOONGARCH_BREAK_INSN: u32 = 0x002a0000; + +#[cfg(target_arch = "riscv64")] +type ArchUserRegs = RiscvUserRegs; +#[cfg(target_arch = "aarch64")] +type ArchUserRegs = Aarch64UserRegs; +#[cfg(target_arch = "loongarch64")] +type ArchUserRegs = LoongarchUserRegs; +#[cfg(target_arch = "riscv64")] +type ArchFpRegs = RiscvFpRegs; +#[cfg(target_arch = "aarch64")] +type ArchFpRegs = Aarch64FpRegs; +#[cfg(target_arch = "loongarch64")] +type ArchFpRegs = LoongarchFpRegs; #[cfg(target_arch = "riscv64")] #[repr(C)] @@ -108,6 +139,46 @@ struct RiscvFpRegs { fcsr: usize, } +#[cfg(target_arch = "aarch64")] +#[repr(C)] +#[derive(Clone, Copy)] +struct Aarch64FpRegs { + vregs: [u128; 32], + fpsr: u32, + fpcr: u32, + __reserved: [u32; 2], +} + +#[cfg(target_arch = "aarch64")] +#[repr(C)] +#[derive(Clone, Copy)] +struct Aarch64UserRegs { + regs: [u64; 31], + sp: u64, + pc: u64, + pstate: u64, +} + +#[cfg(target_arch = "loongarch64")] +#[repr(C)] +#[derive(Clone, Copy)] +struct LoongarchUserRegs { + regs: [u64; 32], + orig_a0: u64, + csr_era: u64, + csr_badv: u64, + reserved: [u64; 10], +} + +#[cfg(target_arch = "loongarch64")] +#[repr(C)] +#[derive(Clone, Copy)] +struct LoongarchFpRegs { + fpr: [u64; 32], + fcc: u64, + fcsr: u32, +} + pub fn sys_ptrace(request: u32, pid: usize, addr: usize, data: usize) -> AxResult { info!("sys_ptrace <= request: {request}, pid: {pid}, addr: {addr:#x}, data: {data:#x}"); @@ -269,7 +340,11 @@ fn ptrace_getsiginfo(pid: usize, data: usize) -> AxResult { .ptrace_stop_siginfo_for(tid) .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; - #[cfg(target_arch = "riscv64")] + #[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" + ))] { let bytes = unsafe { slice::from_raw_parts( @@ -281,14 +356,22 @@ fn ptrace_getsiginfo(pid: usize, data: usize) -> AxResult { Ok(0) } - #[cfg(not(target_arch = "riscv64"))] + #[cfg(not(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" + )))] { let _ = (data, siginfo); Err(AxError::Unsupported) } } -#[cfg(target_arch = "riscv64")] +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] fn ptrace_setsiginfo(pid: usize, data: usize) -> AxResult { if data == 0 { return Err(AxError::InvalidInput); @@ -302,7 +385,11 @@ fn ptrace_setsiginfo(pid: usize, data: usize) -> AxResult { Ok(0) } -#[cfg(not(target_arch = "riscv64"))] +#[cfg(not(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +)))] fn ptrace_setsiginfo(pid: usize, data: usize) -> AxResult { let _ = (pid, data); Err(AxError::Unsupported) @@ -311,7 +398,11 @@ fn ptrace_setsiginfo(pid: usize, data: usize) -> AxResult { fn ptrace_getregset(pid: usize, addr: usize, data: usize) -> AxResult { match addr { NT_PRSTATUS => ptrace_getregset_prstatus(pid, data), - #[cfg(target_arch = "riscv64")] + #[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" + ))] NT_FPREGSET => ptrace_getregset_fpregset(pid, data), _ => Err(AxError::Unsupported), } @@ -320,13 +411,21 @@ fn ptrace_getregset(pid: usize, addr: usize, data: usize) -> AxResult { fn ptrace_setregset(pid: usize, addr: usize, data: usize) -> AxResult { match addr { NT_PRSTATUS => ptrace_setregset_prstatus(pid, data), - #[cfg(target_arch = "riscv64")] + #[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" + ))] NT_FPREGSET => ptrace_setregset_fpregset(pid, data), _ => Err(AxError::Unsupported), } } -#[cfg(target_arch = "riscv64")] +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] fn ptrace_getregs(pid: usize, data: usize) -> AxResult { if data == 0 { return Err(AxError::InvalidInput); @@ -334,21 +433,29 @@ fn ptrace_getregs(pid: usize, data: usize) -> AxResult { let regs = ptrace_read_stopped_user_regs(pid)?; let bytes = unsafe { slice::from_raw_parts( - (®s as *const RiscvUserRegs).cast::(), - size_of::(), + (®s as *const ArchUserRegs).cast::(), + size_of::(), ) }; vm_write_slice(data as *mut u8, bytes)?; Ok(0) } -#[cfg(not(target_arch = "riscv64"))] +#[cfg(not(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +)))] fn ptrace_getregs(pid: usize, data: usize) -> AxResult { let _ = (pid, data); Err(AxError::Unsupported) } -#[cfg(target_arch = "riscv64")] +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] fn ptrace_setregs(pid: usize, data: usize) -> AxResult { if data == 0 { return Err(AxError::InvalidInput); @@ -357,7 +464,11 @@ fn ptrace_setregs(pid: usize, data: usize) -> AxResult { ptrace_write_stopped_user_regs(pid, regs) } -#[cfg(not(target_arch = "riscv64"))] +#[cfg(not(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +)))] fn ptrace_setregs(pid: usize, data: usize) -> AxResult { let _ = (pid, data); Err(AxError::Unsupported) @@ -436,7 +547,11 @@ fn ptrace_interrupt(pid: usize) -> AxResult { Ok(0) } -#[cfg(target_arch = "riscv64")] +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] fn ptrace_getregset_prstatus(pid: usize, data: usize) -> AxResult { if data == 0 { return Err(AxError::InvalidInput); @@ -449,8 +564,8 @@ fn ptrace_getregset_prstatus(pid: usize, data: usize) -> AxResult { let bytes = unsafe { slice::from_raw_parts( - (®s as *const RiscvUserRegs).cast::(), - size_of::(), + (®s as *const ArchUserRegs).cast::(), + size_of::(), ) }; let copy_len = (iov.iov_len as usize).min(bytes.len()); @@ -460,19 +575,27 @@ fn ptrace_getregset_prstatus(pid: usize, data: usize) -> AxResult { Ok(0) } -#[cfg(not(target_arch = "riscv64"))] +#[cfg(not(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +)))] fn ptrace_getregset_prstatus(pid: usize, data: usize) -> AxResult { let _ = (pid, data); Err(AxError::Unsupported) } -#[cfg(target_arch = "riscv64")] +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] fn ptrace_setregset_prstatus(pid: usize, data: usize) -> AxResult { if data == 0 { return Err(AxError::InvalidInput); } let iov = (data as *const IoVec).vm_read()?; - if iov.iov_len < size_of::() as isize { + if iov.iov_len < size_of::() as isize { return Err(AxError::InvalidInput); } @@ -480,30 +603,42 @@ fn ptrace_setregset_prstatus(pid: usize, data: usize) -> AxResult { ptrace_write_stopped_user_regs(pid, regs) } -#[cfg(target_arch = "riscv64")] -fn ptrace_read_stopped_user_regs(pid: usize) -> AxResult { +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] +fn ptrace_read_stopped_user_regs(pid: usize) -> AxResult { let (tracee, tid) = ptrace_stopped_tracee_with_tid(pid)?; let uctx = tracee .ptrace_stop_user_context_for(tid) .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; - Ok(RiscvUserRegs::from(&uctx)) + Ok(ArchUserRegs::from(&uctx)) } -#[cfg(target_arch = "riscv64")] -fn ptrace_read_user_regs(data: usize) -> AxResult { - let mut regs = MaybeUninit::::uninit(); +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] +fn ptrace_read_user_regs(data: usize) -> AxResult { + let mut regs = MaybeUninit::::uninit(); let bytes = unsafe { slice::from_raw_parts_mut( regs.as_mut_ptr().cast::>(), - size_of::(), + size_of::(), ) }; starry_vm::vm_read_slice(data as *const u8, bytes)?; Ok(unsafe { regs.assume_init() }) } -#[cfg(target_arch = "riscv64")] -fn ptrace_write_stopped_user_regs(pid: usize, regs: RiscvUserRegs) -> AxResult { +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] +fn ptrace_write_stopped_user_regs(pid: usize, regs: ArchUserRegs) -> AxResult { let (tracee, tid) = ptrace_stopped_tracee_with_tid(pid)?; let mut uctx = tracee .ptrace_stop_user_context_for(tid) @@ -515,26 +650,34 @@ fn ptrace_write_stopped_user_regs(pid: usize, regs: RiscvUserRegs) -> AxResult AxResult { let _ = (pid, data); Err(AxError::Unsupported) } -#[cfg(target_arch = "riscv64")] +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] fn ptrace_getregset_fpregset(pid: usize, data: usize) -> AxResult { if data == 0 { return Err(AxError::InvalidInput); } let regs = ptrace_read_stopped_fp_regs(pid)?; let mut iov = (data as *const IoVec).vm_read()?; - if iov.iov_len < size_of::() as isize { + if iov.iov_len < size_of::() as isize { return Err(AxError::InvalidInput); } let bytes = unsafe { slice::from_raw_parts( - (®s as *const RiscvFpRegs).cast::(), - size_of::(), + (®s as *const ArchFpRegs).cast::(), + size_of::(), ) }; vm_write_slice(iov.iov_base, bytes)?; @@ -543,45 +686,58 @@ fn ptrace_getregset_fpregset(pid: usize, data: usize) -> AxResult { Ok(0) } -#[cfg(target_arch = "riscv64")] +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] fn ptrace_setregset_fpregset(pid: usize, data: usize) -> AxResult { if data == 0 { return Err(AxError::InvalidInput); } let iov = (data as *const IoVec).vm_read()?; - if iov.iov_len < size_of::() as isize { + if iov.iov_len < size_of::() as isize { return Err(AxError::InvalidInput); } let regs = ptrace_read_user_fpregs(iov.iov_base as usize)?; ptrace_write_stopped_fp_regs(pid, regs) } -#[cfg(target_arch = "riscv64")] -fn ptrace_read_stopped_fp_regs(pid: usize) -> AxResult { +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] +fn ptrace_read_stopped_fp_regs(pid: usize) -> AxResult { let (tracee, tid) = ptrace_stopped_tracee_with_tid(pid)?; let fp_data = tracee .ptrace_stop_fp_data_for(tid) .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; - Ok(RiscvFpRegs { - f: fp_data.0, - fcsr: fp_data.1, - }) + Ok(ArchFpRegs::from(fp_data)) } -#[cfg(target_arch = "riscv64")] -fn ptrace_read_user_fpregs(data: usize) -> AxResult { - let mut regs = MaybeUninit::::uninit(); +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] +fn ptrace_read_user_fpregs(data: usize) -> AxResult { + let mut regs = MaybeUninit::::uninit(); let bytes = unsafe { slice::from_raw_parts_mut( regs.as_mut_ptr().cast::>(), - size_of::(), + size_of::(), ) }; starry_vm::vm_read_slice(data as *const u8, bytes)?; Ok(unsafe { regs.assume_init() }) } -#[cfg(target_arch = "riscv64")] +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] fn ptrace_read_user_siginfo(data: usize) -> AxResult { let mut siginfo = MaybeUninit::::uninit(); let bytes = unsafe { @@ -594,16 +750,37 @@ fn ptrace_read_user_siginfo(data: usize) -> AxResult AxResult { let signo = unsafe { siginfo.__bindgen_anon_1.__bindgen_anon_1.si_signo }; Signo::from_repr(signo as u8).ok_or(AxError::InvalidInput) } -#[cfg(target_arch = "riscv64")] -fn ptrace_write_stopped_fp_regs(pid: usize, regs: RiscvFpRegs) -> AxResult { +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] +fn ptrace_write_stopped_fp_regs(pid: usize, regs: ArchFpRegs) -> AxResult { let (tracee, tid) = ptrace_stopped_tracee_with_tid(pid)?; - if !tracee.set_ptrace_stop_fp_data_for(tid, (regs.f, regs.fcsr)) { + #[cfg(target_arch = "loongarch64")] + let fp_data = { + let mut fp_data = tracee + .ptrace_stop_fp_data_for(tid) + .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; + fp_data.regs = regs.fpr; + fp_data.fcc = loongarch_unpack_fcc(regs.fcc); + fp_data.fcsr = regs.fcsr; + fp_data + }; + #[cfg(not(target_arch = "loongarch64"))] + let fp_data = PtraceStopFpData::from(regs); + + if !tracee.set_ptrace_stop_fp_data_for(tid, fp_data) { return Err(AxError::from(LinuxError::ESRCH)); } Ok(0) @@ -900,6 +1077,107 @@ pub fn ptrace_setup_singlestep( ax_runtime::hal::cpu::asm::flush_icache_all(); } +#[cfg(target_arch = "aarch64")] +pub fn ptrace_setup_singlestep( + tracee: &ProcessData, + tid: Pid, + uctx: &mut ax_runtime::hal::cpu::uspace::UserContext, +) { + let pc = uctx.ip(); + let next_insn_addr = pc.wrapping_add(4); + let aspace = tracee.aspace(); + let mut aspace = aspace.lock(); + + let saved = tracee.take_ptrace_ss_saved_insn_for(tid); + if let Some((saved_addr, saved_insn)) = saved { + let _ = ptrace_write_u32_unlocked(&mut aspace, saved_addr, saved_insn as u32); + } + + let orig_insn = match ptrace_read_u32_unlocked(&aspace, next_insn_addr) { + Ok(insn) => insn, + Err(_) => { + tracee.set_ptrace_ss_saved_insn_for(tid, None); + return; + } + }; + if orig_insn == AARCH64_BRK_INSN { + tracee.set_ptrace_ss_saved_insn_for(tid, None); + ax_runtime::hal::cpu::asm::flush_icache_all(); + return; + } + + let _ = ptrace_write_u32_unlocked(&mut aspace, next_insn_addr, AARCH64_BRK_INSN); + tracee.set_ptrace_ss_saved_insn_for(tid, Some((next_insn_addr, orig_insn as usize))); + ax_runtime::hal::cpu::asm::flush_icache_all(); +} + +#[cfg(target_arch = "loongarch64")] +pub fn ptrace_setup_singlestep( + tracee: &ProcessData, + tid: Pid, + uctx: &mut ax_runtime::hal::cpu::uspace::UserContext, +) { + let next_insn_addr = uctx.ip().wrapping_add(4); + let aspace = tracee.aspace(); + let mut aspace = aspace.lock(); + + let saved = tracee.take_ptrace_ss_saved_insn_for(tid); + if let Some((saved_addr, saved_insn)) = saved { + let _ = ptrace_write_u32_unlocked(&mut aspace, saved_addr, saved_insn as u32); + } + + let orig_insn = match ptrace_read_u32_unlocked(&aspace, next_insn_addr) { + Ok(insn) => insn, + Err(_) => { + tracee.set_ptrace_ss_saved_insn_for(tid, None); + return; + } + }; + if orig_insn == LOONGARCH_BREAK_INSN { + tracee.set_ptrace_ss_saved_insn_for(tid, None); + ax_runtime::hal::cpu::asm::flush_icache_all(); + return; + } + + let _ = ptrace_write_u32_unlocked(&mut aspace, next_insn_addr, LOONGARCH_BREAK_INSN); + tracee.set_ptrace_ss_saved_insn_for(tid, Some((next_insn_addr, orig_insn as usize))); + ax_runtime::hal::cpu::asm::flush_icache_all(); +} + +#[cfg(target_arch = "riscv64")] +pub fn ptrace_restore_singlestep_insn( + tracee: &ProcessData, + tid: Pid, + addr: usize, + insn: usize, +) -> bool { + let aspace = tracee.aspace(); + let mut aspace = aspace.lock(); + let restored = ptrace_write_u16_unlocked(&mut aspace, addr, insn as u16).is_ok(); + ax_runtime::hal::cpu::asm::flush_icache_all(); + if !restored { + tracee.set_ptrace_ss_saved_insn_for(tid, Some((addr, insn))); + } + restored +} + +#[cfg(any(target_arch = "aarch64", target_arch = "loongarch64"))] +pub fn ptrace_restore_singlestep_insn( + tracee: &ProcessData, + tid: Pid, + addr: usize, + insn: usize, +) -> bool { + let aspace = tracee.aspace(); + let mut aspace = aspace.lock(); + let restored = ptrace_write_u32_unlocked(&mut aspace, addr, insn as u32).is_ok(); + ax_runtime::hal::cpu::asm::flush_icache_all(); + if !restored { + tracee.set_ptrace_ss_saved_insn_for(tid, Some((addr, insn))); + } + restored +} + #[cfg(target_arch = "riscv64")] fn riscv_insn_len(first_half: u16) -> usize { if first_half & 0x3 != 0x3 { 2 } else { 4 } @@ -1089,7 +1367,11 @@ fn ptrace_read_u16_unlocked(aspace: &AddrSpace, addr: usize) -> AxResult { Ok(u16::from_ne_bytes(bytes)) } -#[cfg(target_arch = "riscv64")] +#[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +))] fn ptrace_read_u32_unlocked(aspace: &AddrSpace, addr: usize) -> AxResult { let mut bytes = [0u8; size_of::()]; aspace.read(VirtAddr::from_usize(addr), &mut bytes)?; @@ -1102,6 +1384,12 @@ fn ptrace_write_u16_unlocked(aspace: &mut AddrSpace, addr: usize, data: u16) -> Ok(()) } +#[cfg(any(target_arch = "aarch64", target_arch = "loongarch64"))] +fn ptrace_write_u32_unlocked(aspace: &mut AddrSpace, addr: usize, data: u32) -> AxResult { + aspace.write(VirtAddr::from_usize(addr), &data.to_ne_bytes())?; + Ok(()) +} + pub fn ptrace_notify_clone(parent_pid: Pid, parent_tid: Pid, child_pid: Pid, event: u32) -> bool { let Ok(parent) = get_process_data(parent_pid) else { return false; @@ -1150,7 +1438,7 @@ pub fn ptrace_notify_exit(tracee_pid: Pid, exit_code: i32) -> bool { true } -pub fn ptrace_notify_vfork_done(parent_pid: Pid, child_pid: Pid) -> bool { +pub fn ptrace_notify_vfork_done(parent_pid: Pid, parent_tid: Pid, child_pid: Pid) -> bool { let Ok(parent) = get_process_data(parent_pid) else { return false; }; @@ -1161,7 +1449,7 @@ pub fn ptrace_notify_vfork_done(parent_pid: Pid, child_pid: Pid) -> bool { if options & PTRACE_O_TRACEVFORKDONE == 0 { return false; } - parent.set_ptrace_pending_event(parent_pid, PTRACE_EVENT_VFORK_DONE, child_pid as usize); + parent.set_ptrace_pending_event(parent_tid, PTRACE_EVENT_VFORK_DONE, child_pid as usize); true } @@ -1244,3 +1532,198 @@ impl RiscvUserRegs { r.t6 = self.t6; } } + +#[cfg(target_arch = "riscv64")] +impl From for RiscvFpRegs { + fn from(data: PtraceStopFpData) -> Self { + Self { + f: data.regs, + fcsr: data.fcsr, + } + } +} + +#[cfg(target_arch = "riscv64")] +impl From for PtraceStopFpData { + fn from(regs: RiscvFpRegs) -> Self { + Self { + regs: regs.f, + fcsr: regs.fcsr, + } + } +} + +#[cfg(target_arch = "aarch64")] +impl From<&ax_runtime::hal::cpu::uspace::UserContext> for Aarch64UserRegs { + fn from(uctx: &ax_runtime::hal::cpu::uspace::UserContext) -> Self { + Self { + regs: uctx.x, + sp: uctx.sp, + pc: uctx.elr, + pstate: uctx.spsr, + } + } +} + +#[cfg(target_arch = "aarch64")] +impl Aarch64UserRegs { + fn write_to(&self, uctx: &mut ax_runtime::hal::cpu::uspace::UserContext) { + uctx.x = self.regs; + uctx.sp = self.sp; + uctx.elr = self.pc; + uctx.spsr = self.pstate; + } +} + +#[cfg(target_arch = "aarch64")] +impl From for Aarch64FpRegs { + fn from(data: PtraceStopFpData) -> Self { + Self { + vregs: data.regs, + fpsr: data.fpsr, + fpcr: data.fpcr, + __reserved: [0; 2], + } + } +} + +#[cfg(target_arch = "aarch64")] +impl From for PtraceStopFpData { + fn from(regs: Aarch64FpRegs) -> Self { + Self { + regs: regs.vregs, + fpcr: regs.fpcr, + fpsr: regs.fpsr, + } + } +} + +#[cfg(target_arch = "loongarch64")] +impl From<&ax_runtime::hal::cpu::uspace::UserContext> for LoongarchUserRegs { + fn from(uctx: &ax_runtime::hal::cpu::uspace::UserContext) -> Self { + let r = &uctx.regs; + Self { + regs: [ + r.zero as u64, + r.ra as u64, + r.tp as u64, + r.sp as u64, + r.a0 as u64, + r.a1 as u64, + r.a2 as u64, + r.a3 as u64, + r.a4 as u64, + r.a5 as u64, + r.a6 as u64, + r.a7 as u64, + r.t0 as u64, + r.t1 as u64, + r.t2 as u64, + r.t3 as u64, + r.t4 as u64, + r.t5 as u64, + r.t6 as u64, + r.t7 as u64, + r.t8 as u64, + r.u0 as u64, + r.fp as u64, + r.s0 as u64, + r.s1 as u64, + r.s2 as u64, + r.s3 as u64, + r.s4 as u64, + r.s5 as u64, + r.s6 as u64, + r.s7 as u64, + r.s8 as u64, + ], + orig_a0: r.a0 as u64, + csr_era: uctx.era as u64, + csr_badv: 0, + reserved: [0; 10], + } + } +} + +#[cfg(target_arch = "loongarch64")] +impl LoongarchUserRegs { + fn write_to(&self, uctx: &mut ax_runtime::hal::cpu::uspace::UserContext) { + let r = &mut uctx.regs; + r.zero = 0; + r.ra = self.regs[1] as usize; + r.tp = self.regs[2] as usize; + r.sp = self.regs[3] as usize; + r.a0 = self.regs[4] as usize; + r.a1 = self.regs[5] as usize; + r.a2 = self.regs[6] as usize; + r.a3 = self.regs[7] as usize; + r.a4 = self.regs[8] as usize; + r.a5 = self.regs[9] as usize; + r.a6 = self.regs[10] as usize; + r.a7 = self.regs[11] as usize; + r.t0 = self.regs[12] as usize; + r.t1 = self.regs[13] as usize; + r.t2 = self.regs[14] as usize; + r.t3 = self.regs[15] as usize; + r.t4 = self.regs[16] as usize; + r.t5 = self.regs[17] as usize; + r.t6 = self.regs[18] as usize; + r.t7 = self.regs[19] as usize; + r.t8 = self.regs[20] as usize; + r.u0 = self.regs[21] as usize; + r.fp = self.regs[22] as usize; + r.s0 = self.regs[23] as usize; + r.s1 = self.regs[24] as usize; + r.s2 = self.regs[25] as usize; + r.s3 = self.regs[26] as usize; + r.s4 = self.regs[27] as usize; + r.s5 = self.regs[28] as usize; + r.s6 = self.regs[29] as usize; + r.s7 = self.regs[30] as usize; + r.s8 = self.regs[31] as usize; + uctx.era = self.csr_era as usize; + } +} + +#[cfg(target_arch = "loongarch64")] +impl From for LoongarchFpRegs { + fn from(data: PtraceStopFpData) -> Self { + Self { + fpr: data.regs, + fcc: loongarch_pack_fcc(data.fcc), + fcsr: data.fcsr, + } + } +} + +#[cfg(target_arch = "loongarch64")] +impl From for PtraceStopFpData { + fn from(regs: LoongarchFpRegs) -> Self { + Self { + regs: regs.fpr, + fp_high: [0; 32], + fp_lasx_hi0: [0; 32], + fp_lasx_hi1: [0; 32], + fcc: loongarch_unpack_fcc(regs.fcc), + fcsr: regs.fcsr, + } + } +} + +#[cfg(target_arch = "loongarch64")] +fn loongarch_pack_fcc(fcc: [u8; 8]) -> u64 { + let mut packed = 0u64; + for (idx, value) in fcc.into_iter().enumerate() { + packed |= (value as u64) << (idx * 8); + } + packed +} + +#[cfg(target_arch = "loongarch64")] +fn loongarch_unpack_fcc(packed: u64) -> [u8; 8] { + let mut fcc = [0u8; 8]; + for (idx, value) in fcc.iter_mut().enumerate() { + *value = ((packed >> (idx * 8)) & 0xff) as u8; + } + fcc +} diff --git a/os/StarryOS/kernel/src/task/mod.rs b/os/StarryOS/kernel/src/task/mod.rs index 8e50be9d12..b9dab13a20 100644 --- a/os/StarryOS/kernel/src/task/mod.rs +++ b/os/StarryOS/kernel/src/task/mod.rs @@ -703,8 +703,7 @@ pub struct ProcessData { ptrace_ss_saved_insn: SpinNoIrq>, /// FP register snapshot captured when entering ptrace stop, keyed by TID. - /// Stored as raw bytes to avoid arch-specific crate dependency. - ptrace_stop_fp_data: SpinNoIrq>, + ptrace_stop_fp_data: SpinNoIrq>, /// Linux process personality flags. Starry does not randomize userspace /// mappings yet, but debuggers still probe and set ADDR_NO_RANDOMIZE. @@ -734,6 +733,40 @@ pub struct ProcessData { cont_event: Arc, } +#[cfg(target_arch = "riscv64")] +#[derive(Clone, Copy)] +pub struct PtraceStopFpData { + pub regs: [u64; 32], + pub fcsr: usize, +} + +#[cfg(target_arch = "aarch64")] +#[derive(Clone, Copy)] +pub struct PtraceStopFpData { + pub regs: [u128; 32], + pub fpcr: u32, + pub fpsr: u32, +} + +#[cfg(target_arch = "loongarch64")] +#[derive(Clone, Copy)] +pub struct PtraceStopFpData { + pub regs: [u64; 32], + pub fp_high: [u64; 32], + pub fp_lasx_hi0: [u64; 32], + pub fp_lasx_hi1: [u64; 32], + pub fcc: [u8; 8], + pub fcsr: u32, +} + +#[cfg(not(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" +)))] +#[derive(Clone, Copy)] +pub struct PtraceStopFpData; + impl ProcessData { /// Create a new [`ProcessData`]. pub fn new( @@ -1541,23 +1574,62 @@ impl ProcessData { let mut fp = ax_cpu::FpState::default(); fp.save(); fp.fs = riscv::register::sstatus::read().fs(); - self.ptrace_stop_fp_data - .lock() - .insert(tid, (fp.fp, fp.fcsr)); + self.ptrace_stop_fp_data.lock().insert( + tid, + PtraceStopFpData { + regs: fp.fp, + fcsr: fp.fcsr, + }, + ); + } + + #[cfg(target_arch = "aarch64")] + pub fn save_current_fp_for_ptrace(&self, tid: u32) { + let mut fp = ax_cpu::FpState::default(); + fp.save(); + self.ptrace_stop_fp_data.lock().insert( + tid, + PtraceStopFpData { + regs: fp.regs, + fpcr: fp.fpcr, + fpsr: fp.fpsr, + }, + ); } - #[cfg(not(target_arch = "riscv64"))] + #[cfg(target_arch = "loongarch64")] + pub fn save_current_fp_for_ptrace(&self, tid: u32) { + let mut fp = ax_cpu::FpuState::default(); + fp.save(); + self.ptrace_stop_fp_data.lock().insert( + tid, + PtraceStopFpData { + regs: fp.fp, + fp_high: fp.fp_high, + fp_lasx_hi0: fp.fp_lasx_hi0, + fp_lasx_hi1: fp.fp_lasx_hi1, + fcc: fp.fcc, + fcsr: fp.fcsr, + }, + ); + } + + #[cfg(not(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" + )))] pub fn save_current_fp_for_ptrace(&self, _tid: u32) {} #[cfg(target_arch = "riscv64")] pub fn restore_current_fp_for_ptrace(&self, tid: u32, uctx: &mut UserContext) { - let Some((fp, fcsr)) = self.ptrace_stop_fp_data.lock().remove(&tid) else { + let Some(fp) = self.ptrace_stop_fp_data.lock().remove(&tid) else { return; }; let fp_state = ax_cpu::FpState { - fp, - fcsr, + fp: fp.regs, + fcsr: fp.fcsr, fs: riscv::register::sstatus::FS::Dirty, }; @@ -1568,14 +1640,51 @@ impl ProcessData { uctx.sstatus.set_fs(riscv::register::sstatus::FS::Dirty); } - #[cfg(not(target_arch = "riscv64"))] + #[cfg(target_arch = "aarch64")] + pub fn restore_current_fp_for_ptrace(&self, tid: u32, _uctx: &mut UserContext) { + let Some(fp) = self.ptrace_stop_fp_data.lock().remove(&tid) else { + return; + }; + + let fp_state = ax_cpu::FpState { + regs: fp.regs, + fpcr: fp.fpcr, + fpsr: fp.fpsr, + }; + + fp_state.restore(); + } + + #[cfg(target_arch = "loongarch64")] + pub fn restore_current_fp_for_ptrace(&self, tid: u32, _uctx: &mut UserContext) { + let Some(fp) = self.ptrace_stop_fp_data.lock().remove(&tid) else { + return; + }; + + let fp_state = ax_cpu::FpuState { + fp: fp.regs, + fp_high: fp.fp_high, + fp_lasx_hi0: fp.fp_lasx_hi0, + fp_lasx_hi1: fp.fp_lasx_hi1, + fcc: fp.fcc, + fcsr: fp.fcsr, + }; + + fp_state.restore(); + } + + #[cfg(not(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" + )))] pub fn restore_current_fp_for_ptrace(&self, _tid: u32, _uctx: &mut UserContext) {} - pub fn ptrace_stop_fp_data_for(&self, tid: u32) -> Option<([u64; 32], usize)> { + pub fn ptrace_stop_fp_data_for(&self, tid: u32) -> Option { self.ptrace_stop_fp_data.lock().get(&tid).copied() } - pub fn set_ptrace_stop_fp_data_for(&self, tid: u32, data: ([u64; 32], usize)) -> bool { + pub fn set_ptrace_stop_fp_data_for(&self, tid: u32, data: PtraceStopFpData) -> bool { self.ptrace_stop_fp_data.lock().insert(tid, data).is_some() } diff --git a/os/StarryOS/kernel/src/task/signal.rs b/os/StarryOS/kernel/src/task/signal.rs index 0883730e6a..c43f115879 100644 --- a/os/StarryOS/kernel/src/task/signal.rs +++ b/os/StarryOS/kernel/src/task/signal.rs @@ -253,7 +253,11 @@ fn ptrace_stop_current_impl( })); } - #[cfg(target_arch = "riscv64")] + #[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" + ))] { thr.proc_data.save_current_fp_for_ptrace(tid); } diff --git a/os/StarryOS/kernel/src/task/user.rs b/os/StarryOS/kernel/src/task/user.rs index 1955c7fba6..b20264fba9 100644 --- a/os/StarryOS/kernel/src/task/user.rs +++ b/os/StarryOS/kernel/src/task/user.rs @@ -36,7 +36,11 @@ pub fn new_user_task(name: &str, mut uctx: UserContext, set_child_tid: usize) -> if thr.proc_data.is_ptrace_singlestep_for(thr.tid()) && (thr.proc_data.is_ptrace_traceme() || thr.proc_data.is_ptrace_attached()) { - #[cfg(target_arch = "riscv64")] + #[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" + ))] crate::syscall::ptrace_setup_singlestep(&thr.proc_data, thr.tid(), &mut uctx); } @@ -144,14 +148,26 @@ pub fn new_user_task(name: &str, mut uctx: UserContext, set_child_tid: usize) -> let saved_insn = thr.proc_data.take_ptrace_ss_saved_insn_for(thr.tid()); if let Some((addr, insn)) = saved_insn { if addr == uctx.ip() { - let aspace = thr.proc_data.aspace(); - let aspace = aspace.lock(); - let _ = aspace.write( - ax_memory_addr::VirtAddr::from_usize(addr), - &(insn as u16).to_ne_bytes(), + #[cfg(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" + ))] + let _ = crate::syscall::ptrace_restore_singlestep_insn( + &thr.proc_data, + thr.tid(), + addr, + insn, + ); + #[cfg(not(any( + target_arch = "riscv64", + target_arch = "aarch64", + target_arch = "loongarch64" + )))] + thr.proc_data.set_ptrace_ss_saved_insn_for( + thr.tid(), + Some((addr, insn)), ); - #[cfg(target_arch = "riscv64")] - ax_runtime::hal::cpu::asm::flush_icache_all(); } else { thr.proc_data.set_ptrace_ss_saved_insn_for( thr.tid(), diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-gdb/CMakeLists.txt b/test-suit/starryos/qemu-smp1/system/test-ptrace-gdb/CMakeLists.txt index c6b3803d6a..249f1c34f9 100644 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-gdb/CMakeLists.txt +++ b/test-suit/starryos/qemu-smp1/system/test-ptrace-gdb/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) -starry_arch_filtered_executable(test-ptrace-gdb "riscv64" "test-ptrace-gdb skipped on unsupported architecture" src/main.c) +starry_arch_filtered_executable(test-ptrace-gdb "riscv64|aarch64|loongarch64" "test-ptrace-gdb skipped on unsupported architecture" src/main.c) target_compile_options(test-ptrace-gdb PRIVATE -Wall -Wextra -Werror) install(TARGETS test-ptrace-gdb RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-gdb/src/main.c b/test-suit/starryos/qemu-smp1/system/test-ptrace-gdb/src/main.c index c3717fd233..f7e8959e5e 100644 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-gdb/src/main.c +++ b/test-suit/starryos/qemu-smp1/system/test-ptrace-gdb/src/main.c @@ -87,6 +87,13 @@ #define PTRACE_EVENT_VFORK_DONE 5 #define PTRACE_EVENT_EXIT 6 +#define ARCH_RISCV 0 +#define ARCH_AARCH64 0 +#define ARCH_LOONGARCH 0 + +#if defined(__riscv) +#undef ARCH_RISCV +#define ARCH_RISCV 1 struct riscv_user_regs { unsigned long pc; unsigned long ra; @@ -121,11 +128,67 @@ struct riscv_user_regs { unsigned long t5; unsigned long t6; }; +typedef struct riscv_user_regs arch_user_regs; struct riscv_user_fpregs { unsigned long f[32]; unsigned long fcsr; }; +#define PTRACE_GDB_REG_PC(r) ((r)->pc) +#define PTRACE_GDB_REG_SP(r) ((r)->sp) +#define PTRACE_GDB_REG_A0(r) ((r)->a0) +#define PTRACE_GDB_REG_A1(r) ((r)->a1) +#define PTRACE_GDB_REG_A2(r) ((r)->a2) +#define PTRACE_GDB_REG_A3(r) ((r)->a3) +#define PTRACE_GDB_REG_A7(r) ((r)->a7) +#define PTRACE_GDB_REG_S0(r) ((r)->s0) + +#elif defined(__aarch64__) +#undef ARCH_AARCH64 +#define ARCH_AARCH64 1 +struct aarch64_user_regs { + unsigned long regs[31]; + unsigned long sp; + unsigned long pc; + unsigned long pstate; +}; +typedef struct aarch64_user_regs arch_user_regs; + +#define PTRACE_GDB_REG_PC(r) ((r)->pc) +#define PTRACE_GDB_REG_SP(r) ((r)->sp) +#define PTRACE_GDB_REG_A0(r) ((r)->regs[0]) +#define PTRACE_GDB_REG_A1(r) ((r)->regs[1]) +#define PTRACE_GDB_REG_A2(r) ((r)->regs[2]) +#define PTRACE_GDB_REG_A3(r) ((r)->regs[3]) +#define PTRACE_GDB_REG_A7(r) ((r)->regs[8]) +#define PTRACE_GDB_REG_S0(r) ((r)->regs[19]) + +#elif defined(__loongarch__) || defined(__loongarch64) +#undef ARCH_LOONGARCH +#define ARCH_LOONGARCH 1 +struct loongarch_user_regs { + unsigned long regs[32]; + unsigned long orig_a0; + unsigned long csr_era; + unsigned long csr_badv; + unsigned long reserved[10]; +}; +_Static_assert(sizeof(struct loongarch_user_regs) == 45 * sizeof(unsigned long), + "loongarch NT_PRSTATUS must match Linux user_pt_regs"); +typedef struct loongarch_user_regs arch_user_regs; + +#define PTRACE_GDB_REG_PC(r) ((r)->csr_era) +#define PTRACE_GDB_REG_SP(r) ((r)->regs[3]) +#define PTRACE_GDB_REG_A0(r) ((r)->regs[4]) +#define PTRACE_GDB_REG_A1(r) ((r)->regs[5]) +#define PTRACE_GDB_REG_A2(r) ((r)->regs[6]) +#define PTRACE_GDB_REG_A3(r) ((r)->regs[7]) +#define PTRACE_GDB_REG_A7(r) ((r)->regs[11]) +#define PTRACE_GDB_REG_S0(r) ((r)->regs[23]) + +#else +#error "test-ptrace-gdb needs an architecture register layout" +#endif static int fail(const char *msg) { @@ -133,16 +196,20 @@ static int fail(const char *msg) return 1; } -static int get_regs(pid_t pid, struct riscv_user_regs *regs) +static int get_regs(pid_t pid, arch_user_regs *regs) { struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; if (ptrace(PTRACE_GETREGSET, pid, (void *)NT_PRSTATUS, &iov) != 0) { return -1; } + if (iov.iov_len != sizeof(*regs)) { + errno = EINVAL; + return -1; + } return 0; } -static int set_regs(pid_t pid, struct riscv_user_regs *regs) +static int set_regs(pid_t pid, arch_user_regs *regs) { struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; if (ptrace(PTRACE_SETREGSET, pid, (void *)NT_PRSTATUS, &iov) != 0) { @@ -184,6 +251,7 @@ static int wait_stop(pid_t pid, int expected_sig) return 0; } +#if ARCH_RISCV __attribute__((naked, noinline, aligned(4))) static void ss_step_target(void) { __asm__ volatile( @@ -273,6 +341,20 @@ __attribute__((naked, noinline, aligned(4))) static void ss_c_jr_landing(void) "addi a2, zero, 777\n" "ebreak\n"); } +#else +__attribute__((naked, noinline, aligned(4))) static void ss_step_target(void) +{ + __asm__ volatile( +#if ARCH_LOONGARCH + "addi.d $a0, $zero, 123\n" + "break 0\n" +#else + "mov x0, #123\n" + "brk #0\n" +#endif + ); +} +#endif static int wait_sigtrap(pid_t pid) { @@ -285,8 +367,9 @@ static int wait_sigtrap(pid_t pid) return 0; } +#if ARCH_RISCV static int check_singlestep_stops_before_target_side_effect(pid_t pid, - struct riscv_user_regs *regs, + arch_user_regs *regs, unsigned long pc, unsigned long a0, unsigned long a1, @@ -298,12 +381,12 @@ static int check_singlestep_stops_before_target_side_effect(pid_t pid, if (get_regs(pid, regs) != 0) { return fail("getregset before control-flow singlestep"); } - regs->pc = pc; - regs->a0 = a0; - regs->a1 = a1; - regs->a2 = 0; - regs->a3 = a3; - regs->s0 = s0; + PTRACE_GDB_REG_PC(regs) = pc; + PTRACE_GDB_REG_A0(regs) = a0; + PTRACE_GDB_REG_A1(regs) = a1; + PTRACE_GDB_REG_A2(regs) = 0; + PTRACE_GDB_REG_A3(regs) = a3; + PTRACE_GDB_REG_S0(regs) = s0; if (set_regs(pid, regs) != 0) { return fail("setregset control-flow singlestep target"); } @@ -311,11 +394,11 @@ static int check_singlestep_stops_before_target_side_effect(pid_t pid, if (get_regs(pid, regs) != 0) { return fail("getregset after setting control-flow singlestep target"); } - if (regs->pc != pc || regs->a0 != a0 || regs->a1 != a1 || regs->a2 != 0 - || regs->a3 != a3 || regs->s0 != s0) { + if (PTRACE_GDB_REG_PC(regs) != pc || PTRACE_GDB_REG_A0(regs) != a0 || PTRACE_GDB_REG_A1(regs) != a1 || PTRACE_GDB_REG_A2(regs) != 0 + || PTRACE_GDB_REG_A3(regs) != a3 || PTRACE_GDB_REG_S0(regs) != s0) { printf("FAIL: setregset control-flow readback pc=%#lx a0=%#lx a1=%#lx a2=%#lx " "a3=%#lx s0=%#lx\n", - regs->pc, regs->a0, regs->a1, regs->a2, regs->a3, regs->s0); + PTRACE_GDB_REG_PC(regs), PTRACE_GDB_REG_A0(regs), PTRACE_GDB_REG_A1(regs), PTRACE_GDB_REG_A2(regs), PTRACE_GDB_REG_A3(regs), PTRACE_GDB_REG_S0(regs)); return 1; } @@ -329,9 +412,9 @@ static int check_singlestep_stops_before_target_side_effect(pid_t pid, if (get_regs(pid, regs) != 0) { return fail("getregset after control-flow singlestep"); } - if (regs->a2 != 0) { + if (PTRACE_GDB_REG_A2(regs) != 0) { printf("FAIL: singlestep ran target side effect early, pc=%#lx s0=%#lx a2=%#lx\n", - regs->pc, regs->s0, regs->a2); + PTRACE_GDB_REG_PC(regs), PTRACE_GDB_REG_S0(regs), PTRACE_GDB_REG_A2(regs)); return 1; } @@ -345,13 +428,14 @@ static int check_singlestep_stops_before_target_side_effect(pid_t pid, if (get_regs(pid, regs) != 0) { return fail("getregset after control-flow landing"); } - if (regs->a2 != expected_a2) { + if (PTRACE_GDB_REG_A2(regs) != expected_a2) { printf("FAIL: control-flow landing a2=%#lx expected %#lx\n", - regs->a2, expected_a2); + PTRACE_GDB_REG_A2(regs), expected_a2); return 1; } return 0; } +#endif static int test_singlestep(void) { @@ -376,14 +460,14 @@ static int test_singlestep(void) return 1; } - struct riscv_user_regs regs; + arch_user_regs regs; memset(®s, 0, sizeof(regs)); if (get_regs(pid, ®s) != 0) { return fail("getregset initial"); } - regs.pc = (unsigned long)ss_step_target; - regs.a0 = 0; + PTRACE_GDB_REG_PC(®s) = (unsigned long)ss_step_target; + PTRACE_GDB_REG_A0(®s) = 0; if (set_regs(pid, ®s) != 0) { return fail("setregset singlestep target"); } @@ -400,11 +484,12 @@ static int test_singlestep(void) if (get_regs(pid, ®s) != 0) { return fail("getregset after known single-step"); } - if (regs.a0 != 123) { - printf("FAIL: singlestep skipped instruction, a0=%#lx expected 123\n", regs.a0); + if (PTRACE_GDB_REG_A0(®s) != 123) { + printf("FAIL: singlestep skipped instruction, a0=%#lx expected 123\n", PTRACE_GDB_REG_A0(®s)); return 1; } + #if ARCH_RISCV if (check_singlestep_stops_before_target_side_effect( pid, ®s, (unsigned long)ss_branch_target, 7, 7, 0, 0, 222) != 0) { @@ -437,12 +522,17 @@ static int test_singlestep(void) != 0) { return 1; } + #endif if (ptrace(PTRACE_KILL, pid, NULL, NULL) != 0) { return fail("kill after known single-step"); } waitpid(pid, &status, 0); + #if ARCH_RISCV printf(" ok: singlestep handled 32-bit and compressed control-flow instructions\n"); + #else + printf(" ok: singlestep handled a known sequential instruction\n"); + #endif return 0; } @@ -495,6 +585,10 @@ static int test_fpregs(void) { printf("test 3: NT_FPREGSET get/set\n"); +#if !ARCH_RISCV + printf(" SKIP: NT_FPREGSET write-back test is riscv-only for now\n"); + return 0; +#else pid_t pid = fork(); if (pid < 0) { return fail("fork"); @@ -567,6 +661,7 @@ static int test_fpregs(void) } printf(" ok: NT_FPREGSET restored f0 into tracee execution state\n"); return 0; +#endif } static void wait_for_release_then_exit(int fd, int exit_code) @@ -640,13 +735,13 @@ static int test_attach(void) return 1; } - struct riscv_user_regs regs; + arch_user_regs regs; memset(®s, 0, sizeof(regs)); if (get_regs(pid, ®s) != 0) { return fail("getregset after attach"); } printf(" ok: attached, child stopped, pc=%#lx sp=%#lx\n", - regs.pc, regs.sp); + PTRACE_GDB_REG_PC(®s), PTRACE_GDB_REG_SP(®s)); if (ptrace(PTRACE_DETACH, pid, NULL, NULL) != 0) { return fail("detach"); @@ -757,8 +852,17 @@ static int test_setoptions(void) __attribute__((naked, noinline, aligned(4))) static void setregs_pc_landing(void) { __asm__ volatile( +#if ARCH_RISCV "addi a2, zero, 77\n" - "ebreak\n"); + "ebreak\n" +#elif ARCH_LOONGARCH + "addi.d $a2, $zero, 77\n" + "break 0\n" +#else + "mov x2, #77\n" + "brk #0\n" +#endif + ); } static int test_setregs(void) @@ -784,10 +888,13 @@ static int test_setregs(void) return 1; } - struct riscv_user_regs regs; + arch_user_regs regs; memset(®s, 0, sizeof(regs)); - regs.pc = (unsigned long)setregs_pc_landing; - regs.a2 = 0; + if (get_regs(pid, ®s) != 0) { + return fail("getregset before setregset pc landing"); + } + PTRACE_GDB_REG_PC(®s) = (unsigned long)setregs_pc_landing; + PTRACE_GDB_REG_A2(®s) = 0; if (set_regs(pid, ®s) != 0) { return fail("setregset pc landing"); } @@ -802,8 +909,8 @@ static int test_setregs(void) if (get_regs(pid, ®s) != 0) { return fail("getregset after setregset pc landing"); } - if (regs.a2 != 77) { - printf("FAIL: SETREGSET PC landing a2=%#lx expected 77\n", regs.a2); + if (PTRACE_GDB_REG_A2(®s) != 77) { + printf("FAIL: SETREGSET PC landing a2=%#lx expected 77\n", PTRACE_GDB_REG_A2(®s)); return 1; } if (ptrace(PTRACE_KILL, pid, NULL, NULL) != 0) { @@ -906,7 +1013,7 @@ static int test_syscall_trace(void) return fail("setoptions TRACESYSGOOD"); } - struct riscv_user_regs regs; + arch_user_regs regs; int expect_entry = 1; int saw_getpid = 0; for (int stops = 0; stops < 80 && !saw_getpid; stops++) { @@ -923,7 +1030,7 @@ static int test_syscall_trace(void) return fail("getregset syscall stop"); } - if (expect_entry && regs.a7 == SYS_getpid) { + if (expect_entry && PTRACE_GDB_REG_A7(®s) == SYS_getpid) { if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL) != 0) { return fail("ptrace getpid exit"); } @@ -935,8 +1042,8 @@ static int test_syscall_trace(void) if (get_regs(pid, ®s) != 0) { return fail("getregset getpid exit"); } - if (regs.a0 != (unsigned long)pid) { - printf("FAIL: getpid exit a0=%lu, expected pid=%d\n", regs.a0, pid); + if (PTRACE_GDB_REG_A0(®s) != (unsigned long)pid) { + printf("FAIL: getpid exit a0=%lu, expected pid=%d\n", PTRACE_GDB_REG_A0(®s), pid); return 1; } saw_getpid = 1; @@ -1053,8 +1160,17 @@ static int test_signal_resume(void) __attribute__((naked, noinline, aligned(4))) static void legacy_setregs_landing(void) { __asm__ volatile( +#if ARCH_RISCV "addi a2, zero, 88\n" - "ebreak\n"); + "ebreak\n" +#elif ARCH_LOONGARCH + "addi.d $a2, $zero, 88\n" + "break 0\n" +#else + "mov x2, #88\n" + "brk #0\n" +#endif + ); } static int test_legacy_regsets(void) @@ -1080,18 +1196,18 @@ static int test_legacy_regsets(void) return 1; } - struct riscv_user_regs regs; + arch_user_regs regs; memset(®s, 0, sizeof(regs)); if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) != 0) { return fail("legacy getregs"); } - if (regs.pc == 0 || regs.sp == 0) { - printf("FAIL: legacy GETREGS returned pc=%#lx sp=%#lx\n", regs.pc, regs.sp); + if (PTRACE_GDB_REG_PC(®s) == 0 || PTRACE_GDB_REG_SP(®s) == 0) { + printf("FAIL: legacy GETREGS returned pc=%#lx sp=%#lx\n", PTRACE_GDB_REG_PC(®s), PTRACE_GDB_REG_SP(®s)); return 1; } - regs.pc = (unsigned long)legacy_setregs_landing; - regs.a2 = 0; + PTRACE_GDB_REG_PC(®s) = (unsigned long)legacy_setregs_landing; + PTRACE_GDB_REG_A2(®s) = 0; if (ptrace(PTRACE_SETREGS, pid, NULL, ®s) != 0) { return fail("legacy setregs"); } @@ -1105,8 +1221,8 @@ static int test_legacy_regsets(void) if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) != 0) { return fail("legacy getregs after landing"); } - if (regs.a2 != 88) { - printf("FAIL: legacy SETREGS landing a2=%#lx expected 88\n", regs.a2); + if (PTRACE_GDB_REG_A2(®s) != 88) { + printf("FAIL: legacy SETREGS landing a2=%#lx expected 88\n", PTRACE_GDB_REG_A2(®s)); return 1; } if (ptrace(PTRACE_KILL, pid, NULL, NULL) != 0) { @@ -1114,6 +1230,10 @@ static int test_legacy_regsets(void) } waitpid(pid, &status, 0); +#if !ARCH_RISCV + printf(" ok: legacy GETREGS/SETREGS work; legacy FPREGS skipped on this arch\n"); + return 0; +#else pid = fork(); if (pid < 0) { return fail("fork legacy fpregs"); @@ -1168,6 +1288,7 @@ static int test_legacy_regsets(void) printf(" ok: legacy register requests share regset semantics\n"); return 0; +#endif } static int test_siginfo_roundtrip(void) @@ -1311,6 +1432,7 @@ static char clone_thread_stack[16384] __attribute__((aligned(16))); static long raw_clone_thread(void *stack_top) { +#if ARCH_RISCV register long a0 __asm__("a0") = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD; register long a1 __asm__("a1") = (long)stack_top; @@ -1330,6 +1452,57 @@ static long raw_clone_thread(void *stack_top) : "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a7) : "memory"); return a0; +#elif ARCH_AARCH64 + register long x0 __asm__("x0") = + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD; + register long x1 __asm__("x1") = (long)stack_top; + register long x2 __asm__("x2") = 0; + register long x3 __asm__("x3") = 0; + register long x4 __asm__("x4") = 0; + register long x8 __asm__("x8") = SYS_clone; + + __asm__ volatile( + "svc #0\n" + "cbnz x0, 1f\n" + "mov x8, #93\n" + "mov x0, #0\n" + "svc #0\n" + "1:\n" + : "+r"(x0) + : "r"(x1), "r"(x2), "r"(x3), "r"(x4), "r"(x8) + : "memory"); + return x0; +#elif ARCH_LOONGARCH + register long a0 __asm__("$a0") = + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD; + register long a1 __asm__("$a1") = (long)stack_top; + register long a2 __asm__("$a2") = 0; + register long a3 __asm__("$a3") = 0; + register long a4 __asm__("$a4") = 0; + register long a7 __asm__("$a7") = SYS_clone; + __asm__ volatile( + "syscall 0\n" + "bnez $a0, 1f\n" + "li.w $a7, 93\n" + "li.w $a0, 0\n" + "syscall 0\n" + "1:\n" + : "+r"(a0) + : "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a7) + : "memory"); + return a0; +#else +#error "raw_clone_thread needs an architecture syscall sequence" +#endif +} + +static pid_t raw_clone_vfork_child_exit(void) +{ + long ret = syscall(SYS_clone, CLONE_VM | CLONE_VFORK | SIGCHLD, 0, 0, 0, 0); + if (ret == 0) { + _exit(0); + } + return (pid_t)ret; } static int test_traceclone_thread_event(void) @@ -1427,10 +1600,7 @@ static int test_tracevforkdone_event(void) } raise(SIGSTOP); - pid_t child = vfork(); - if (child == 0) { - _exit(0); - } + pid_t child = raw_clone_vfork_child_exit(); if (child < 0) { _exit(2); }