/** * Demonstrates the use of a signal handler to create data lazily * * Compile with -std=gnu++11 * * * Copyright 2014 Florian Philipp * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include // using std::set #include // using std::fill #include // using std::terminate, std::exception #include // using std::logic_error #include // using std::system_error, std::system_category #include // using errno #include // using assert #include // using std::fprintf #include // using std::size_t #include // using sigaction #include // using mprotect, mmap #include // using sysconf /** * Private C++ implementation follows */ namespace { /** * Returns bytes per memory page */ std::size_t pagelen() { return sysconf(_SC_PAGESIZE); } /** * Segmentation fault handler * * Uses a dedicated memory area that is filled with data on demand. * A singleton in production code, but not necessarily in testing code * * TODO: Make thread-safe * * BUG: Not async-signal-safe */ class SegvHandler { typedef std::set::iterator iterator; /** * pointer to the start of the dedicated memory area or nullptr */ void* base_addr; /** * pointer past the end of the dedicated memory area or nullptr */ void* mapped_end; /** * Addresses of pages with read or read/write access permissions */ std::set mapped_ro, mapped_rw; /** * Fills page with meaningful data * * TODO: Stub */ void populate_page(void* page) { unsigned* typed = static_cast(page); unsigned* page_end = typed + pagelen() / sizeof(*typed); std::fill(typed, page_end, 0xDEADBEEF); } /** * Flushes changed page to shared storage or whatever * * TODO: Stub */ void commit_page(void* page) { char* byte_addr = static_cast(page); char* byte_base = static_cast(this->base_addr); std::size_t offset = byte_addr - byte_base; std::printf("Range [%zu, %zu) changed\n", offset, offset + pagelen()); } /** * Returns page address of segfault */ static void* get_page(const siginfo_t* siginfo) { std::size_t addr = reinterpret_cast(siginfo->si_addr); addr &= ~(pagelen() - 1); return reinterpret_cast(addr); } /** * Invokes handler for segmentation faults that cannot be handled otherwise */ static void real_segfault(const siginfo_t* siginfo) { // TODO: Replace with original segfault handler std::fprintf(stderr, "SEGMENTATION FAULT %p\n", siginfo->si_addr); std::terminate(); } /** * Throws an std::system_error constructed from errno */ static void throw_sys_err(const char* what) { throw std::system_error(errno, std::system_category(), what); } /** * Invokes mprotect. Wraps errors in std::system_error */ static void change_protection(void* page, int permissions, std::size_t length = pagelen()) { if(mprotect(page, length, permissions)) throw_sys_err("mprotect"); } /** * Permits write access to page. Marks page as dirty * * Precondition: page is removed from this->mapped_ro */ void map_rw(void* page) { change_protection(page, PROT_READ | PROT_WRITE); this->mapped_rw.insert(page); } /** * Populates page and permits read access */ void map_ro(void* page) { change_protection(page, PROT_READ | PROT_WRITE); this->populate_page(page); change_protection(page, PROT_READ); this->mapped_ro.insert(page); } /** * Returns true if this segfault can be avoided */ bool is_magic_segfault(const siginfo_t* siginfo) const { void* addr = siginfo->si_addr; return siginfo->si_code == SEGV_ACCERR && addr >= this->base_addr && addr < this->mapped_end && ! this->mapped_rw.count(get_page(siginfo)); } /** * Signal handler compatible with sigaction */ static void signal_handler(int signum, siginfo_t* siginfo, void* ucontext) { try { assert(signum == SIGSEGV); SegvHandler* self = global_self(); void* page = get_page(siginfo); if(! self->is_magic_segfault(siginfo)) real_segfault(siginfo); if(self->mapped_ro.erase(page)) self->map_rw(page); else self->map_ro(page); } catch(std::exception& err) { std::fprintf(stderr, "segfault handler: %s\n", err.what()); std::terminate(); } } public: /** * Initializes empty, unmapped SegvHandler */ SegvHandler() : base_addr(nullptr), mapped_end(nullptr) {} /** * Returns singleton */ static SegvHandler* global_self() { static SegvHandler* self = new SegvHandler(); return self; } /** * Installs the global signal handler */ static void install() { struct sigaction action; action.sa_sigaction = &SegvHandler::signal_handler; action.sa_mask = sigset_t(); action.sa_flags = SA_SIGINFO; if(sigaction(SIGSEGV, &action, nullptr)) throw_sys_err("sigaction"); } /** * Allocates the dedicated memory area */ void init_mapping() { if(this->base_addr) throw std::logic_error("segfault handler double initialization"); const std::size_t mapping_len = pagelen() * 16; // TODO: placeholder void* mapped = mmap(nullptr, mapping_len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if(mapped == MAP_FAILED) throw_sys_err("mmap"); change_protection(mapped, PROT_NONE, mapping_len); this->base_addr = mapped; this->mapped_end = static_cast(mapped) + mapping_len; } /** * Commits all dirty pages and marks them as clean */ void commit_changes() { iterator first = this->mapped_rw.begin(); iterator last = this->mapped_rw.end(); while(first != last) { void* page = *first; this->commit_page(page); change_protection(page, PROT_READ); this->mapped_rw.erase(first++); this->mapped_ro.insert(page); } } /** * Discards all dirty pages. Will re-populate them on demand */ void discard_changes() { iterator first = this->mapped_rw.begin(); iterator last = this->mapped_rw.end(); while(first != last) { void* page = *first; change_protection(page, PROT_NONE); this->mapped_rw.erase(first++); } } /** * Returns starting address of the dedicated memory area */ void* get_base_addr() const { return this->base_addr; } }; /** * Wraps a void functor so that is compatible to C * * Exceptions are converted to errno values. * Exceptions that indicate programming errors result in EINVAL * and output on stderr. * * \return 0 on success, -1 on exceptions */ template int c_style_call(Callable&& function) { try { function(); return 0; } catch(std::system_error& err) { errno = err.code().value(); } catch(std::bad_alloc& err) { errno = ENOMEM; } catch(std::exception& err) { std::fprintf(stderr, "segfault handler: %s\n", err.what()); errno = EINVAL; } return -1; } } // namespace /** * Public C interface follows */ extern "C" { /** * Installs and initializes the segmentation fault handler * * \return 0 on success, -1 otherwise. Sets errno */ int sigsegv_install() { auto lambda = []() { SegvHandler::global_self()->init_mapping(); SegvHandler::install(); }; return c_style_call(lambda); } /** * Commits all changes * * \return 0 on success, -1 otherwise. Sets errno */ int sigsegv_commit() { auto lambda = []() { SegvHandler::global_self()->commit_changes(); }; return c_style_call(lambda); } /** * Discards all changes * * \return 0 on success, -1 otherwise. Sets errno */ int sigsegv_discard() { auto lambda = []() { SegvHandler::global_self()->discard_changes(); }; return c_style_call(lambda); } /** * Returns base address of the dedicated memory area */ void* sigsegv_baseptr() { void* base = nullptr; auto lambda = [&base]() { base = SegvHandler::global_self()->get_base_addr(); }; c_style_call(lambda); return base; } } // extern "C" /** * Some simple testing code * * Observe it with strace */ int main() { sigsegv_install(); unsigned* base = static_cast(sigsegv_baseptr()); std::printf("Accessing RO %p = 0x%x\n", base + 3, base[3]); base[3] = 0; std::puts("Committing"); sigsegv_commit(); std::size_t otherpage = pagelen()/sizeof(unsigned) + 3; std::puts("Making direct RW access"); base[otherpage] = 0; std::puts("Discarding"); sigsegv_discard(); std::printf("Accessing RO %p = 0x%x\n", base + otherpage, base[otherpage]); return 0; }