On Tue, Apr 16, 2024 at 4:01 AM Samuel Thibault <samuel.thiba...@gnu.org> wrote: > Ah, no, I mis read the result. It does stay stuck on x86_64.
Indeed, thanks. Reproduced and fixed; I was accidentally using rsp (instead of ursp) in one place. Sergey -- >8 -- This tests generating and handling exceptions, thread_get_state(), thread_set_state(), and newly added thread_set_self_state(). It does many of the same things that glibc does when handling a signal. --- tests/test-thread-state.c | 215 ++++++++++++++++++++++++++++++++++++++ tests/user-qemu.mk | 3 +- 2 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 tests/test-thread-state.c diff --git a/tests/test-thread-state.c b/tests/test-thread-state.c new file mode 100644 index 00000000..b78ab110 --- /dev/null +++ b/tests/test-thread-state.c @@ -0,0 +1,215 @@ +/* + * 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 this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <syscalls.h> +#include <testlib.h> +#include <mach/exception.h> +#include <mach.user.h> +#include <mach_port.user.h> + +#if defined(__x86_64__) || defined(__i386__) +#define THREAD_STATE_FLAVOR i386_THREAD_STATE +#define THREAD_STATE_COUNT i386_THREAD_STATE_COUNT +#elif defined(__aarch64__) +#define THREAD_STATE_FLAVOR AARCH64_THREAD_STATE +#define THREAD_STATE_COUNT AARCH64_THREAD_STATE_COUNT +#else +#error "Don't know which state to use on this platform" +#endif + +/* + * We'll make the thread itself run this function when it faults. + * This simulates handling a Unix SIGSEGV. + */ +static void __attribute__((noreturn)) fault_handler( + vm_offset_t fault_address, + thread_state_t state) +{ + kern_return_t kr; + vm_size_t i; + vm_offset_t addr = fault_address; + + printf("Handling a fault at 0x%p\n", fault_address); + + /* + * Allocate the missing area of memory. + */ + kr = vm_allocate(mach_task_self(), &addr, vm_page_size, FALSE); + ASSERT_RET(kr, "failed to allocate missing memory"); + + /* + * Fill it with some data. + */ + for (i = 0; i < vm_page_size / sizeof(int); i++) + *((int *) addr + i) = i; + + /* + * Return back to the interrupted code. + */ + kr = thread_set_self_state(THREAD_STATE_FLAVOR, state, + THREAD_STATE_COUNT); + ASSERT_RET(kr, "thread_set_self_state failed"); + FAILURE("thread_set_self_state returned"); +} + +/* + * The exception_raise() RPC handler. Mach calls this when the other thread faults. + * This runs in a different thread; it could fix things up directly and resume the + * thread. Instead, it sets things up so that the thread itself will fix things + * for itself, and then return back to what it was doing. + */ +kern_return_t catch_exception_raise( + mach_port_t exception_port, + thread_t thread, + task_t task, + integer_t exception, + integer_t code, + long_integer_t subcode) +{ + kern_return_t kr; + vm_offset_t off; +#if defined(__x86_64__) || defined(__i386__) + struct i386_thread_state state; +#elif defined(__aarch64__) + struct aarch64_thread_state state; +#else +#error "Don't know which state to use on this platform" +#endif + mach_msg_type_number_t state_count = THREAD_STATE_COUNT; + + + printf("Received exception_raise(%u %u 0x%lx)\n", exception, code, subcode); + + /* + * We only want to handle EXC_BAD_ACCESS/KERN_INVALID_ADDRESS. + * Return an error to proceed with the default handling otherwise. + */ + if (exception != EXC_BAD_ACCESS) + return KERN_FAILURE; + if (code != KERN_INVALID_ADDRESS) + return KERN_FAILURE; + + kr = thread_get_state(thread, THREAD_STATE_FLAVOR, + (thread_state_t) &state, &state_count); + ASSERT_RET(kr, "thread_get_state get failed"); + ASSERT(state_count == THREAD_STATE_COUNT, "bad state_count"); + +#if defined(__x86_64__) + /* + * Place a copy of the state on the thread's stack. + */ + off = ((state.ursp - 128 - sizeof(state)) & ~15UL) - 8; + memcpy((void *) off, &state, sizeof(state)); + + /* + * Make it call fault_handler(subcode, off). + */ + state.ursp = off; + state.rip = (vm_offset_t) fault_handler; + state.rdi = (vm_offset_t) subcode; + state.rsi = off; +#elif defined(__i386__) + /* + * Place a copy of the state on the thread's stack. + */ + off = state.uesp - sizeof(state); + memcpy((void *) off, &state, sizeof(state)); + + /* + * Make it call fault_handler(subcode, off). + */ + *(vm_offset_t *) (off - 4) = off; + *(vm_offset_t *) (off - 8) = (vm_offset_t) subcode; + state.uesp = off - 12; + state.eip = (vm_offset_t) fault_handler; +#elif defined(__aarch64__) + /* + * Place a copy of the state on the thread's stack. + */ + off = (state.sp - sizeof(state)) & ~15UL; + memcpy((void *) off, &state, sizeof(state)); + + /* + * Make it call fault_handler(subcode, off). + */ + state.sp = off; + state.pc = (vm_offset_t) fault_handler; + state.x[0] = (vm_offset_t) subcode; + state.x[1] = off; +#else +#error "Don't know how to manipulate state to use on this platform" +#endif + + kr = thread_set_state(thread, THREAD_STATE_FLAVOR, + (thread_state_t) &state, state_count); + ASSERT_RET(kr, "thread_set_state failed"); + + /* + * Our job here is done! Returning success resumes the thread. + */ + mach_port_deallocate(mach_task_self(), thread); + mach_port_deallocate(mach_task_self(), task); + + return KERN_SUCCESS; +} + +static void exc_server_thread_body(void *arg) +{ + kern_return_t kr; + mach_port_t exc_port = (mach_port_t) (vm_offset_t) arg; + + boolean_t exc_server( + mach_msg_header_t *request, + mach_msg_header_t *reply); + + kr = mach_msg_server(exc_server, 4096, exc_port, MACH_MSG_OPTION_NONE); + ASSERT_RET(kr, "error in mach_msg_server"); +} + +static void do_count(void) +{ + const int *arr = (const int *) 0x10000000; + int i; + unsigned long count = 0; + + for (i = 0; i < vm_page_size / sizeof(int) * 3; i++) + count += arr[i]; + + ASSERT(vm_page_size == 4096, "need a different answer for a different page size"); + ASSERT(count == 0x17fa00, "bad count"); +} + +int main(int argc, char *argv[], int envc, char *envp[]) +{ + kern_return_t kr; + mach_port_t exc_port = mach_reply_port(); + + test_thread_start(mach_task_self(), exc_server_thread_body, + (void *) (vm_offset_t) exc_port); + + kr = mach_port_insert_right(mach_task_self(), exc_port, + exc_port, MACH_MSG_TYPE_MAKE_SEND); + ASSERT_RET(kr, "mach_port_insert_right"); + + kr = thread_set_exception_port(mach_thread_self(), exc_port); + ASSERT_RET(kr, "thread_set_exception_port failed"); + + do_count(); + + return 0; +} diff --git a/tests/user-qemu.mk b/tests/user-qemu.mk index 3b546252..a3013b59 100644 --- a/tests/user-qemu.mk +++ b/tests/user-qemu.mk @@ -209,7 +209,8 @@ USER_TESTS := \ tests/test-syscalls \ tests/test-machmsg \ tests/test-task \ - tests/test-threads + tests/test-threads \ + tests/test-thread-state USER_TESTS_CLEAN = $(subst tests/,clean-,$(USER_TESTS)) -- 2.44.0