homer512 icon

C segfault handler

homer512 | PRO | 10/28/14 08:12:34 PM UTC | 0 ⭐ | 750 👁️ | Never ⏰ | []
C |

9.26 KB

|

None

|

0 👍

/

0 👎

/**
 * 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;
}

Comments