diff options
author | Pasha <pasha@member.fsf.org> | 2024-02-20 18:49:50 +0000 |
---|---|---|
committer | Pasha <pasha@member.fsf.org> | 2024-02-20 18:49:50 +0000 |
commit | 5e0b8d508ed51004bd836384293be00950ee62c9 (patch) | |
tree | e3f16b1aa8b7177032ce3ec429fbad2b1d92a876 /tests | |
download | gnumach-riscv-5e0b8d508ed51004bd836384293be00950ee62c9.tar.gz gnumach-riscv-5e0b8d508ed51004bd836384293be00950ee62c9.tar.bz2 |
init gnumach copy
Diffstat (limited to 'tests')
-rw-r--r-- | tests/.gitignore | 1 | ||||
-rw-r--r-- | tests/Makefrag.am | 34 | ||||
-rw-r--r-- | tests/README | 37 | ||||
-rw-r--r-- | tests/configfrag.ac | 43 | ||||
-rw-r--r-- | tests/grub.cfg.single.template | 4 | ||||
-rw-r--r-- | tests/include/device/cons.h | 27 | ||||
l--------- | tests/include/kern/printf.h | 1 | ||||
-rw-r--r-- | tests/include/mach/mig_support.h | 71 | ||||
-rw-r--r-- | tests/include/syscalls.h | 83 | ||||
-rw-r--r-- | tests/include/testlib.h | 75 | ||||
l--------- | tests/include/util/atoi.h | 1 | ||||
-rw-r--r-- | tests/run-qemu.sh.template | 38 | ||||
-rw-r--r-- | tests/start.S | 28 | ||||
-rw-r--r-- | tests/syscalls.S | 4 | ||||
-rw-r--r-- | tests/test-gsync.c | 122 | ||||
-rw-r--r-- | tests/test-hello.c | 26 | ||||
-rw-r--r-- | tests/test-mach_host.c | 81 | ||||
-rw-r--r-- | tests/test-mach_port.c | 121 | ||||
-rw-r--r-- | tests/test-machmsg.c | 405 | ||||
-rw-r--r-- | tests/test-multiboot.in | 30 | ||||
-rw-r--r-- | tests/test-syscalls.c | 166 | ||||
-rw-r--r-- | tests/test-task.c | 171 | ||||
-rw-r--r-- | tests/test-threads.c | 104 | ||||
-rw-r--r-- | tests/test-vm.c | 85 | ||||
-rw-r--r-- | tests/testlib.c | 114 | ||||
-rw-r--r-- | tests/testlib_thread_start.c | 86 | ||||
-rw-r--r-- | tests/user-qemu.mk | 221 |
27 files changed, 2179 insertions, 0 deletions
diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..6e86af1 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +/test-mbchk diff --git a/tests/Makefrag.am b/tests/Makefrag.am new file mode 100644 index 0000000..faabdf4 --- /dev/null +++ b/tests/Makefrag.am @@ -0,0 +1,34 @@ +# Makefile fragment for the test suite. + +# Copyright (C) 2006, 2007 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# +# Tests. +# + +if !PLATFORM_xen + +include tests/user-qemu.mk + +TESTS += \ + tests/test-multiboot \ + $(USER_TESTS) + +clean-local: $(USER_TESTS_CLEAN) + rm -fr tests/include-mach + +endif # !PLATFORM_xen diff --git a/tests/README b/tests/README new file mode 100644 index 0000000..3dacc18 --- /dev/null +++ b/tests/README @@ -0,0 +1,37 @@ + +There are some basic tests that can be run qith qemu. You can run all the tests with + + $ make check + +or selectively with: + + $ make run-hello + +Also, you can debug the existing tests, or a new one, by starting on one shell + + $ make debug-hello + +and on another shell you can attach with gdb, load the symbols of the +bootstrap module and break on its _start(): + + $ gdb gnumach + ... + (gdb) target remote :1234 + ... + (gdb) b setup_main + Breakpoint 11 at 0xffffffff81019d60: file ../kern/startup.c, line 98. + (gdb) c + Continuing. + + Breakpoint 11, setup_main () at ../kern/startup.c:98 + 98 cninit(); + (gdb) add-symbol-file ../gnumach/build-64/module-task + Reading symbols from ../gnumach/build-64/module-task... + (gdb) b _start + Breakpoint 12 at 0x40324a: _start. (2 locations) + (gdb) c + Continuing. + + Breakpoint 12, _start () at ../tests/testlib.c:96 + 96 { + (gdb) diff --git a/tests/configfrag.ac b/tests/configfrag.ac new file mode 100644 index 0000000..de87cba --- /dev/null +++ b/tests/configfrag.ac @@ -0,0 +1,43 @@ +dnl Configure fragment for the test suite. + +dnl Copyright (C) 2006, 2007 Free Software Foundation, Inc. + +dnl This program is free software; you can redistribute it and/or modify it +dnl under the terms of the GNU General Public License as published by the +dnl Free Software Foundation; either version 2, or (at your option) any later +dnl version. +dnl +dnl This program is distributed in the hope that it will be useful, but +dnl WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +dnl or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +dnl for more details. +dnl +dnl You should have received a copy of the GNU General Public License along +dnl with this program; if not, write to the Free Software Foundation, Inc., +dnl 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# +# Tests. +# + +AC_CONFIG_FILES([tests/test-multiboot], [chmod +x tests/test-multiboot]) + + +[if test x"$enable_user32" = xyes ; then + ac_miguser=$user32_cpu-gnu-mig +else + ac_miguser=$host_cpu-gnu-mig +fi] + +AC_CHECK_PROG([USER_MIG], [$ac_miguser], [$ac_miguser]) +AC_ARG_VAR([USER_MIG], [Path to the mig tool for user-space tests]) +AC_CHECK_PROG([USER_CC], [$CC], [$CC], [none]) +AC_ARG_VAR([USER_CC], [C compiler command for user-space tests]) +AC_CHECK_PROG([USER_CPP], [$CPP], [$CPP], [none]) +AC_ARG_VAR([USER_CPP], [C preprocessor for user-space tests]) +AC_ARG_VAR([USER_CFLAGS], [C compiler flags for user-space tests]) +AC_ARG_VAR([USER_CPPFLAGS], [C preprocessor flags for user-space tests]) + +dnl Local Variables: +dnl mode: autoconf +dnl End: diff --git a/tests/grub.cfg.single.template b/tests/grub.cfg.single.template new file mode 100644 index 0000000..4432be3 --- /dev/null +++ b/tests/grub.cfg.single.template @@ -0,0 +1,4 @@ +echo TEST_START_MARKER +multiboot /boot/gnumach GNUMACHARGS +module /boot/BOOTMODULE BOOTMODULE '${host-port}' '${device-port}' '$(task-create)' '$(task-resume)' +boot diff --git a/tests/include/device/cons.h b/tests/include/device/cons.h new file mode 100644 index 0000000..f4d8fe1 --- /dev/null +++ b/tests/include/device/cons.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef CONS_H +#define CONS_H + +#include <mach/machine/vm_types.h> + +void cnputc(char c, vm_offset_t cookie); +static inline int cngetc() { return 0; } + +#endif /* CONS_H */ diff --git a/tests/include/kern/printf.h b/tests/include/kern/printf.h new file mode 120000 index 0000000..c61f3e0 --- /dev/null +++ b/tests/include/kern/printf.h @@ -0,0 +1 @@ +../../../kern/printf.h
\ No newline at end of file diff --git a/tests/include/mach/mig_support.h b/tests/include/mach/mig_support.h new file mode 100644 index 0000000..bf67008 --- /dev/null +++ b/tests/include/mach/mig_support.h @@ -0,0 +1,71 @@ +/* + * Mach Operating System + * Copyright (c) 1992 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * Abstract: + * MIG helpers for gnumach tests, mainly copied from glibc + * + */ + +#ifndef _MACH_MIG_SUPPORT_H_ +#define _MACH_MIG_SUPPORT_H_ + +#include <string.h> + +#include <mach/message.h> +#include <mach/mach_types.h> + +#include <syscalls.h> + +static inline void mig_init(void *_first) +{} + +static inline void mig_allocate(vm_address_t *addr, vm_size_t size) +{ + if (syscall_vm_allocate(mach_task_self(), addr, size, 1) != KERN_SUCCESS) + *addr = 0; +} +static inline void mig_deallocate(vm_address_t addr, vm_size_t size) +{ + syscall_vm_deallocate (mach_task_self(), addr, size); +} +static inline void mig_dealloc_reply_port(mach_port_t port) +{} +static inline void mig_put_reply_port(mach_port_t port) +{} +static inline mach_port_t mig_get_reply_port(void) +{ + return mach_reply_port(); +} +static inline void mig_reply_setup(const mach_msg_header_t *_request, + mach_msg_header_t *reply) +{} + +static inline vm_size_t mig_strncpy (char *dst, const char *src, vm_size_t len) +{ + return dst - strncpy(dst, src, len); +} + +#endif /* not defined(_MACH_MIG_SUPPORT_H_) */ diff --git a/tests/include/syscalls.h b/tests/include/syscalls.h new file mode 100644 index 0000000..f958154 --- /dev/null +++ b/tests/include/syscalls.h @@ -0,0 +1,83 @@ +/* + * Mach Operating System + * Copyright (c) 1992 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie Mellon + * the rights to redistribute these changes. + */ +/* + * Abstract: + * Syscall functions + * + */ + +#ifndef _SYSCALLS_ +#define _SYSCALLS_ + +#include <device/device_types.h> +#include <mach/message.h> + +// TODO: there is probably a better way to define these + +#define MACH_SYSCALL0(syscallid, retval, name) \ + retval name(void) __attribute__((naked)); + +#define MACH_SYSCALL1(syscallid, retval, name, arg1) \ + retval name(arg1 a1) __attribute__((naked)); + +#define MACH_SYSCALL2(syscallid, retval, name, arg1, arg2) \ + retval name(arg1 a1, arg2 a2) __attribute__((naked)); + +#define MACH_SYSCALL3(syscallid, retval, name, arg1, arg2, arg3) \ + retval name(arg1 a1, arg2 a2, arg3 a3) __attribute__((naked)); + +#define MACH_SYSCALL4(syscallid, retval, name, arg1, arg2, arg3, arg4) \ + retval name(arg1 a1, arg2 a2, arg3 a3, arg4 a4) __attribute__((naked)); + +#define MACH_SYSCALL6(syscallid, retval, name, arg1, arg2, arg3, arg4, arg5, arg6) \ + retval name(arg1 a1, arg2 a2, arg3 a3, arg4 a4, arg5 a5, arg6 a6) __attribute__((naked)); + +#define MACH_SYSCALL7(syscallid, retval, name, arg1, arg2, arg3, arg4, arg5, arg6, arg7) \ + retval name(arg1 a1, arg2 a2, arg3 a3, arg4 a4, arg5 a5, arg6 a6, arg7 a7) __attribute__((naked)); + +#define mach_msg mach_msg_trap + +MACH_SYSCALL0(26, mach_port_name_t, mach_reply_port) +MACH_SYSCALL0(27, mach_port_name_t, mach_thread_self) +MACH_SYSCALL0(28, mach_port_name_t, mach_task_self) +MACH_SYSCALL0(29, mach_port_name_t, mach_host_self) +MACH_SYSCALL1(30, void, mach_print, const char*) +MACH_SYSCALL0(31, kern_return_t, invalid_syscall) +MACH_SYSCALL4(65, kern_return_t, syscall_vm_allocate, mach_port_t, vm_offset_t*, vm_size_t, boolean_t) +MACH_SYSCALL3(66, kern_return_t, syscall_vm_deallocate, mach_port_t, vm_offset_t, vm_size_t) +MACH_SYSCALL3(72, kern_return_t, syscall_mach_port_allocate, mach_port_t, mach_port_right_t, mach_port_t*) +MACH_SYSCALL2(73, kern_return_t, syscall_mach_port_deallocate, mach_port_t, mach_port_t) + +/* + todo: swtch_pri swtch ... + these seem obsolete: evc_wait + evc_wait_clear syscall_device_writev_request + syscall_device_write_request ... + */ +MACH_SYSCALL6(40, io_return_t, syscall_device_write_request, mach_port_name_t, + mach_port_name_t, dev_mode_t, recnum_t, vm_offset_t, vm_size_t) + +#endif /* SYSCALLS */ diff --git a/tests/include/testlib.h b/tests/include/testlib.h new file mode 100644 index 0000000..a3f3a6a --- /dev/null +++ b/tests/include/testlib.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef TESTLIB_H +#define TESTLIB_H + +// in freestanding we can only use a few standard headers +// float.h iso646.h limits.h stdarg.h stdbool.h stddef.h stdint.h + +#include <stddef.h> +#include <stdint.h> +#include <stdarg.h> +#include <stdbool.h> + +#include <string.h> // we shouldn't include this from gcc, but it seems to be ok + +#include <mach/mach_types.h> +#include <kern/printf.h> +#include <util/atoi.h> +#include <syscalls.h> + +#define ASSERT(cond, msg) do { \ + if (!(cond)) \ + { \ + printf("%s: " #cond " failed: %s\n", \ + TEST_FAILURE_MARKER, (msg)); \ + halt(); \ + } \ + } while (0) + +#define ASSERT_RET(ret, msg) do { \ + if ((ret) != KERN_SUCCESS) \ + { \ + printf("%s %s (0x%x): %s\n", \ + TEST_FAILURE_MARKER, e2s((ret)), (ret), (msg)); \ + halt(); \ + } \ + } while (0) + +#define FAILURE(msg) do { \ + printf("%s: %s\n", TEST_FAILURE_MARKER, (msg)); \ + halt(); \ + } while (0) + + +extern const char* TEST_SUCCESS_MARKER; +extern const char* TEST_FAILURE_MARKER; + +const char* e2s(int err); +const char* e2s_gnumach(int err); +void halt(); +int msleep(uint32_t timeout); +thread_t test_thread_start(task_t task, void(*routine)(void*), void* arg); + +mach_port_t host_priv(void); +mach_port_t device_priv(void); + +int main(int argc, char *argv[], int envc, char *envp[]); + +#endif /* TESTLIB_H */ diff --git a/tests/include/util/atoi.h b/tests/include/util/atoi.h new file mode 120000 index 0000000..c32c258 --- /dev/null +++ b/tests/include/util/atoi.h @@ -0,0 +1 @@ +../../../util/atoi.h
\ No newline at end of file diff --git a/tests/run-qemu.sh.template b/tests/run-qemu.sh.template new file mode 100644 index 0000000..aba8d68 --- /dev/null +++ b/tests/run-qemu.sh.template @@ -0,0 +1,38 @@ +#!/bin/sh +# Copyright (C) 2024 Free Software Foundation +# +# This program is free software ; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation ; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY ; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with the program ; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +set -e + +cmd="QEMU_BIN QEMU_OPTS -cdrom tests/test-TESTNAME.iso" +log="tests/test-TESTNAME.raw" + +echo "temp log $log" +if which QEMU_BIN >/dev/null ; then + if ! timeout -v --foreground --kill-after=3 15s $cmd \ + | tee $log | sed -n "/TEST_START_MARKER/"',$p' ; then + exit 10 # timeout + fi + if grep -qi 'TEST_FAILURE_MARKER' $log; then + exit 99 # error marker found, test explicitely failed + fi + if ! grep -q 'TEST_SUCCESS_MARKER' $log; then + exit 12 # missing reboot marker, maybe the kernel crashed + fi +else + echo "skipping, QEMU_BIN not found" + exit 77 +fi diff --git a/tests/start.S b/tests/start.S new file mode 100644 index 0000000..b795bfb --- /dev/null +++ b/tests/start.S @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + .global _start +_start: +#ifdef __i386__ + pushl %esp + call c_start +#endif /* __i386__ */ +#ifdef __x86_64__ + movq %rsp,%rdi + callq c_start +#endif /* __x86_64__ */ diff --git a/tests/syscalls.S b/tests/syscalls.S new file mode 100644 index 0000000..df9c9bc --- /dev/null +++ b/tests/syscalls.S @@ -0,0 +1,4 @@ + + #include <mach/syscall_sw.h> + + kernel_trap(invalid_syscall,-31,0) diff --git a/tests/test-gsync.c b/tests/test-gsync.c new file mode 100644 index 0000000..a516065 --- /dev/null +++ b/tests/test-gsync.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <syscalls.h> +#include <testlib.h> + +#include <mach/machine/vm_param.h> +#include <mach/std_types.h> +#include <mach/mach_types.h> +#include <mach/vm_wire.h> + +#include <mach.user.h> +#include <gnumach.user.h> + +/* Gsync flags. */ +#ifndef GSYNC_SHARED +# define GSYNC_SHARED 0x01 +# define GSYNC_QUAD 0x02 +# define GSYNC_TIMED 0x04 +# define GSYNC_BROADCAST 0x08 +# define GSYNC_MUTATE 0x10 +#endif + +static uint32_t single_shared; +static struct { + uint32_t val1; + uint32_t val2; +} single_shared_quad; + +static void test_single() +{ + int err; + single_shared = 0; + err = gsync_wait(mach_task_self(), (vm_offset_t)&single_shared, 0, 0, 100, GSYNC_TIMED); + ASSERT(err == KERN_TIMEDOUT, "gsync_wait did not timeout"); + + single_shared = 1; + err = gsync_wait(mach_task_self(), (vm_offset_t)&single_shared, 0, 0, 100, GSYNC_TIMED); + ASSERT(err == KERN_INVALID_ARGUMENT, "gsync_wait on wrong value"); + err = gsync_wait(mach_task_self(), (vm_offset_t)&single_shared, 1, 0, 100, GSYNC_TIMED); + ASSERT(err == KERN_TIMEDOUT, "gsync_wait again on correct value did not timeout"); + + single_shared_quad.val1 = 1; + single_shared_quad.val2 = 2; + err = gsync_wait(mach_task_self(), (vm_offset_t)&single_shared_quad, 99, 88, + 100, GSYNC_TIMED | GSYNC_QUAD); + ASSERT(err == KERN_INVALID_ARGUMENT, "gsync_wait on wrong quad value"); + err = gsync_wait(mach_task_self(), (vm_offset_t)&single_shared_quad, 1, 2, + 100, GSYNC_TIMED | GSYNC_QUAD); + ASSERT(err == KERN_TIMEDOUT, "gsync_wait again on correct value did not timeout"); + + err = gsync_wake(mach_task_self(), (vm_offset_t)&single_shared, 0, 0); + ASSERT_RET(err, "gsync_wake with nobody waiting"); + + err = gsync_wait(mach_task_self(), (vm_offset_t)&single_shared, 1, 0, 100, GSYNC_TIMED); + ASSERT(err == KERN_TIMEDOUT, "gsync_wait not affected by previous gsync_wake"); + + err = gsync_wake(mach_task_self(), (vm_offset_t)&single_shared, 0, GSYNC_BROADCAST); + ASSERT_RET(err, "gsync_wake broadcast with nobody waiting"); + + err = gsync_wait(mach_task_self(), (vm_offset_t)&single_shared, 1, 0, 100, GSYNC_TIMED); + ASSERT(err == KERN_TIMEDOUT, "gsync_wait not affected by previous gsync_wake"); + + err = gsync_wake(mach_task_self(), (vm_offset_t)&single_shared, 2, GSYNC_MUTATE); + ASSERT_RET(err, "gsync_wake nobody + mutate"); + ASSERT(single_shared == 2, "gsync_wake single_shared did not mutate"); + + err = gsync_wake(mach_task_self(), (vm_offset_t)&single_shared, 0, 0); + ASSERT_RET(err, "gsync_wake nobody without mutate"); + err = gsync_wake(mach_task_self(), (vm_offset_t)&single_shared, 0, 0); + ASSERT_RET(err, "gsync_wake 3a"); +} + +static void single_thread_setter(void *arg) +{ + int err; + int val = (long)arg; + + /* It should be enough to sleep a bit for our creator to call + gsync_wait(). To verify that the test is performed with the + correct sequence, we also change the value so if the wait is + called after our wake it will fail with KERN_INVALID_ARGUMENT */ + msleep(100); + + err = gsync_wake(mach_task_self(), (vm_offset_t)&single_shared, val, GSYNC_MUTATE); + ASSERT_RET(err, "gsync_wake from thread + mutate"); + + thread_terminate(mach_thread_self()); + FAILURE("thread_terminate"); +} + +static void test_single_from_thread() +{ + int err; + single_shared = 10; + test_thread_start(mach_task_self(), single_thread_setter, (void*)11); + err = gsync_wait(mach_task_self(), (vm_offset_t)&single_shared, 10, 0, 0, 0); + ASSERT_RET(err, "gsync_wait without timeout for wake from another thread"); + ASSERT(single_shared == 11, "wake didn't mutate"); +} + +int main(int argc, char *argv[], int envc, char *envp[]) +{ + test_single_from_thread(); + test_single(); + return 0; +} diff --git a/tests/test-hello.c b/tests/test-hello.c new file mode 100644 index 0000000..0d739c6 --- /dev/null +++ b/tests/test-hello.c @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <testlib.h> + +int main(int argc, char *argv[], int envc, char *envp[]) +{ + int ret = printf("hello!!\n"); + ASSERT_RET(ret, "printf() should return 0 here"); + return 0; +} diff --git a/tests/test-mach_host.c b/tests/test-mach_host.c new file mode 100644 index 0000000..53f3024 --- /dev/null +++ b/tests/test-mach_host.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <testlib.h> + +#include <mach_host.user.h> + +void test_kernel_version() +{ + int err; + kernel_version_t kver; + err = host_get_kernel_version(mach_host_self(), kver); + ASSERT_RET(err, "host_kernel_info"); + printf("kernel version: %s\n", kver); +} + +void test_host_info() +{ + int err; + mach_msg_type_number_t count; + mach_port_t thishost = mach_host_self(); + + host_basic_info_data_t binfo; + count = HOST_BASIC_INFO_COUNT; + err = host_info(thishost, HOST_BASIC_INFO, (host_info_t)&binfo, &count); + ASSERT_RET(err, "host_basic_info"); + ASSERT(count == HOST_BASIC_INFO_COUNT, ""); + ASSERT(binfo.max_cpus > 0, "no cpu?"); + ASSERT(binfo.avail_cpus > 0, "no cpu available?"); + ASSERT(binfo.memory_size > (1024 * 1024), "memory too low"); + + const int maxcpus = 255; + int proc_slots[maxcpus]; + count = maxcpus; + err = host_info(thishost, HOST_PROCESSOR_SLOTS, (host_info_t)&proc_slots, &count); + ASSERT_RET(err, "host_processor_slots"); + ASSERT((1 <= count) && (count <= maxcpus), ""); + + host_sched_info_data_t sinfo; + count = HOST_SCHED_INFO_COUNT; + err = host_info(thishost, HOST_SCHED_INFO, (host_info_t)&sinfo, &count); + ASSERT_RET(err, "host_sched_info"); + ASSERT(count == HOST_SCHED_INFO_COUNT, ""); + ASSERT(sinfo.min_timeout < 1000, "timeout unexpectedly high"); + ASSERT(sinfo.min_quantum < 1000, "quantum unexpectedly high"); + + host_load_info_data_t linfo; + count = HOST_LOAD_INFO_COUNT; + err = host_info(thishost, HOST_LOAD_INFO, (host_info_t)&linfo, &count); + ASSERT_RET(err, "host_load_info"); + ASSERT(count == HOST_LOAD_INFO_COUNT, ""); + for (int i=0; i<3; i++) + { + printf("avenrun %d\n", linfo.avenrun[i]); + printf("mach_factor %d\n", linfo.mach_factor[i]); + } +} + +// TODO processor sets + +int main(int argc, char *argv[], int envc, char *envp[]) +{ + test_kernel_version(); + test_host_info(); + return 0; +} diff --git a/tests/test-mach_port.c b/tests/test-mach_port.c new file mode 100644 index 0000000..81a1072 --- /dev/null +++ b/tests/test-mach_port.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <mach/message.h> +#include <mach/mach_types.h> +#include <mach/vm_param.h> + +#include <syscalls.h> +#include <testlib.h> + +#include <mach.user.h> +#include <mach_port.user.h> + +void test_mach_port(void) +{ + kern_return_t err; + + mach_port_name_t *namesp; + mach_msg_type_number_t namesCnt=0; + mach_port_type_t *typesp; + mach_msg_type_number_t typesCnt=0; + err = mach_port_names(mach_task_self(), &namesp, &namesCnt, &typesp, &typesCnt); + ASSERT_RET(err, "mach_port_names"); + printf("mach_port_names: type/name length: %d %d\n", namesCnt, typesCnt); + ASSERT((namesCnt != 0) && (namesCnt == typesCnt), + "mach_port_names: wrong type/name length"); + for (int i=0; i<namesCnt; i++) + printf("port name %d type %x\n", namesp[i], typesp[i]); + + /* + * test mach_port_type() + * use the ports we have already as bootstrap modules + * maybe we could do more checks on the bootstrap ports, on other modules + */ + mach_port_type_t pt; + err = mach_port_type(mach_task_self(), host_priv(), &pt); + ASSERT_RET(err, "mach_port_type host_priv"); + ASSERT(pt == MACH_PORT_TYPE_SEND, "wrong type for host_priv"); + + err = mach_port_type(mach_task_self(), device_priv(), &pt); + ASSERT_RET(err, "mach_port_type device_priv"); + ASSERT(pt == MACH_PORT_TYPE_SEND, "wrong type for device_priv"); + + err = mach_port_rename(mach_task_self(), device_priv(), 111); + ASSERT_RET(err, "mach_port_rename"); + // FIXME: it seems we can't restore the old name + err = mach_port_rename(mach_task_self(), 111, 112); + ASSERT_RET(err, "mach_port_rename restore orig"); + + const mach_port_t nrx = 1000, nset = 1001, ndead = 1002; + err = mach_port_allocate_name(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, nrx); + ASSERT_RET(err, "mach_port_allocate_name rx"); + err = mach_port_allocate_name(mach_task_self(), MACH_PORT_RIGHT_PORT_SET, nset); + ASSERT_RET(err, "mach_port_allocate_name pset"); + err = mach_port_allocate_name(mach_task_self(), MACH_PORT_RIGHT_DEAD_NAME, ndead); + ASSERT_RET(err, "mach_port_allocate_name dead"); + + // set to a valid name to check it's really allocated to a new one + mach_port_t newname = nrx, oldname = nrx; + err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &newname); + ASSERT_RET(err, "mach_port_allocate"); + ASSERT(newname != nrx, "allocated name didn't change"); + + oldname = newname; + newname = nrx; + err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_PORT_SET, &newname); + ASSERT_RET(err, "mach_port_allocate"); + ASSERT(newname != nrx, "allocated name didn't change"); + ASSERT(newname != oldname, "allocated name is duplicated"); + + oldname = newname; + newname = nrx; + err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_DEAD_NAME, &newname); + ASSERT_RET(err, "mach_port_allocate"); + ASSERT(newname != nrx, "allocated name didn't change"); + ASSERT(newname != oldname, "allocated name is duplicated"); + + err = mach_port_destroy(mach_task_self(), newname); + ASSERT_RET(err, "mach_port_destroy"); + + mach_port_urefs_t urefs; + err = mach_port_get_refs(mach_task_self(), nrx, MACH_PORT_RIGHT_RECEIVE, &urefs); + ASSERT_RET(err, "mach_port_get_refs"); + ASSERT(urefs == 1, "rx port urefs"); + err = mach_port_get_refs(mach_task_self(), nset, MACH_PORT_RIGHT_PORT_SET, &urefs); + ASSERT_RET(err, "mach_port_get_refs"); + ASSERT(urefs == 1, "pset port urefs"); + err = mach_port_get_refs(mach_task_self(), ndead, MACH_PORT_RIGHT_DEAD_NAME, &urefs); + ASSERT_RET(err, "mach_port_get_refs"); + ASSERT(urefs == 1, "dead port urefs"); + + err = mach_port_destroy(mach_task_self(), nrx); + ASSERT_RET(err, "mach_port_destroy rx"); + err = mach_port_destroy(mach_task_self(), nset); + ASSERT_RET(err, "mach_port_destroy pset"); + err = mach_port_deallocate(mach_task_self(), ndead); + ASSERT_RET(err, "mach_port_deallocate dead"); + + // TODO test other rpc +} + +int main(int argc, char *argv[], int envc, char *envp[]) +{ + test_mach_port(); + return 0; +} diff --git a/tests/test-machmsg.c b/tests/test-machmsg.c new file mode 100644 index 0000000..60f3f49 --- /dev/null +++ b/tests/test-machmsg.c @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <mach/message.h> +#include <mach/mach_types.h> +#include <mach/vm_param.h> + +#include <syscalls.h> +#include <testlib.h> + +#include <mach.user.h> +#include <mach_port.user.h> +#include <mach_host.user.h> + +#define ECHO_MAX_BODY_LEN 256 + +static uint32_t align(uint32_t val, size_t aln) +{ + // we should check aln is a power of 2 + aln--; + return (val + aln) & (~aln); +} + +#define ALIGN_INLINE(val, n) { (val) = align((val), (n)); } + +struct echo_params +{ + mach_port_t rx_port; + mach_msg_size_t rx_size; + mach_msg_size_t rx_number; +}; + +void echo_thread (void *arg) +{ + struct echo_params *params = arg; + int err; + struct message + { + mach_msg_header_t header; + char body[ECHO_MAX_BODY_LEN]; + } message; + + printf ("thread echo started\n"); + for (mach_msg_size_t i=0; i<params->rx_number; i++) + { + message.header.msgh_local_port = params->rx_port; + message.header.msgh_size = sizeof (message); + + err = mach_msg (&message.header, + MACH_RCV_MSG, + 0, sizeof (message), + params->rx_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + ASSERT_RET(err, "mach_msg echo rx"); + printf("echo rx %d expected 5d\n", + message.header.msgh_size, params->rx_size); + ASSERT(message.header.msgh_size == params->rx_size, + "wrong size in echo rx"); + + message.header.msgh_local_port = MACH_PORT_NULL; + printf ("echo: msgh_id %d\n", message.header.msgh_id); + + err = mach_msg (&message.header, + MACH_SEND_MSG, + message.header.msgh_size, 0, + MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + ASSERT_RET(err, "mach_msg echo tx"); + } + printf ("thread echo stopped\n"); + thread_terminate (mach_thread_self ()); + FAILURE("thread_terminate"); +} + +#define TEST_ITERATIONS 3 + +// TODO run_test_iterations +void +test_iterations (void) +{ + mach_port_t port, receive; + int err; + struct message + { + mach_msg_header_t header; + mach_msg_type_t type; + char data[64]; + } message; + + err = mach_port_allocate (mach_task_self (), + MACH_PORT_RIGHT_RECEIVE, &port); + ASSERT_RET(err, "mach_port_allocate"); + + err = mach_port_allocate (mach_task_self (), + MACH_PORT_RIGHT_RECEIVE, &receive); + ASSERT_RET(err, "mach_port_allocate 2"); + + struct echo_params params; + params.rx_port = port; + params.rx_size = sizeof(message.header) + sizeof(message.type) + 5; + ALIGN_INLINE(params.rx_size, MACH_MSG_USER_ALIGNMENT); + params.rx_number = TEST_ITERATIONS; + test_thread_start (mach_task_self (), echo_thread, ¶ms); + + time_value_t start_time; + err = host_get_time (mach_host_self (), &start_time); + ASSERT_RET(err, "host_get_time"); + + /* Send a message down the port */ + for (int i = 0; i < TEST_ITERATIONS; i++) + { + struct message message; + + memset (&message, 0, sizeof (message)); + strcpy (message.data, "ciao"); + size_t datalen = strlen (message.data) + 1; + + message.header.msgh_bits + = MACH_MSGH_BITS (MACH_MSG_TYPE_MAKE_SEND, + MACH_MSG_TYPE_MAKE_SEND_ONCE); + message.header.msgh_remote_port = port; /* Request port */ + message.header.msgh_local_port = receive; /* Reply port */ + message.header.msgh_id = 123; /* Message id */ + message.header.msgh_size = sizeof (message.header) + sizeof (message.type) + datalen; /* Message size */ + ALIGN_INLINE(message.header.msgh_size, 4); + message.type.msgt_name = MACH_MSG_TYPE_STRING; /* Parameter type */ + message.type.msgt_size = 8 * datalen; /* # Bits */ + message.type.msgt_number = 1; /* Number of elements */ + message.type.msgt_inline = TRUE; /* Inlined */ + message.type.msgt_longform = FALSE; /* Shortform */ + message.type.msgt_deallocate = FALSE; /* Do not deallocate */ + message.type.msgt_unused = 0; /* = 0 */ + + /* Send the message on its way and wait for a reply. */ + err = mach_msg (&message.header, /* The header */ + MACH_SEND_MSG | MACH_RCV_MSG, /* Flags */ + message.header.msgh_size, /* Send size */ + sizeof (message), /* Max receive Size */ + receive, /* Receive port */ + MACH_MSG_TIMEOUT_NONE, /* No timeout */ + MACH_PORT_NULL); /* No notification */ + ASSERT_RET(err, "mach_msg txrx"); + } + + time_value_t stop_time; + err = host_get_time (mach_host_self (), &stop_time); + ASSERT_RET(err, "host_get_time"); + + printf ("start: %d.%06d\n", start_time.seconds, start_time.microseconds); + printf ("stop: %d.%06d\n", stop_time.seconds, stop_time.microseconds); +} + +/* + Test a specific message type on tx, rx and rx-continue paths + We need to be able to create a thread for this, so some rpc must work. +*/ +void +run_test_simple(void *msg, mach_msg_size_t msglen, mach_msg_id_t msgid) +{ + mach_msg_header_t *head = msg; + mach_port_t port, receive; + int err; + + err = syscall_mach_port_allocate (mach_task_self (), + MACH_PORT_RIGHT_RECEIVE, &port); + ASSERT_RET(err, "syscall_mach_port_allocate"); + + err = syscall_mach_port_allocate (mach_task_self (), + MACH_PORT_RIGHT_RECEIVE, &receive); + ASSERT_RET(err, "syscall_mach_port_allocate 2"); + + struct echo_params params; + params.rx_port = port; + params.rx_size = msglen; + params.rx_number = 1; + test_thread_start (mach_task_self (), echo_thread, ¶ms); + + head->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, + MACH_MSG_TYPE_MAKE_SEND_ONCE); + head->msgh_remote_port = port; + head->msgh_local_port = receive; + head->msgh_id = msgid; + head->msgh_size = msglen; + + err = mach_msg (msg, + MACH_SEND_MSG | MACH_RCV_MSG, + msglen, + msglen, + receive, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + ASSERT_RET(err, "mach_msg txrx"); + + printf("size in final rx: %d expected %d\n", head->msgh_size, msglen); + ASSERT(head->msgh_size == msglen, "wrong size in final rx"); +} + +void +run_test_simple_self(void *msg, mach_msg_size_t msglen, mach_msg_id_t msgid) +{ + mach_msg_header_t *head = msg; + mach_port_t port, receive; + int err; + + err = syscall_mach_port_allocate (mach_task_self (), + MACH_PORT_RIGHT_RECEIVE, &port); + ASSERT_RET(err, "syscall_mach_port_allocate"); + + head->msgh_bits + = MACH_MSGH_BITS (MACH_MSG_TYPE_MAKE_SEND, + MACH_MSG_TYPE_MAKE_SEND_ONCE); + /* head->msgh_bits */ + /* = MACH_MSGH_BITS (MACH_MSG_TYPE_MAKE_SEND_ONCE, */ + /* MACH_MSG_TYPE_COPY_SEND); */ + + head->msgh_bits |= MACH_MSGH_BITS_COMPLEX; + head->msgh_remote_port = port; + head->msgh_local_port = port; + head->msgh_id = msgid; + head->msgh_size = msglen; + + err = mach_msg (msg, + MACH_SEND_MSG | MACH_RCV_MSG, + msglen, + msglen, + port, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + ASSERT_RET(err, "mach_msg txrx"); + + printf("size in final rx: %d expected %d\n", head->msgh_size, msglen); + ASSERT(head->msgh_size == msglen, "wrong size in final rx\n"); +} + + +void test_msg_string(void) +{ + struct message + { + mach_msg_header_t header; + mach_msg_type_t type; + char data[64]; + } msg; + char *test_strings[] = {"123", "12345", "ciaociao"}; + + memset (&msg, 0, sizeof (struct message)); + strcpy (msg.data, "ciao"); + size_t datalen = strlen (msg.data) + 1; + + int msgid = 123; + int msglen = sizeof (msg.header) + sizeof (msg.type) + datalen; + ALIGN_INLINE(msglen, MACH_MSG_USER_ALIGNMENT); + msg.type.msgt_name = MACH_MSG_TYPE_STRING; + msg.type.msgt_size = 8 * datalen; + msg.type.msgt_number = 1; + msg.type.msgt_inline = TRUE; + msg.type.msgt_longform = FALSE; + msg.type.msgt_deallocate = FALSE; + msg.type.msgt_unused = 0; + + run_test_simple_self(&msg, msglen, msgid); + run_test_simple(&msg, msglen, msgid); +} + +void test_msg_string2(void) +{ + struct message + { + mach_msg_header_t header; + mach_msg_type_t type; + char data[10]; + mach_msg_type_t type2; + char data2[5]; + } msg; + const int len1 = 10; + const int len2 = 5; + + memset (&msg, 0, sizeof (struct message)); + int msgid = 123; + int msglen = sizeof (msg.header) + sizeof (msg.type) + len1; + ALIGN_INLINE(msglen, MACH_MSG_USER_ALIGNMENT); + msglen += sizeof (msg.type2) + len2; + ALIGN_INLINE(msglen, MACH_MSG_USER_ALIGNMENT); + msg.type.msgt_name = MACH_MSG_TYPE_STRING; + msg.type.msgt_size = 8 * len1; + msg.type.msgt_number = 1; + msg.type.msgt_inline = TRUE; + msg.type.msgt_longform = FALSE; + msg.type.msgt_deallocate = FALSE; + msg.type.msgt_unused = 0; + memset (msg.data, 'c', len1); + msg.type2.msgt_name = MACH_MSG_TYPE_CHAR; + msg.type2.msgt_size = 8; + msg.type2.msgt_number = len2; + msg.type2.msgt_inline = TRUE; + msg.type2.msgt_longform = FALSE; + msg.type2.msgt_deallocate = FALSE; + msg.type2.msgt_unused = 0; + memset (msg.data2, 'x', len2); + + run_test_simple_self(&msg, msglen, msgid); + run_test_simple(&msg, msglen, msgid); +} + + +void test_msg_ports(void) +{ + struct message + { + mach_msg_header_t head; + mach_msg_type_t type; + mach_port_t *portp; + } msg; + mach_port_t msgports[3]; + + memset (&msg, 0, sizeof (struct message)); + + int msgid = 123; + int msglen = sizeof (msg.head) + sizeof (msg.type) + sizeof(msg.portp); + msg.type.msgt_name = MACH_MSG_TYPE_MOVE_SEND; + msg.type.msgt_size = 8*sizeof(mach_port_t); + msg.type.msgt_number = 3; + msg.type.msgt_inline = FALSE; + msg.type.msgt_longform = FALSE; + msg.type.msgt_deallocate = FALSE; + msg.type.msgt_unused = 0; + msg.portp = msgports; + msgports[0] = mach_host_self(); + msgports[1] = mach_task_self(); + msgports[2] = mach_thread_self(); + + run_test_simple_self(&msg, msglen, msgid); + run_test_simple(&msg, msglen, msgid); +} + +void test_msg_emptydesc(void) +{ + struct message + { + mach_msg_header_t header; + mach_msg_type_t type_empty; + vm_offset_t addr_empty; + mach_msg_type_t type; + char data[64]; + } msg; + + memset (&msg, 0, sizeof (struct message)); + strcpy (msg.data, "ciao"); + size_t datalen = strlen (msg.data) + 1; + + int msgid = 123; + int msglen = sizeof (msg.header); + msglen += sizeof (msg.type_empty)+ sizeof (msg.addr_empty); + msglen += sizeof (msg.type) + datalen; + ALIGN_INLINE(msglen, MACH_MSG_USER_ALIGNMENT); + msg.type_empty.msgt_name = MACH_MSG_TYPE_STRING; + msg.type_empty.msgt_size = 8; + msg.type_empty.msgt_number = 0; + msg.type_empty.msgt_inline = FALSE; + msg.type_empty.msgt_longform = FALSE; + msg.type_empty.msgt_deallocate = FALSE; + msg.type_empty.msgt_unused = 0; + msg.addr_empty = 0; + + msg.type.msgt_name = MACH_MSG_TYPE_STRING; + msg.type.msgt_size = 8 * datalen; + msg.type.msgt_number = 1; + msg.type.msgt_inline = TRUE; + msg.type.msgt_longform = FALSE; + msg.type.msgt_deallocate = FALSE; + msg.type.msgt_unused = 0; + + run_test_simple_self(&msg, msglen, msgid); + run_test_simple(&msg, msglen, msgid); +} + + +int +main (int argc, char *argv[], int envc, char *envp[]) +{ + printf("test_msg_string()\n"); + test_msg_string(); + printf("test_msg_string2()\n"); + test_msg_string2(); + printf("test_msg_ports()\n"); + test_msg_ports(); + printf("test_msg_emptydesc()\n"); + test_msg_emptydesc(); + printf("test_iters()\n"); + test_iterations(); + return 0; +} diff --git a/tests/test-multiboot.in b/tests/test-multiboot.in new file mode 100644 index 0000000..20ab330 --- /dev/null +++ b/tests/test-multiboot.in @@ -0,0 +1,30 @@ +#!@SHELL@ + +# Test if the kernel image complies with the multiboot specification. + +# Copyright (C) 2023 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +if grub-file --help > /dev/null 2>&1 +then grub-file --is-x86-multiboot gnumach +else + # `grub-file' is not available -- ignore this test. + exit 77 +fi + +# Local Variables: +# mode: shell-script +# End: diff --git a/tests/test-syscalls.c b/tests/test-syscalls.c new file mode 100644 index 0000000..be4df8c --- /dev/null +++ b/tests/test-syscalls.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <syscalls.h> +#include <testlib.h> + +#include <mach/exception.h> +#include <mach/mig_errors.h> +#include <mach/vm_param.h> + +#include <mach.user.h> +#include <mach_port.user.h> +#include <exc.server.h> + + +static struct { + mach_port_t exception_port; + mach_port_t thread; + mach_port_t task; + integer_t exception; + integer_t code; + integer_t subcode; +} last_exc; +kern_return_t catch_exception_raise(mach_port_t exception_port, + mach_port_t thread, mach_port_t task, + integer_t exception, integer_t code, + long_integer_t subcode) +{ + printf("received catch_exception_raise(%u %u %u %d %d %d)\n", + exception_port, thread, task, exception, code, subcode); + last_exc.exception_port = exception_port; + last_exc.thread = thread; + last_exc.task = task; + last_exc.exception = exception; + last_exc.code = code; + last_exc.subcode = subcode; + return KERN_SUCCESS; +} + +static char simple_request_data[PAGE_SIZE]; +static char simple_reply_data[PAGE_SIZE]; +int simple_msg_server(boolean_t (*demuxer) (mach_msg_header_t *request, + mach_msg_header_t *reply), + mach_port_t rcv_port_name, + int num_msgs) +{ + int midx = 0, mok = 0; + int ret; + mig_reply_header_t *request = (mig_reply_header_t*)simple_request_data; + mig_reply_header_t *reply = (mig_reply_header_t*)simple_reply_data; + while ((midx - num_msgs) < 0) + { + ret = mach_msg(&request->Head, MACH_RCV_MSG, 0, PAGE_SIZE, + rcv_port_name, 0, MACH_PORT_NULL); + switch (ret) + { + case MACH_MSG_SUCCESS: + if ((*demuxer)(&request->Head, &reply->Head)) + mok++; // TODO send reply + else + FAILURE("demuxer didn't handle the message"); + break; + default: + ASSERT_RET(ret, "receiving in msg_server"); + break; + } + midx++; + } + if (mok != midx) + FAILURE("wrong number of message received"); + return mok != midx; +} + + +void test_syscall_bad_arg_on_stack(void *arg) +{ + /* mach_msg() has 7 arguments, so the last one should be always + passed on the stack on x86. Here we make ESP/RSP point to the + wrong place to test the access check */ +#ifdef __x86_64__ + asm volatile("movq $0x123,%rsp;" \ + "movq $-25,%rax;" \ + "syscall;" \ + ); +#else + asm volatile("mov $0x123,%esp;" \ + "mov $-25,%eax;" \ + "lcall $0x7,$0x0;" \ + ); +#endif + FAILURE("we shouldn't be here!"); +} + +void test_bad_syscall_num(void *arg) +{ +#ifdef __x86_64__ + asm volatile("movq $0x123456,%rax;" \ + "syscall;" \ + ); +#else + asm volatile("mov $0x123456,%eax;" \ + "lcall $0x7,$0x0;" \ + ); +#endif + FAILURE("we shouldn't be here!"); +} + + +int main(int argc, char *argv[], int envc, char *envp[]) +{ + int err; + mach_port_t excp; + + err = mach_port_allocate(mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &excp); + ASSERT_RET(err, "creating exception port"); + + err = mach_port_insert_right(mach_task_self(), excp, excp, + MACH_MSG_TYPE_MAKE_SEND); + ASSERT_RET(err, "inserting send right into exception port"); + + err = task_set_special_port(mach_task_self(), TASK_EXCEPTION_PORT, excp); + ASSERT_RET(err, "setting task exception port"); + + /* FIXME: receiving an exception with small size causes GP on 64 bit userspace */ + /* mig_reply_header_t msg; */ + /* err = mach_msg(&msg.Head, /\* The header *\/ */ + /* MACH_RCV_MSG, */ + /* 0, */ + /* sizeof (msg), /\* Max receive Size *\/ */ + /* excp, */ + /* 1000, */ + /* MACH_PORT_NULL); */ + + // FIXME: maybe MIG should provide this prototype? + boolean_t exc_server + (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP); + + memset(&last_exc, 0, sizeof(last_exc)); + test_thread_start(mach_task_self(), test_bad_syscall_num, NULL); + ASSERT_RET(simple_msg_server(exc_server, excp, 1), "error in exc server"); + ASSERT((last_exc.exception == EXC_BAD_INSTRUCTION) && (last_exc.code == EXC_I386_INVOP), + "bad exception for test_bad_syscall_num()"); + + memset(&last_exc, 0, sizeof(last_exc)); + test_thread_start(mach_task_self(), test_syscall_bad_arg_on_stack, NULL); + ASSERT_RET(simple_msg_server(exc_server, excp, 1), "error in exc server"); + ASSERT((last_exc.exception == EXC_BAD_ACCESS) && (last_exc.code == KERN_INVALID_ADDRESS), + "bad exception for test_syscall_bad_arg_on_stack()"); + + return 0; +} diff --git a/tests/test-task.c b/tests/test-task.c new file mode 100644 index 0000000..cbc75e2 --- /dev/null +++ b/tests/test-task.c @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <syscalls.h> +#include <testlib.h> + +#include <mach/machine/vm_param.h> +#include <mach/std_types.h> +#include <mach/mach_types.h> +#include <mach/vm_wire.h> +#include <mach/vm_param.h> + +#include <gnumach.user.h> +#include <mach.user.h> + + +void test_task() +{ + mach_port_t ourtask = mach_task_self(); + mach_msg_type_number_t count; + int err; + + struct task_basic_info binfo; + count = TASK_BASIC_INFO_COUNT; + err = task_info(ourtask, TASK_BASIC_INFO, (task_info_t)&binfo, &count); + ASSERT_RET(err, "TASK_BASIC_INFO"); + ASSERT(binfo.virtual_size > binfo.resident_size, "wrong memory counters"); + + struct task_events_info einfo; + count = TASK_EVENTS_INFO_COUNT; + err = task_info(ourtask, TASK_EVENTS_INFO, (task_info_t)&einfo, &count); + ASSERT_RET(err, "TASK_EVENTS_INFO"); + printf("msgs sent %llu received %llu\n", + einfo.messages_sent, einfo.messages_received); + + struct task_thread_times_info ttinfo; + count = TASK_THREAD_TIMES_INFO_COUNT; + err = task_info(ourtask, TASK_THREAD_TIMES_INFO, (task_info_t)&ttinfo, &count); + ASSERT_RET(err, "TASK_THREAD_TIMES_INFO"); + printf("run user %lld system %lld\n", + ttinfo.user_time64.seconds, ttinfo.user_time64.nanoseconds); +} + + +void dummy_thread(void *arg) +{ + printf("started dummy thread\n"); + while (1) + ; +} + +void check_threads(thread_t *threads, mach_msg_type_number_t nthreads) +{ + for (int tid=0; tid<nthreads; tid++) + { + struct thread_basic_info tinfo; + mach_msg_type_number_t thcount = THREAD_BASIC_INFO_COUNT; + int err = thread_info(threads[tid], THREAD_BASIC_INFO, (thread_info_t)&tinfo, &thcount); + ASSERT_RET(err, "thread_info"); + ASSERT(thcount == THREAD_BASIC_INFO_COUNT, "thcount"); + printf("th%d (port %d):\n", tid, threads[tid]); + printf(" user time %d.%06d\n", tinfo.user_time.seconds, tinfo.user_time.microseconds); + printf(" system time %d.%06d\n", tinfo.system_time.seconds, tinfo.system_time.microseconds); + printf(" cpu usage %d\n", tinfo.cpu_usage); + printf(" creation time %d.%06d\n", tinfo.creation_time.seconds, tinfo.creation_time.microseconds); + } +} + +static void test_task_threads() +{ + thread_t *threads; + mach_msg_type_number_t nthreads; + int err; + + err = task_threads(mach_task_self(), &threads, &nthreads); + ASSERT_RET(err, "task_threads"); + ASSERT(nthreads == 1, "nthreads"); + check_threads(threads, nthreads); + + thread_t t1 = test_thread_start(mach_task_self(), dummy_thread, 0); + + thread_t t2 = test_thread_start(mach_task_self(), dummy_thread, 0); + + // let the threads run + msleep(100); + + err = task_threads(mach_task_self(), &threads, &nthreads); + ASSERT_RET(err, "task_threads"); + ASSERT(nthreads == 3, "nthreads"); + check_threads(threads, nthreads); + + err = thread_terminate(t1); + ASSERT_RET(err, "thread_terminate"); + err = thread_terminate(t2); + ASSERT_RET(err, "thread_terminate"); + + err = task_threads(mach_task_self(), &threads, &nthreads); + ASSERT_RET(err, "task_threads"); + ASSERT(nthreads == 1, "nthreads"); + check_threads(threads, nthreads); +} + +void test_new_task() +{ + int err; + task_t newtask; + err = task_create(mach_task_self(), 1, &newtask); + ASSERT_RET(err, "task_create"); + + err = task_suspend(newtask); + ASSERT_RET(err, "task_suspend"); + + err = task_set_name(newtask, "newtask"); + ASSERT_RET(err, "task_set_name"); + + thread_t *threads; + mach_msg_type_number_t nthreads; + err = task_threads(newtask, &threads, &nthreads); + ASSERT_RET(err, "task_threads"); + ASSERT(nthreads == 0, "nthreads 0"); + + test_thread_start(newtask, dummy_thread, 0); + + err = task_resume(newtask); + ASSERT_RET(err, "task_resume"); + + msleep(100); // let the thread run a bit + + err = task_threads(newtask, &threads, &nthreads); + ASSERT_RET(err, "task_threads"); + ASSERT(nthreads == 1, "nthreads 1"); + check_threads(threads, nthreads); + + err = thread_terminate(threads[0]); + ASSERT_RET(err, "thread_terminate"); + + err = task_terminate(newtask); + ASSERT_RET(err, "task_terminate"); +} + +int test_errors() +{ + int err; + err = task_resume(MACH_PORT_NAME_DEAD); + ASSERT(err == MACH_SEND_INVALID_DEST, "task DEAD"); +} + + +int main(int argc, char *argv[], int envc, char *envp[]) +{ + test_task(); + test_task_threads(); + test_new_task(); + test_errors(); + return 0; +} diff --git a/tests/test-threads.c b/tests/test-threads.c new file mode 100644 index 0000000..06630be --- /dev/null +++ b/tests/test-threads.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <mach/machine/thread_status.h> + +#include <syscalls.h> +#include <testlib.h> + +#include <mach.user.h> + +void sleeping_thread(void* arg) +{ + printf("starting thread %d\n", arg); + for (int i=0; i<100; i++) + msleep(50); + printf("stopping thread %d\n", arg); + thread_terminate(mach_thread_self()); + FAILURE("thread_terminate"); +} + +void test_many(void) +{ + for (long tid=0; tid<10; tid++) + { + test_thread_start(mach_task_self(), sleeping_thread, (void*)tid); + } + // TODO: wait for thread end notifications + msleep(6000); +} + +#ifdef __x86_64__ +void test_fsgs_base_thread(void* tid) +{ + int err; +#if defined(__SEG_FS) && defined(__SEG_GS) + long __seg_fs *fs_ptr; + long __seg_gs *gs_ptr; + long fs_value; + long gs_value; + + struct i386_fsgs_base_state state; + state.fs_base = (unsigned long)&fs_value; + state.gs_base = (unsigned long)&gs_value; + err = thread_set_state(mach_thread_self(), i386_FSGS_BASE_STATE, + (thread_state_t) &state, i386_FSGS_BASE_STATE_COUNT); + ASSERT_RET(err, "thread_set_state"); + + fs_value = 0x100 + (long)tid; + gs_value = 0x200 + (long)tid; + + msleep(50); // allow the others to set their segment base + + fs_ptr = 0; + gs_ptr = 0; + long rdvalue = *fs_ptr; + printf("FS expected %lx read %lx\n", fs_value, rdvalue); + ASSERT(fs_value == rdvalue, "FS base error\n"); + rdvalue = *gs_ptr; + printf("GS expected %lx read %lx\n", gs_value, rdvalue); + ASSERT(gs_value == rdvalue, "GS base error\n"); +#else +#error " missing __SEG_FS and __SEG_GS" +#endif + + thread_terminate(mach_thread_self()); + FAILURE("thread_terminate"); +} +#endif + +void test_fsgs_base(void) +{ +#ifdef __x86_64__ + int err; + for (long tid=0; tid<10; tid++) + { + test_thread_start(mach_task_self(), test_fsgs_base_thread, (void*)tid); + } + msleep(1000); // TODO: wait for threads +#endif +} + + +int main(int argc, char *argv[], int envc, char *envp[]) +{ + test_fsgs_base(); + test_many(); + return 0; +} diff --git a/tests/test-vm.c b/tests/test-vm.c new file mode 100644 index 0000000..4ece792 --- /dev/null +++ b/tests/test-vm.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <syscalls.h> +#include <testlib.h> + +#include <mach/machine/vm_param.h> +#include <mach/std_types.h> +#include <mach/mach_types.h> +#include <mach/vm_wire.h> +#include <mach/vm_param.h> + +#include <device.user.h> +#include <gnumach.user.h> +#include <mach.user.h> +#include <mach_port.user.h> + + +static void test_memobj() +{ + // this emulates maptime() mapping and reading + struct mapped_time_value *mtime; + int64_t secs, usecs; + mach_port_t device, memobj; + int err; + + err = device_open (device_priv(), 0, "time", &device); + ASSERT_RET(err, "device_open"); + err = device_map (device, VM_PROT_READ, 0, sizeof(*mtime), &memobj, 0); + ASSERT_RET(err, "device_map"); + err = mach_port_deallocate (mach_task_self (), device); + ASSERT_RET(err, "mach_port_deallocate"); + mtime = 0; + err = vm_map(mach_task_self (), (vm_address_t *)&mtime, sizeof *mtime, 0, 1, + memobj, 0, 0, VM_PROT_READ, VM_PROT_READ, VM_INHERIT_NONE); + ASSERT_RET(err, "vm_map"); + + do + { + secs = mtime->seconds; + __sync_synchronize (); + usecs = mtime->microseconds; + __sync_synchronize (); + } + while (secs != mtime->check_seconds); + printf("mapped time is %lld.%lld\n",secs, usecs); + + err = mach_port_deallocate (mach_task_self (), memobj); + ASSERT_RET(err, "mach_port_deallocate"); + err = vm_deallocate(mach_task_self(), (vm_address_t)mtime, sizeof(*mtime)); + ASSERT_RET(err, "vm_deallocate"); +} + +static void test_wire() +{ + int err = vm_wire_all(host_priv(), mach_task_self(), VM_WIRE_ALL); + ASSERT_RET(err, "vm_wire_all-ALL"); + err = vm_wire_all(host_priv(), mach_task_self(), VM_WIRE_NONE); + ASSERT_RET(err, "vm_wire_all-NONE"); + // TODO check that all memory is actually wired or unwired +} + +int main(int argc, char *argv[], int envc, char *envp[]) +{ + printf("VM_MIN_ADDRESS=0x%p\n", VM_MIN_ADDRESS); + printf("VM_MAX_ADDRESS=0x%p\n", VM_MAX_ADDRESS); + test_wire(); + test_memobj(); + return 0; +} diff --git a/tests/testlib.c b/tests/testlib.c new file mode 100644 index 0000000..2eaeb59 --- /dev/null +++ b/tests/testlib.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 Free Software Foundation + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <testlib.h> + +#include <device/cons.h> +#include <mach/kern_return.h> +#include <mach/message.h> +#include <mach/mig_errors.h> +#include <mach/vm_param.h> + +#include <mach.user.h> +#include <mach_host.user.h> + + +static int argc = 0; +static char *argv_unknown[] = {"unknown", "m1", "123", "456"}; +static char **argv = argv_unknown; +static char **envp = NULL; +static int envc = 0; + +static mach_port_t host_priv_port = 1; +static mach_port_t device_master_port = 2; + +void cnputc(char c, vm_offset_t cookie) +{ + char buf[2] = {c, 0}; + mach_print(buf); +} + +mach_port_t host_priv(void) +{ + return host_priv_port; +} + +mach_port_t device_priv(void) +{ + return device_master_port; +} + +void halt() +{ + int ret = host_reboot(host_priv_port, 0); + ASSERT_RET(ret, "host_reboot() failed!"); + while (1) + ; +} + +int msleep(uint32_t timeout) +{ + mach_port_t recv = mach_reply_port(); + return mach_msg(NULL, MACH_RCV_MSG|MACH_RCV_TIMEOUT|MACH_RCV_INTERRUPT, + 0, 0, recv, timeout, MACH_PORT_NULL); +} + +const char* e2s(int err) +{ + const char* s = e2s_gnumach(err); + if (s != NULL) + return s; + else + switch (err) + { + default: return "unknown"; + } +} + +/* + * Minimal _start() for test modules, we just take the arguments from the + * kernel, call main() and reboot. As in glibc, we expect the argument pointer + * as a first asrgument. + */ +void __attribute__((used, retain)) +c_start(void **argptr) +{ + intptr_t* argcptr = (intptr_t*)argptr; + argc = argcptr[0]; + argv = (char **) &argcptr[1]; + envp = &argv[argc + 1]; + envc = 0; + + while (envp[envc]) + ++envc; + + mach_atoi(argv[1], &host_priv_port); + mach_atoi(argv[2], &device_master_port); + + printf("started %s", argv[0]); + for (int i=1; i<argc; i++) + { + printf(" %s", argv[i]); + } + printf("\n"); + + int ret = main(argc, argv, envc, envp); + + printf("%s: test %s exit code %x\n", TEST_SUCCESS_MARKER, argv[0], ret); + halt(); +} diff --git a/tests/testlib_thread_start.c b/tests/testlib_thread_start.c new file mode 100644 index 0000000..fa8af0e --- /dev/null +++ b/tests/testlib_thread_start.c @@ -0,0 +1,86 @@ +/* + * MIT License + * + * Copyright (c) 2017 Luc Chabassier + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* This small helper was started from + * https://github.com/dwarfmaster/mach-ipc/blob/master/minimal_threads/main.c + * and then reworked. */ + +#include <testlib.h> +#include <mach/vm_param.h> +#include <mach.user.h> + +/* This is just a temporary mapping to set up the stack */ +static long stack_top[PAGE_SIZE/sizeof(long)] __attribute__ ((aligned (PAGE_SIZE))); + +thread_t test_thread_start(task_t task, void(*routine)(void*), void* arg) { + const vm_size_t stack_size = PAGE_SIZE * 16; + kern_return_t ret; + vm_address_t stack; + + ret = vm_allocate(task, &stack, stack_size, TRUE); + ASSERT_RET(ret, "can't allocate the stack for a new thread"); + + ret = vm_protect(task, stack, PAGE_SIZE, FALSE, VM_PROT_NONE); + ASSERT_RET(ret, "can't protect the stack from overflows"); + + long *top = (long*)((vm_offset_t)stack_top + PAGE_SIZE) - 1; +#ifdef __i386__ + *top = (long)arg; /* The argument is passed on the stack on x86_32 */ + *(top - 1) = 0; /* The return address */ +#elif defined(__x86_64__) + *top = 0; /* The return address */ +#endif + ret = vm_write(task, stack + stack_size - PAGE_SIZE, (vm_offset_t)stack_top, PAGE_SIZE); + ASSERT_RET(ret, "can't initialize the stack for the new thread"); + + thread_t thread; + ret = thread_create(task, &thread); + ASSERT_RET(ret, "thread_create()"); + + struct i386_thread_state state; + unsigned int count; + count = i386_THREAD_STATE_COUNT; + ret = thread_get_state(thread, i386_REGS_SEGS_STATE, + (thread_state_t) &state, &count); + ASSERT_RET(ret, "thread_get_state()"); + +#ifdef __i386__ + state.eip = (long) routine; + state.uesp = (long) (stack + stack_size - sizeof(long) * 2); + state.ebp = 0; +#elif defined(__x86_64__) + state.rip = (long) routine; + state.ursp = (long) (stack + stack_size - sizeof(long) * 1); + state.rbp = 0; + state.rdi = (long)arg; +#endif + ret = thread_set_state(thread, i386_REGS_SEGS_STATE, + (thread_state_t) &state, i386_THREAD_STATE_COUNT); + ASSERT_RET(ret, "thread_set_state"); + + ret = thread_resume(thread); + ASSERT_RET(ret, "thread_resume"); + + return thread; +} diff --git a/tests/user-qemu.mk b/tests/user-qemu.mk new file mode 100644 index 0000000..fd5ae1a --- /dev/null +++ b/tests/user-qemu.mk @@ -0,0 +1,221 @@ +# Copyright (C) 2024 Free Software Foundation + +# This program is free software ; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation ; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY ; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with the program ; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +# +# MIG stubs generation for user-space tests +# + +MACH_TESTINSTALL = $(builddir)/tests/include-mach +MACH_TESTINCLUDE = $(MACH_TESTINSTALL)/$(prefix)/include + +MIGCOMUSER = $(USER_MIG) -n -cc cat - /dev/null +MIG_OUTDIR = $(builddir)/tests/mig-out +MIG_CPPFLAGS = -x c -nostdinc -I$(MACH_TESTINCLUDE) + +# FIXME: how can we reliably detect a change on any header and reinstall them? +$(MACH_TESTINSTALL): + mkdir -p $@ + $(MAKE) install-data DESTDIR=$@ + +prepare-test: $(MACH_TESTINSTALL) + +$(MIG_OUTDIR): + mkdir -p $@ + +define generate_mig_client +$(MIG_OUTDIR)/$(2).user.c: prepare-test $(MIG_OUTDIR) $(MACH_TESTINCLUDE)/$(1)/$(2).defs + $(USER_CPP) $(USER_CPPFLAGS) $(MIG_CPPFLAGS) \ + -o $(MIG_OUTDIR)/$(2).user.defs \ + $(MACH_TESTINCLUDE)/$(1)/$(2).defs + $(MIGCOMUSER) $(MIGCOMFLAGS) $(MIGCOMUFLAGS) \ + -user $(MIG_OUTDIR)/$(2).user.c \ + -header $(MIG_OUTDIR)/$(2).user.h \ + -list $(MIG_OUTDIR)/$(2).user.msgids \ + < $(MIG_OUTDIR)/$(2).user.defs +endef + +define generate_mig_server +$(MIG_OUTDIR)/$(2).server.c: prepare-test $(MIG_OUTDIR) $(srcdir)/include/$(1)/$(2).defs + $(USER_CPP) $(USER_CPPFLAGS) $(MIG_CPPFLAGS) \ + -o $(MIG_OUTDIR)/$(2).server.defs \ + $(srcdir)/include/$(1)/$(2).defs + $(MIGCOMUSER) $(MIGCOMFLAGS) $(MIGCOMUFLAGS) \ + -server $(MIG_OUTDIR)/$(2).server.c \ + -header $(MIG_OUTDIR)/$(2).server.h \ + -list $(MIG_OUTDIR)/$(2).server.msgids \ + < $(MIG_OUTDIR)/$(2).server.defs +endef + +# These are all the IPC implemented in the kernel, both as a server or as a client. +# Files are sorted as in +# find builddir/tests/include-mach/ -name *.defs | grep -v types | sort +# eval->info for debug of generated rules +$(eval $(call generate_mig_client,device,device)) +$(eval $(call generate_mig_client,device,device_reply)) +$(eval $(call generate_mig_client,device,device_request)) +$(eval $(call generate_mig_client,mach_debug,mach_debug)) +# default_pager.defs? +$(eval $(call generate_mig_server,mach,exc)) +# experimental.defs? +$(eval $(call generate_mig_client,mach,gnumach)) +$(eval $(call generate_mig_client,mach,mach4)) +$(eval $(call generate_mig_client,mach,mach)) +$(eval $(call generate_mig_client,mach,mach_host)) +$(eval $(call generate_mig_client,mach,mach_port)) +# memory_object{_default}.defs? +# notify.defs? +$(eval $(call generate_mig_server,mach,task_notify)) +if HOST_ix86 +$(eval $(call generate_mig_client,mach/i386,mach_i386)) +endif +if HOST_x86_64 +$(eval $(call generate_mig_client,mach/x86_64,mach_i386)) +endif + +# NOTE: keep in sync with the rules above +MIG_GEN_CC = \ + $(MIG_OUTDIR)/device.user.c \ + $(MIG_OUTDIR)/device_reply.user.c \ + $(MIG_OUTDIR)/device_request.user.c \ + $(MIG_OUTDIR)/mach_debug.user.c \ + $(MIG_OUTDIR)/exc.server.c \ + $(MIG_OUTDIR)/gnumach.user.c \ + $(MIG_OUTDIR)/mach4.user.c \ + $(MIG_OUTDIR)/mach.user.c \ + $(MIG_OUTDIR)/mach_host.user.c \ + $(MIG_OUTDIR)/mach_port.user.c \ + $(MIG_OUTDIR)/task_notify.server.c \ + $(MIG_OUTDIR)/mach_i386.user.c + +# +# compilation of user space tests and utilities +# + +TEST_START_MARKER = booting-start-of-test +TEST_SUCCESS_MARKER = gnumach-test-success-and-reboot +TEST_FAILURE_MARKER = gnumach-test-failure + +TESTCFLAGS = -static -nostartfiles -nolibc \ + -ffreestanding \ + -ftrivial-auto-var-init=pattern \ + -I$(srcdir)/tests/include \ + -I$(MACH_TESTINCLUDE) \ + -I$(MIG_OUTDIR) \ + -ggdb3 \ + -DMIG_EOPNOTSUPP + +SRC_TESTLIB= \ + $(srcdir)/i386/i386/strings.c \ + $(srcdir)/kern/printf.c \ + $(srcdir)/kern/strings.c \ + $(srcdir)/util/atoi.c \ + $(srcdir)/tests/syscalls.S \ + $(srcdir)/tests/start.S \ + $(srcdir)/tests/testlib.c \ + $(srcdir)/tests/testlib_thread_start.c \ + $(builddir)/tests/errlist.c \ + $(MIG_GEN_CC) + +tests/errlist.c: $(addprefix $(srcdir)/include/mach/,message.h kern_return.h mig_errors.h) + echo "/* autogenerated file */" >$@ + echo "#include <mach/message.h>" >>$@ + echo "#include <mach/kern_return.h>" >>$@ + echo "#include <mach/mig_errors.h>" >>$@ + echo "#include <testlib.h>" >>$@ + echo "#include <stddef.h>" >>$@ + echo "const char* TEST_SUCCESS_MARKER = \"$(TEST_SUCCESS_MARKER)\";" >>$@ + echo "const char* TEST_FAILURE_MARKER = \"$(TEST_FAILURE_MARKER)\";" >>$@ + echo "const char* e2s_gnumach(int err) { switch (err) {" >>$@ + grep "define[[:space:]]MIG" $(srcdir)/include/mach/mig_errors.h | \ + awk '{printf " case %s: return \"%s\";\n", $$2, $$2}' >>$@ + grep "define[[:space:]]KERN" $(srcdir)/include/mach/kern_return.h | \ + awk '{printf " case %s: return \"%s\";\n", $$2, $$2}' >>$@ + awk 'f;/MACH_MSG_SUCCESS/{f=1}' $(srcdir)/include/mach/message.h | \ + grep "define[[:space:]]MACH" | \ + awk '{printf " case %s: return \"%s\";\n", $$2, $$2}' >>$@ + echo " default: return NULL;" >>$@ + echo "}}" >>$@ + +tests/module-%: $(srcdir)/tests/test-%.c $(SRC_TESTLIB) $(MACH_TESTINSTALL) + $(USER_CC) $(USER_CFLAGS) $(TESTCFLAGS) $< $(SRC_TESTLIB) -o $@ + +# +# packaging of qemu bootable image and test runner +# + +GNUMACH_ARGS = console=com0 +QEMU_OPTS = -m 2048 -nographic -no-reboot -boot d +QEMU_GDB_PORT ?= 1234 + +if HOST_ix86 +QEMU_BIN = qemu-system-i386 +QEMU_OPTS += -cpu pentium3-v1 +endif +if HOST_x86_64 +QEMU_BIN = qemu-system-x86_64 +QEMU_OPTS += -cpu core2duo-v1 +endif + +tests/test-%.iso: tests/module-% gnumach $(srcdir)/tests/grub.cfg.single.template + rm -rf $(builddir)/tests/isofiles + mkdir -p $(builddir)/tests/isofiles/boot/grub/ + < $(srcdir)/tests/grub.cfg.single.template \ + sed -e "s|BOOTMODULE|$(notdir $<)|g" \ + -e "s/GNUMACHARGS/$(GNUMACH_ARGS)/g" \ + -e "s/TEST_START_MARKER/$(TEST_START_MARKER)/g" \ + >$(builddir)/tests/isofiles/boot/grub/grub.cfg + cp gnumach $< $(builddir)/tests/isofiles/boot/ + grub-mkrescue -o $@ $(builddir)/tests/isofiles + +tests/test-%: tests/test-%.iso $(srcdir)/tests/run-qemu.sh.template + < $(srcdir)/tests/run-qemu.sh.template \ + sed -e "s|TESTNAME|$(subst tests/test-,,$@)|g" \ + -e "s/QEMU_OPTS/$(QEMU_OPTS)/g" \ + -e "s/QEMU_BIN/$(QEMU_BIN)/g" \ + -e "s/TEST_START_MARKER/$(TEST_START_MARKER)/g" \ + -e "s/TEST_SUCCESS_MARKER/$(TEST_SUCCESS_MARKER)/g" \ + -e "s/TEST_FAILURE_MARKER/$(TEST_FAILURE_MARKER)/g" \ + >$@ + chmod +x $@ + +clean-test-%: + rm -f tests/test-$*.iso tests/module-$* tests/test-$** + + +USER_TESTS := \ + tests/test-hello \ + tests/test-mach_host \ + tests/test-gsync \ + tests/test-mach_port \ + tests/test-vm \ + tests/test-syscalls \ + tests/test-machmsg \ + tests/test-task \ + tests/test-threads + +USER_TESTS_CLEAN = $(subst tests/,clean-,$(USER_TESTS)) + +# +# helpers for interactive test run and debug +# + +run-%: tests/test-% + $^ + +# don't reuse the launcher script as the timeout would kill the debug session +debug-%: tests/test-%.iso + $(QEMU_BIN) $(QEMU_OPTS) -cdrom $< -gdb tcp::$(QEMU_GDB_PORT) -S \ + | sed -n "/$(TEST_START_MARKER)/"',$$p' |