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
This commit is contained in:
Norman Feske 2024-07-01 16:29:20 +02:00
parent 0288cffaee
commit 943dfa10e7
4 changed files with 115 additions and 192 deletions

View File

@ -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() { }

View File

@ -375,76 +375,28 @@ class Genode::Child : protected Rpc_object<Parent>,
} _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<Entry, Load_error>;
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> _process { };
Start_result _start_result { };
/*
* The child's environment sessions
@ -732,7 +684,7 @@ class Genode::Child : protected Rpc_object<Parent>,
* 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

View File

@ -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();
}

View File

@ -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>(
addr_t const elf_addr = local_rm.attach(elf_ds, Region_map::Attr{}).convert<addr_t>(
[&] (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<Dataspace_capability>(
[&] (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<Start_result>(
[&] (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() { }