Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * Demonstrates the use of a signal handler to create data lazily
- *
- *
- * 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.
- */
- #define _GNU_SOURCE
- /* using tdestroy */
- #include <stddef.h>
- /* using size_t */
- #include <stdbool.h>
- /* using bool */
- #include <stdio.h>
- /* using printf, fprintf, perror, fputs */
- #include <stdlib.h>
- /* using exit */
- #include <assert.h>
- /* using assert */
- #include <errno.h>
- /* using errno */
- #include <unistd.h>
- /* using sysconf */
- #include <sys/mman.h>
- /* using mprotect, mmap, munmap */
- #include <search.h>
- /* using tsearch, tfind, tdestroy */
- #include <signal.h>
- /* using sigaction */
- /**
- * 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
- */
- struct _SegvHandler
- {
- /**
- * pointer to the start of the dedicated memory area or NULL
- */
- void* base_addr;
- /**
- * pointer past the end of the dedicated memory area or NULL
- */
- void* mapped_end;
- /**
- * search.h tree of addresses of pages with read permissions
- */
- void* mapped_ro;
- /**
- * search.h tree of addresses of pages with write permissions
- */
- void* mapped_rw;
- int async_err;
- };
- /**
- * Signal handler singleton
- *
- * Default initialization with 0 is valid for all attributes
- */
- static struct _SegvHandler _segv_global_self;
- /**
- * Comparison of memory addresses. Used with search.h
- */
- static int _segv_page_cmp(const void* first, const void* second)
- {
- if(first < second)
- return -1;
- else if(first == second)
- return 0;
- else
- return 1;
- }
- /**
- * Sets async_err from errno unless an error is already set
- */
- static void _segv_store_errno(struct _SegvHandler* self)
- {
- if(! self->async_err)
- self->async_err = errno;
- }
- /**
- * Returns bytes per memory page
- */
- static size_t _segv_pagelen()
- {
- return sysconf(_SC_PAGESIZE);
- }
- /**
- * Fills page with meaningful data
- *
- * TODO: Stub
- */
- static void _segv_populate_page(void* page)
- {
- unsigned* typed = page;
- unsigned* end = page + _segv_pagelen() / sizeof(*typed);
- for(; typed != end; ++typed)
- *typed = 0xDEADBEEF;
- }
- /**
- * Flushes changed page to shared storage or whatever
- *
- * TODO: Stub
- */
- static void _segv_commit_page(struct _SegvHandler* self, void* page)
- {
- char* byte_addr = page;
- char* byte_base = self->base_addr;
- size_t offset = byte_addr - byte_base;
- printf("Range [%zu, %zu) changed\n", offset, offset + _segv_pagelen());
- }
- /**
- * Returns page address of segfault
- */
- static void* _segv_get_page(const siginfo_t* siginfo)
- {
- size_t addr = (size_t) siginfo->si_addr;
- addr &= ~(_segv_pagelen() - 1);
- return (void*) addr;
- }
- /**
- * Invokes handler for segmentation faults that cannot be handled otherwise
- *
- * TODO: Replace with original segfault handler
- */
- static void _segv_real_segfault(const siginfo_t* siginfo)
- {
- fprintf(stderr, "SEGMENTATION FAULT %p\n", siginfo->si_addr);
- exit(EXIT_FAILURE);
- }
- /**
- * Permits write access to page. Marks page as dirty
- *
- * Precondition: page is removed from self->mapped_ro
- * Postcondition on error: Page is still write-protected
- */
- static int _segv_map_rw(struct _SegvHandler* self, void* page)
- {
- size_t pagelen = _segv_pagelen();
- if(mprotect(page, pagelen, PROT_READ | PROT_WRITE))
- goto err_rtrn;
- if(tsearch(page, &self->mapped_rw, _segv_page_cmp) == NULL)
- goto err_nomem;
- return 0;
- err_nomem:
- mprotect(page, pagelen, PROT_READ);
- errno = ENOMEM;
- err_rtrn:
- return -1;
- }
- /**
- * Populates page and permits read access
- */
- static int _segv_map_ro(struct _SegvHandler* self, void* page)
- {
- size_t pagelen = _segv_pagelen();
- if(mprotect(page, pagelen, PROT_READ | PROT_WRITE))
- goto err_rtrn;
- _segv_populate_page(page);
- if(mprotect(page, pagelen, PROT_READ))
- goto err_rtrn;
- if(! tsearch(page, &self->mapped_ro, _segv_page_cmp))
- goto err_nomem;
- return 0;
- err_nomem:
- mprotect(page, pagelen, PROT_NONE);
- errno = ENOMEM;
- err_rtrn:
- return -1;
- }
- /**
- * Returns true if this segfault can be avoided
- */
- static bool _segv_is_magic_segfault(const struct _SegvHandler* self,
- const siginfo_t* siginfo)
- {
- void* addr = siginfo->si_addr;
- if(siginfo->si_code != SEGV_ACCERR)
- return false;
- if(addr < self->base_addr || addr >= self->mapped_end)
- return false;
- void* page = _segv_get_page(siginfo);
- if(tfind(page, &self->mapped_rw, _segv_page_cmp))
- return false;
- return true;
- }
- /**
- * Signal handler compatible with sigaction
- */
- static void _segv_signal_handler(int signum, siginfo_t* siginfo,
- void* ucontext)
- {
- assert(signum == SIGSEGV);
- struct _SegvHandler* self = &_segv_global_self;
- void* page = _segv_get_page(siginfo);
- if(! _segv_is_magic_segfault(self, siginfo))
- _segv_real_segfault(siginfo);
- if(tdelete(page, &self->mapped_ro, _segv_page_cmp)) {
- if(_segv_map_rw(self, page))
- goto err;
- }
- else {
- if(_segv_map_ro(self, page))
- goto err;
- }
- return;
- err:
- perror("segfault handler");
- exit(EXIT_FAILURE);
- }
- /**
- * Installs the global signal handler
- */
- static int _segv_install()
- {
- struct sigaction action = {
- .sa_sigaction = _segv_signal_handler,
- .sa_mask = {{0}},
- .sa_flags = SA_SIGINFO,
- };
- return sigaction(SIGSEGV, &action, NULL);
- }
- /**
- * Allocates the dedicated memory area
- */
- static int _segv_init_mapping(struct _SegvHandler* self)
- {
- int err;
- if(self->base_addr)
- goto err_double;
- size_t mapping_len = _segv_pagelen() * 16;
- void* mapped = mmap(NULL, mapping_len, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if(mapped == MAP_FAILED)
- goto err_rtrn;
- if(mprotect(mapped, mapping_len, PROT_NONE))
- goto err_unmap;
- self->base_addr = mapped;
- self->mapped_end = ((char*) mapped) + mapping_len;
- return 0;
- err_unmap:
- err = errno;
- munmap(mapped, mapping_len);
- errno = err;
- goto err_rtrn;
- err_double:
- fputs("segfault handler double initialization\n", stderr);
- errno = EINVAL;
- err_rtrn:
- return -1;
- }
- /**
- * Commits a dirty pages and marks it as clean
- *
- * Callback for tdestroy
- */
- static void _segv_commit_action(void* page)
- {
- struct _SegvHandler* self = &_segv_global_self;
- _segv_commit_page(self, page);
- size_t pagelen = _segv_pagelen();
- if(mprotect(page, pagelen, PROT_READ))
- goto err_store;
- if(! tsearch(page, &self->mapped_ro, _segv_page_cmp))
- goto err_mem;
- return;
- err_mem:
- mprotect(page, pagelen, PROT_NONE);
- errno = ENOMEM;
- err_store:
- _segv_store_errno(self);
- }
- /**
- * Discards a dirty page. Will re-populate it on demand
- *
- * Callback for tdestroy
- */
- static void _segv_discard_action(void* page)
- {
- if(mprotect(page, _segv_pagelen(), PROT_NONE))
- _segv_store_errno(&_segv_global_self);
- }
- /**
- * Removes all entries from the global mapped_rw set
- *
- * \param a callback called on every page
- * \return async_err
- */
- static int _segv_clear_rw(void (*action)(void*))
- {
- struct _SegvHandler* self = &_segv_global_self;
- self->async_err = 0;
- tdestroy(self->mapped_rw, action);
- self->mapped_rw = NULL;
- if(self->async_err)
- errno = self->async_err;
- return self->async_err;
- }
- /**
- * Installs and initializes the segmentation fault handler
- *
- * \return 0 on success, -1 otherwise. Sets errno
- */
- int sigsegv_install()
- {
- if(_segv_init_mapping(&_segv_global_self))
- return -1;
- return _segv_install();
- }
- /**
- * Commits all changes
- *
- * \return 0 on success, -1 otherwise. Sets errno
- */
- int sigsegv_commit()
- {
- return _segv_clear_rw(_segv_commit_action);
- }
- /**
- * Discards all changes
- *
- * \return 0 on success, -1 otherwise. Sets errno
- */
- int sigsegv_discard()
- {
- return _segv_clear_rw(_segv_discard_action);
- }
- /**
- * Returns starting address of the dedicated memory area
- */
- void* sigsegv_baseptr()
- {
- const struct _SegvHandler* self = &_segv_global_self;
- return self->base_addr;
- }
- /**
- * Some simple testing code
- *
- * Observe it with strace
- */
- int main()
- {
- if(sigsegv_install())
- goto err;
- unsigned* base = sigsegv_baseptr();
- printf("Accessing RO %p = 0x%x\n", base + 3, base[3]);
- base[3] = 0;
- puts("Committing");
- if(sigsegv_commit())
- goto err;
- size_t otherpage = _segv_pagelen() / sizeof(unsigned) + 3;
- puts("Making direct RW access");
- base[otherpage] = 0;
- puts("Discarding");
- if(sigsegv_discard())
- goto err;
- printf("Accessing RO %p = 0x%x\n", base + otherpage, base[otherpage]);
- return EXIT_SUCCESS;
- err:
- perror("sigsegv");
- return EXIT_FAILURE;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement