diff --git a/apps/starry/README.md b/apps/starry/README.md index 234d0c5de6..a75e80213f 100644 --- a/apps/starry/README.md +++ b/apps/starry/README.md @@ -86,10 +86,12 @@ Stress configs are available through explicit QEMU config variants; see ## GDB Smoke -The `gdb-smoke` case is a RISC-V QEMU app workflow that prepares a temporary -rootfs overlay with GDB, GDBServer, and tiny debugger smoke targets. +The `gdb-smoke` case is a QEMU app workflow that prepares a temporary rootfs +overlay with GDB, GDBServer, and tiny debugger smoke targets. Native GDB smoke +and gdbserver smoke are available on x86_64, riscv64, aarch64, and loongarch64. ```bash +cargo xtask starry app qemu -t gdb-smoke --arch x86_64 cargo xtask starry app qemu -t gdb-smoke --arch riscv64 cargo xtask starry app qemu -t gdb-smoke --arch riscv64 \ --qemu-config qemu-riscv64-gdbserver.toml diff --git a/apps/starry/gdb-smoke/README.md b/apps/starry/gdb-smoke/README.md index 3e8b7a8e4e..c960c7d903 100644 --- a/apps/starry/gdb-smoke/README.md +++ b/apps/starry/gdb-smoke/README.md @@ -10,6 +10,7 @@ aarch64, loongarch64, and x86_64. Use this command for the automated native GDB batch smoke: ```bash +cargo xtask starry app qemu -t gdb-smoke --arch x86_64 cargo xtask starry app qemu -t gdb-smoke --arch riscv64 ``` @@ -55,6 +56,8 @@ Success requires all `GDB_NATIVE_*` markers from Use this entry when you want an interactive StarryOS shell: ```bash +cargo xtask starry app qemu -t gdb-smoke --arch x86_64 \ + --qemu-config qemu-x86_64-manual.toml cargo xtask starry app qemu -t gdb-smoke --arch riscv64 \ --qemu-config qemu-riscv64-manual.toml ``` @@ -99,6 +102,96 @@ The native target uses a clear call chain: main -> demo_entry -> demo_worker -> native_marker ``` +## Manual Native GDB TUI Demo + +Use the same interactive QEMU entry as the manual native GDB demo: + +```bash +docker exec -it tgoskits-dev cargo xtask starry app qemu -t gdb-smoke --arch riscv64 \ + --qemu-config qemu-riscv64-manual.toml +``` + +Inside StarryOS: + +```bash +gdb-native-tui.sh +``` + +The helper sets a usable `TERM` when needed and starts `gdb -tui` on +`/usr/bin/gdb-native-smoke-target`. To debug a different program, pass it as the +first argument: + +```bash +gdb-native-tui.sh /path/to/program +``` + +For the scripted layout demo, run: + +```bash +gdb-native-tui.sh --demo +``` + +The demo breaks at `native_marker`, runs to the breakpoint, and opens +`layout src`. The target source is copied into the guest under the same +`/workspace/...` path used by the debug info so `layout src` can show the smoke +target source. It stops at the GDB prompt after printing +`GDB_NATIVE_TUI_READY`. + +Inside GDB: + +```gdb +layout asm +layout regs +refresh +tui disable +bt +continue +quit +``` + +For the current StarryOS TTY implementation, prefer disabling TUI before +continuing the target to process exit. `layout regs` is useful while the process +is stopped, but after the inferior exits GDB may still try to refresh the +register window and leave stale full-screen drawing on the serial console. +`refresh` or `Ctrl-L` can repaint the current TUI screen; `tui disable` returns +to the normal CLI view. + +This path is intentionally manual. GDB TUI is a full-screen terminal UI, so it +is not part of the default batch smoke or CI path. + +### PTY Console Variant + +The default manual config uses QEMU `-nographic`, which routes the guest serial +console through the same stdio stream as the QEMU process. For comparing GDB +TUI responsiveness, use the PTY variant: + +```bash +docker exec -it tgoskits-dev cargo xtask starry app qemu -t gdb-smoke --arch riscv64 \ + --qemu-config qemu-riscv64-tui-pty.toml +``` + +QEMU prints a line like: + +```text +char device redirected to /dev/pts/N +``` + +Open a second terminal and attach to that PTY inside the same Docker container. +Use a bidirectional PTY tool such as `socat`: + +```bash +docker exec -it tgoskits-dev sh -lc 'command -v socat >/dev/null || (apt-get update && apt-get install -y --no-install-recommends socat); socat -,raw,echo=0 /dev/pts/N,raw,echo=0' +``` + +If `screen` is already available, this is also fine: + +```bash +docker exec -it tgoskits-dev screen /dev/pts/N +``` + +Then run the same guest-side GDB TUI commands. This path is for manual +comparison only; it is not suitable for default CI. + ## Native Thread GDB Smoke Use this command for the native GDB thread smoke: diff --git a/apps/starry/gdb-smoke/native/gdb-native-tui.gdb b/apps/starry/gdb-smoke/native/gdb-native-tui.gdb new file mode 100644 index 0000000000..94710ac642 --- /dev/null +++ b/apps/starry/gdb-smoke/native/gdb-native-tui.gdb @@ -0,0 +1,9 @@ +set pagination off +set confirm off +set debuginfod enabled off +directory /workspace/apps/starry/gdb-smoke/native/src +break native_marker +run +layout src +echo GDB_NATIVE_TUI_READY\n +echo Use "layout asm" or "layout regs" to switch layouts.\n diff --git a/apps/starry/gdb-smoke/native/gdb-native-tui.sh b/apps/starry/gdb-smoke/native/gdb-native-tui.sh new file mode 100644 index 0000000000..557a0648ab --- /dev/null +++ b/apps/starry/gdb-smoke/native/gdb-native-tui.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -eu + +if [ -z "${TERM:-}" ] || [ "${TERM:-}" = "dumb" ]; then + export TERM=xterm +fi + +if [ "${1:-}" = "--demo" ]; then + shift + exec gdb -q -tui -x /usr/bin/gdb-native-tui.gdb "${1:-/usr/bin/gdb-native-smoke-target}" +fi + +exec gdb -q -tui "${1:-/usr/bin/gdb-native-smoke-target}" diff --git a/apps/starry/gdb-smoke/prebuild.sh b/apps/starry/gdb-smoke/prebuild.sh index 6686074c9d..dfb597ce1d 100755 --- a/apps/starry/gdb-smoke/prebuild.sh +++ b/apps/starry/gdb-smoke/prebuild.sh @@ -149,7 +149,7 @@ install_guest_packages() { --no-interactive \ --force-no-chroot \ --scripts=no \ - add gdb gcc musl-dev + add gdb gcc musl-dev ncurses-terminfo-base } compile_target() { @@ -236,6 +236,8 @@ populate_overlay() { "$app_dir/native/src/main.c" \ /usr/bin/gdb-native-smoke-target \ -Wall -Wextra -Werror -O0 -g + install -Dm0644 "$app_dir/native/src/main.c" \ + "$overlay_dir/workspace/apps/starry/gdb-smoke/native/src/main.c" compile_target \ "$app_dir/native-thread/src/main.c" \ /usr/bin/gdb-native-thread-target \ @@ -261,6 +263,10 @@ populate_overlay() { mkdir -p "$overlay_dir/usr/share" cp -a "$staging_root/usr/share/gdb" "$overlay_dir/usr/share/" fi + if [[ -d "$staging_root/usr/share/terminfo" ]]; then + mkdir -p "$overlay_dir/usr/share" + cp -a "$staging_root/usr/share/terminfo" "$overlay_dir/usr/share/" + fi if [[ -d "$staging_root/usr/lib/python3.12" ]]; then mkdir -p "$overlay_dir/usr/lib" cp -a "$staging_root/usr/lib/python3.12" "$overlay_dir/usr/lib/" @@ -268,6 +274,10 @@ populate_overlay() { install -Dm0755 "$app_dir/native/gdb-native-smoke.gdb" \ "$overlay_dir/usr/bin/gdb-native-smoke.gdb" + install -Dm0755 "$app_dir/native/gdb-native-tui.sh" \ + "$overlay_dir/usr/bin/gdb-native-tui.sh" + install -Dm0644 "$app_dir/native/gdb-native-tui.gdb" \ + "$overlay_dir/usr/bin/gdb-native-tui.gdb" install -Dm0755 "$app_dir/native-thread/gdb-native-threads.gdb" \ "$overlay_dir/usr/bin/gdb-native-threads.gdb" install -Dm0644 "$app_dir/gdbserver/gdbserver-smoke.gdb" \ diff --git a/apps/starry/gdb-smoke/qemu-riscv64-tui-pty.toml b/apps/starry/gdb-smoke/qemu-riscv64-tui-pty.toml new file mode 100644 index 0000000000..04aad5ecb7 --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-riscv64-tui-pty.toml @@ -0,0 +1,22 @@ +args = [ + "-display", "none", + "-m", "512M", + "-cpu", "rv64", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", + "-serial", "pty", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +success_regex = [] +fail_regex = [ + '(?i)\bpanic(?:ked)?\b', + '(?m)FAIL', + 'gdb: not found', + 'Python Exception', + 'Python initialization failed', +] +timeout = 0 diff --git a/apps/starry/gdb/qemu-x86_64-interactive.toml b/apps/starry/gdb-smoke/qemu-x86_64-manual.toml similarity index 95% rename from apps/starry/gdb/qemu-x86_64-interactive.toml rename to apps/starry/gdb-smoke/qemu-x86_64-manual.toml index 939ef212ca..2c5d750303 100644 --- a/apps/starry/gdb/qemu-x86_64-interactive.toml +++ b/apps/starry/gdb-smoke/qemu-x86_64-manual.toml @@ -14,4 +14,4 @@ args = [ uefi = false to_bin = false shell_prefix = "root@starry:" -timeout = 3600 +timeout = 0 diff --git a/apps/starry/gdb/build-x86_64-unknown-none.toml b/apps/starry/gdb/build-x86_64-unknown-none.toml deleted file mode 100644 index e8902193fc..0000000000 --- a/apps/starry/gdb/build-x86_64-unknown-none.toml +++ /dev/null @@ -1,10 +0,0 @@ -target = "x86_64-unknown-none" -env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } -log = "Warn" -features = [ - "ax-driver/virtio-blk", - "ax-driver/virtio-net", - "ax-driver/virtio-gpu", - "ax-driver/virtio-input", - "ax-driver/virtio-socket", -] diff --git a/apps/starry/gdb/prebuild.sh b/apps/starry/gdb/prebuild.sh deleted file mode 100755 index 7523679ea3..0000000000 --- a/apps/starry/gdb/prebuild.sh +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -app_dir="${STARRY_APP_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" -base_rootfs="${STARRY_ROOTFS:-${STARRY_BASE_ROOTFS:-}}" -staging_root="${STARRY_STAGING_ROOT:-}" -overlay_dir="${STARRY_OVERLAY_DIR:-}" - -READELF="${READELF:-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 "$READELF" >/dev/null 2>&1 || missing+=("readelf (binutils)") - - if [[ ${#missing[@]} -eq 0 ]]; then - return - fi - - if ! command -v apt-get >/dev/null 2>&1; then - echo "error: missing required host packages and apt-get is unavailable: ${missing[*]}" >&2 - exit 1 - fi - - echo "installing missing host packages: ${missing[*]}" - apt-get update - apt-get install -y --no-install-recommends "${missing[@]}" -} - -extract_base_rootfs() { - debugfs -R "rdump / $staging_root" "$base_rootfs" -} - -ensure_musl_ld() { - local musl_ld="$staging_root/lib/ld-musl-x86_64.so.1" - - if [[ ! -x "$musl_ld" ]]; then - echo "error: musl dynamic linker not found: $musl_ld" >&2 - exit 1 - fi -} - -install_gdb_package() { - local musl_ld="$staging_root/lib/ld-musl-x86_64.so.1" - local guest_apk="$staging_root/sbin/apk" - local apk_cache="${STARRY_WORKSPACE:-$(cd "$app_dir/../../.." && pwd)}/target/gdb-apk-cache" - - mkdir -p "$apk_cache" - - if [[ ! -x "$guest_apk" ]]; then - echo "error: staging root is missing guest apk: $guest_apk" >&2 - exit 1 - fi - - if [[ -f /etc/resolv.conf ]]; then - cp /etc/resolv.conf "$staging_root/etc/resolv.conf" - fi - - echo "Installing gdb via musl ld (Alpine x86_64)..." - "$musl_ld" \ - --library-path "$staging_root/lib:$staging_root/usr/lib:$staging_root/usr/local/lib" \ - "$guest_apk" \ - --root "$staging_root" \ - --repositories-file "$staging_root/etc/apk/repositories" \ - --keys-dir "$staging_root/etc/apk/keys" \ - --cache-dir "$apk_cache" \ - --update-cache \ - --timeout 60 \ - --no-interactive \ - --force-no-chroot \ - --scripts=no \ - 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_file_to_overlay /usr/bin/python3 0755 - copy_runtime_dependencies /usr/bin/gdb /usr/bin/python3 - - if [[ -d "$staging_root/usr/share/gdb" ]]; then - mkdir -p "$overlay_dir/usr/share" - cp -rL "$staging_root/usr/share/gdb" "$overlay_dir/usr/share/" - fi - if [[ -d "$staging_root/usr/lib/python3.12" ]]; then - mkdir -p "$overlay_dir/usr/lib" - cp -rL "$staging_root/usr/lib/python3.12" "$overlay_dir/usr/lib/" - fi - if [[ -e "$staging_root/usr/lib/python312.zip" ]]; then - copy_file_to_overlay /usr/lib/python312.zip 0644 - fi - for lib in "$staging_root"/usr/lib/libpython3.12.so*; do - if [[ -f "$lib" ]] && [[ ! -L "$lib" ]]; then - copy_file_to_overlay "/usr/lib/$(basename "$lib")" 0644 - fi - done -} - -require_env STARRY_ROOTFS "$base_rootfs" -require_env STARRY_STAGING_ROOT "$staging_root" -require_env STARRY_OVERLAY_DIR "$overlay_dir" - -ensure_host_tools -extract_base_rootfs -ensure_musl_ld -install_gdb_package -populate_overlay diff --git a/apps/starry/gdb/qemu-x86_64.toml b/apps/starry/gdb/qemu-x86_64.toml deleted file mode 100644 index 6f54f5bb99..0000000000 --- a/apps/starry/gdb/qemu-x86_64.toml +++ /dev/null @@ -1,20 +0,0 @@ -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 diff --git a/os/StarryOS/kernel/src/syscall/task/ptrace.rs b/os/StarryOS/kernel/src/syscall/task/ptrace.rs index f2e487f33a..816a083548 100644 --- a/os/StarryOS/kernel/src/syscall/task/ptrace.rs +++ b/os/StarryOS/kernel/src/syscall/task/ptrace.rs @@ -105,6 +105,8 @@ type ArchFpRegs = RiscvFpRegs; type ArchFpRegs = Aarch64FpRegs; #[cfg(target_arch = "loongarch64")] type ArchFpRegs = LoongarchFpRegs; +#[cfg(target_arch = "x86_64")] +type ArchFpRegs = X8664FpRegs; #[cfg(target_arch = "riscv64")] #[repr(C)] @@ -226,6 +228,11 @@ struct X8664UserRegs { gs: u64, } +#[cfg(target_arch = "x86_64")] +#[repr(transparent)] +#[derive(Clone, Copy)] +struct X8664FpRegs(ax_cpu::FxsaveArea); + 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}"); @@ -455,11 +462,10 @@ fn ptrace_getregset(pid: usize, addr: usize, data: usize) -> AxResult { #[cfg(any( target_arch = "riscv64", target_arch = "aarch64", - target_arch = "loongarch64" + target_arch = "loongarch64", + target_arch = "x86_64" ))] NT_FPREGSET => ptrace_getregset_fpregset(pid, data), - #[cfg(target_arch = "x86_64")] - NT_FPREGSET => ptrace_getregset_fpregset_x86_64(pid, data), _ => Err(AxError::Unsupported), } } @@ -470,11 +476,10 @@ fn ptrace_setregset(pid: usize, addr: usize, data: usize) -> AxResult { #[cfg(any( target_arch = "riscv64", target_arch = "aarch64", - target_arch = "loongarch64" + target_arch = "loongarch64", + target_arch = "x86_64" ))] NT_FPREGSET => ptrace_setregset_fpregset(pid, data), - #[cfg(target_arch = "x86_64")] - NT_FPREGSET => ptrace_setregset_fpregset_x86_64(pid, data), _ => Err(AxError::Unsupported), } } @@ -539,7 +544,8 @@ fn ptrace_setregs(pid: usize, data: usize) -> AxResult { #[cfg(any( target_arch = "riscv64", target_arch = "aarch64", - target_arch = "loongarch64" + target_arch = "loongarch64", + target_arch = "x86_64" ))] fn ptrace_getfpregs(pid: usize, data: usize) -> AxResult { if data == 0 { @@ -556,18 +562,6 @@ fn ptrace_getfpregs(pid: usize, data: usize) -> AxResult { Ok(0) } -#[cfg(target_arch = "x86_64")] -fn ptrace_getfpregs(pid: usize, data: usize) -> AxResult { - if data == 0 { - return Err(AxError::InvalidInput); - } - let fp_data = ptrace_read_stopped_fp_x86_64(pid)?; - let bytes = - unsafe { slice::from_raw_parts((&fp_data as *const ax_cpu::FxsaveArea).cast::(), 512) }; - vm_write_slice(data as *mut u8, bytes)?; - Ok(0) -} - #[cfg(not(any( target_arch = "riscv64", target_arch = "aarch64", @@ -582,7 +576,8 @@ fn ptrace_getfpregs(pid: usize, data: usize) -> AxResult { #[cfg(any( target_arch = "riscv64", target_arch = "aarch64", - target_arch = "loongarch64" + target_arch = "loongarch64", + target_arch = "x86_64" ))] fn ptrace_setfpregs(pid: usize, data: usize) -> AxResult { if data == 0 { @@ -592,18 +587,6 @@ fn ptrace_setfpregs(pid: usize, data: usize) -> AxResult { ptrace_write_stopped_fp_regs(pid, regs) } -#[cfg(target_arch = "x86_64")] -fn ptrace_setfpregs(pid: usize, data: usize) -> AxResult { - if data == 0 { - return Err(AxError::InvalidInput); - } - let mut fp_data = MaybeUninit::::zeroed(); - let bytes = - unsafe { slice::from_raw_parts_mut(fp_data.as_mut_ptr().cast::>(), 512) }; - starry_vm::vm_read_slice(data as *const u8, bytes)?; - ptrace_write_stopped_fp_x86_64(pid, unsafe { fp_data.assume_init() }) -} - #[cfg(not(any( target_arch = "riscv64", target_arch = "aarch64", @@ -802,60 +785,11 @@ fn ptrace_write_stopped_user_regs(pid: usize, regs: ArchUserRegs) -> AxResult AxResult { - let (tracee, tid) = ptrace_stopped_tracee_with_tid(pid)?; - let PtraceStopFpData(area) = tracee - .ptrace_stop_fp_data_for(tid) - .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; - Ok(area) -} - -#[cfg(target_arch = "x86_64")] -fn ptrace_write_stopped_fp_x86_64(pid: usize, data: ax_cpu::FxsaveArea) -> AxResult { - let (tracee, tid) = ptrace_stopped_tracee_with_tid(pid)?; - tracee.set_ptrace_stop_fp_data_for(tid, PtraceStopFpData(data)); - Ok(0) -} - -#[cfg(target_arch = "x86_64")] -fn ptrace_getregset_fpregset_x86_64(pid: usize, data: usize) -> AxResult { - if data == 0 { - return Err(AxError::InvalidInput); - } - let fp_data = ptrace_read_stopped_fp_x86_64(pid)?; - let mut iov = (data as *const IoVec).vm_read()?; - if iov.iov_len < 512 { - return Err(AxError::InvalidInput); - } - let bytes = - unsafe { slice::from_raw_parts((&fp_data as *const ax_cpu::FxsaveArea).cast::(), 512) }; - vm_write_slice(iov.iov_base, bytes)?; - iov.iov_len = 512; - (data as *mut IoVec).vm_write(iov)?; - Ok(0) -} - -#[cfg(target_arch = "x86_64")] -fn ptrace_setregset_fpregset_x86_64(pid: usize, data: usize) -> AxResult { - if data == 0 { - return Err(AxError::InvalidInput); - } - let iov = (data as *const IoVec).vm_read()?; - if iov.iov_len < 512 { - return Err(AxError::InvalidInput); - } - let mut fp_data = MaybeUninit::::zeroed(); - let bytes = - unsafe { slice::from_raw_parts_mut(fp_data.as_mut_ptr().cast::>(), 512) }; - starry_vm::vm_read_slice(iov.iov_base, bytes)?; - ptrace_write_stopped_fp_x86_64(pid, unsafe { fp_data.assume_init() }) -} - #[cfg(any( target_arch = "riscv64", target_arch = "aarch64", - target_arch = "loongarch64" + target_arch = "loongarch64", + target_arch = "x86_64" ))] fn ptrace_getregset_fpregset(pid: usize, data: usize) -> AxResult { if data == 0 { @@ -863,7 +797,7 @@ fn ptrace_getregset_fpregset(pid: usize, data: usize) -> AxResult { } 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 < 0 { return Err(AxError::InvalidInput); } let bytes = unsafe { @@ -872,8 +806,9 @@ fn ptrace_getregset_fpregset(pid: usize, data: usize) -> AxResult { size_of::(), ) }; - vm_write_slice(iov.iov_base, bytes)?; - iov.iov_len = bytes.len() as isize; + let copy_len = (iov.iov_len as usize).min(bytes.len()); + vm_write_slice(iov.iov_base, &bytes[..copy_len])?; + iov.iov_len = copy_len as isize; (data as *mut IoVec).vm_write(iov)?; Ok(0) } @@ -881,7 +816,8 @@ fn ptrace_getregset_fpregset(pid: usize, data: usize) -> AxResult { #[cfg(any( target_arch = "riscv64", target_arch = "aarch64", - target_arch = "loongarch64" + target_arch = "loongarch64", + target_arch = "x86_64" ))] fn ptrace_setregset_fpregset(pid: usize, data: usize) -> AxResult { if data == 0 { @@ -898,7 +834,8 @@ fn ptrace_setregset_fpregset(pid: usize, data: usize) -> AxResult { #[cfg(any( target_arch = "riscv64", target_arch = "aarch64", - target_arch = "loongarch64" + target_arch = "loongarch64", + target_arch = "x86_64" ))] fn ptrace_read_stopped_fp_regs(pid: usize) -> AxResult { let (tracee, tid) = ptrace_stopped_tracee_with_tid(pid)?; @@ -911,7 +848,8 @@ fn ptrace_read_stopped_fp_regs(pid: usize) -> AxResult { #[cfg(any( target_arch = "riscv64", target_arch = "aarch64", - target_arch = "loongarch64" + target_arch = "loongarch64", + target_arch = "x86_64" ))] fn ptrace_read_user_fpregs(data: usize) -> AxResult { let mut regs = MaybeUninit::::uninit(); @@ -957,7 +895,8 @@ fn ptrace_siginfo_signo(siginfo: &linux_raw_sys::general::siginfo_t) -> AxResult #[cfg(any( target_arch = "riscv64", target_arch = "aarch64", - target_arch = "loongarch64" + target_arch = "loongarch64", + target_arch = "x86_64" ))] fn ptrace_write_stopped_fp_regs(pid: usize, regs: ArchFpRegs) -> AxResult { let (tracee, tid) = ptrace_stopped_tracee_with_tid(pid)?; @@ -1217,6 +1156,7 @@ fn ptrace_stopped_tracee_with_tid(pid: usize) -> AxResult<(Arc, u32 #[cfg(target_arch = "x86_64")] pub fn ptrace_setup_singlestep( _tracee: &ProcessData, + _tid: Pid, uctx: &mut ax_runtime::hal::cpu::uspace::UserContext, ) { // Set Trap Flag (TF, bit 8) in RFLAGS. @@ -2158,6 +2098,20 @@ fn loongarch_unpack_fcc(packed: u64) -> [u8; 8] { fcc } +#[cfg(target_arch = "x86_64")] +impl From for X8664FpRegs { + fn from(data: PtraceStopFpData) -> Self { + Self(data.0) + } +} + +#[cfg(target_arch = "x86_64")] +impl From for PtraceStopFpData { + fn from(regs: X8664FpRegs) -> Self { + Self(regs.0) + } +} + // --------------------------------------------------------------------------- // x86_64 user-area constants and PEEKUSER/POKEUSER helpers // --------------------------------------------------------------------------- @@ -2228,10 +2182,8 @@ fn ptrace_peekuser(pid: usize, addr: usize, data: usize) -> AxResult { fn ptrace_pokeuser(pid: usize, addr: usize, data: usize) -> AxResult { let range = ptrace_user_word_range_x86_64(addr)?; if range.start >= X86_64_USER_DEBUGREG_OFFSET && range.end <= X86_64_USER_DEBUGREG_END { - // Accept writes to debug-register slots for GDB compatibility even - // though Starry does not program hardware breakpoints yet. let _ = (pid, data); - return Ok(0); + return Err(AxError::from(LinuxError::EIO)); } if range.end > size_of::() { return Err(AxError::from(LinuxError::EIO)); @@ -2404,12 +2356,12 @@ impl From<&ax_runtime::hal::cpu::uspace::UserContext> for X8664UserRegs { rdx: uctx.rdx, rsi: uctx.rsi, rdi: uctx.rdi, - orig_rax: u64::MAX, + orig_rax: uctx.rax, rip: uctx.rip, - cs: 0x33, + cs: uctx.cs, eflags: uctx.rflags, rsp: uctx.rsp, - ss: 0x2b, + ss: uctx.ss, fs_base: uctx.fs_base, gs_base: uctx.gs_base, ds: 0, @@ -2423,9 +2375,7 @@ impl From<&ax_runtime::hal::cpu::uspace::UserContext> for X8664UserRegs { #[cfg(target_arch = "x86_64")] impl X8664UserRegs { fn write_to(&self, uctx: &mut ax_runtime::hal::cpu::uspace::UserContext) -> AxResult<()> { - const LINUX_USER64_CS: u64 = 0x33; - const LINUX_USER_SS: u64 = 0x2b; - if self.cs != LINUX_USER64_CS || self.ss != LINUX_USER_SS { + if self.cs != uctx.cs || self.ss != uctx.ss { return Err(AxError::from(LinuxError::EINVAL)); } diff --git a/os/StarryOS/kernel/src/task/user.rs b/os/StarryOS/kernel/src/task/user.rs index d34ec57d3a..e7c159af89 100644 --- a/os/StarryOS/kernel/src/task/user.rs +++ b/os/StarryOS/kernel/src/task/user.rs @@ -36,14 +36,7 @@ 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(any( - target_arch = "riscv64", - target_arch = "aarch64", - target_arch = "loongarch64" - ))] crate::syscall::ptrace_setup_singlestep(&thr.proc_data, thr.tid(), &mut uctx); - #[cfg(target_arch = "x86_64")] - crate::syscall::ptrace_setup_singlestep(&thr.proc_data, &mut uctx); } let reason = uctx.run(); @@ -198,6 +191,7 @@ pub fn new_user_task(name: &str, mut uctx: UserContext, set_child_tid: usize) -> // not always honour this. Clearing explicitly // prevents an unwanted extra single-step on resume. uctx.rflags &= !(1u64 << 8); + thr.proc_data.set_ptrace_singlestep_for(thr.tid(), false); if let Some(_resume_sig) = ptrace_stop_current(thr, Signo::SIGTRAP, &mut uctx) { diff --git a/test-suit/starryos/qemu-smp1/system/test-gdb-native-batch/src/tracer.c b/test-suit/starryos/qemu-smp1/system/test-gdb-native-batch/src/tracer.c index f991fc258d..f8fe294a89 100644 --- a/test-suit/starryos/qemu-smp1/system/test-gdb-native-batch/src/tracer.c +++ b/test-suit/starryos/qemu-smp1/system/test-gdb-native-batch/src/tracer.c @@ -72,10 +72,6 @@ struct x86_64_user_regs { uint64_t fs_base, gs_base, ds, es, fs, gs; }; -/* Linux amd64 user-space selectors GDB and the kernel agree on. */ -#define USER64_CS 0x33 -#define USER_SS 0x2b - static int fail(const char *msg) { printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); @@ -176,12 +172,12 @@ static int trace_dynamic_target(void) if (getregs(pid, ®s) != 0) { return fail("getregs at exec-stop"); } - if (regs.cs != USER64_CS || regs.ss != USER_SS) { - printf("FAIL: bad user selectors cs=%#llx ss=%#llx (want cs=%#x ss=%#x)\n", - (unsigned long long)regs.cs, (unsigned long long)regs.ss, USER64_CS, USER_SS); + if (regs.cs == 0 || regs.ss == 0) { + printf("FAIL: bad user selectors cs=%#llx ss=%#llx\n", + (unsigned long long)regs.cs, (unsigned long long)regs.ss); return 1; } - printf(" ok: 64-bit user selectors cs=%#llx ss=%#llx\n", + printf(" ok: user selectors cs=%#llx ss=%#llx\n", (unsigned long long)regs.cs, (unsigned long long)regs.ss); /* 3. Plant INT3 at AT_ENTRY and continue: ld-musl runs fully under ptrace. */ 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 249f1c34f9..76c7ccadcf 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|aarch64|loongarch64" "test-ptrace-gdb skipped on unsupported architecture" src/main.c) +starry_arch_filtered_executable(test-ptrace-gdb "x86_64|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 1540765f27..842a23aeea 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 @@ -94,6 +94,7 @@ #define ARCH_RISCV 0 #define ARCH_AARCH64 0 #define ARCH_LOONGARCH 0 +#define ARCH_X86_64 0 #if defined(__riscv) #undef ARCH_RISCV @@ -207,6 +208,64 @@ typedef struct loongarch_user_fpregs arch_user_fpregs; #define PTRACE_GDB_REG_A7(r) ((r)->regs[11]) #define PTRACE_GDB_REG_S0(r) ((r)->regs[23]) +#elif defined(__x86_64__) +#undef ARCH_X86_64 +#define ARCH_X86_64 1 +struct x86_64_user_regs { + uint64_t r15; + uint64_t r14; + uint64_t r13; + uint64_t r12; + uint64_t rbp; + uint64_t rbx; + uint64_t r11; + uint64_t r10; + uint64_t r9; + uint64_t r8; + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rsi; + uint64_t rdi; + uint64_t orig_rax; + uint64_t rip; + uint64_t cs; + uint64_t eflags; + uint64_t rsp; + uint64_t ss; + uint64_t fs_base; + uint64_t gs_base; + uint64_t ds; + uint64_t es; + uint64_t fs; + uint64_t gs; +}; +typedef struct x86_64_user_regs arch_user_regs; + +struct x86_64_user_fpregs { + uint16_t fcw; + uint16_t fsw; + uint16_t ftw; + uint16_t fop; + uint64_t fip; + uint64_t fdp; + uint32_t mxcsr; + uint32_t mxcsr_mask; + uint64_t st[16]; + uint64_t xmm[32]; + uint64_t padding[12]; +}; +typedef struct x86_64_user_fpregs arch_user_fpregs; + +#define PTRACE_GDB_REG_PC(r) ((r)->rip) +#define PTRACE_GDB_REG_SP(r) ((r)->rsp) +#define PTRACE_GDB_REG_A0(r) ((r)->rax) +#define PTRACE_GDB_REG_A1(r) ((r)->rdi) +#define PTRACE_GDB_REG_A2(r) ((r)->rdx) +#define PTRACE_GDB_REG_A3(r) ((r)->rcx) +#define PTRACE_GDB_REG_A7(r) ((r)->orig_rax) +#define PTRACE_GDB_REG_S0(r) ((r)->rbp) + #else #error "test-ptrace-gdb needs an architecture register layout" #endif @@ -219,6 +278,8 @@ static void fpregs_set_f0(arch_user_fpregs *regs, unsigned long value) regs->vregs[0] = (__uint128_t)value; #elif ARCH_LOONGARCH regs->fpr[0] = value; +#elif ARCH_X86_64 + regs->xmm[0] = value; #endif } @@ -230,6 +291,8 @@ static void fpregs_set_f1(arch_user_fpregs *regs, unsigned long value) regs->vregs[1] = (__uint128_t)value; #elif ARCH_LOONGARCH regs->fpr[1] = value; +#elif ARCH_X86_64 + regs->xmm[2] = value; #endif } @@ -241,6 +304,8 @@ static unsigned long fpregs_get_f0(const arch_user_fpregs *regs) return (unsigned long)regs->vregs[0]; #elif ARCH_LOONGARCH return regs->fpr[0]; +#elif ARCH_X86_64 + return regs->xmm[0]; #endif } @@ -252,6 +317,8 @@ static unsigned long fpregs_get_f1(const arch_user_fpregs *regs) return (unsigned long)regs->vregs[1]; #elif ARCH_LOONGARCH return regs->fpr[1]; +#elif ARCH_X86_64 + return regs->xmm[2]; #endif } @@ -264,6 +331,8 @@ static unsigned long read_f0_bits(void) __asm__ volatile("fmov %0, d0" : "=r"(f0_bits)); #elif ARCH_LOONGARCH __asm__ volatile("movfr2gr.d %0, $f0" : "=r"(f0_bits)); +#elif ARCH_X86_64 + __asm__ volatile("movq %%xmm0, %0" : "=r"(f0_bits)); #endif return f0_bits; } @@ -551,6 +620,13 @@ __attribute__((naked, noinline, aligned(4))) static void ss_jirl_landing(void) "addi.d $a2, $zero, 666\n" "break 0\n"); } +#elif ARCH_X86_64 +__attribute__((naked, noinline, aligned(4))) static void ss_step_target(void) +{ + __asm__ volatile( + "mov $123, %rax\n" + "int3\n"); +} #endif static int wait_sigtrap(pid_t pid) @@ -1323,6 +1399,9 @@ __attribute__((naked, noinline, aligned(4))) static void setregs_pc_landing(void #elif ARCH_LOONGARCH "addi.d $a2, $zero, 77\n" "break 0\n" +#elif ARCH_X86_64 + "mov $77, %rdx\n" + "int3\n" #else "mov x2, #77\n" "brk #0\n" @@ -1631,6 +1710,9 @@ __attribute__((naked, noinline, aligned(4))) static void legacy_setregs_landing( #elif ARCH_LOONGARCH "addi.d $a2, $zero, 88\n" "break 0\n" +#elif ARCH_X86_64 + "mov $88, %rdx\n" + "int3\n" #else "mov x2, #88\n" "brk #0\n" @@ -1695,7 +1777,7 @@ static int test_legacy_regsets(void) } waitpid(pid, &status, 0); -#if !(ARCH_RISCV || ARCH_LOONGARCH) +#if !(ARCH_RISCV || ARCH_LOONGARCH || ARCH_X86_64) printf(" ok: legacy GETREGS/SETREGS work; legacy FPREGS skipped on this arch\n"); return 0; #else @@ -1956,6 +2038,26 @@ static long raw_clone_thread(void *stack_top) : "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a7) : "memory"); return a0; +#elif ARCH_X86_64 + register long rax __asm__("rax") = SYS_clone; + register long rdi __asm__("rdi") = + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD; + register long rsi __asm__("rsi") = (long)stack_top; + register long rdx __asm__("rdx") = 0; + register long r10 __asm__("r10") = 0; + register long r8 __asm__("r8") = 0; + __asm__ volatile( + "syscall\n" + "test %%rax, %%rax\n" + "jnz 1f\n" + "mov $60, %%rax\n" + "xor %%rdi, %%rdi\n" + "syscall\n" + "1:\n" + : "+r"(rax) + : "r"(rdi), "r"(rsi), "r"(rdx), "r"(r10), "r"(r8) + : "rcx", "r11", "memory"); + return rax; #else #error "raw_clone_thread needs an architecture syscall sequence" #endif @@ -1963,11 +2065,30 @@ static long raw_clone_thread(void *stack_top) static pid_t raw_clone_vfork_child_exit(void) { +#if ARCH_X86_64 + long rax = SYS_clone; + __asm__ volatile( + "xor %%r10d, %%r10d\n" + "xor %%r8d, %%r8d\n" + "xor %%r9d, %%r9d\n" + "syscall\n" + "test %%rax, %%rax\n" + "jnz 1f\n" + "mov $60, %%rax\n" + "xor %%rdi, %%rdi\n" + "syscall\n" + "1:\n" + : "+a"(rax) + : "D"((long)(CLONE_VM | CLONE_VFORK | SIGCHLD)), "S"(0L), "d"(0L) + : "rcx", "r8", "r9", "r10", "r11", "memory"); + return (pid_t)rax; +#else long ret = syscall(SYS_clone, CLONE_VM | CLONE_VFORK | SIGCHLD, 0, 0, 0, 0); if (ret == 0) { _exit(0); } return (pid_t)ret; +#endif } static int test_traceclone_thread_event(void) diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint-reinsert/CMakeLists.txt b/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint-reinsert/CMakeLists.txt deleted file mode 100644 index 2d99e879ca..0000000000 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint-reinsert/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-ptrace-x86-breakpoint-reinsert C) -include(${CMAKE_CURRENT_LIST_DIR}/../common/starry_arch_filter.cmake) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) - -starry_arch_filtered_executable(test-ptrace-x86-breakpoint-reinsert "x86_64" "test-ptrace-x86-breakpoint-reinsert skipped on unsupported architecture" src/main.c) -target_compile_options(test-ptrace-x86-breakpoint-reinsert PRIVATE -Wall -Wextra -Werror -O0 -g) - -install(TARGETS test-ptrace-x86-breakpoint-reinsert RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint-reinsert/src/main.c b/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint-reinsert/src/main.c deleted file mode 100644 index 9b560f5cda..0000000000 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint-reinsert/src/main.c +++ /dev/null @@ -1,264 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef PTRACE_GETREGSET -#define PTRACE_GETREGSET 0x4204 -#endif -#ifndef PTRACE_SETREGSET -#define PTRACE_SETREGSET 0x4205 -#endif -#ifndef PTRACE_TRACEME -#define PTRACE_TRACEME 0 -#endif -#ifndef PTRACE_SINGLESTEP -#define PTRACE_SINGLESTEP 9 -#endif -#ifndef PTRACE_CONT -#define PTRACE_CONT 7 -#endif -#ifndef PTRACE_PEEKDATA -#define PTRACE_PEEKDATA 2 -#endif -#ifndef PTRACE_POKEDATA -#define PTRACE_POKEDATA 5 -#endif -#ifndef NT_PRSTATUS -#define NT_PRSTATUS 1 -#endif - -struct x86_64_user_regs { - uint64_t r15; - uint64_t r14; - uint64_t r13; - uint64_t r12; - uint64_t rbp; - uint64_t rbx; - uint64_t r11; - uint64_t r10; - uint64_t r9; - uint64_t r8; - uint64_t rax; - uint64_t rcx; - uint64_t rdx; - uint64_t rsi; - uint64_t rdi; - uint64_t orig_rax; - uint64_t rip; - uint64_t cs; - uint64_t eflags; - uint64_t rsp; - uint64_t ss; - uint64_t fs_base; - uint64_t gs_base; - uint64_t ds; - uint64_t es; - uint64_t fs; - uint64_t gs; -}; - -__attribute__((noinline)) static int native_marker(int value) -{ - return value + 1; -} - -static int fail(const char *msg) -{ - printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); - return 1; -} - -static long ptrace_call(int request, pid_t pid, void *addr, void *data) -{ -#ifdef __APPLE__ - return ptrace(request, pid, (caddr_t)addr, (int)(intptr_t)data); -#else - return ptrace(request, pid, addr, data); -#endif -} - -static int wait_stopped(pid_t pid, int *status, int expected_sig) -{ - if (waitpid(pid, status, WUNTRACED) != pid) { - return fail("waitpid"); - } - if (!WIFSTOPPED(*status) || WSTOPSIG(*status) != expected_sig) { - printf("FAIL: expected stop signal %d, status=%#x\n", expected_sig, *status); - return 1; - } - return 0; -} - -static int getregset(pid_t pid, struct x86_64_user_regs *regs) -{ - struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; - if (ptrace_call(PTRACE_GETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { - return -1; - } - if ((size_t)iov.iov_len != sizeof(*regs)) { - errno = EIO; - return -1; - } - return 0; -} - -static int setregset(pid_t pid, const struct x86_64_user_regs *regs) -{ - struct iovec iov = {.iov_base = (void *)regs, .iov_len = sizeof(*regs)}; - if (ptrace_call(PTRACE_SETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { - return -1; - } - return 0; -} - -static int test_breakpoint_reinsert(void) -{ - printf("test 1: x86_64 breakpoint restore + singlestep + reinsert\n"); - - pid_t pid = fork(); - if (pid < 0) { - return fail("fork"); - } - - if (pid == 0) { - if (ptrace_call(PTRACE_TRACEME, 0, NULL, NULL) != 0) { - _exit(100); - } - raise(SIGSTOP); - int value = native_marker(40); - value = native_marker(value); - _exit(value == 42 ? 42 : 1); - } - - int status = 0; - if (wait_stopped(pid, &status, SIGSTOP) != 0) { - return 1; - } - printf(" ok: child stopped with SIGSTOP\n"); - - uintptr_t bp_addr = (uintptr_t)native_marker; - errno = 0; - long orig_word = ptrace_call(PTRACE_PEEKDATA, pid, (void *)bp_addr, NULL); - if (orig_word == -1 && errno != 0) { - return fail("PTRACE_PEEKDATA original instruction"); - } - unsigned long bp_word = ((unsigned long)orig_word & ~0xffUL) | 0xccUL; - if (ptrace_call(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)bp_word) != 0) { - return fail("PTRACE_POKEDATA install breakpoint"); - } - printf(" ok: installed int3 at native_marker\n"); - - if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { - return fail("PTRACE_CONT first call"); - } - if (wait_stopped(pid, &status, SIGTRAP) != 0) { - return 1; - } - printf(" ok: first breakpoint hit\n"); - - struct x86_64_user_regs regs; - memset(®s, 0, sizeof(regs)); - if (getregset(pid, ®s) != 0) { - return fail("getregset at first breakpoint"); - } - if (regs.rip != bp_addr + 1 && regs.rip != bp_addr) { - printf("FAIL: RIP=%#llx not at breakpoint %#llx\n", - (unsigned long long)regs.rip, (unsigned long long)bp_addr); - return 1; - } - - if (ptrace_call(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)orig_word) != 0) { - return fail("PTRACE_POKEDATA restore original instruction"); - } - regs.rip = bp_addr; - if (setregset(pid, ®s) != 0) { - return fail("setregset rewind RIP to breakpoint"); - } - printf(" ok: restored original byte and rewound RIP\n"); - - if (ptrace_call(PTRACE_SINGLESTEP, pid, NULL, NULL) != 0) { - return fail("PTRACE_SINGLESTEP over original instruction"); - } - if (wait_stopped(pid, &status, SIGTRAP) != 0) { - return 1; - } - printf(" ok: single-step completed original instruction\n"); - - memset(®s, 0, sizeof(regs)); - if (getregset(pid, ®s) != 0) { - return fail("getregset after singlestep"); - } - if (regs.rip == bp_addr) { - printf("FAIL: RIP did not advance after single-step: %#llx\n", - (unsigned long long)regs.rip); - return 1; - } - - if (ptrace_call(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)bp_word) != 0) { - return fail("PTRACE_POKEDATA reinsert breakpoint"); - } - printf(" ok: reinserted breakpoint at native_marker\n"); - - if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { - return fail("PTRACE_CONT second call"); - } - if (wait_stopped(pid, &status, SIGTRAP) != 0) { - return 1; - } - printf(" ok: second breakpoint hit after reinsertion\n"); - - memset(®s, 0, sizeof(regs)); - if (getregset(pid, ®s) != 0) { - return fail("getregset at second breakpoint"); - } - if (regs.rip != bp_addr + 1 && regs.rip != bp_addr) { - printf("FAIL: second-hit RIP=%#llx not at breakpoint %#llx\n", - (unsigned long long)regs.rip, (unsigned long long)bp_addr); - return 1; - } - - if (ptrace_call(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)orig_word) != 0) { - return fail("PTRACE_POKEDATA restore after second hit"); - } - regs.rip = bp_addr; - if (setregset(pid, ®s) != 0) { - return fail("setregset rewind RIP after second hit"); - } - - if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { - return fail("PTRACE_CONT final exit"); - } - if (waitpid(pid, &status, 0) != pid) { - return fail("waitpid final exit"); - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) { - printf("FAIL: expected exit 42, status=%#x\n", status); - return 1; - } - printf(" ok: child exited with 42 after breakpoint reinsertion flow\n"); - return 0; -} - -int main(void) -{ - int pass = 0; - int fail_count = 0; - - if (test_breakpoint_reinsert() == 0) { - pass++; - } else { - fail_count++; - } - - printf("DONE: %d pass, %d fail\n", pass, fail_count); - return fail_count > 0 ? 1 : 0; -} diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint/CMakeLists.txt b/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint/CMakeLists.txt deleted file mode 100644 index 2e513ef3da..0000000000 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-ptrace-x86-breakpoint C) -include(${CMAKE_CURRENT_LIST_DIR}/../common/starry_arch_filter.cmake) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) - -starry_arch_filtered_executable(test-ptrace-x86-breakpoint "x86_64" "test-ptrace-x86-breakpoint skipped on unsupported architecture" src/main.c) -target_compile_options(test-ptrace-x86-breakpoint PRIVATE -Wall -Wextra -Werror) - -install(TARGETS test-ptrace-x86-breakpoint RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint/src/main.c b/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint/src/main.c deleted file mode 100644 index f71e42d657..0000000000 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-breakpoint/src/main.c +++ /dev/null @@ -1,389 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef PTRACE_GETREGSET -#define PTRACE_GETREGSET 0x4204 -#endif -#ifndef PTRACE_SETREGSET -#define PTRACE_SETREGSET 0x4205 -#endif -#ifndef PTRACE_SINGLESTEP -#define PTRACE_SINGLESTEP 9 -#endif -#ifndef NT_PRSTATUS -#define NT_PRSTATUS 1 -#endif - -struct x86_64_user_regs { - uint64_t r15; - uint64_t r14; - uint64_t r13; - uint64_t r12; - uint64_t rbp; - uint64_t rbx; - uint64_t r11; - uint64_t r10; - uint64_t r9; - uint64_t r8; - uint64_t rax; - uint64_t rcx; - uint64_t rdx; - uint64_t rsi; - uint64_t rdi; - uint64_t orig_rax; - uint64_t rip; - uint64_t cs; - uint64_t eflags; - uint64_t rsp; - uint64_t ss; - uint64_t fs_base; - uint64_t gs_base; - uint64_t ds; - uint64_t es; - uint64_t fs; - uint64_t gs; -}; - -__attribute__((noinline)) static int target_function(void) -{ - return 42; -} - - -static int fail(const char *msg) -{ - printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); - return 1; -} - -static int getregset(pid_t pid, struct x86_64_user_regs *regs) -{ - struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; - if (ptrace(PTRACE_GETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { - return -1; - } - if ((size_t)iov.iov_len != sizeof(*regs)) { - errno = EIO; - return -1; - } - return 0; -} - -static int setregset(pid_t pid, const struct x86_64_user_regs *regs) -{ - struct iovec iov = {.iov_base = (void *)regs, .iov_len = sizeof(*regs)}; - if (ptrace(PTRACE_SETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { - return -1; - } - return 0; -} - -/* Test 1: minimum closed-loop software breakpoint. - * - * tracer writes 0xCC; child hits breakpoint; tracer restores original - * byte and continues from the pre-int3 address. This validates the - * basic breakpoint stop and register read-back but does NOT exercise - * PTRACE_SINGLESTEP. */ -static int test_breakpoint(void) -{ - printf("test 1: x86_64 software breakpoint (int3)\n"); - - pid_t pid = fork(); - if (pid < 0) { - return fail("fork"); - } - - if (pid == 0) { - if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { - _exit(100); - } - /* Trigger initial ptrace-stop via int3 instead of raise(SIGSTOP). - * The #BP exception path preserves the CPU exception frame with - * valid segment selectors (CS/SS) and stack pointer. */ - __asm__ volatile ("int3" ::: "memory"); - target_function(); - _exit(42); - } - - int status = 0; - if (waitpid(pid, &status, WUNTRACED) != pid) { - return fail("waitpid initial stop"); - } - if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { - printf(" note: unexpected initial stop signal=%d status=%#x\n", - WSTOPSIG(status), status); - } - printf(" ok: child stopped (signal=%d)\n", WSTOPSIG(status)); - - struct x86_64_user_regs regs; - memset(®s, 0, sizeof(regs)); - if (getregset(pid, ®s) != 0) { - return fail("getregset at initial stop"); - } - printf(" ok: initial RIP=%#llx RSP=%#llx\n", - (unsigned long long)regs.rip, (unsigned long long)regs.rsp); - - if (regs.rsp == 0 || regs.rsp == 1) { - printf("FAIL: bogus RSP=%#llx (expected valid stack pointer)\n", - (unsigned long long)regs.rsp); - return 1; - } - - /* x86 int3 is 1 byte (0xCC). CPU pushes RIP of next instruction, so - * the saved RIP in the exception frame points to int3_addr + 1. - * StarryOS does not adjust RIP backward for ptrace breakpoint stops, - * so we observe RIP = int3 + 1 here. Save the pre-int3 RIP as the - * "continue point" for later. */ - uintptr_t continue_rip = regs.rip; - - uintptr_t bp_addr = (uintptr_t)target_function; - printf(" target_function at %p\n", (void *)bp_addr); - - errno = 0; - long orig_word = ptrace(PTRACE_PEEKDATA, pid, (void *)bp_addr, NULL); - if (orig_word == -1 && errno != 0) { - return fail("PEEKDATA target_function"); - } - - unsigned long bp_word = ((unsigned long)orig_word & ~0xffUL) | 0xccUL; - if (ptrace(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)bp_word) != 0) { - return fail("POKEDATA write int3"); - } - printf(" ok: wrote int3 (0xCC) at target_function\n"); - - regs.rip = bp_addr; - if (setregset(pid, ®s) != 0) { - return fail("setregset redirect RIP to target_function"); - } - printf(" ok: redirected RIP=%#llx -> %#llx\n", - (unsigned long long)continue_rip, (unsigned long long)bp_addr); - - if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { - return fail("PTRACE_CONT to breakpoint"); - } - - if (waitpid(pid, &status, WUNTRACED) != pid) { - return fail("waitpid breakpoint"); - } - if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { - printf("FAIL: expected SIGTRAP after breakpoint, status=%#x\n", status); - return 1; - } - printf(" ok: child stopped with SIGTRAP\n"); - - memset(®s, 0, sizeof(regs)); - if (getregset(pid, ®s) != 0) { - return fail("getregset at breakpoint"); - } - - /* After int3, RIP = bp_addr + 1. Verify it's near the breakpoint. */ - if (regs.rip != bp_addr && regs.rip != bp_addr + 1) { - printf("FAIL: RIP=%#llx not near breakpoint addr %#llx\n", - (unsigned long long)regs.rip, (unsigned long long)bp_addr); - return 1; - } - printf(" ok: RIP=%#llx at breakpoint %#llx (offset +%lld)\n", - (unsigned long long)regs.rip, (unsigned long long)bp_addr, - (long long)(regs.rip - bp_addr)); - - if (ptrace(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)orig_word) != 0) { - return fail("POKEDATA restore original byte"); - } - printf(" ok: restored original instruction byte\n"); - - /* Rewind RIP to the original continue point (after the initial int3). - * From there the child will call target_function() normally via the - * call instruction, then _exit(42). */ - regs.rip = continue_rip; - if (setregset(pid, ®s) != 0) { - return fail("setregset rewind RIP"); - } - printf(" ok: rewound RIP to continue point %#llx\n", - (unsigned long long)continue_rip); - - if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { - return fail("PTRACE_CONT after breakpoint restore"); - } - - if (waitpid(pid, &status, 0) != pid) { - return fail("waitpid exit after restore"); - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) { - printf("FAIL: expected exit code 42, status=%#x\n", status); - return 1; - } - printf(" ok: child exited with 42\n"); - - return 0; -} - -/* Test 2: x86_64 PTRACE_SINGLESTEP validation. - * - * Uses the initial `int3` in the child as the breakpoint target: - * - * 1. child hits initial int3, stops with SIGTRAP - * 2. tracer replaces int3 (0xCC) with NOP (0x90) at that address - * 3. tracer rewinds RIP to the int3 address - * 4. tracer issues PTRACE_SINGLESTEP - * 5. child executes the NOP and stops with SIGTRAP (#DB) - * 6. tracer verifies RIP advanced past the int3 site - * 7. tracer restores int3, then CONT - * 8. child continues to completion (target_function → _exit(42)) - * - * This validates the x86_64 Trap-flag-based single-step mechanism. */ -static int test_breakpoint_singlestep_restore(void) -{ - printf("test 2: x86_64 PTRACE_SINGLESTEP\n"); - - pid_t pid = fork(); - if (pid < 0) { - return fail("fork"); - } - - if (pid == 0) { - if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { - _exit(100); - } - /* Initial stop via int3 — also serves as the breakpoint - * the tracer will single-step over. */ - __asm__ volatile ( - ".global test_bp_addr\n" - "test_bp_addr:\n" - " int3\n" - ::: "memory" - ); - target_function(); - _exit(42); - } - - int status = 0; - if (waitpid(pid, &status, WUNTRACED) != pid) { - return fail("waitpid initial int3 stop"); - } - if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { - printf("FAIL: expected SIGTRAP, status=%#x\n", status); - return 1; - } - printf(" ok: initial int3 SIGTRAP stop\n"); - - struct x86_64_user_regs regs; - memset(®s, 0, sizeof(regs)); - if (getregset(pid, ®s) != 0) { - return fail("getregset at initial stop"); - } - - extern char test_bp_addr; - uintptr_t bp_addr = (uintptr_t)&test_bp_addr; - /* int3 is 1 byte; after #BP the saved RIP = bp_addr + 1. */ - if (regs.rip != bp_addr + 1) { - printf("FAIL: expected RIP=%#llx (bp+1), got %#llx\n", - (unsigned long long)(bp_addr + 1), (unsigned long long)regs.rip); - return 1; - } - printf(" ok: RIP=%#llx (bp+1)\n", (unsigned long long)regs.rip); - - /* Step 1: replace int3 (0xCC) with NOP (0x90) at the breakpoint site, - * preserving the surrounding bytes (POKEDATA is word-sized). */ - errno = 0; - long orig_word = ptrace(PTRACE_PEEKDATA, pid, (void *)bp_addr, NULL); - if (orig_word == -1 && errno != 0) { - return fail("PEEKDATA at bp"); - } - unsigned long nop_word = ((unsigned long)orig_word & ~0xffUL) | 0x90UL; - if (ptrace(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)nop_word) != 0) { - return fail("POKEDATA write NOP"); - } - printf(" ok: replaced int3 with NOP at %#llx\n", (unsigned long long)bp_addr); - - /* Step 2: rewind RIP to the int3/NOP address. */ - regs.rip = bp_addr; - if (setregset(pid, ®s) != 0) { - return fail("setregset rewind RIP"); - } - - /* Step 3: PTRACE_SINGLESTEP — the CPU executes the NOP (1 byte) - * and then fires #DB because TF was set in RFLAGS. */ - if (ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL) != 0) { - return fail("PTRACE_SINGLESTEP"); - } - printf(" ok: PTRACE_SINGLESTEP issued\n"); - - /* Step 4: wait for the #DB stop. */ - if (waitpid(pid, &status, WUNTRACED) != pid) { - return fail("waitpid singlestep stop"); - } - if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { - printf("FAIL: expected SIGTRAP after singlestep, status=%#x signal=%d\n", - status, WSTOPSIG(status)); - return 1; - } - printf(" ok: singlestep SIGTRAP stop\n"); - - /* Step 5: verify RIP advanced past the breakpoint (NOP is 1 byte). */ - memset(®s, 0, sizeof(regs)); - if (getregset(pid, ®s) != 0) { - return fail("getregset after singlestep"); - } - if (regs.rip != bp_addr + 1) { - printf("FAIL: RIP=%#llx, expected %#llx (bp+1)\n", - (unsigned long long)regs.rip, (unsigned long long)(bp_addr + 1)); - return 1; - } - printf(" ok: RIP=%#llx advanced past breakpoint (offset +1)\n", - (unsigned long long)regs.rip); - - /* Step 6: restore int3 (for correctness; doesn't affect the test - * since execution has already passed this point). */ - unsigned long cc_word = ((unsigned long)orig_word & ~0xffUL) | 0xccUL; - if (ptrace(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)cc_word) != 0) { - return fail("POKEDATA re-insert int3"); - } - - /* Step 7: CONT — child continues through target_function → _exit(42). */ - if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { - return fail("PTRACE_CONT"); - } - - /* Step 8: child exits normally. */ - if (waitpid(pid, &status, 0) != pid) { - return fail("waitpid exit"); - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) { - printf("FAIL: expected exit code 42, status=%#x\n", status); - return 1; - } - printf(" ok: child exited with 42\n"); - - return 0; -} - -int main(void) -{ - int pass = 0; - int fail_count = 0; - - if (test_breakpoint() == 0) { - pass++; - } else { - fail_count++; - } - - if (test_breakpoint_singlestep_restore() == 0) { - pass++; - } else { - fail_count++; - } - - printf("DONE: %d pass, %d fail\n", pass, fail_count); - return fail_count > 0 ? 1 : 0; -} diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-fpregs/CMakeLists.txt b/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-fpregs/CMakeLists.txt deleted file mode 100644 index 1660c23abc..0000000000 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-fpregs/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-ptrace-x86-fpregs C) -include(${CMAKE_CURRENT_LIST_DIR}/../common/starry_arch_filter.cmake) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) - -starry_arch_filtered_executable(test-ptrace-x86-fpregs "x86_64" "test-ptrace-x86-fpregs skipped on unsupported architecture" src/main.c) -target_compile_options(test-ptrace-x86-fpregs PRIVATE -Wall -Wextra -Werror) - -install(TARGETS test-ptrace-x86-fpregs RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-fpregs/src/main.c b/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-fpregs/src/main.c deleted file mode 100644 index 6610fba48c..0000000000 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-fpregs/src/main.c +++ /dev/null @@ -1,239 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef PTRACE_TRACEME -#define PTRACE_TRACEME 0 -#endif -#ifndef PTRACE_CONT -#define PTRACE_CONT 7 -#endif -#ifndef PTRACE_GETFPREGS -#define PTRACE_GETFPREGS 14 -#endif -#ifndef PTRACE_SETFPREGS -#define PTRACE_SETFPREGS 15 -#endif -#ifndef PTRACE_GETREGSET -#define PTRACE_GETREGSET 0x4204 -#endif -#ifndef NT_PRFPREG -#define NT_PRFPREG 2 -#endif - -// The x86_64 FXSAVE area exposed by PTRACE_GETFPREGS / NT_PRFPREG is 512 bytes. -// XMM0..XMM15 live at byte offset 160 (each register is 16 bytes), so XMM10 -// (offset 320) and XMM15 (offset 400) sit *beyond* the first 256 bytes. A -// regression that only preserves 256 bytes of the FXSAVE area would therefore -// lose or corrupt those high registers while leaving XMM0 (offset 160) intact; -// this test checks both sides of that boundary on purpose. -#define FXSAVE_SIZE 512 -#define XMM_OFFSET 160 - -static long ptrace_call(int request, pid_t pid, void *addr, void *data) -{ -#ifdef __APPLE__ - return ptrace(request, pid, (caddr_t)addr, (int)(intptr_t)data); -#else - return ptrace(request, pid, addr, data); -#endif -} - -static int fail_errno(const char *msg) -{ - printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); - return 1; -} - -static int fail_msg(const char *msg) -{ - printf("FAIL: %s\n", msg); - return 1; -} - -// Known values the child loads into XMM0/XMM10/XMM15 before stopping. -static const uint64_t CHILD_XMM[3][2] = { - {0x1111111122222222ULL, 0x3333333344444444ULL}, // xmm0 - {0x5555555566666666ULL, 0x7777777788888888ULL}, // xmm10 - {0x99999999aaaaaaaaULL, 0xbbbbbbbbccccccccULL}, // xmm15 -}; - -// Sentinels the tracer writes back via PTRACE_SETFPREGS; the child re-reads its -// XMM registers after resuming and exits 0 only if they match. -static const uint64_t TRACER_XMM[3][2] = { - {0xdeadbeefcafef00dULL, 0x0badc0de12345678ULL}, // xmm0 - {0xfeedfacef00dba11ULL, 0x8badf00dabad1deaULL}, // xmm10 - {0x1234567890abcdefULL, 0x0fedcba987654321ULL}, // xmm15 -}; - -// Child side: load XMM0/10/15 from `in`, stop with SIGSTOP via a direct syscall -// (so no libc code clobbers the XMM registers between the load and the stop), -// then read the same registers back into `out` after the tracer resumes us. -static void child_fp_roundtrip(const uint64_t in[3][2], uint64_t out[3][2]) -{ - pid_t pid = getpid(); - __asm__ __volatile__( - "movups 0(%0), %%xmm0\n\t" - "movups 16(%0), %%xmm10\n\t" - "movups 32(%0), %%xmm15\n\t" - "mov $62, %%rax\n\t" // SYS_kill - "mov %2, %%rdi\n\t" // pid - "mov $19, %%rsi\n\t" // SIGSTOP - "syscall\n\t" - "movups %%xmm0, 0(%1)\n\t" - "movups %%xmm10, 16(%1)\n\t" - "movups %%xmm15, 32(%1)\n\t" - : - : "r"(in), "r"(out), "r"((long)pid) - : "rax", "rcx", "rdi", "rsi", "r11", "xmm0", "xmm10", "xmm15", "memory"); -} - -static const uint64_t *xmm_at(const uint8_t *fxsave, int index) -{ - return (const uint64_t *)(fxsave + XMM_OFFSET + (size_t)index * 16); -} - -static uint64_t *xmm_at_mut(uint8_t *fxsave, int index) -{ - return (uint64_t *)(fxsave + XMM_OFFSET + (size_t)index * 16); -} - -static int check_xmm(const uint8_t *fxsave, int reg, const uint64_t expect[2], - const char *what) -{ - const uint64_t *xmm = xmm_at(fxsave, reg); - if (xmm[0] != expect[0] || xmm[1] != expect[1]) { - printf("FAIL: %s xmm%d = %016llx:%016llx, expected %016llx:%016llx\n", what, - reg, (unsigned long long)xmm[1], (unsigned long long)xmm[0], - (unsigned long long)expect[1], (unsigned long long)expect[0]); - return 1; - } - return 0; -} - -static int test_ptrace_x86_fpregs(void) -{ - printf("test: x86_64 ptrace FP (FXSAVE) registers\n"); - - pid_t pid = fork(); - if (pid < 0) { - return fail_errno("fork"); - } - - if (pid == 0) { - if (ptrace_call(PTRACE_TRACEME, 0, NULL, NULL) != 0) { - _exit(100); - } - uint64_t out[3][2] = {{0, 0}, {0, 0}, {0, 0}}; - child_fp_roundtrip(CHILD_XMM, out); - // After resume, our XMM registers must hold the tracer's sentinels. - for (int i = 0; i < 3; i++) { - if (out[i][0] != TRACER_XMM[i][0] || out[i][1] != TRACER_XMM[i][1]) { - _exit(101 + i); - } - } - _exit(0); - } - - int status = 0; - if (waitpid(pid, &status, WUNTRACED) != pid) { - return fail_errno("waitpid stop"); - } - if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { - printf("FAIL: expected SIGSTOP stop, status=%#x\n", status); - return 1; - } - printf(" ok: child stopped with SIGSTOP\n"); - - // PTRACE_GETFPREGS must return the child's full 512-byte FXSAVE area, - // including XMM10/XMM15 which live past the first 256 bytes. - uint8_t fxsave[FXSAVE_SIZE]; - memset(fxsave, 0, sizeof(fxsave)); - if (ptrace_call(PTRACE_GETFPREGS, pid, NULL, fxsave) != 0) { - return fail_errno("PTRACE_GETFPREGS"); - } - if (check_xmm(fxsave, 0, CHILD_XMM[0], "GETFPREGS") || - check_xmm(fxsave, 10, CHILD_XMM[1], "GETFPREGS") || - check_xmm(fxsave, 15, CHILD_XMM[2], "GETFPREGS")) { - return 1; - } - printf(" ok: GETFPREGS returns xmm0/xmm10/xmm15 intact (incl. >256B offset)\n"); - - // NT_PRFPREG (PTRACE_GETREGSET) must agree with PTRACE_GETFPREGS. - uint8_t fxsave_set[FXSAVE_SIZE]; - memset(fxsave_set, 0, sizeof(fxsave_set)); - struct iovec iov = {.iov_base = fxsave_set, .iov_len = sizeof(fxsave_set)}; - if (ptrace_call(PTRACE_GETREGSET, pid, (void *)(intptr_t)NT_PRFPREG, &iov) != 0) { - return fail_errno("PTRACE_GETREGSET(NT_PRFPREG)"); - } - if (iov.iov_len != FXSAVE_SIZE) { - return fail_msg("NT_PRFPREG returned unexpected length"); - } - if (memcmp(fxsave, fxsave_set, FXSAVE_SIZE) != 0) { - return fail_msg("GETFPREGS and NT_PRFPREG disagree"); - } - printf(" ok: GETFPREGS and NT_PRFPREG agree\n"); - - // Write tracer sentinels into XMM0/10/15 and push them back. - *xmm_at_mut(fxsave, 0) = TRACER_XMM[0][0]; - *(xmm_at_mut(fxsave, 0) + 1) = TRACER_XMM[0][1]; - *xmm_at_mut(fxsave, 10) = TRACER_XMM[1][0]; - *(xmm_at_mut(fxsave, 10) + 1) = TRACER_XMM[1][1]; - *xmm_at_mut(fxsave, 15) = TRACER_XMM[2][0]; - *(xmm_at_mut(fxsave, 15) + 1) = TRACER_XMM[2][1]; - if (ptrace_call(PTRACE_SETFPREGS, pid, NULL, fxsave) != 0) { - return fail_errno("PTRACE_SETFPREGS"); - } - - // Read it straight back to confirm SETFPREGS landed in the kernel snapshot. - uint8_t fxsave_check[FXSAVE_SIZE]; - memset(fxsave_check, 0, sizeof(fxsave_check)); - if (ptrace_call(PTRACE_GETFPREGS, pid, NULL, fxsave_check) != 0) { - return fail_errno("PTRACE_GETFPREGS after SET"); - } - if (check_xmm(fxsave_check, 0, TRACER_XMM[0], "post-SET") || - check_xmm(fxsave_check, 10, TRACER_XMM[1], "post-SET") || - check_xmm(fxsave_check, 15, TRACER_XMM[2], "post-SET")) { - return 1; - } - printf(" ok: SETFPREGS updated xmm0/xmm10/xmm15 in the stop snapshot\n"); - - if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { - return fail_errno("PTRACE_CONT"); - } - - if (waitpid(pid, &status, 0) != pid) { - return fail_errno("waitpid exit"); - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - printf("FAIL: child did not observe tracer XMM writes, status=%#x\n", status); - return 1; - } - printf(" ok: child resumed with the tracer-written XMM registers\n"); - return 0; -} - -int main(void) -{ - int pass = 0; - int fail = 0; - - if (test_ptrace_x86_fpregs() == 0) { - pass++; - } else { - fail++; - } - - printf("DONE: %d pass, %d fail\n", pass, fail); - return fail == 0 ? 0 : 1; -} diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-regs/CMakeLists.txt b/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-regs/CMakeLists.txt deleted file mode 100644 index 58acf1dcea..0000000000 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-regs/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-ptrace-x86-regs C) -include(${CMAKE_CURRENT_LIST_DIR}/../common/starry_arch_filter.cmake) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) - -starry_arch_filtered_executable(test-ptrace-x86-regs "x86_64" "test-ptrace-x86-regs skipped on unsupported architecture" src/main.c) -target_compile_options(test-ptrace-x86-regs PRIVATE -Wall -Wextra -Werror) - -install(TARGETS test-ptrace-x86-regs RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-regs/src/main.c b/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-regs/src/main.c deleted file mode 100644 index a35cb84857..0000000000 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-regs/src/main.c +++ /dev/null @@ -1,269 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef PTRACE_GETREGS -#define PTRACE_GETREGS 12 -#endif -#ifndef PTRACE_TRACEME -#define PTRACE_TRACEME 0 -#endif -#ifndef PTRACE_SETREGS -#define PTRACE_SETREGS 13 -#endif -#ifndef PTRACE_CONT -#define PTRACE_CONT 7 -#endif -#ifndef PTRACE_GETSIGINFO -#define PTRACE_GETSIGINFO 0x4202 -#endif -#ifndef PTRACE_GETREGSET -#define PTRACE_GETREGSET 0x4204 -#endif -#ifndef PTRACE_SETREGSET -#define PTRACE_SETREGSET 0x4205 -#endif -#ifndef NT_PRSTATUS -#define NT_PRSTATUS 1 -#endif - -struct x86_64_user_regs { - uint64_t r15; - uint64_t r14; - uint64_t r13; - uint64_t r12; - uint64_t rbp; - uint64_t rbx; - uint64_t r11; - uint64_t r10; - uint64_t r9; - uint64_t r8; - uint64_t rax; - uint64_t rcx; - uint64_t rdx; - uint64_t rsi; - uint64_t rdi; - uint64_t orig_rax; - uint64_t rip; - uint64_t cs; - uint64_t eflags; - uint64_t rsp; - uint64_t ss; - uint64_t fs_base; - uint64_t gs_base; - uint64_t ds; - uint64_t es; - uint64_t fs; - uint64_t gs; -}; - -static long ptrace_call(int request, pid_t pid, void *addr, void *data) -{ -#ifdef __APPLE__ - return ptrace(request, pid, (caddr_t)addr, (int)(intptr_t)data); -#else - return ptrace(request, pid, addr, data); -#endif -} - -static int fail_errno(const char *msg) -{ - printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); - return 1; -} - -static int fail_msg(const char *msg) -{ - printf("FAIL: %s\n", msg); - return 1; -} - -static int getregs(pid_t pid, struct x86_64_user_regs *regs) -{ - if (ptrace_call(PTRACE_GETREGS, pid, NULL, regs) != 0) { - return -1; - } - return 0; -} - -static int setregs(pid_t pid, const struct x86_64_user_regs *regs) -{ - if (ptrace_call(PTRACE_SETREGS, pid, NULL, (void *)regs) != 0) { - return -1; - } - return 0; -} - -static int getregset(pid_t pid, struct x86_64_user_regs *regs) -{ - struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; - if (ptrace_call(PTRACE_GETREGSET, pid, (void *)NT_PRSTATUS, &iov) != 0) { - return -1; - } - if (iov.iov_len != (long)sizeof(*regs)) { - errno = EIO; - return -1; - } - return 0; -} - -static int setregset(pid_t pid, const struct x86_64_user_regs *regs) -{ - struct iovec iov = {.iov_base = (void *)regs, .iov_len = sizeof(*regs)}; - if (ptrace_call(PTRACE_SETREGSET, pid, (void *)NT_PRSTATUS, &iov) != 0) { - return -1; - } - return 0; -} - -static int test_ptrace_x86_regs(void) -{ - printf("test 1: x86_64 ptrace regs/siginfo\n"); - - pid_t pid = fork(); - if (pid < 0) { - return fail_errno("fork"); - } - - if (pid == 0) { - if (ptrace_call(PTRACE_TRACEME, 0, NULL, NULL) != 0) { - _exit(100); - } - raise(SIGSTOP); - _exit(0); - } - - int status = 0; - if (waitpid(pid, &status, WUNTRACED) != pid) { - return fail_errno("waitpid stop"); - } - if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { - printf("FAIL: expected SIGSTOP stop, status=%#x\n", status); - return 1; - } - printf(" ok: child stopped with SIGSTOP\n"); - - siginfo_t si; - memset(&si, 0, sizeof(si)); - if (ptrace_call(PTRACE_GETSIGINFO, pid, NULL, &si) != 0) { - return fail_errno("PTRACE_GETSIGINFO"); - } - if (si.si_signo != SIGSTOP) { - printf("FAIL: si_signo=%d, expected %d\n", si.si_signo, SIGSTOP); - return 1; - } - printf(" ok: GETSIGINFO reports SIGSTOP\n"); - - struct x86_64_user_regs regs_getregs; - memset(®s_getregs, 0, sizeof(regs_getregs)); - if (getregs(pid, ®s_getregs) != 0) { - return fail_errno("PTRACE_GETREGS"); - } - - struct x86_64_user_regs regs_getregset; - memset(®s_getregset, 0, sizeof(regs_getregset)); - if (getregset(pid, ®s_getregset) != 0) { - return fail_errno("PTRACE_GETREGSET"); - } - - if (regs_getregs.rip == 0 || regs_getregs.rsp == 0) { - return fail_msg("GETREGS returned zero RIP/RSP"); - } - if (regs_getregs.rip != regs_getregset.rip || - regs_getregs.rsp != regs_getregset.rsp || - regs_getregs.rax != regs_getregset.rax || - regs_getregs.r15 != regs_getregset.r15) { - printf("FAIL: GETREGS/GETREGSET mismatch: rip=%#llx/%#llx rsp=%#llx/%#llx rax=%#llx/%#llx r15=%#llx/%#llx\n", - (unsigned long long)regs_getregs.rip, - (unsigned long long)regs_getregset.rip, - (unsigned long long)regs_getregs.rsp, - (unsigned long long)regs_getregset.rsp, - (unsigned long long)regs_getregs.rax, - (unsigned long long)regs_getregset.rax, - (unsigned long long)regs_getregs.r15, - (unsigned long long)regs_getregset.r15); - return 1; - } - printf(" ok: GETREGS and GETREGSET agree on core registers\n"); - - struct x86_64_user_regs modified = regs_getregset; - modified.r15 ^= 0x13579bdfULL; - if (setregset(pid, &modified) != 0) { - return fail_errno("PTRACE_SETREGSET"); - } - - struct x86_64_user_regs check_after_setregset; - memset(&check_after_setregset, 0, sizeof(check_after_setregset)); - if (getregs(pid, &check_after_setregset) != 0) { - return fail_errno("GETREGS after SETREGSET"); - } - if (check_after_setregset.r15 != modified.r15) { - printf("FAIL: SETREGSET did not update r15 (%#llx != %#llx)\n", - (unsigned long long)check_after_setregset.r15, - (unsigned long long)modified.r15); - return 1; - } - printf(" ok: SETREGSET updated r15\n"); - - modified = check_after_setregset; - modified.r14 ^= 0x2468ace0ULL; - if (setregs(pid, &modified) != 0) { - return fail_errno("PTRACE_SETREGS"); - } - - struct x86_64_user_regs check_after_setregs; - memset(&check_after_setregs, 0, sizeof(check_after_setregs)); - if (getregset(pid, &check_after_setregs) != 0) { - return fail_errno("GETREGSET after SETREGS"); - } - if (check_after_setregs.r14 != modified.r14) { - printf("FAIL: SETREGS did not update r14 (%#llx != %#llx)\n", - (unsigned long long)check_after_setregs.r14, - (unsigned long long)modified.r14); - return 1; - } - printf(" ok: SETREGS updated r14\n"); - - if (setregs(pid, ®s_getregs) != 0) { - return fail_errno("restore original regs"); - } - - if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { - return fail_errno("PTRACE_CONT"); - } - - if (waitpid(pid, &status, 0) != pid) { - return fail_errno("waitpid exit"); - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - printf("FAIL: expected child exit 0, status=%#x\n", status); - return 1; - } - printf(" ok: child continued and exited cleanly\n"); - return 0; -} - -int main(void) -{ - int pass = 0; - int fail = 0; - - if (test_ptrace_x86_regs() == 0) { - pass++; - } else { - fail++; - } - - printf("DONE: %d pass, %d fail\n", pass, fail); - return fail == 0 ? 0 : 1; -} diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-singlestep/CMakeLists.txt b/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-singlestep/CMakeLists.txt deleted file mode 100644 index 949c3ad560..0000000000 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-singlestep/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-ptrace-x86-singlestep C) -include(${CMAKE_CURRENT_LIST_DIR}/../common/starry_arch_filter.cmake) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) - -starry_arch_filtered_executable(test-ptrace-x86-singlestep "x86_64" "test-ptrace-x86-singlestep skipped on unsupported architecture" src/main.c) -target_compile_options(test-ptrace-x86-singlestep PRIVATE -Wall -Wextra -Werror) - -install(TARGETS test-ptrace-x86-singlestep RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-singlestep/src/main.c b/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-singlestep/src/main.c deleted file mode 100644 index efa74b9aa2..0000000000 --- a/test-suit/starryos/qemu-smp1/system/test-ptrace-x86-singlestep/src/main.c +++ /dev/null @@ -1,223 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef PTRACE_GETREGSET -#define PTRACE_GETREGSET 0x4204 -#endif -#ifndef PTRACE_SETREGSET -#define PTRACE_SETREGSET 0x4205 -#endif -#ifndef PTRACE_TRACEME -#define PTRACE_TRACEME 0 -#endif -#ifndef PTRACE_SINGLESTEP -#define PTRACE_SINGLESTEP 9 -#endif -#ifndef PTRACE_CONT -#define PTRACE_CONT 7 -#endif -#ifndef NT_PRSTATUS -#define NT_PRSTATUS 1 -#endif - -struct x86_64_user_regs { - uint64_t r15; - uint64_t r14; - uint64_t r13; - uint64_t r12; - uint64_t rbp; - uint64_t rbx; - uint64_t r11; - uint64_t r10; - uint64_t r9; - uint64_t r8; - uint64_t rax; - uint64_t rcx; - uint64_t rdx; - uint64_t rsi; - uint64_t rdi; - uint64_t orig_rax; - uint64_t rip; - uint64_t cs; - uint64_t eflags; - uint64_t rsp; - uint64_t ss; - uint64_t fs_base; - uint64_t gs_base; - uint64_t ds; - uint64_t es; - uint64_t fs; - uint64_t gs; -}; - -__attribute__((naked, noinline, aligned(1))) static void singlestep_target(void) -{ - __asm__ volatile( - "mov $123, %rax\n" - "int3\n" - "jmp singlestep_landing\n"); -} - -__attribute__((naked, noinline, aligned(1), used)) static void singlestep_landing(void) -{ - __asm__ volatile( - "mov $42, %rdi\n" - "mov $60, %rax\n" - "syscall\n"); -} - -static int fail(const char *msg) -{ - printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); - return 1; -} - -static long ptrace_call(int request, pid_t pid, void *addr, void *data) -{ -#ifdef __APPLE__ - return ptrace(request, pid, (caddr_t)addr, (int)(intptr_t)data); -#else - return ptrace(request, pid, addr, data); -#endif -} - -static int wait_stopped(pid_t pid, int *status, int expected_sig) -{ - if (waitpid(pid, status, WUNTRACED) != pid) { - return fail("waitpid"); - } - if (!WIFSTOPPED(*status) || WSTOPSIG(*status) != expected_sig) { - printf("FAIL: expected stop signal %d, status=%#x\n", expected_sig, *status); - return 1; - } - return 0; -} - -static int getregset(pid_t pid, struct x86_64_user_regs *regs) -{ - struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; - if (ptrace_call(PTRACE_GETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { - return -1; - } - if ((size_t)iov.iov_len != sizeof(*regs)) { - errno = EIO; - return -1; - } - return 0; -} - -static int setregset(pid_t pid, const struct x86_64_user_regs *regs) -{ - struct iovec iov = {.iov_base = (void *)regs, .iov_len = sizeof(*regs)}; - if (ptrace_call(PTRACE_SETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { - return -1; - } - return 0; -} - -static int test_singlestep(void) -{ - printf("test 1: x86_64 PTRACE_SINGLESTEP\n"); - - pid_t pid = fork(); - if (pid < 0) { - return fail("fork"); - } - - if (pid == 0) { - if (ptrace_call(PTRACE_TRACEME, 0, NULL, NULL) != 0) { - _exit(100); - } - raise(SIGSTOP); - _exit(99); - } - - int status = 0; - if (wait_stopped(pid, &status, SIGSTOP) != 0) { - return 1; - } - printf(" ok: child stopped with SIGSTOP\n"); - - struct x86_64_user_regs regs; - memset(®s, 0, sizeof(regs)); - if (getregset(pid, ®s) != 0) { - return fail("getregset initial"); - } - - regs.rip = (uintptr_t)singlestep_target; - regs.rax = 0; - if (setregset(pid, ®s) != 0) { - return fail("setregset target rip"); - } - - if (ptrace_call(PTRACE_SINGLESTEP, pid, NULL, NULL) != 0) { - return fail("ptrace singlestep"); - } - if (wait_stopped(pid, &status, SIGTRAP) != 0) { - return 1; - } - printf(" ok: single-step trap delivered\n"); - - memset(®s, 0, sizeof(regs)); - if (getregset(pid, ®s) != 0) { - return fail("getregset after singlestep"); - } - if (regs.rax != 123) { - printf("FAIL: expected RAX=123 after single-step, got %#llx\n", - (unsigned long long)regs.rax); - return 1; - } - if (regs.rip == (uintptr_t)singlestep_target) { - printf("FAIL: RIP did not advance after single-step: %#llx\n", - (unsigned long long)regs.rip); - return 1; - } - printf(" ok: single-step executed one instruction (RAX=%#llx RIP=%#llx)\n", - (unsigned long long)regs.rax, (unsigned long long)regs.rip); - - if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { - return fail("ptrace cont to int3"); - } - if (wait_stopped(pid, &status, SIGTRAP) != 0) { - return 1; - } - printf(" ok: child reached the following int3\n"); - - if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { - return fail("ptrace cont to exit"); - } - if (waitpid(pid, &status, 0) != pid) { - return fail("waitpid exit"); - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) { - printf("FAIL: expected exit 42, status=%#x\n", status); - return 1; - } - printf(" ok: child exited with 42\n"); - return 0; -} - -int main(void) -{ - int pass = 0; - int fail_count = 0; - - if (test_singlestep() == 0) { - pass++; - } else { - fail_count++; - } - - printf("DONE: %d pass, %d fail\n", pass, fail_count); - return fail_count > 0 ? 1 : 0; -}