From 943dfa10e757059851e376cca950dc289e69cf31 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Mon, 1 Jul 2024 16:29:20 +0200 Subject: [PATCH] base/child.h: remove exceptions from process init This patch replaces the former Child::Process and Child::Process::Loaded_executable classes by static functions that return failure conditions as return values. Issue #5245 --- .../base-linux/src/lib/base/child_process.cc | 51 ++----- repos/base/include/base/child.h | 84 +++-------- repos/base/src/lib/base/child.cc | 32 ++-- repos/base/src/lib/base/child_process.cc | 140 +++++++++--------- 4 files changed, 115 insertions(+), 192 deletions(-) diff --git a/repos/base-linux/src/lib/base/child_process.cc b/repos/base-linux/src/lib/base/child_process.cc index 8b9723991b..fad039cdcd 100644 --- a/repos/base-linux/src/lib/base/child_process.cc +++ b/repos/base-linux/src/lib/base/child_process.cc @@ -55,47 +55,14 @@ Child::Initial_thread::~Initial_thread() { } void Child::Initial_thread::start(addr_t, Start &) { } -/* - * On Linux, the ELF loading is performed by the kernel - */ -Child::Process::Loaded_executable::Loaded_executable(Type, - Dataspace_capability, - Ram_allocator &, - Region_map &, - Region_map &, - Parent_capability) { } - - -Child::Process::Process(Type type, - Dataspace_capability ldso_ds, - Pd_session &pd, - Initial_thread_base &, - Initial_thread::Start &, - Region_map &local_rm, - Region_map &remote_rm, - Parent_capability parent_cap) -: - loaded_executable(type, ldso_ds, pd, local_rm, remote_rm, parent_cap) +Child::Start_result Child::_start_process(Dataspace_capability ldso_ds, + Pd_session &pd, + Initial_thread_base &, + Initial_thread::Start &, + Region_map &, + Region_map &, + Parent_capability) { - /* skip loading when called during fork */ - if (type == TYPE_FORKED) - return; - - /* - * If the specified executable is a dynamically linked program, we load - * the dynamic linker instead. - */ - if (!ldso_ds.valid()) { - error("attempt to start dynamic executable without dynamic linker"); - throw Missing_dynamic_linker(); - } - - pd.assign_parent(parent_cap); - - Linux_native_pd_client lx_pd(pd.native_pd()); - - lx_pd.start(ldso_ds); + Linux_native_pd_client(pd.native_pd()).start(ldso_ds); + return Start_result::OK; } - - -Child::Process::~Process() { } diff --git a/repos/base/include/base/child.h b/repos/base/include/base/child.h index a7cc559198..7c70c369bd 100644 --- a/repos/base/include/base/child.h +++ b/repos/base/include/base/child.h @@ -375,76 +375,28 @@ class Genode::Child : protected Rpc_object, } _initial_thread_start { _policy }; - struct Process - { - class Missing_dynamic_linker : Exception { }; - class Invalid_executable : Exception { }; + struct Entry { addr_t ip; }; - enum Type { TYPE_LOADED, TYPE_FORKED }; + enum class Load_error { INVALID, OUT_OF_RAM, OUT_OF_CAPS }; + using Load_result = Attempt; - struct Loaded_executable - { - /** - * Initial instruction pointer of the new process, as defined - * in the header of the executable. - */ - addr_t entry { 0 }; + static Load_result _load_static_elf(Dataspace_capability elf_ds, + Ram_allocator &ram, + Region_map &local_rm, + Region_map &remote_rm, + Parent_capability parent_cap); - /** - * Constructor parses the executable and sets up segment - * dataspaces - * - * \param local_rm local address space, needed to make the - * segment dataspaces temporarily visible in - * the local address space to initialize their - * content with the data from the 'elf_ds' - * - * \throw Region_map::Region_conflict - * \throw Region_map::Invalid_dataspace - * \throw Invalid_executable - * \throw Missing_dynamic_linker - * \throw Out_of_ram - * \throw Out_of_caps - */ - Loaded_executable(Type type, - Dataspace_capability ldso_ds, - Ram_allocator &ram, - Region_map &local_rm, - Region_map &remote_rm, - Parent_capability parent_cap); - } loaded_executable; + enum class Start_result { UNKNOWN, OK, OUT_OF_RAM, OUT_OF_CAPS, INVALID }; - /** - * Constructor - * - * \throw Missing_dynamic_linker - * \throw Invalid_executable - * \throw Region_map::Region_conflict - * \throw Region_map::Invalid_dataspace - * \throw Out_of_ram - * \throw Out_of_caps - * - * On construction of a protection domain, the initial thread is - * started immediately. - * - * The 'type' 'TYPE_FORKED' creates an empty process. In this case, - * all process initialization steps except for the creation of the - * initial thread must be done manually, i.e., as done for - * implementing fork. - */ - Process(Type type, - Dataspace_capability ldso_ds, - Pd_session &pd, - Initial_thread_base &, - Initial_thread::Start &, - Region_map &local_rm, - Region_map &remote_rm, - Parent_capability parent); + static Start_result _start_process(Dataspace_capability ldso_ds, + Pd_session &pd, + Initial_thread_base &, + Initial_thread::Start &, + Region_map &local_rm, + Region_map &remote_rm, + Parent_capability parent); - ~Process(); - }; - - Constructible _process { }; + Start_result _start_result { }; /* * The child's environment sessions @@ -732,7 +684,7 @@ class Genode::Child : protected Rpc_object, * environment sessions could not be established, e.g., the ROM session * of the binary could not be obtained. */ - bool active() const { return _process.constructed(); } + bool active() const { return _start_result == Start_result::OK; } /** * Initialize the child's PD session diff --git a/repos/base/src/lib/base/child.cc b/repos/base/src/lib/base/child.cc index 8f6fc60d7e..98834b8a60 100644 --- a/repos/base/src/lib/base/child.cc +++ b/repos/base/src/lib/base/child.cc @@ -754,24 +754,32 @@ void Child::_try_construct_env_dependent_members() if (session.phase == Session_state::AVAILABLE) session.phase = Session_state::CAP_HANDED_OUT; }); - if (_process.constructed()) + if (_start_result == Start_result::OK || _start_result == Start_result::INVALID) return; _policy.init(_cpu.session(), _cpu.cap()); - Process::Type const type = _policy.forked() - ? Process::TYPE_FORKED : Process::TYPE_LOADED; try { _initial_thread.construct(_cpu.session(), _pd.cap(), _policy.name()); - _policy.with_address_space(_pd.session(), [&] (Region_map &address_space) { - _process.construct(type, _linker_dataspace(), _pd.session(), - *_initial_thread, _initial_thread_start, - _local_rm, address_space, cap()); }); } - catch (Out_of_ram) { _error("out of RAM during ELF loading"); } - catch (Out_of_caps) { _error("out of caps during ELF loading"); } - catch (Process::Missing_dynamic_linker) { _error("dynamic linker unavailable"); } - catch (Process::Invalid_executable) { _error("invalid ELF executable"); } + catch (Out_of_ram) { _error("out of RAM while creating initial child thread"); } + catch (Out_of_caps) { _error("out of caps while creating initial child thread"); } + + _pd.session().assign_parent(cap()); + + if (_policy.forked()) { + _start_result = Start_result::OK; + } else { + _policy.with_address_space(_pd.session(), [&] (Region_map &address_space) { + _start_result = _start_process(_linker_dataspace(), _pd.session(), + *_initial_thread, _initial_thread_start, + _local_rm, address_space, cap()); + }); + } + + if (_start_result == Start_result::OUT_OF_RAM) _error("out of RAM during ELF loading"); + if (_start_result == Start_result::OUT_OF_CAPS) _error("out of caps during ELF loading"); + if (_start_result == Start_result::INVALID) _error("attempt to load an invalid executable"); } @@ -929,6 +937,4 @@ Child::Child(Region_map &local_rm, Child::~Child() { close_all_sessions(); - _process.destruct(); } - diff --git a/repos/base/src/lib/base/child_process.cc b/repos/base/src/lib/base/child_process.cc index 3cc428b0d0..89f56ad012 100644 --- a/repos/base/src/lib/base/child_process.cc +++ b/repos/base/src/lib/base/child_process.cc @@ -6,7 +6,7 @@ */ /* - * Copyright (C) 2006-2017 Genode Labs GmbH + * Copyright (C) 2006-2024 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -24,24 +24,13 @@ using namespace Genode; -Child::Process::Loaded_executable::Loaded_executable(Type type, - Dataspace_capability ldso_ds, - Ram_allocator &ram, - Region_map &local_rm, - Region_map &remote_rm, - Parent_capability parent_cap) +Child::Load_result Child::_load_static_elf(Dataspace_capability elf_ds, + Ram_allocator &ram, + Region_map &local_rm, + Region_map &remote_rm, + Parent_capability parent_cap) { - /* skip loading when called during fork */ - if (type == TYPE_FORKED) - return; - - /* locally attach ELF binary of the dynamic linker */ - if (!ldso_ds.valid()) { - error("attempt to start dynamic executable without dynamic linker"); - throw Missing_dynamic_linker(); - } - - addr_t const elf_addr = local_rm.attach(ldso_ds, Region_map::Attr{}).convert( + addr_t const elf_addr = local_rm.attach(elf_ds, Region_map::Attr{}).convert( [&] (Region_map::Range range) { return range.start; }, [&] (Region_map::Attach_error const e) -> addr_t { if (e == Region_map::Attach_error::INVALID_DATASPACE) @@ -51,11 +40,19 @@ Child::Process::Loaded_executable::Loaded_executable(Type type, return 0; }); if (!elf_addr) - return; + return Load_error::INVALID; + + /* detach ELF binary from local address space when leaving the scope */ + struct Elf_detach_guard + { + Region_map &local_rm; + addr_t const addr; + ~Elf_detach_guard() { local_rm.detach(addr); } + } elf_detach_guard { .local_rm = local_rm, .addr = elf_addr }; Elf_binary elf(elf_addr); - entry = elf.entry(); + Entry const entry { elf.entry() }; /* setup region map for the new pd */ Elf_segment seg; @@ -87,10 +84,20 @@ Child::Process::Loaded_executable::Loaded_executable(Type type, */ /* alloc dataspace */ - Dataspace_capability ds_cap; - try { ds_cap = ram.alloc(size); } - catch (Out_of_ram) { - error("allocation of read-write segment failed"); throw; }; + Ram_allocator::Alloc_result const alloc_result = ram.try_alloc(size); + + if (alloc_result.failed()) + error("allocation of read-write segment failed"); + + using Alloc_error = Ram_allocator::Alloc_error; + + if (alloc_result == Alloc_error::OUT_OF_RAM) return Load_error::OUT_OF_RAM; + if (alloc_result == Alloc_error::OUT_OF_CAPS) return Load_error::OUT_OF_CAPS; + if (alloc_result.failed()) return Load_error::INVALID; + + Dataspace_capability ds_cap = alloc_result.convert( + [&] (Ram_dataspace_capability cap) { return cap; }, + [&] (Alloc_error) { /* handled above */ return Dataspace_capability(); }); /* attach dataspace */ Region_map::Attr attr { }; @@ -103,6 +110,8 @@ Child::Process::Loaded_executable::Loaded_executable(Type type, if (e == Region_map::Attach_error::REGION_CONFLICT) error("region conflict while locally attaching ELF segment"); return nullptr; }); + if (!ptr) + return Load_error::INVALID; addr_t const laddr = elf_addr + seg.file_offset(); @@ -124,19 +133,19 @@ Child::Process::Loaded_executable::Loaded_executable(Type type, /* detach dataspace */ local_rm.detach(addr_t(ptr)); - remote_rm.attach(ds_cap, Region_map::Attr { + auto remote_attach_result = remote_rm.attach(ds_cap, Region_map::Attr { .size = size, .offset = { }, .use_at = true, .at = addr, .executable = false, .writeable = true - }).with_result( - [&] (Region_map::Range) { }, - [&] (Region_map::Attach_error) { - error("region conflict while remotely attaching ELF segment"); - error("addr=", (void *)addr, " size=", (void *)size); } - ); + }); + if (remote_attach_result.failed()) { + error("failed to remotely attach writable ELF segment"); + error("addr=", (void *)addr, " size=", (void *)size); + return Load_error::INVALID; + } } else { /* read-only segment */ @@ -144,28 +153,22 @@ Child::Process::Loaded_executable::Loaded_executable(Type type, if (seg.file_size() != seg.mem_size()) warning("filesz and memsz for read-only segment differ"); - remote_rm.attach(ldso_ds, Region_map::Attr { + auto remote_attach_result = remote_rm.attach(elf_ds, Region_map::Attr { .size = size, .offset = seg.file_offset(), .use_at = true, .at = addr, .executable = seg.flags().x, .writeable = false - }).with_result( - [&] (Region_map::Range) { }, - [&] (Region_map::Attach_error const e) { - if (e == Region_map::Attach_error::REGION_CONFLICT) - error("region conflict while remotely attaching read-only ELF segment"); - if (e == Region_map::Attach_error::INVALID_DATASPACE) - error("attempt to attach invalid read-only segment dataspace"); - error("addr=", (void *)addr, " size=", (void *)size); - } - ); + }); + if (remote_attach_result.failed()) { + error("failed to remotely attach read-only ELF segment"); + error("addr=", (void *)addr, " size=", (void *)size); + return Load_error::INVALID; + } } } - - /* detach ELF */ - local_rm.detach(elf_addr); + return entry; } @@ -199,31 +202,26 @@ void Child::Initial_thread::start(addr_t ip, Start &start) } -Child::Process::Process(Type type, - Dataspace_capability ldso_ds, - Pd_session &pd, - Initial_thread_base &initial_thread, - Initial_thread::Start &start, - Region_map &local_rm, - Region_map &remote_rm, - Parent_capability parent_cap) -: - loaded_executable(type, ldso_ds, pd, local_rm, remote_rm, parent_cap) +Child::Start_result Child::_start_process(Dataspace_capability ldso_ds, + Pd_session &pd, + Initial_thread_base &initial_thread, + Initial_thread::Start &start, + Region_map &local_rm, + Region_map &remote_rm, + Parent_capability parent_cap) { - /* register parent interface for new protection domain */ - pd.assign_parent(parent_cap); - - /* - * Inhibit start of main thread if the new process happens to be forked - * from another. In this case, the main thread will get manually - * started after constructing the 'Process'. - */ - if (type == TYPE_FORKED) - return; - - /* start main thread */ - initial_thread.start(loaded_executable.entry, start); + return _load_static_elf(ldso_ds, pd, local_rm, remote_rm, parent_cap).convert( + [&] (Entry entry) { + initial_thread.start(entry.ip, start); + return Start_result::OK; + }, + [&] (Load_error e) { + switch (e) { + case Load_error::OUT_OF_RAM: return Start_result::OUT_OF_RAM; + case Load_error::OUT_OF_CAPS: return Start_result::OUT_OF_CAPS; + case Load_error::INVALID: break; + } + return Start_result::INVALID; + } + ); } - - -Child::Process::~Process() { }