From 0d295f75a1131d8552aa1e723051e1fc8adc9b12 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Mon, 12 Dec 2016 17:40:55 +0100 Subject: [PATCH] base: apply routing policy to environment sessions This patch changes the child-construction procedure to allow the routing of environment sessions to arbitrary servers, not only to the parent. In particular, it restores the ability to route the LOG session of the child to a LOG service provided by a child of init. In principle, it becomes possible to also route the immediate child's PD, CPU, and RAM environment sessions in arbitrary ways, which simplifies scenarios that intercept those sessions, e.g., the CPU sampler. Note that the latter ability should be used with great caution because init needs to interact with these sessions to create/destruct the child. Normally, the sessions are provided by the parent. So init is safe at all times. If they are routed to a child however, init will naturally become dependent on this particular child. For the LOG session, this is actually not a problem because even though the parent creates the LOG session as part of the child's environment, it never interacts with the session directly. Fixes #2197 --- .../base-linux/src/lib/base/child_process.cc | 3 +- repos/base/include/base/child.h | 275 +++++++++++------- repos/base/include/base/local_connection.h | 13 +- repos/base/include/base/service.h | 2 +- repos/base/src/lib/base/child.cc | 100 +++++-- repos/base/src/lib/base/child_process.cc | 1 - repos/demo/include/launchpad/launchpad.h | 2 - repos/demo/src/lib/launchpad/launchpad.cc | 4 +- .../platform/spec/x86/pci_session_component.h | 19 +- 9 files changed, 270 insertions(+), 149 deletions(-) diff --git a/repos/base-linux/src/lib/base/child_process.cc b/repos/base-linux/src/lib/base/child_process.cc index 773c778c71..cdb2523c53 100644 --- a/repos/base-linux/src/lib/base/child_process.cc +++ b/repos/base-linux/src/lib/base/child_process.cc @@ -63,12 +63,11 @@ Child::Process::Process(Dataspace_capability elf_ds, Pd_session_capability pd_cap, Pd_session &pd, Ram_session &ram, - Initial_thread_base &initial_thread, + Initial_thread_base &, Region_map &local_rm, Region_map &remote_rm, Parent_capability parent_cap) : - initial_thread(initial_thread), loaded_executable(elf_ds, ldso_ds, ram, local_rm, remote_rm, parent_cap) { /* skip loading when called during fork */ diff --git a/repos/base/include/base/child.h b/repos/base/include/base/child.h index c5e826e673..d106103b7a 100644 --- a/repos/base/include/base/child.h +++ b/repos/base/include/base/child.h @@ -217,7 +217,7 @@ class Genode::Child : protected Rpc_object, /** * Return capability of the initial thread */ - virtual Capability cap() = 0; + virtual Capability cap() const = 0; }; struct Initial_thread : Initial_thread_base @@ -242,102 +242,17 @@ class Genode::Child : protected Rpc_object, void start(addr_t) override; - Capability cap() { return _cap; } + Capability cap() const { return _cap; } }; /* child policy */ Child_policy &_policy; - /* sessions opened by the child */ - Id_space _id_space; + /* print error message with the child's name prepended */ + template + void _error(ARGS &&... args) { error(_policy.name(), ": ", args...); } - typedef Session_state::Args Args; - - template - struct Env_connection - { - typedef String<64> Label; - - Args const _args; - Service &_service; - Local_connection _connection; - - /** - * Construct session arguments with the child policy applied - */ - Args _construct_args(Child_policy &policy, Label const &label) - { - /* copy original arguments into modifiable buffer */ - char buf[Session_state::Args::capacity()]; - buf[0] = 0; - - /* supply label as session argument */ - if (label.valid()) - Arg_string::set_arg_string(buf, sizeof(buf), "label", label.string()); - - /* apply policy to argument buffer */ - policy.filter_session_args(CONNECTION::service_name(), buf, sizeof(buf)); - - return Session_state::Args(Cstring(buf)); - } - - Env_connection(Child_policy &policy, Id_space &id_space, - Id_space::Id id, Label const &label = Label()) - : - _args(_construct_args(policy, label)), - _service(policy.resolve_session_request(CONNECTION::service_name(), _args)), - _connection(_service, id_space, id, _args, - policy.filter_session_affinity(Affinity())) - { } - - typedef typename CONNECTION::Session_type SESSION; - - SESSION &session() { return _connection.session(); } - Capability cap() const { return _connection.cap(); } - }; - - Env_connection _ram { _policy, - _id_space, Parent::Env::ram(), _policy.name() }; - - Env_connection _pd { _policy, - _id_space, Parent::Env::pd(), _policy.name() }; - - Env_connection _cpu { _policy, - _id_space, Parent::Env::cpu(), _policy.name() }; - - Env_connection _log { _policy, - _id_space, Parent::Env::log(), _policy.name() }; - - Env_connection _binary { _policy, - _id_space, Parent::Env::binary(), _policy.binary_name() }; - - Constructible > _linker { _policy, - _id_space, Parent::Env::linker(), _policy.linker_name() }; - - /* call 'Child_policy::init' methods for the environment sessions */ - void _init_env_sessions() - { - _policy.init(_ram.session(), _ram.cap()); - _policy.init(_cpu.session(), _cpu.cap()); - _policy.init(_pd.session(), _pd.cap()); - } - bool const _env_sessions_initialized = ( _init_env_sessions(), true ); - - Dataspace_capability _linker_dataspace() - { - try { - _linker.construct(_policy, _id_space, - Parent::Env::linker(), _policy.linker_name()); - return _linker->session().dataspace(); - } - catch (Parent::Service_denied) { return Rom_dataspace_capability(); } - } - - /* heap for child-specific allocations using the child's quota */ - Heap _heap; - - /* factory for dynamically created session-state objects */ - Session_state::Factory _session_factory { _heap }; + Region_map &_local_rm; Rpc_entrypoint &_entrypoint; Parent_capability _parent_cap; @@ -351,15 +266,31 @@ class Genode::Child : protected Rpc_object, Lock _yield_request_lock; Resource_args _yield_request_args; - Initial_thread _initial_thread { _cpu.session(), _pd.cap(), "initial" }; + /* sessions opened by the child */ + Id_space _id_space; + + typedef Session_state::Args Args; + + /* + * Members that are initialized not before the child's environment is + * complete. + */ + + void _try_construct_env_dependent_members(); + + /* heap for child-specific allocations using the child's quota */ + Constructible _heap; + + /* factory for dynamically created session-state objects */ + Constructible _session_factory; + + Constructible _initial_thread; struct Process { class Missing_dynamic_linker : Exception { }; class Invalid_executable : Exception { }; - Initial_thread_base &initial_thread; - struct Loaded_executable { /** @@ -426,7 +357,108 @@ class Genode::Child : protected Rpc_object, ~Process(); }; - Process _process; + Constructible _process; + + /* + * The child's environment sessions + */ + + template + struct Env_connection + { + typedef String<64> Label; + + Args const _args; + + Service &_service; + + /* + * The 'Env_service' monitors session responses in order to attempt + * to 'Child::_try_construct_env_dependent_members()' on the + * arrival of environment sessions. + */ + struct Env_service : Service, Session_state::Ready_callback + { + Child &_child; + Service &_service; + + Env_service(Child &child, Service &service) + : + Genode::Service(CONNECTION::service_name(), service.ram()), + _child(child), _service(service) + { } + + void initiate_request(Session_state &session) override + { + session.ready_callback = this; + session.async_client_notify = true; + _service.initiate_request(session); + } + + /** + * Session_state::Ready_callback + */ + void session_ready(Session_state &session) override + { + _child._try_construct_env_dependent_members(); + } + + void wakeup() override { _service.wakeup(); } + + } _env_service; + + Local_connection _connection; + + /** + * Construct session arguments with the child policy applied + */ + Args _construct_args(Child_policy &policy, Label const &label) + { + /* copy original arguments into modifiable buffer */ + char buf[Session_state::Args::capacity()]; + buf[0] = 0; + + /* supply label as session argument */ + if (label.valid()) + Arg_string::set_arg_string(buf, sizeof(buf), "label", label.string()); + + /* apply policy to argument buffer */ + policy.filter_session_args(CONNECTION::service_name(), buf, sizeof(buf)); + + return Session_state::Args(Cstring(buf)); + } + + static char const *_service_name() { return CONNECTION::service_name(); } + + Env_connection(Child &child, Id_space::Id id, + Label const &label = Label()) + : + _args(_construct_args(child._policy, label)), + _service(child._policy.resolve_session_request(_service_name(), _args)), + _env_service(child, _service), + _connection(_env_service, child._id_space, id, _args, + child._policy.filter_session_affinity(Affinity())) + { } + + typedef typename CONNECTION::Session_type SESSION; + + SESSION &session() { return _connection.session(); } + Capability cap() const { return _connection.cap(); } + }; + + Env_connection _ram { *this, Env::ram(), _policy.name() }; + Env_connection _pd { *this, Env::pd(), _policy.name() }; + Env_connection _cpu { *this, Env::cpu(), _policy.name() }; + Env_connection _log { *this, Env::log(), _policy.name() }; + Env_connection _binary { *this, Env::binary(), _policy.binary_name() }; + + Constructible > _linker; + + Dataspace_capability _linker_dataspace() + { + return _linker.constructed() ? _linker->session().dataspace() + : Rom_dataspace_capability(); + } void _revert_quota_and_destroy(Session_state &); @@ -444,20 +476,6 @@ class Genode::Child : protected Rpc_object, public: - /** - * Exception type - * - * The startup of the physical process of the child may fail if the - * ELF binary is invalid, if the ELF binary is dynamically linked - * but no dynamic linker is provided, if the creation of the initial - * thread failed, or if the RAM session of the child is exhausted. - * Each of those conditions will result in a diagnostic log message. - * But for the error handling, we only distinguish the RAM exhaustion - * from the other conditions and subsume the latter as - * 'Process_startup_failed'. - */ - class Process_startup_failed : public Exception { }; - /** * Constructor * @@ -469,8 +487,6 @@ class Genode::Child : protected Rpc_object, * \throw Parent::Service_denied if the initial sessions for the * child's environment could not be * opened - * \throw Ram_session::Alloc_failed - * \throw Process_startup_failed */ Child(Region_map &rm, Rpc_entrypoint &entrypoint, Child_policy &policy); @@ -482,6 +498,22 @@ class Genode::Child : protected Rpc_object, */ virtual ~Child(); + /** + * Return true if the child has been started + * + * After the child's construction, the child is not always able to run + * immediately. In particular, a session of the child's environment + * may still be pending. This method returns true only if the child's + * environment is completely initialized at the time of calling. + * + * If all environment sessions are immediately available (as is the + * case for local services or parent services), the return value is + * expected to be true. If this is not the case, one of child's + * environment sessions could not be established, e.g., the ROM session + * of the binary could not be obtained. + */ + bool active() const { return _process.constructed(); } + /** * RAM quota unconditionally consumed by the child's environment */ @@ -506,7 +538,7 @@ class Genode::Child : protected Rpc_object, /** * Return heap that uses the child's quota */ - Allocator &heap() { return _heap; } + Allocator &heap() { return *_heap; } Ram_session_capability ram_session_cap() const { return _ram.cap(); } @@ -516,7 +548,24 @@ class Genode::Child : protected Rpc_object, Cpu_session &cpu() { return _cpu.session(); } Pd_session &pd() { return _pd .session(); } - Session_state::Factory &session_factory() { return _session_factory; } + /** + * Exception type + */ + class Inactive : Exception { }; + + /** + * Request factory for creating session-state objects + * + * \throw Inactive factory cannot by provided because the child it + * not yet completely initialized. + */ + Session_state::Factory &session_factory() + { + if (_session_factory.constructed()) + return *_session_factory; + + throw Inactive(); + } /** * Instruct the child to yield resources diff --git a/repos/base/include/base/local_connection.h b/repos/base/include/base/local_connection.h index 21bc5aac26..cf79d3c00d 100644 --- a/repos/base/include/base/local_connection.h +++ b/repos/base/include/base/local_connection.h @@ -98,16 +98,18 @@ class Genode::Local_connection : Local_connection_base * If session comes from a local service (e.g,. a virtualized * RAM session, we return the reference to the corresponding * component object, which can be called directly. - * - * Otherwise, if the session is provided */ if (_session_state.local_ptr) return *static_cast(_session_state.local_ptr); /* - * The session is provided remotely. So return a client-stub - * for interacting with the session. + * The session is provided remotely. So return a client stub for + * interacting with the session. We construct the client object if + * we have a valid session capability. */ + if (!_client.constructed() && _session_state.cap.valid()) + _client.construct(cap()); + if (_client.constructed()) return *_client; @@ -127,8 +129,7 @@ class Genode::Local_connection : Local_connection_base Local_connection_base(service, id_space, id, args, affinity, CONNECTION::RAM_QUOTA) { - if (_session_state.cap.valid()) - _client.construct(cap()); + service.wakeup(); } }; diff --git a/repos/base/include/base/service.h b/repos/base/include/base/service.h index 3a932d7ee6..4e9fd36635 100644 --- a/repos/base/include/base/service.h +++ b/repos/base/include/base/service.h @@ -347,7 +347,7 @@ class Genode::Child_service : public Service /* * In contrast to local services and parent services, session-state * objects for child services are owned by the server. This enables - * the server to asynchronouly respond to close requests when the + * the server to asynchronously respond to close requests when the * client is already gone. */ Factory &_factory(Factory &) override { return _server_factory; } diff --git a/repos/base/src/lib/base/child.cc b/repos/base/src/lib/base/child.cc index 0f89285414..f09aef2637 100644 --- a/repos/base/src/lib/base/child.cc +++ b/repos/base/src/lib/base/child.cc @@ -205,7 +205,7 @@ Session_capability Child::session(Parent::Client::Id id, Service &service = _policy.resolve_session_request(name.string(), argbuf); Session_state &session = - create_session(_policy.name(), service, _session_factory, + create_session(_policy.name(), service, *_session_factory, _id_space, id, argbuf, filtered_affinity); session.ready_callback = this; @@ -482,7 +482,7 @@ void Child::deliver_session_cap(Server::Id id, Session_capability cap) _policy.server_id_space().apply(id, [&] (Session_state &session) { if (session.cap.valid()) { - error("attempt to assign session cap twice"); + _error("attempt to assign session cap twice"); return; } @@ -512,7 +512,12 @@ void Child::exit(int exit_value) Thread_capability Child::main_thread_cap() const { - return _process.initial_thread.cap(); + /* + * The '_initial_thread' is always constructed when this function is + * called because the RPC call originates from the active child. + */ + return _initial_thread.constructed() ? _initial_thread->cap() + : Thread_capability(); } @@ -566,24 +571,72 @@ namespace { } +void Child::_try_construct_env_dependent_members() +{ + /* check if the environment sessions are complete */ + if (!_ram.cap().valid() || !_pd .cap().valid() || + !_cpu.cap().valid() || !_log.cap().valid() || !_binary.cap().valid()) + return; + + /* + * If the ROM-session request for the dynamic linker was granted but the + * response to the session request is still outstanding, we have to wait. + * Note that we proceed if the session request was denied by the policy, + * which may be the case when using a statically linked executable. + */ + if (_linker.constructed() && !_linker->cap().valid()) + return; + + /* + * Mark all environment sessions as handed out to prevent the triggering + * of signals by 'Child::session_sigh' for these sessions. + */ + _id_space.for_each([&] (Session_state &session) { + if (session.phase == Session_state::AVAILABLE) + session.phase = Session_state::CAP_HANDED_OUT; }); + + /* call 'Child_policy::init' methods for the environment sessions */ + _policy.init(_ram.session(), _ram.cap()); + _policy.init(_cpu.session(), _cpu.cap()); + _policy.init(_pd.session(), _pd.cap()); + + _heap.construct(&_ram.session(), &_local_rm); + _session_factory.construct(*_heap); + + try { + _initial_thread.construct(_cpu.session(), _pd.cap(), "initial"); + _process.construct(_binary.session().dataspace(), _linker_dataspace(), + _pd.cap(), _pd.session(), _ram.session(), + *_initial_thread, _local_rm, + Child_address_space(_pd.session(), _policy).region_map(), + _parent_cap); + } + catch (Ram_session::Alloc_failed) { _error("RAM allocation failed during ELF loading"); } + catch (Cpu_session::Thread_creation_failed) { _error("unable to create initial thread"); } + catch (Cpu_session::Out_of_metadata) { _error("CPU session quota exhausted"); } + catch (Process::Missing_dynamic_linker) { _error("dynamic linker unavailable"); } + catch (Process::Invalid_executable) { _error("invalid ELF executable"); } + catch (Region_map::Attach_failed) { _error("ELF loading failed"); } +} + + Child::Child(Region_map &local_rm, Rpc_entrypoint &entrypoint, Child_policy &policy) -try : - _policy(policy), - _heap(&_ram.session(), &local_rm), - _entrypoint(entrypoint), - _parent_cap(_entrypoint.manage(this)), - _process(_binary.session().dataspace(), _linker_dataspace(), - _pd.cap(), _pd.session(), _ram.session(), _initial_thread, local_rm, - Child_address_space(_pd.session(), _policy).region_map(), - _parent_cap) -{ } -catch (Cpu_session::Thread_creation_failed) { throw Process_startup_failed(); } -catch (Cpu_session::Out_of_metadata) { throw Process_startup_failed(); } -catch (Process::Missing_dynamic_linker) { throw Process_startup_failed(); } -catch (Process::Invalid_executable) { throw Process_startup_failed(); } -catch (Region_map::Attach_failed) { throw Process_startup_failed(); } +: + _policy(policy), _local_rm(local_rm), _entrypoint(entrypoint), + _parent_cap(_entrypoint.manage(this)) +{ + /* + * Issue environment-session request for obtaining the linker binary. We + * accept this request to fail. In this case, the child creation may still + * succeed if the binary is statically linked. + */ + try { _linker.construct(*this, Parent::Env::linker(), _policy.linker_name()); } + catch (Parent::Service_denied) { } + + _try_construct_env_dependent_members(); +} Child::~Child() @@ -632,5 +685,16 @@ Child::~Child() }; while (_id_space.apply_any(close_fn)); + + /* + * Make sure to destroy the users of the child's environment sessions + * before destructing those sessions. E.g., as the environment RAM session + * provides the backing store for the '_heap', we must not destroy the heap + * after the RAM session. + */ + _process.destruct(); + _initial_thread.destruct(); + _session_factory.destruct(); + _heap.destruct(); } diff --git a/repos/base/src/lib/base/child_process.cc b/repos/base/src/lib/base/child_process.cc index a952daa286..c09cf720fa 100644 --- a/repos/base/src/lib/base/child_process.cc +++ b/repos/base/src/lib/base/child_process.cc @@ -192,7 +192,6 @@ Child::Process::Process(Dataspace_capability elf_ds, Region_map &remote_rm, Parent_capability parent_cap) : - initial_thread(initial_thread), loaded_executable(elf_ds, ldso_ds, ram, local_rm, remote_rm, parent_cap) { /* register parent interface for new protection domain */ diff --git a/repos/demo/include/launchpad/launchpad.h b/repos/demo/include/launchpad/launchpad.h index d6b5e3e2c4..05206e2952 100644 --- a/repos/demo/include/launchpad/launchpad.h +++ b/repos/demo/include/launchpad/launchpad.h @@ -115,8 +115,6 @@ class Launchpad_child : public Genode::Child_policy, Genode::destroy(_child.heap(), &service); }); } - Genode::Allocator &heap() { return _child.heap(); } - /**************************** ** Child_policy interface ** diff --git a/repos/demo/src/lib/launchpad/launchpad.cc b/repos/demo/src/lib/launchpad/launchpad.cc index 8ff58f907c..87e463af08 100644 --- a/repos/demo/src/lib/launchpad/launchpad.cc +++ b/repos/demo/src/lib/launchpad/launchpad.cc @@ -177,7 +177,7 @@ Launchpad_child *Launchpad::start_child(Launchpad_child::Name const &binary_name Lock::Guard lock_guard(_children_lock); _children.insert(c); - add_child(unique_name, ram_quota, *c, c->heap()); + add_child(unique_name, ram_quota, *c, _heap); return c; } catch (...) { @@ -189,7 +189,7 @@ Launchpad_child *Launchpad::start_child(Launchpad_child::Name const &binary_name void Launchpad::exit_child(Launchpad_child &child) { - remove_child(child.name(), child.heap()); + remove_child(child.name(), _heap); Lock::Guard lock_guard(_children_lock); _children.remove(&child); diff --git a/repos/os/src/drivers/platform/spec/x86/pci_session_component.h b/repos/os/src/drivers/platform/spec/x86/pci_session_component.h index 14ca90894a..458741f446 100644 --- a/repos/os/src/drivers/platform/spec/x86/pci_session_component.h +++ b/repos/os/src/drivers/platform/spec/x86/pci_session_component.h @@ -213,13 +213,24 @@ class Platform::Session_component : public Genode::Rpc_object class Device_pd { + public: + + class Startup_failed : Genode::Exception { }; + private: enum { RAM_QUOTA = 190 * 4096 }; - Quota_reservation const _reservation; - Device_pd_policy _policy; - Genode::Child _child; + Quota_reservation const _reservation; + Device_pd_policy _policy; + Genode::Child _child; + + void _check_child_started_up() const { + if (!_child.active()) + throw Startup_failed(); } + + bool const _active = (_check_child_started_up(), true); + Genode::Slave::Connection _connection; public: @@ -229,7 +240,7 @@ class Platform::Session_component : public Genode::Rpc_object * * \throw Out_of_metadata session RAM does not suffice * for creating device PD - * \throw Process_startup_failed by 'Child' + * \throw Startup_failed child could not be started * \throw Parent::Service_denied by 'Slave::Connection' */ Device_pd(Genode::Region_map &local_rm,