| // Copyright 2025 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/zx/channel.h> |
| #include <zircon/assert.h> |
| #include <zircon/sanitizer.h> |
| #include <zircon/startup.h> |
| #include <zircon/types.h> |
| |
| #include <array> |
| |
| // This implements a static PIE that overrides the <zircon/startup.h> API |
| // functions to make the static libc.a startup code work with a custom |
| // bootstrap protocol. That protocol is defined in the header shared with the |
| // test component that launches this static PIE. |
| // |
| // This is meant to exercise the libc startup code and ensure that full |
| // initialization happens in the proper order and the API contract of the |
| // <zircon/startup.h> functions is held up by libc. This program will just |
| // crash with assertion failures (that probably won't get printed anywhere, |
| // just crashes) if the various kinds of initializer / constructor hook aren't |
| // all called in the right way in the right order, finishing with calling main. |
| // Finally main sends a "pong" reply to the test harness and exits with code 0. |
| |
| #include "custom-startup-test.h" |
| |
| namespace { |
| |
| using InitFn = void(); |
| |
| enum State { |
| kInitial, |
| kGetHandles, |
| kGetArguments, |
| kPreinitHook, |
| kPreinitArray, |
| kCtor, |
| kMain, |
| }; |
| |
| void Check(State new_state) { |
| static State gState = kInitial; |
| ZX_ASSERT(gState == new_state - 1); |
| gState = new_state; |
| } |
| |
| [[gnu::constructor]] void Ctor() { Check(kCtor); } |
| |
| void PreinitArrayFn() { Check(kPreinitArray); } |
| |
| [[gnu::section(".preinit_array"), gnu::used, |
| gnu::retain]] alignas(InitFn*) InitFn* const kCallPreinitArrayFn = PreinitArrayFn; |
| |
| struct Hook { |
| zx::channel channel; |
| }; |
| |
| Hook gHook; |
| |
| } // namespace |
| |
| zx_startup_handles_t _zx_startup_get_handles(zx_handle_t process_start_handle) { |
| Check(kGetHandles); |
| |
| gHook = {.channel{process_start_handle}}; |
| |
| zx_signals_t pending; |
| zx_status_t status = gHook.channel.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), &pending); |
| ZX_ASSERT(status == ZX_OK); |
| ZX_ASSERT(pending & ZX_CHANNEL_READABLE); |
| |
| std::array<char, kPing.size()> message_bytes; |
| std::array<zx_handle_t, kMessageHandles> message_handles; |
| uint32_t actual_bytes, actual_handles; |
| status = gHook.channel.read(0, message_bytes.data(), message_handles.data(), message_bytes.size(), |
| message_handles.size(), &actual_bytes, &actual_handles); |
| ZX_ASSERT(status == ZX_OK); |
| |
| ZX_ASSERT(actual_bytes == kPing.size()); |
| ZX_ASSERT(std::string_view(message_bytes.data(), actual_bytes) == kPing); |
| |
| return { |
| .process_self = message_handles[kProcessSelfHandle], |
| .thread_self = message_handles[kThreadSelfHandle], |
| .allocation_vmar = message_handles[kAllocationVmarHandle], |
| .executable_image_vmar = message_handles[kImageVarHandle], |
| .log = message_handles[kLogHandle], |
| .hook = &gHook, |
| }; |
| } |
| |
| zx_startup_arguments_t _zx_startup_get_arguments(void* hook) { |
| Check(kGetArguments); |
| ZX_ASSERT(hook == &gHook); |
| return {}; |
| } |
| |
| void _zx_startup_preinit(void* hook) { |
| Check(kPreinitHook); |
| ZX_ASSERT(hook == &gHook); |
| } |
| |
| int main() { |
| Check(kMain); |
| |
| __sanitizer_log_write(kLog.data(), kLog.size()); |
| |
| zx_status_t status = gHook.channel.write(0, kPong.data(), kPong.size(), nullptr, 0); |
| ZX_ASSERT(status == ZX_OK); |
| |
| return 0; |
| } |