diff options
-rw-r--r-- | Android.bp | 3 | ||||
-rw-r--r-- | code_gen_lib/code_gen_lib_riscv64_test.cc | 177 | ||||
-rw-r--r-- | code_gen_lib/gen_wrapper_riscv64_to_x86_64.cc | 39 | ||||
-rw-r--r-- | guest_abi/guest_function_wrapper_signature_test.cc | 6 | ||||
-rw-r--r-- | guest_abi/include/berberis/guest_abi/guest_function_wrapper_signature.h | 45 | ||||
-rw-r--r-- | guest_os_primitives/guest_signal_handling.cc | 16 | ||||
-rw-r--r-- | guest_os_primitives/guest_thread.cc | 14 | ||||
-rw-r--r-- | guest_os_primitives/guest_thread_clone.cc | 9 | ||||
-rw-r--r-- | guest_os_primitives/include/berberis/guest_os_primitives/guest_thread.h | 13 | ||||
-rw-r--r-- | guest_state/riscv64/get_cpu_state.cc | 22 | ||||
-rw-r--r-- | jni/jni_trampolines.cc | 20 | ||||
-rw-r--r-- | runtime_primitives/interpret_helpers_riscv64.cc | 13 | ||||
-rw-r--r-- | tests/ndk_program_tests/Android.bp | 1 | ||||
-rw-r--r-- | tests/ndk_program_tests/clone_test.cc | 141 |
14 files changed, 453 insertions, 66 deletions
@@ -150,7 +150,6 @@ cc_library_shared { "libberberis_backend_riscv64_to_x86_64", "libberberis_code_gen_lib_riscv64", "libberberis_guest_abi_riscv64", - "libberberis_guest_state_riscv64", "libberberis_heavy_optimizer_riscv64", "libberberis_interpreter_riscv64", "libberberis_kernel_api_riscv64", @@ -164,6 +163,8 @@ cc_library_shared { // Proxy libraries reference symbols from guest_os_primitives, // so we need to keep them all. "libberberis_guest_os_primitives_riscv64", + // Android debuggerd reference symbols from get_cpu_state. + "libberberis_guest_state_riscv64", "libberberis_runtime_riscv64_to_x86_64", ], export_static_lib_headers: [ diff --git a/code_gen_lib/code_gen_lib_riscv64_test.cc b/code_gen_lib/code_gen_lib_riscv64_test.cc index db81612c..789cf5fe 100644 --- a/code_gen_lib/code_gen_lib_riscv64_test.cc +++ b/code_gen_lib/code_gen_lib_riscv64_test.cc @@ -147,24 +147,163 @@ TEST(CodeGenLib, GenWrapGuestFunction) { ASSERT_TRUE(g_called); } +void Run10UInt8(GuestAddr pc, GuestArgumentBuffer* buf) { + ASSERT_EQ(ToGuestAddr(&g_insn), pc); + ASSERT_NE(buf, nullptr); + ASSERT_EQ(buf->argc, 8); + ASSERT_EQ(buf->stack_argc, 16); + ASSERT_EQ(buf->resc, 1); + ASSERT_EQ(buf->argv[0], 0U); + ASSERT_EQ(buf->argv[1], 0xffU); + ASSERT_EQ(buf->argv[2], 2U); + ASSERT_EQ(buf->argv[3], 3U); + ASSERT_EQ(buf->argv[4], 4U); + ASSERT_EQ(buf->argv[5], 5U); + ASSERT_EQ(buf->argv[6], 6U); + ASSERT_EQ(buf->argv[7], 0xf9U); + ASSERT_EQ(buf->stack_argv[0], 0xf8U); + ASSERT_EQ(buf->stack_argv[1], 9U); + buf->argv[0] = 0xf6; +} + +TEST(CodeGenLib, GenWrapGuestFunction_Run10UInt8) { + MachineCode machine_code; + + GenWrapGuestFunction( + &machine_code, ToGuestAddr(&g_insn), "zzzzzzzzzzz", AsHostCode(Run10UInt8), "Run10UInt8"); + + ScopedExecRegion exec(&machine_code); + + using Func = uint8_t( + uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t); + uint8_t res = exec.get<Func>()(0, 0xff, 2, 3, 4, 5, 6, 0xf9, 0xf8, 9); + ASSERT_EQ(res, 0xF6u); +} + +void Run10Int8(GuestAddr pc, GuestArgumentBuffer* buf) { + ASSERT_EQ(ToGuestAddr(&g_insn), pc); + ASSERT_NE(buf, nullptr); + ASSERT_EQ(buf->argc, 8); + ASSERT_EQ(buf->stack_argc, 16); + ASSERT_EQ(buf->resc, 1); + ASSERT_EQ(buf->argv[0], 0U); + ASSERT_EQ(buf->argv[1], 0xffff'ffff'ffff'ffffULL); + ASSERT_EQ(buf->argv[2], 2U); + ASSERT_EQ(buf->argv[3], 3U); + ASSERT_EQ(buf->argv[4], 4U); + ASSERT_EQ(buf->argv[5], 5U); + ASSERT_EQ(buf->argv[6], 6U); + ASSERT_EQ(buf->argv[7], 0xffff'ffff'ffff'fff9ULL); + ASSERT_EQ(buf->stack_argv[0], 0xffff'ffff'ffff'fff8ULL); + ASSERT_EQ(buf->stack_argv[1], 9U); + buf->argv[0] = 0xffff'ffff'ffff'fff6; +} + +TEST(CodeGenLib, GenWrapGuestFunction_Run10Int8) { + MachineCode machine_code; + + GenWrapGuestFunction( + &machine_code, ToGuestAddr(&g_insn), "bbbbbbbbbbb", AsHostCode(Run10Int8), "Run10Int8"); + + ScopedExecRegion exec(&machine_code); + + using Func = + int8_t(int8_t, int8_t, int8_t, int8_t, int8_t, int8_t, int8_t, int8_t, int8_t, int8_t); + int8_t res = exec.get<Func>()(0, -1, 2, 3, 4, 5, 6, -7, -8, 9); + ASSERT_EQ(res, -10); +} + +void Run10UInt16(GuestAddr pc, GuestArgumentBuffer* buf) { + ASSERT_EQ(ToGuestAddr(&g_insn), pc); + ASSERT_NE(buf, nullptr); + ASSERT_EQ(buf->argc, 8); + ASSERT_EQ(buf->stack_argc, 16); + ASSERT_EQ(buf->resc, 1); + ASSERT_EQ(buf->argv[0], 0U); + ASSERT_EQ(buf->argv[1], 0xffffU); + ASSERT_EQ(buf->argv[2], 2U); + ASSERT_EQ(buf->argv[3], 3U); + ASSERT_EQ(buf->argv[4], 4U); + ASSERT_EQ(buf->argv[5], 5U); + ASSERT_EQ(buf->argv[6], 6U); + ASSERT_EQ(buf->argv[7], 0xfff9U); + ASSERT_EQ(buf->stack_argv[0], 0xfff8U); + ASSERT_EQ(buf->stack_argv[1], 9U); + buf->argv[0] = 0xfff6; +} + +TEST(CodeGenLib, GenWrapGuestFunction_Run10UInt16) { + MachineCode machine_code; + + GenWrapGuestFunction( + &machine_code, ToGuestAddr(&g_insn), "ccccccccccc", AsHostCode(Run10UInt16), "Run10UInt16"); + + ScopedExecRegion exec(&machine_code); + + using Func = uint16_t(uint16_t, + uint16_t, + uint16_t, + uint16_t, + uint16_t, + uint16_t, + uint16_t, + uint16_t, + uint16_t, + uint16_t); + uint16_t res = exec.get<Func>()(0, 0xffff, 2, 3, 4, 5, 6, 0xfff9, 0xfff8, 9); + ASSERT_EQ(res, 0xfff6U); +} + +void Run10Int16(GuestAddr pc, GuestArgumentBuffer* buf) { + ASSERT_EQ(ToGuestAddr(&g_insn), pc); + ASSERT_NE(buf, nullptr); + ASSERT_EQ(buf->argc, 8); + ASSERT_EQ(buf->stack_argc, 16); + ASSERT_EQ(buf->resc, 1); + ASSERT_EQ(buf->argv[0], 0U); + ASSERT_EQ(buf->argv[1], 0xffff'ffff'ffff'ffffULL); + ASSERT_EQ(buf->argv[2], 2U); + ASSERT_EQ(buf->argv[3], 3U); + ASSERT_EQ(buf->argv[4], 4U); + ASSERT_EQ(buf->argv[5], 5U); + ASSERT_EQ(buf->argv[6], 6U); + ASSERT_EQ(buf->argv[7], 0xffff'ffff'ffff'fff9ULL); + ASSERT_EQ(buf->stack_argv[0], 0xffff'ffff'ffff'fff8ULL); + ASSERT_EQ(buf->stack_argv[1], 9U); + buf->argv[0] = 0xffff'ffff'ffff'fff6; +} + +TEST(CodeGenLib, GenWrapGuestFunction_Run10Int16) { + MachineCode machine_code; + + GenWrapGuestFunction( + &machine_code, ToGuestAddr(&g_insn), "sssssssssss", AsHostCode(Run10Int16), "Run10Int16"); + + ScopedExecRegion exec(&machine_code); + + using Func = int16_t( + int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t); + int16_t res = exec.get<Func>()(0, -1, 2, 3, 4, 5, 6, -7, -8, 9); + ASSERT_EQ(res, -10); +} + void Run10Int(GuestAddr pc, GuestArgumentBuffer* buf) { - ASSERT_EQ(pc, ToGuestAddr(&g_insn)); - ASSERT_NE(nullptr, buf); - ASSERT_EQ(8, buf->argc); - ASSERT_EQ(16, buf->stack_argc); - ASSERT_EQ(1, buf->resc); - // For 32-bit parameters, only least-significant bits are defined! - ASSERT_EQ(0u, static_cast<uint32_t>(buf->argv[0])); - ASSERT_EQ(1u, static_cast<uint32_t>(buf->argv[1])); - ASSERT_EQ(2u, static_cast<uint32_t>(buf->argv[2])); - ASSERT_EQ(3u, static_cast<uint32_t>(buf->argv[3])); - ASSERT_EQ(4u, static_cast<uint32_t>(buf->argv[4])); - ASSERT_EQ(5u, static_cast<uint32_t>(buf->argv[5])); - ASSERT_EQ(6u, static_cast<uint32_t>(buf->argv[6])); - ASSERT_EQ(7u, static_cast<uint32_t>(buf->argv[7])); - ASSERT_EQ(8u, static_cast<uint32_t>(buf->stack_argv[0])); - ASSERT_EQ(9u, static_cast<uint32_t>(buf->stack_argv[1])); - buf->argv[0] = 45; + ASSERT_EQ(ToGuestAddr(&g_insn), pc); + ASSERT_NE(buf, nullptr); + ASSERT_EQ(buf->argc, 8); + ASSERT_EQ(buf->stack_argc, 16); + ASSERT_EQ(buf->resc, 1); + ASSERT_EQ(buf->argv[0], 0U); + ASSERT_EQ(buf->argv[1], 0xffff'ffff'ffff'ffffULL); + ASSERT_EQ(buf->argv[2], 2U); + ASSERT_EQ(buf->argv[3], 3U); + ASSERT_EQ(buf->argv[4], 4U); + ASSERT_EQ(buf->argv[5], 5U); + ASSERT_EQ(buf->argv[6], 6U); + ASSERT_EQ(buf->argv[7], 0xffff'ffff'ffff'fff9ULL); + ASSERT_EQ(buf->stack_argv[0], 0xffff'ffff'ffff'fff8ULL); + ASSERT_EQ(buf->stack_argv[1], 9U); + buf->argv[0] = 0xffff'ffff'ffff'fff6; } TEST(CodeGenLib, GenWrapGuestFunction_Run10Int) { @@ -176,8 +315,8 @@ TEST(CodeGenLib, GenWrapGuestFunction_Run10Int) { ScopedExecRegion exec(&machine_code); using Func = int(int, int, int, int, int, int, int, int, int, int); - int res = exec.get<Func>()(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - ASSERT_EQ(45, res); + int res = exec.get<Func>()(0, -1, 2, 3, 4, 5, 6, -7, -8, 9); + ASSERT_EQ(res, -10); } void Run10Fp(GuestAddr pc, GuestArgumentBuffer* buf) { diff --git a/code_gen_lib/gen_wrapper_riscv64_to_x86_64.cc b/code_gen_lib/gen_wrapper_riscv64_to_x86_64.cc index 5ec2be55..65c2811b 100644 --- a/code_gen_lib/gen_wrapper_riscv64_to_x86_64.cc +++ b/code_gen_lib/gen_wrapper_riscv64_to_x86_64.cc @@ -28,6 +28,31 @@ namespace berberis { using x86_64::Assembler; +namespace { + +void ExtendIntArg(MacroAssembler<Assembler>& as, + char type, + Assembler::Register dst, + Assembler::Register src) { + if (type == 'z') { + as.Movzxbq(dst, src); + } else if (type == 'b') { + as.Movsxbq(dst, src); + } else if (type == 's') { + as.Movsxwq(dst, src); + } else if (type == 'c') { + as.Movzxwq(dst, src); + } else if (type == 'i') { + as.Movsxlq(dst, src); + } else if (type == 'p' || type == 'l') { + // Do nothing. The parameter is already 64 bits. + } else { + FATAL("non-integer signature char '%c'", type); + } +} + +} // namespace + void GenWrapGuestFunction(MachineCode* mc, GuestAddr pc, const char* signature, @@ -80,7 +105,8 @@ void GenWrapGuestFunction(MachineCode* mc, int stack_argc = 0; int host_stack_argc = 0; for (size_t i = 1; signature[i] != '\0'; ++i) { - if (signature[i] == 'i' || signature[i] == 'p' || signature[i] == 'l') { + if (signature[i] == 'z' || signature[i] == 'b' || signature[i] == 's' || signature[i] == 'c' || + signature[i] == 'i' || signature[i] == 'p' || signature[i] == 'l') { static constexpr Assembler::Register kParamRegs[] = { Assembler::rdi, Assembler::rsi, @@ -90,16 +116,19 @@ void GenWrapGuestFunction(MachineCode* mc, Assembler::r9, }; if (argc < static_cast<int>(std::size(kParamRegs))) { + ExtendIntArg(as, signature[i], kParamRegs[argc], kParamRegs[argc]); as.Movq({.base = Assembler::rsp, .disp = kArgvOffset + argc * 8}, kParamRegs[argc]); } else if (argc < 8) { as.Movq(Assembler::rax, {.base = Assembler::rsp, .disp = params_offset + host_stack_argc * 8}); ++host_stack_argc; + ExtendIntArg(as, signature[i], Assembler::rax, Assembler::rax); as.Movq({.base = Assembler::rsp, .disp = kArgvOffset + argc * 8}, Assembler::rax); } else { as.Movq(Assembler::rax, {.base = Assembler::rsp, .disp = params_offset + host_stack_argc * 8}); ++host_stack_argc; + ExtendIntArg(as, signature[i], Assembler::rax, Assembler::rax); as.Movq({.base = Assembler::rsp, .disp = kStackArgvOffset + stack_argc * 8}, Assembler::rax); ++stack_argc; @@ -151,7 +180,8 @@ void GenWrapGuestFunction(MachineCode* mc, as.Movl({.base = Assembler::rsp, .disp = kStackArgcOffset}, stack_argc * 8); // Set resc. - if (signature[0] == 'i' || signature[0] == 'p' || signature[0] == 'l') { + if (signature[0] == 'z' || signature[0] == 'b' || signature[0] == 's' || signature[0] == 'c' || + signature[0] == 'i' || signature[0] == 'p' || signature[0] == 'l') { as.Movl({.base = Assembler::rsp, .disp = kRescOffset}, 1); as.Movl({.base = Assembler::rsp, .disp = kFpRescOffset}, 0); } else if (signature[0] == 'f' || signature[0] == 'd') { @@ -169,7 +199,10 @@ void GenWrapGuestFunction(MachineCode* mc, as.Call(guest_runner); // Get the result. - if (signature[0] == 'i' || signature[0] == 'p' || signature[0] == 'l') { + if (signature[0] == 'z' || signature[0] == 'b' || signature[0] == 's' || signature[0] == 'c' || + signature[0] == 'i' || signature[0] == 'p' || signature[0] == 'l') { + // It is not necessary to unbox integer return values. The callee will truncate rax to only + // retrieve the bits appropriate for the return type. as.Movq(Assembler::rax, {.base = Assembler::rsp, .disp = kArgvOffset}); } else if (signature[0] == 'f') { // Only take the lower 32 bits of the result register because floats are 1-extended (NaN boxed) diff --git a/guest_abi/guest_function_wrapper_signature_test.cc b/guest_abi/guest_function_wrapper_signature_test.cc index 6ac05860..a136779f 100644 --- a/guest_abi/guest_function_wrapper_signature_test.cc +++ b/guest_abi/guest_function_wrapper_signature_test.cc @@ -22,8 +22,10 @@ namespace berberis { namespace { -static_assert('i' == kGuestFunctionWrapperSignatureChar<bool>); -static_assert('i' == kGuestFunctionWrapperSignatureChar<char>); +static_assert('z' == kGuestFunctionWrapperSignatureChar<bool>); +static_assert('b' == kGuestFunctionWrapperSignatureChar<char>); +static_assert('s' == kGuestFunctionWrapperSignatureChar<int16_t>); +static_assert('c' == kGuestFunctionWrapperSignatureChar<uint16_t>); static_assert('i' == kGuestFunctionWrapperSignatureChar<int>); static_assert('l' == kGuestFunctionWrapperSignatureChar<long long>); static_assert('p' == kGuestFunctionWrapperSignatureChar<void*>); diff --git a/guest_abi/include/berberis/guest_abi/guest_function_wrapper_signature.h b/guest_abi/include/berberis/guest_abi/guest_function_wrapper_signature.h index f879e37f..382e83ae 100644 --- a/guest_abi/include/berberis/guest_abi/guest_function_wrapper_signature.h +++ b/guest_abi/include/berberis/guest_abi/guest_function_wrapper_signature.h @@ -27,8 +27,12 @@ namespace berberis { // // Supported types: // 'v': void (as return type) -// 'i': integer and enum types <= 32bit -// 'l': integer and enum types == 64bit +// 'z': unsigned integer and enum types == 8bit (jboolean equivalent) +// 'b': signed integer and enum types == 8bit (jbyte equivalent) +// 's': signed integer and enum types == 16bit (jshort equivalent) +// 'c': unsigned integer types == 16bit (jchar equivalent) +// 'i': integer and enum types == 32bit (jint equivalent) +// 'l': integer and enum types == 64bit (jfloat equivalent) // 'p': pointers (to objects and functions but not to members) // 'f': float (floating point 32 bits) // 'd': double (floating point 64 bits) @@ -47,7 +51,42 @@ class kGuestFunctionWrapperSignatureCharHelper { } template <typename Type, - std::enable_if_t<(std::is_integral_v<Type> || std::is_enum_v<Type>)&&sizeof(Type) <= + std::enable_if_t<(std::is_integral_v<Type> || + std::is_enum_v<Type>)&&std::is_unsigned_v<Type> && + sizeof(Type) == sizeof(uint8_t), + int> = 0> + static constexpr char Value() { + return 'z'; + } + + template < + typename Type, + std::enable_if_t<(std::is_integral_v<Type> || std::is_enum_v<Type>)&&std::is_signed_v<Type> && + sizeof(Type) == sizeof(int8_t), + int> = 0> + static constexpr char Value() { + return 'b'; + } + + template < + typename Type, + std::enable_if_t<(std::is_integral_v<Type> || std::is_enum_v<Type>)&&std::is_signed_v<Type> && + sizeof(Type) == sizeof(int16_t), + int> = 0> + static constexpr char Value() { + return 's'; + } + + template <typename Type, + std::enable_if_t<std::is_integral_v<Type> && std::is_unsigned_v<Type> && + sizeof(Type) == sizeof(uint16_t), + int> = 0> + static constexpr char Value() { + return 'c'; + } + + template <typename Type, + std::enable_if_t<(std::is_integral_v<Type> || std::is_enum_v<Type>)&&sizeof(Type) == sizeof(int32_t), int> = 0> static constexpr char Value() { diff --git a/guest_os_primitives/guest_signal_handling.cc b/guest_os_primitives/guest_signal_handling.cc index a6b0ba49..6739ea5f 100644 --- a/guest_os_primitives/guest_signal_handling.cc +++ b/guest_os_primitives/guest_signal_handling.cc @@ -16,6 +16,7 @@ #include <atomic> #include <csignal> +#include <memory> #include <mutex> #if defined(__BIONIC__) @@ -162,12 +163,15 @@ bool IsReservedSignal(int signal) { } // namespace void GuestThread::SetDefaultSignalActionsTable() { - signal_actions_ = &g_signal_actions; + // We need to initialize shared_ptr, but we don't want to attempt to delete the default + // signal actions when guest thread terminates. Hence we specify a void deleter. + signal_actions_ = std::shared_ptr<GuestSignalActionsTable>(&g_signal_actions, [](auto) {}); } -void GuestThread::CloneSignalActionsTableTo(GuestSignalActionsTable& new_table_storage) { - new_table_storage = *signal_actions_; - signal_actions_ = &new_table_storage; +void GuestThread::CloneSignalActionsTableFrom(GuestSignalActionsTable* from_table) { + // Need lock to make sure from_table isn't changed concurrently. + std::lock_guard<std::mutex> lock(g_signal_actions_guard_mutex); + signal_actions_ = std::make_shared<GuestSignalActionsTable>(*from_table); } // Can be interrupted by another SetSignal! @@ -298,7 +302,7 @@ void GuestThread::ProcessPendingSignalsImpl() { siginfo_t* signal_info; while ((signal_info = pending_signals_.DequeueSignalUnsafe())) { - const Guest_sigaction* sa = FindSignalHandler(*signal_actions_, signal_info->si_signo); + const Guest_sigaction* sa = FindSignalHandler(*signal_actions_.get(), signal_info->si_signo); ProcessGuestSignal(this, sa, signal_info); pending_signals_.FreeSignal(signal_info); } @@ -318,8 +322,8 @@ bool SetGuestSignalHandler(int signal, act = nullptr; } - GuestSignalAction& action = GetCurrentGuestThread()->GetSignalActionsTable()->at(signal - 1); std::lock_guard<std::mutex> lock(g_signal_actions_guard_mutex); + GuestSignalAction& action = GetCurrentGuestThread()->GetSignalActionsTable()->at(signal - 1); return action.Change(signal, act, HandleHostSignal, old_act, error); } diff --git a/guest_os_primitives/guest_thread.cc b/guest_os_primitives/guest_thread.cc index 3a02c7f6..1979a81b 100644 --- a/guest_os_primitives/guest_thread.cc +++ b/guest_os_primitives/guest_thread.cc @@ -82,13 +82,11 @@ GuestThread* GuestThread::Create() { intrinsics::InitState(); - thread->SetDefaultSignalActionsTable(); - return thread; } // static -GuestThread* GuestThread::CreateClone(const GuestThread* parent) { +GuestThread* GuestThread::CreateClone(const GuestThread* parent, bool share_signal_actions) { GuestThread* thread = Create(); if (thread == nullptr) { return nullptr; @@ -106,6 +104,13 @@ GuestThread* GuestThread::CreateClone(const GuestThread* parent) { SetCPUState(*thread->state(), GetCPUState(*parent->state())); SetTlsAddr(*thread->state(), GetTlsAddr(*parent->state())); + if (share_signal_actions) { + // New shared_ptr user. + thread->signal_actions_ = parent->signal_actions_; + } else { + thread->CloneSignalActionsTableFrom(parent->signal_actions_.get()); + } + return thread; } @@ -137,6 +142,8 @@ GuestThread* GuestThread::CreatePthread(void* stack, size_t stack_size, size_t g return nullptr; } + thread->SetDefaultSignalActionsTable(); + return thread; } @@ -158,6 +165,7 @@ void GuestThread::Destroy(GuestThread* thread) { if (ArePendingSignalsPresent(*thread->state_)) { TRACE("thread destroyed with pending signals, signals ignored!"); } + thread->signal_actions_.reset(); if (thread->host_stack_) { // This happens only on cleanup after failed creation. diff --git a/guest_os_primitives/guest_thread_clone.cc b/guest_os_primitives/guest_thread_clone.cc index be5f6bb8..31062464 100644 --- a/guest_os_primitives/guest_thread_clone.cc +++ b/guest_os_primitives/guest_thread_clone.cc @@ -49,7 +49,6 @@ struct GuestThreadCloneInfo { GuestThread* thread; HostSigset mask; sem_t sem; - bool share_signal_handlers; }; int RunClonedGuestThread(void* arg) { @@ -60,11 +59,6 @@ int RunClonedGuestThread(void* arg) { // TODO(b/280551726): Clear guest thread in exit syscall. InsertCurrentThread(thread, false); - GuestSignalActionsTable signal_actions_storage; - if (!info->share_signal_handlers) { - thread->CloneSignalActionsTableTo(signal_actions_storage); - } - // ExecuteGuest requires pending signals enabled. ScopedPendingSignalsEnabler scoped_pending_signals_enabler(thread); @@ -124,11 +118,10 @@ pid_t CloneGuestThread(GuestThread* thread, GuestThreadCloneInfo info; - info.thread = GuestThread::CreateClone(thread); + info.thread = GuestThread::CreateClone(thread, (flags & CLONE_SIGHAND) != 0); if (info.thread == nullptr) { return EAGAIN; } - info.share_signal_handlers = (flags & CLONE_SIGHAND) != 0; ThreadState& clone_thread_state = *info.thread->state(); diff --git a/guest_os_primitives/include/berberis/guest_os_primitives/guest_thread.h b/guest_os_primitives/include/berberis/guest_os_primitives/guest_thread.h index 4dccc019..d500428e 100644 --- a/guest_os_primitives/include/berberis/guest_os_primitives/guest_thread.h +++ b/guest_os_primitives/include/berberis/guest_os_primitives/guest_thread.h @@ -19,6 +19,7 @@ #include <csetjmp> // jmp_buf #include <csignal> // stack_t +#include <memory> #include "berberis/guest_state/guest_addr.h" #include "berberis/guest_state/guest_state_opaque.h" @@ -58,7 +59,7 @@ struct GuestCallExecution { class GuestThread { public: static GuestThread* CreatePthread(void* stack, size_t stack_size, size_t guard_size); - static GuestThread* CreateClone(const GuestThread* parent); + static GuestThread* CreateClone(const GuestThread* parent, bool share_signal_actions); static GuestThread* CreateForTest(ThreadState* state); static void Destroy(GuestThread* thread); static void Exit(GuestThread* thread, int status); @@ -101,9 +102,9 @@ class GuestThread { // TODO(b/156271630): Refactor to make this private. void* GetHostStackTop() const; - [[nodiscard]] GuestSignalActionsTable* GetSignalActionsTable() { return signal_actions_; } + [[nodiscard]] GuestSignalActionsTable* GetSignalActionsTable() { return signal_actions_.get(); } // Use to unshare signal handlers for CLONE_VM without CLONE_SIGHAND. - void CloneSignalActionsTableTo(GuestSignalActionsTable& new_table_storage); + void CloneSignalActionsTableFrom(GuestSignalActionsTable* from_table); private: GuestThread() = default; @@ -137,7 +138,11 @@ class GuestThread { ThreadState* state_ = nullptr; SignalQueue pending_signals_; - GuestSignalActionsTable* signal_actions_; + // When created with CreateClone guest threads either share or fork signal handlers + // from the parent. We implement this using shared_ptr semantics. + // When created with CreatePthread guest threads use the default global handlers, + // which we should never delete. So we create shared_ptr with void deleter in this case. + std::shared_ptr<GuestSignalActionsTable> signal_actions_; GuestCallExecution* guest_call_execution_ = nullptr; diff --git a/guest_state/riscv64/get_cpu_state.cc b/guest_state/riscv64/get_cpu_state.cc index 879c6e78..a16723ff 100644 --- a/guest_state/riscv64/get_cpu_state.cc +++ b/guest_state/riscv64/get_cpu_state.cc @@ -17,22 +17,20 @@ #include "berberis/base/checks.h" #include "berberis/guest_state/get_cpu_state_opaque.h" #include "berberis/guest_state/guest_state_arch.h" -#include "include/berberis/guest_state/guest_state_opaque.h" +#include "berberis/guest_state/guest_state_opaque.h" #include "native_bridge_support/guest_state_accessor/accessor.h" namespace berberis { -class GuestStateAccessorImplementation { - public: - int LoadGuestStateRegisters(const void* guest_state_data, - size_t guest_state_data_size, - NativeBridgeGuestRegs* guest_regs) { - CHECK_GT(guest_state_data_size, 0); - guest_regs->guest_arch = NATIVE_BRIDGE_ARCH_RISCV64; - GetCpuState(guest_regs, &(static_cast<const ThreadState*>(guest_state_data))->cpu); - return 0; - } -}; +extern "C" __attribute__((visibility("default"))) int LoadGuestStateRegisters( + const void* guest_state_data, + size_t guest_state_data_size, + NativeBridgeGuestRegs* guest_regs) { + CHECK_GT(guest_state_data_size, 0); + guest_regs->guest_arch = NATIVE_BRIDGE_ARCH_RISCV64; + GetCpuState(guest_regs, &(static_cast<const ThreadState*>(guest_state_data))->cpu); + return 0; +} void GetCpuState(NativeBridgeGuestRegs* guest_regs, const CPUState* state) { CHECK_EQ(guest_regs->guest_arch, NATIVE_BRIDGE_ARCH_RISCV64); diff --git a/jni/jni_trampolines.cc b/jni/jni_trampolines.cc index cea6acba..c2ca4d6e 100644 --- a/jni/jni_trampolines.cc +++ b/jni/jni_trampolines.cc @@ -43,9 +43,13 @@ char ConvertDalvikTypeCharToWrapperTypeChar(char c) { case 'V': // void return 'v'; case 'Z': // boolean + return 'z'; case 'B': // byte + return 'b'; case 'S': // short + return 's'; case 'C': // char + return 'c'; case 'I': // int return 'i'; case 'L': // class object - pointer @@ -132,10 +136,18 @@ std::vector<jvalue> ConvertVAList(JNIEnv* env, jmethodID methodID, GuestVAListPa jvalue& arg = result[i]; char c = short_signature[i]; switch (c) { - case 'Z': // boolean (u8) - passed as int - case 'B': // byte (i8) - passed as int - case 'S': // short (i16) - passed as int - case 'C': // char (u16) - passed as int + case 'Z': // boolean (u8) + arg.z = params.GetParam<uint8_t>(); + break; + case 'B': // byte (i8) + arg.b = params.GetParam<int8_t>(); + break; + case 'S': // short (i16) + arg.s = params.GetParam<int16_t>(); + break; + case 'C': // char (u16) + arg.c = params.GetParam<uint16_t>(); + break; case 'I': // int (i32) arg.i = params.GetParam<int32_t>(); break; diff --git a/runtime_primitives/interpret_helpers_riscv64.cc b/runtime_primitives/interpret_helpers_riscv64.cc index 428bcc30..5494d8e4 100644 --- a/runtime_primitives/interpret_helpers_riscv64.cc +++ b/runtime_primitives/interpret_helpers_riscv64.cc @@ -16,7 +16,10 @@ #include "berberis/runtime_primitives/interpret_helpers.h" -#include <csignal> +#include <signal.h> +#include <sys/syscall.h> +#include <unistd.h> + #include <cstdint> #include "berberis/base/checks.h" @@ -50,7 +53,15 @@ void UndefinedInsn(GuestAddr pc) { memcpy(&code, addr, sizeof(code)); ALOGE("Undefined riscv64 instruction 0x%" PRIx32 " at %p", code, addr); } +#ifdef __GLIBC__ + // Our old 2.17 glibc has a bug resulting in raise() failing after any CLONE_VM. + // Work around by calling tgkill directly. + pid_t pid = syscall(__NR_getpid); + pid_t tid = syscall(__NR_gettid); + syscall(SYS_tgkill, pid, tid, SIGILL); +#else raise(SIGILL); +#endif } } // namespace berberis diff --git a/tests/ndk_program_tests/Android.bp b/tests/ndk_program_tests/Android.bp index 7ac61638..f7141639 100644 --- a/tests/ndk_program_tests/Android.bp +++ b/tests/ndk_program_tests/Android.bp @@ -28,6 +28,7 @@ filegroup { name: "berberis_ndk_program_tests_srcs", srcs: [ "atomics_test.cc", + "clone_test.cc", "condvar_test.cc", "cpp_test.cc", "ctype_test.cc", diff --git a/tests/ndk_program_tests/clone_test.cc b/tests/ndk_program_tests/clone_test.cc new file mode 100644 index 00000000..2284eb3a --- /dev/null +++ b/tests/ndk_program_tests/clone_test.cc @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gtest/gtest.h" + +#include <linux/sched.h> +#include <sched.h> +#include <sys/wait.h> + +#include <atomic> +#include <csignal> +#include <cstdlib> + +#include "berberis/ndk_program_tests/scoped_sigaction.h" + +namespace { + +constexpr size_t kChildStack = 1024; + +bool g_parent_handler_called; +bool g_child_handler_called; +bool g_grandchild_handler_called; + +void VerifySignalHandler(bool* flag) { + *flag = false; + raise(SIGPWR); + ASSERT_TRUE(*flag); +} + +template <size_t kStackSize, typename Runner> +void CloneVMAndWait(Runner runner, int extra_flags, int expect_return) { + void* child_stack[kStackSize]; + pid_t tid = clone(runner, &child_stack[kStackSize], CLONE_VM | extra_flags, nullptr); + int status; + ASSERT_EQ(tid, TEMP_FAILURE_RETRY(waitpid(tid, &status, __WCLONE))); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), expect_return); +} + +int SharedSighandRunner(void*) { + // Grandchild shared handlers with child. + VerifySignalHandler(&g_child_handler_called); + struct sigaction sa { + .sa_handler = +[](int) { g_grandchild_handler_called = true; } + }; + // We intentionally do not restore sigaction to verify that this change + // will also change the handler in child (parent of grandchild). + EXPECT_EQ(sigaction(SIGPWR, &sa, nullptr), 0); + VerifySignalHandler(&g_grandchild_handler_called); + return 21; +} + +int UnsharedSighandRunner(void*) { + // Child inherits a copy of parent handlers. + VerifySignalHandler(&g_parent_handler_called); + struct sigaction sa { + .sa_handler = +[](int) { g_child_handler_called = true; } + }; + // We intentionally do not restore sigaction to verify that this change + // doesn't affect signal handlers in parent. + EXPECT_EQ(sigaction(SIGPWR, &sa, nullptr), 0); + VerifySignalHandler(&g_child_handler_called); + // Now clone with shared handlers. + CloneVMAndWait<kChildStack>(SharedSighandRunner, CLONE_SIGHAND, 21); + VerifySignalHandler(&g_grandchild_handler_called); + return 42; +} + +TEST(Clone, CloneVMSighandSharing) { + struct sigaction sa { + .sa_handler = +[](int) { g_parent_handler_called = true; } + }; + ScopedSigaction scoped_sa(SIGPWR, &sa); + // Clone a child with non-shared signal handlers. + // Note that child's stack contains grandchild's stack, so should be larger. + CloneVMAndWait<kChildStack * 2>(UnsharedSighandRunner, 0, 42); + // Verify that children didn't alter parent's signal handlers. + VerifySignalHandler(&g_parent_handler_called); +} + +// We cannot accurately detect when grandchild stack can be free'd. So +// we just keep it in a global variable and never free. +void* g_grandchild_stack[kChildStack]; +std::atomic<bool> g_child_finished; +std::atomic<bool> g_grandchild_finished; + +int WaitUntilParentExitsAndVerifySignalHandlers(void*) { + while (!g_child_finished) { + sched_yield(); + } + + // Grandchild shares handlers with child and should still + // be able to use them after child terminated. + VerifySignalHandler(&g_child_handler_called); + + g_grandchild_finished = true; + return 0; +} + +int CloneOutlivingChild(void*) { + struct sigaction sa { + .sa_handler = +[](int) { g_child_handler_called = true; } + }; + EXPECT_EQ(sigaction(SIGPWR, &sa, nullptr), 0); + + clone(WaitUntilParentExitsAndVerifySignalHandlers, + &g_grandchild_stack[kChildStack], + CLONE_VM | CLONE_SIGHAND, + nullptr); + return 42; +} + +TEST(Clone, CloneVMChildOutlivingParent) { + // We'll test grandchild outliving child. + g_child_finished = false; + g_grandchild_finished = false; + + CloneVMAndWait<kChildStack>(CloneOutlivingChild, 0, 42); + + g_child_finished = true; + + // Wait for grandchild to finish. + while (!g_grandchild_finished) { + sched_yield(); + } +} + +} // namespace |