diff --git a/tests/TestSvc/source/test_thread_creation.arch.arm64.s b/tests/TestSvc/source/test_thread_creation.arch.arm64.s new file mode 100644 index 000000000..22aa7fcde --- /dev/null +++ b/tests/TestSvc/source/test_thread_creation.arch.arm64.s @@ -0,0 +1,43 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + */ + +/* ams::test::TestThreadCreateRegistersOnFunctionEntry(void *ctx) */ +.section .text._ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv, "ax", %progbits +.global _ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv +.type _ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv, %function +_ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv: + /* Save all registers to our context. */ + stp x0, x1, [x0, #0x00] + stp x2, x3, [x0, #0x10] + stp x4, x5, [x0, #0x20] + stp x6, x7, [x0, #0x30] + stp x8, x9, [x0, #0x40] + stp x10, x11, [x0, #0x50] + stp x12, x13, [x0, #0x60] + stp x14, x15, [x0, #0x70] + stp x16, x17, [x0, #0x80] + stp x18, x19, [x0, #0x90] + stp x20, x21, [x0, #0xA0] + stp x22, x23, [x0, #0xB0] + stp x24, x25, [x0, #0xC0] + stp x26, x27, [x0, #0xD0] + stp x28, x29, [x0, #0xE0] + + mov x1, sp + stp x30, x1, [x0, #0xF0] + + /* Exit the thread. */ + svc 0xa \ No newline at end of file diff --git a/tests/TestSvc/source/test_thread_creation.cpp b/tests/TestSvc/source/test_thread_creation.cpp new file mode 100644 index 000000000..e5a1e715c --- /dev/null +++ b/tests/TestSvc/source/test_thread_creation.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + */ +#include +#include "util_common.hpp" +#include "util_scoped_heap.hpp" + +namespace ams::test { + + void TestThreadCreateRegistersOnFunctionEntry(void *ctx); + + DOCTEST_TEST_CASE( "Creating a thread results in fixed register contents." ) { + /* Create heap. */ + ScopedHeap heap(os::MemoryPageSize); + + /* Create register buffer. */ + u64 thread_registers[32]; + std::memset(thread_registers, 0xCC, sizeof(thread_registers)); + + /* Create thread. */ + svc::Handle thread_handle; + DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(std::addressof(thread_handle), reinterpret_cast(&TestThreadCreateRegistersOnFunctionEntry), reinterpret_cast(thread_registers), heap.GetAddress() + os::MemoryPageSize, HighestTestPriority, NumCores - 1))); + + /* Start thread. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handle))); + + /* Wait for thread to exit. */ + s32 dummy; + DOCTEST_CHECK(R_SUCCEEDED(svc::WaitSynchronization(std::addressof(dummy), std::addressof(thread_handle), 1, -1))); + + /* Close thread handle. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handle))); + + /* Check thread initial registers. */ + for (size_t i = 0; i < util::size(thread_registers); ++i) { + if (i == 0) { + /* X0 is argument. */ + DOCTEST_CHECK(thread_registers[i] == reinterpret_cast(thread_registers)); + } else if (i == 18) { + /* X18 is an odd cfi value. */ + DOCTEST_CHECK(thread_registers[i] != 0); + DOCTEST_CHECK((thread_registers[i] & 0x1) != 0); + } else if (i == 31) { + /* SP is user-provided sp. */ + DOCTEST_CHECK(thread_registers[i] == (heap.GetAddress() + os::MemoryPageSize)); + } else { + /* All other registers are zero. */ + DOCTEST_CHECK(thread_registers[i] == 0); + } + } + } + +} \ No newline at end of file diff --git a/tests/TestSvc/source/util_common.hpp b/tests/TestSvc/source/util_common.hpp index 0ba2e9c3b..86bfa9f9e 100644 --- a/tests/TestSvc/source/util_common.hpp +++ b/tests/TestSvc/source/util_common.hpp @@ -46,7 +46,7 @@ namespace ams::test { /* Wait long enough that we can be confident preemption will occur, and therefore our interrupt flag will be set. */ { - constexpr auto MinimumTicksToGuaranteeInterruptFlag = ::ams::svc::Tick(PreemptionTimeSpan) + 1; + constexpr auto MinimumTicksToGuaranteeInterruptFlag = ::ams::svc::Tick(PreemptionTimeSpan) + ::ams::svc::Tick(PreemptionTimeSpan) + 2; auto GetSystemTickForPinnedThread = []() ALWAYS_INLINE_LAMBDA -> ::ams::svc::Tick { s64 v;