homer512 icon

segfault handler

homer512 | PRO | 10/27/14 10:06:16 PM UTC | 0 ⭐ | 756 👁️ | Never ⏰ | []
C++ |

9.84 KB

|

None

|

0 👍

/

0 👎

/**
 * 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 <set>
// using std::set
#include <algorithm>
// using std::fill
#include <exception>
// using std::terminate, std::exception
#include <stdexcept>
// using std::logic_error
#include <system_error>
// using std::system_error, std::system_category
#include <cerrno>
// using errno
#include <cassert>
// using assert
#include <cstdio>
// using std::fprintf
#include <cstddef>
// using std::size_t
#include <signal.h>
// using sigaction
#include <sys/mman.h>
// using mprotect, mmap
#include <unistd.h>
// 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<void*>::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<void*> mapped_ro, mapped_rw;
 
    /**
     * Fills page with meaningful data
     *
     * TODO: Stub
     */
    void populate_page(void* page)
    {
      unsigned* typed = static_cast<unsigned*>(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<char*>(page);
      char* byte_base = static_cast<char*>(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<std::size_t>(siginfo->si_addr);
      addr &= ~(pagelen() - 1);
      return reinterpret_cast<void*>(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<char*>(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<class Callable>
  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<unsigned*>(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;
}

Comments