From 385b7cdd31e2cd5e482bb257f0577f7a989722f8 Mon Sep 17 00:00:00 2001 From: Christian Helmuth Date: Fri, 6 Sep 2013 16:30:46 +0200 Subject: [PATCH] base-linux: revised region management Revised region management detects region conflicts by using _soft_ mappings per default. Overmapping is activated for population of managed dataspaces only. For more information see header documentation of base-linux/src/base/env/rm_session_mmap.cc. Fixes #883. --- base-linux/run/lx_rmap.inc | 81 ++++++++++++ base-linux/run/lx_rmap_dynamic.run | 11 ++ base-linux/run/lx_rmap_static.run | 11 ++ base-linux/src/base/env/platform_env.h | 10 +- base-linux/src/base/env/rm_session_mmap.cc | 116 +++++++++++++++--- base-linux/src/core/context_area.cc | 10 +- base-linux/src/platform/context_area.h | 64 ++++++++++ base-linux/src/platform/linux_syscalls.h | 30 ----- base-linux/src/platform/main_bootstrap.cc | 10 -- base-linux/src/test/lx_rmap/dynamic/target.mk | 5 + base-linux/src/test/lx_rmap/main.cc | 89 ++++++++++++++ base-linux/src/test/lx_rmap/static/target.mk | 5 + 12 files changed, 381 insertions(+), 61 deletions(-) create mode 100644 base-linux/run/lx_rmap.inc create mode 100644 base-linux/run/lx_rmap_dynamic.run create mode 100644 base-linux/run/lx_rmap_static.run create mode 100644 base-linux/src/platform/context_area.h create mode 100644 base-linux/src/test/lx_rmap/dynamic/target.mk create mode 100644 base-linux/src/test/lx_rmap/main.cc create mode 100644 base-linux/src/test/lx_rmap/static/target.mk diff --git a/base-linux/run/lx_rmap.inc b/base-linux/run/lx_rmap.inc new file mode 100644 index 000000000..9d1eebc2c --- /dev/null +++ b/base-linux/run/lx_rmap.inc @@ -0,0 +1,81 @@ +# +# \brief Test for Linux-specific region map +# \author Christian Helmuth +# \date 2013-09-06 +# + +assert_spec linux + +if {[have_spec always_hybrid]} { + puts "\nTest does not support always_hybrid mode." + exit 0 +} + +# +# Build +# + +set build_components { core init } + +lappend_if [expr {$test_type eq "static"}] build_components test/lx_rmap/static +lappend_if [expr {$test_type eq "dynamic"}] build_components test/lx_rmap/dynamic + +build $build_components + +create_boot_directory + +# +# Config +# + +set config { + + + + + + + + + + + + + + + } + +append_if [expr {$test_type eq "static"}] config { + } +append_if [expr {$test_type eq "dynamic"}] config { + } + +append config { + + +} + +install_config $config + +# +# Boot modules +# + +set boot_modules { core init} + +lappend_if [expr {$test_type eq "static"}] boot_modules test-lx_rmap_static +lappend_if [expr {$test_type eq "dynamic"}] boot_modules test-lx_rmap_dynamic +lappend_if [expr {$test_type eq "dynamic"}] boot_modules ld.lib.so +lappend_if [expr {$test_type eq "dynamic"}] boot_modules libc.lib.so +lappend_if [expr {$test_type eq "dynamic"}] boot_modules libc_log.lib.so +lappend_if [expr {$test_type eq "dynamic"}] boot_modules libm.lib.so + +build_boot_image $boot_modules + +# +# Execute test +# + +run_genode_until forever + +# vi: set ft=tcl : diff --git a/base-linux/run/lx_rmap_dynamic.run b/base-linux/run/lx_rmap_dynamic.run new file mode 100644 index 000000000..d2f0e7eeb --- /dev/null +++ b/base-linux/run/lx_rmap_dynamic.run @@ -0,0 +1,11 @@ +# +# \brief Test for Linux-specific region map (dynamic binary) +# \author Christian Helmuth +# \date 2013-09-06 +# + +set test_type "dynamic" + +source ${genode_dir}/base-linux/run/lx_rmap.inc + +# vi: set ft=tcl : diff --git a/base-linux/run/lx_rmap_static.run b/base-linux/run/lx_rmap_static.run new file mode 100644 index 000000000..6aaa40462 --- /dev/null +++ b/base-linux/run/lx_rmap_static.run @@ -0,0 +1,11 @@ +# +# \brief Test for Linux-specific region map (static binary) +# \author Christian Helmuth +# \date 2013-09-06 +# + +set test_type "static" + +source ${genode_dir}/base-linux/run/lx_rmap.inc + +# vi: set ft=tcl : diff --git a/base-linux/src/base/env/platform_env.h b/base-linux/src/base/env/platform_env.h index cbda866c1..bc58ade66 100644 --- a/base-linux/src/base/env/platform_env.h +++ b/base-linux/src/base/env/platform_env.h @@ -196,6 +196,13 @@ namespace Genode { void _add_to_rmap(Region const &); + /** + * Reserve VM region for sub-rm dataspace + */ + addr_t _reserve_local(bool use_local_addr, + addr_t local_addr, + Genode::size_t size); + /** * Map dataspace into local address space */ @@ -204,7 +211,8 @@ namespace Genode { addr_t offset, bool use_local_addr, addr_t local_addr, - bool executable); + bool executable, + bool overmap = false); /** * Determine size of dataspace diff --git a/base-linux/src/base/env/rm_session_mmap.cc b/base-linux/src/base/env/rm_session_mmap.cc index 59ecdad1d..c521c1362 100644 --- a/base-linux/src/base/env/rm_session_mmap.cc +++ b/base-linux/src/base/env/rm_session_mmap.cc @@ -2,6 +2,27 @@ * \brief Implementation of Linux-specific local region manager * \author Norman Feske * \date 2008-10-22 + * + * Under Linux, region management happens at the mercy of the Linux kernel. So, + * all we can do in user land is 1) keep track of regions and (managed) + * dataspaces and 2) get the kernel to manage VM regions as we intent. + * + * The kernel sets up mappings for the binary on execve(), which are text and + * data segments, the context area and special regions (stack, vdso, vsyscall). + * Later mappings are done by the Genode program itself, which knows nothing + * about these initial mappings. Therefore, most mmap() operations are _soft_ + * to detect region conflicts with existing mappings or let the kernel find + * some empty VM area (as core does on other platforms). The only _hard_ + * overmaps happen on attachment and population of managed dataspaces. Mapped, + * but not populated dataspaces are "holes" in the Linux VM space represented + * by PROT_NONE mappings (see _reserve_local()). + * + * The context area is a managed dataspace as on other platforms, which is + * created and attached during program launch. The managed dataspace replaces + * the inital reserved area, which is therefore flushed beforehand. Hybrid + * programs have no context area. + * + * Note, we do not support nesting of managed dataspaces. */ /* @@ -15,6 +36,7 @@ #include #include #include +#include /* local includes */ #include @@ -31,18 +53,66 @@ static bool is_sub_rm_session(Dataspace_capability ds) } +addr_t Platform_env_base::Rm_session_mmap::_reserve_local(bool use_local_addr, + addr_t local_addr, + Genode::size_t size) +{ + /* special handling for context area */ + if (use_local_addr + && local_addr == Native_config::context_area_virtual_base() + && size == Native_config::context_area_virtual_size()) { + + /* + * On the first request to reserve the context area, we flush the + * initial mapping preserved in linker script and apply the actual + * reservation. Subsequent requests are just ignored. + */ + + static struct Context + { + Context() + { + flush_context_area(); + reserve_context_area(); + } + } inst; + + return local_addr; + } + + int const flags = MAP_ANONYMOUS | MAP_PRIVATE; + int const prot = PROT_NONE; + void * const addr_in = use_local_addr ? (void *)local_addr : 0; + void * const addr_out = lx_mmap(addr_in, size, prot, flags, -1, 0); + + /* reserve at local address failed - unmap incorrect mapping */ + if (use_local_addr && addr_in != addr_out) + lx_munmap((void *)addr_out, size); + + if ((use_local_addr && addr_in != addr_out) + || (((long)addr_out < 0) && ((long)addr_out > -4095))) { + PERR("_reserve_local: lx_mmap failed (addr_in=%p,addr_out=%p/%ld)", + addr_in, addr_out, (long)addr_out); + throw Rm_session::Region_conflict(); + } + + return (addr_t) addr_out; +} + + void * Platform_env_base::Rm_session_mmap::_map_local(Dataspace_capability ds, Genode::size_t size, addr_t offset, bool use_local_addr, addr_t local_addr, - bool executable) + bool executable, + bool overmap) { int const fd = _dataspace_fd(ds); bool const writable = _dataspace_writable(ds); - int const flags = MAP_SHARED | (use_local_addr ? MAP_FIXED : 0); + int const flags = MAP_SHARED | (overmap ? MAP_FIXED : 0); int const prot = PROT_READ | (writable ? PROT_WRITE : 0) | (executable ? PROT_EXEC : 0); @@ -57,8 +127,14 @@ Platform_env_base::Rm_session_mmap::_map_local(Dataspace_capability ds, */ lx_close(fd); - if (((long)addr_out < 0) && ((long)addr_out > -4095)) { - PERR("_map_local: return value of mmap is %ld", (long)addr_out); + /* attach at local address failed - unmap incorrect mapping */ + if (use_local_addr && addr_in != addr_out) + lx_munmap((void *)addr_out, size); + + if ((use_local_addr && addr_in != addr_out) + || (((long)addr_out < 0) && ((long)addr_out > -4095))) { + PERR("_map_local: lx_mmap failed (addr_in=%p,addr_out=%p/%ld) overmap=%d", + addr_in, addr_out, (long)addr_out, overmap); throw Rm_session::Region_conflict(); } @@ -142,11 +218,12 @@ Platform_env::Rm_session_mmap::attach(Dataspace_capability ds, * Case 3.1 * * This RM session is a sub RM session. If the sub RM session is - * attached (_base > 0), add its attachement offset to the local base - * and map it. + * attached (_base > 0), add its attachment offset to the local base + * and map it. We have to enforce the mapping via the 'overmap' + * argument as the region was reserved by a PROT_NONE mapping. */ if (_is_attached()) - _map_local(ds, region_size, offset, true, _base + (addr_t)local_addr, executable); + _map_local(ds, region_size, offset, true, _base + (addr_t)local_addr, executable, true); return (void *)local_addr; @@ -171,20 +248,19 @@ Platform_env::Rm_session_mmap::attach(Dataspace_capability ds, throw Out_of_metadata(); } - _add_to_rmap(Region(local_addr, offset, ds, region_size)); - /* - * Allocate local address range that can hold the entire sub RM + * Reserve local address range that can hold the entire sub RM * session. */ - rm->_base = lx_vm_reserve(use_local_addr ? (addr_t)local_addr : 0, - region_size); + rm->_base = _reserve_local(use_local_addr, local_addr, region_size); + + _add_to_rmap(Region(rm->_base, offset, ds, region_size)); /* * Cases 2.2, 3.2 * * The sub rm session was not attached until now but it may have - * been populated with dataspaces. Go through all regions an map + * been populated with dataspaces. Go through all regions and map * each of them. */ for (int i = 0; i < Region_map::MAX_REGIONS; i++) { @@ -192,9 +268,13 @@ Platform_env::Rm_session_mmap::attach(Dataspace_capability ds, if (!region.used()) continue; + /* + * We have to enforce the mapping via the 'overmap' argument as + * the region was reserved by a PROT_NONE mapping. + */ _map_local(region.dataspace(), region.size(), region.offset(), true, rm->_base + region.start() + region.offset(), - executable); + executable, true); } return rm->_base; @@ -205,6 +285,7 @@ Platform_env::Rm_session_mmap::attach(Dataspace_capability ds, * Case 1 * * Boring, a plain dataspace is attached to a root RM session. + * Note, we do not overmap. */ void *addr = _map_local(ds, region_size, offset, use_local_addr, local_addr, executable); @@ -252,8 +333,10 @@ void Platform_env::Rm_session_mmap::detach(Rm_session::Local_addr local_addr) * If we are not attached, no local address-space manipulation is * needed. */ - if (_is_attached()) - lx_vm_reserve((addr_t)local_addr + _base, region.size()); + if (_is_attached()) { + lx_munmap((void *)((addr_t)local_addr + _base), region.size()); + _reserve_local(true, (addr_t)local_addr + _base, region.size()); + } } else { @@ -277,5 +360,4 @@ void Platform_env::Rm_session_mmap::detach(Rm_session::Local_addr local_addr) if (rm) rm->_base = 0; } - } diff --git a/base-linux/src/core/context_area.cc b/base-linux/src/core/context_area.cc index c5b6859b0..43ff8907a 100644 --- a/base-linux/src/core/context_area.cc +++ b/base-linux/src/core/context_area.cc @@ -17,9 +17,7 @@ #include #include -/* Linux includes */ -#include -#include +#include /** @@ -34,6 +32,12 @@ class Context_area_rm_session : public Genode::Rm_session { public: + Context_area_rm_session() + { + flush_context_area(); + reserve_context_area(); + } + /** * Attach backing store to thread-context area */ diff --git a/base-linux/src/platform/context_area.h b/base-linux/src/platform/context_area.h new file mode 100644 index 000000000..489218c19 --- /dev/null +++ b/base-linux/src/platform/context_area.h @@ -0,0 +1,64 @@ +/* + * \brief Linux-specific utilities for context area + * \author Christian Helmuth + * \date 2013-09-26 + */ + +/* + * Copyright (C) 2010-2013 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _PLATFORM__CONTEXT_AREA_H_ +#define _PLATFORM__CONTEXT_AREA_H_ + +/* Genode includes */ +#include +#include + +#include + +/* Linux includes */ +#include + + +static inline void flush_context_area() +{ + using namespace Genode; + + void * const base = (void *) Native_config::context_area_virtual_base(); + size_t const size = Native_config::context_area_virtual_size(); + + int ret; + if ((ret = lx_munmap(base, size)) < 0) { + PERR("%s: failed ret=%d", __func__, ret); + throw Rm_session::Region_conflict(); + } +} + + +static inline Genode::addr_t reserve_context_area() +{ + using namespace Genode; + + int const flags = MAP_ANONYMOUS | MAP_PRIVATE; + int const prot = PROT_NONE; + size_t const size = Native_config::context_area_virtual_size(); + void * const addr_in = (void *)Native_config::context_area_virtual_base(); + void * const addr_out = lx_mmap(addr_in, size, prot, flags, -1, 0); + + /* reserve at local address failed - unmap incorrect mapping */ + if (addr_in != addr_out) { + lx_munmap((void *)addr_out, size); + + PERR("%s: failed addr_in=%p addr_out=%p ret=%ld)", __func__, + addr_in, addr_out, (long)addr_out); + throw Rm_session::Region_conflict(); + } + + return (addr_t) addr_out; +} + +#endif /* _PLATFORM__CONTEXT_AREA_H_ */ diff --git a/base-linux/src/platform/linux_syscalls.h b/base-linux/src/platform/linux_syscalls.h index e75ddbd75..6e0d47d9d 100644 --- a/base-linux/src/platform/linux_syscalls.h +++ b/base-linux/src/platform/linux_syscalls.h @@ -203,36 +203,6 @@ inline int lx_munmap(void *addr, size_t length) } -/** - * Exclude local virtual memory area from being used by mmap - * - * \param base base address of area to reserve - * \param size number of bytes to reserve - * - * \return start of allocated reserved area, or ~0 on failure - */ -inline Genode::addr_t lx_vm_reserve(Genode::addr_t base, Genode::size_t size) -{ - /* we cannot include sys/mman.h from here */ - enum { - LX_MAP_PRIVATE = 0x02, - LX_MAP_FIXED = 0x10, - LX_MAP_ANONYMOUS = 0x20, - LX_PROT_NONE = 0x0 - }; - - int const flags = LX_MAP_ANONYMOUS | LX_MAP_PRIVATE - | (base ? LX_MAP_FIXED : 0); - - void * const res = lx_mmap((void *)base, size, LX_PROT_NONE, flags, -1, 0); - - if (base) - return ((Genode::addr_t)res == base) ? base : ~0; - else - return (Genode::addr_t)res; -} - - /*********************************************************************** ** Functions used by thread lib and core's cancel-blocking mechanism ** ***********************************************************************/ diff --git a/base-linux/src/platform/main_bootstrap.cc b/base-linux/src/platform/main_bootstrap.cc index e6f4eb440..67cfecf41 100644 --- a/base-linux/src/platform/main_bootstrap.cc +++ b/base-linux/src/platform/main_bootstrap.cc @@ -56,16 +56,6 @@ void Genode::platform_main_bootstrap() * __initial_sp[3] = environ */ lx_environ = (char**)&__initial_sp[3]; - - /* - * Free context area preserved in linker script - */ - addr_t base = Native_config::context_area_virtual_base(); - Genode::size_t size = Native_config::context_area_virtual_size(); - int ret; - if ((ret = lx_munmap((void *)base, size)) < 0) - PERR("flushing of context area [%lx,%lx) failed (ret=%d)", - (unsigned long) base, (unsigned long) base + size, ret); } } bootstrap; } diff --git a/base-linux/src/test/lx_rmap/dynamic/target.mk b/base-linux/src/test/lx_rmap/dynamic/target.mk new file mode 100644 index 000000000..8ad37993e --- /dev/null +++ b/base-linux/src/test/lx_rmap/dynamic/target.mk @@ -0,0 +1,5 @@ +TARGET = test-lx_rmap_dynamic +SRC_CC = main.cc +LIBS = base libc + +vpath main.cc $(PRG_DIR)/.. diff --git a/base-linux/src/test/lx_rmap/main.cc b/base-linux/src/test/lx_rmap/main.cc new file mode 100644 index 000000000..55f215218 --- /dev/null +++ b/base-linux/src/test/lx_rmap/main.cc @@ -0,0 +1,89 @@ +/* + * \brief Linux region-map test + * \author Christian Helmuth + * \date 2013-09-06 + */ + +/* + * Copyright (C) 2006-2013 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include + + +extern "C" void wait_for_continue(); + + +int main() +{ + using namespace Genode; + + /* activate for early printf in Rm_session_mmap::attach() etc. */ + if (0) Thread_base::trace("FOO"); + + /* induce initial heap expansion to remove RM noise */ + if (1) { + void *addr(env()->heap()->alloc(0x100000)); + env()->heap()->free(addr, 0); + } + + addr_t beg((addr_t)&_prog_img_beg); + addr_t end(align_addr((addr_t)&_prog_img_end, 12)); + + size_t size(end - beg); + + PLOG("program-image region [%016lx,%016lx) size=%zx", beg, end, size); + + /* RAM dataspace attachment overlapping binary */ + try { + Ram_dataspace_capability ds(env()->ram_session()->alloc(size)); + + PLOG("before RAM dataspace attach"); + env()->rm_session()->attach_at(ds, beg); + PERR("after RAM dataspace attach -- ERROR"); + } catch (Rm_session::Region_conflict) { + PLOG("OK caught Region_conflict exception"); + } + + /* empty managed dataspace overlapping binary */ + try { + Rm_connection rm(0, size); + Dataspace_capability ds(rm.dataspace()); + + PLOG("before sub-RM dataspace attach"); + env()->rm_session()->attach_at(ds, beg); + PERR("after sub-RM dataspace attach -- ERROR"); + } catch (Rm_session::Region_conflict) { + PLOG("OK caught Region_conflict exception"); + } + + /* sparsely populated managed dataspace in free VM area */ + try { + Rm_connection rm(0, 0x100000); + + rm.attach_at(env()->ram_session()->alloc(0x1000), 0x1000); + + Dataspace_capability ds(rm.dataspace()); + + PLOG("before populated sub-RM dataspace attach"); + char *addr = (char *)env()->rm_session()->attach(ds) + 0x1000; + PLOG("after populated sub-RM dataspace attach / before touch"); + char const val = *addr; + *addr = 0x55; + PLOG("after touch (%x/%x)", val, *addr); + } catch (Rm_session::Region_conflict) { + PLOG("OK caught Region_conflict exception -- ERROR"); + } + + sleep_forever(); +} diff --git a/base-linux/src/test/lx_rmap/static/target.mk b/base-linux/src/test/lx_rmap/static/target.mk new file mode 100644 index 000000000..491e93ee9 --- /dev/null +++ b/base-linux/src/test/lx_rmap/static/target.mk @@ -0,0 +1,5 @@ +TARGET = test-lx_rmap_static +SRC_CC = main.cc +LIBS = base + +vpath main.cc $(PRG_DIR)/..