From 3897fe557f857078becc3c87d39755bf4d08e798 Mon Sep 17 00:00:00 2001 From: Anthony Pesch Date: Fri, 11 Sep 2015 22:13:29 -0700 Subject: [PATCH] invalidate textures through process signals --- CMakeLists.txt | 12 ++ src/core/core.h | 2 - src/core/interval_tree.h | 309 +++++++++++++++++++++++++++ src/hw/dreamcast.cc | 15 +- src/hw/dreamcast.h | 14 +- src/hw/holly/tile_accelerator.cc | 92 -------- src/hw/holly/tile_accelerator.h | 17 +- src/hw/holly/tile_texture_cache.cc | 127 +++++++++++ src/hw/holly/tile_texture_cache.h | 41 ++++ src/jit/ir/ir_builder.h | 1 + src/sigsegv/sigsegv_handler.cc | 70 ++++++ src/sigsegv/sigsegv_handler.h | 61 ++++++ src/sigsegv/sigsegv_handler_linux.cc | 61 ++++++ src/sigsegv/sigsegv_handler_linux.h | 24 +++ src/sigsegv/sigsegv_handler_mac.cc | 141 ++++++++++++ src/sigsegv/sigsegv_handler_mac.h | 28 +++ src/sigsegv/sigsegv_handler_win.cc | 56 +++++ src/sigsegv/sigsegv_handler_win.h | 24 +++ test/test_interval_tree.cc | 150 +++++++++++++ test/test_intrusive_list.cc | 1 + test/test_ring_buffer.cc | 1 + 21 files changed, 1127 insertions(+), 120 deletions(-) create mode 100644 src/core/interval_tree.h create mode 100644 src/hw/holly/tile_texture_cache.cc create mode 100644 src/hw/holly/tile_texture_cache.h create mode 100644 src/sigsegv/sigsegv_handler.cc create mode 100644 src/sigsegv/sigsegv_handler.h create mode 100644 src/sigsegv/sigsegv_handler_linux.cc create mode 100644 src/sigsegv/sigsegv_handler_linux.h create mode 100644 src/sigsegv/sigsegv_handler_mac.cc create mode 100644 src/sigsegv/sigsegv_handler_mac.h create mode 100644 src/sigsegv/sigsegv_handler_win.cc create mode 100644 src/sigsegv/sigsegv_handler_win.h create mode 100644 test/test_interval_tree.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f18d58c..09345cd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,7 @@ set(DREAVM_SOURCES src/hw/holly/pvr2.cc src/hw/holly/tile_accelerator.cc src/hw/holly/tile_renderer.cc + src/hw/holly/tile_texture_cache.cc src/hw/maple/maple.cc src/hw/maple/maple_controller.cc src/hw/sh4/sh4.cc @@ -141,12 +142,22 @@ set(DREAVM_SOURCES src/jit/runtime.cc src/renderer/gl_backend.cc src/renderer/gl_shader.cc + src/sigsegv/sigsegv_handler.cc src/system/keys.cc src/system/system.cc src/trace/trace.cc src/trace/trace_viewer.cc src/main.cc) +if(WIN32) + list(APPEND DREAVM_SOURCES src/sigsegv/sigsegv_handler_win.cc) +elseif(APPLE) + list(APPEND DREAVM_SOURCES src/sigsegv/sigsegv_handler_mac.cc) +else() + list(APPEND DREAVM_SOURCES src/sigsegv/sigsegv_handler_linux.cc) +endif() + + # assign source groups for visual studio projects source_group_by_dir(DREAVM_SOURCES) @@ -277,6 +288,7 @@ endif() set(DREAVM_TEST_SOURCES ${DREAVM_SOURCES} test/sh4_test.cc + test/test_interval_tree.cc test/test_intrusive_list.cc test/test_ring_buffer.cc test/test_sh4.cc diff --git a/src/core/core.h b/src/core/core.h index 0d2cb9f5..0ff12646 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -5,11 +5,9 @@ #include #include "core/arena.h" #include "core/assert.h" -#include "core/intrusive_list.h" #include "core/log.h" #include "core/math.h" #include "core/filesystem.h" #include "core/platform.h" -#include "core/ring_buffer.h" #endif diff --git a/src/core/interval_tree.h b/src/core/interval_tree.h new file mode 100644 index 00000000..cf3db170 --- /dev/null +++ b/src/core/interval_tree.h @@ -0,0 +1,309 @@ +#ifndef INTERVAL_TREE_H +#define INTERVAL_TREE_H + +#include +#include +#include +#include +#include +#include +#include "core/assert.h" + +namespace dreavm { + +// Interval tree implemented using a randomized bst. Based on implementation at +// http://algs4.cs.princeton.edu/93intersection/IntervalST.java +// +// Parent pointers have been added in order to make removal by node (as opposed +// to key) possible. + +template +class IntervalTree { + public: + struct Node; + + typedef IT interval_type; + typedef VT value_type; + typedef IntervalTree self_type; + typedef std::function iterate_cb; + + struct Node { + Node(const interval_type &low, const interval_type &high, + const value_type &value) + : parent(nullptr), + left(nullptr), + right(nullptr), + low(low), + high(high), + max(high), + value(value), + num(1) {} + + bool operator<(const Node &rhs) const { + return low < rhs.low || (low == rhs.low && high < rhs.high); + } + + Node *parent, *left, *right; + interval_type low, high, max; + value_type value; + int num; + }; + + IntervalTree() : root_(nullptr) {} + + ~IntervalTree() { Clear(); } + + int Size() { return Size(root_); } + + int Height() { return Height(root_); } + + Node *Insert(const interval_type &low, const interval_type &high, + const value_type &value) { + Node *n = new Node(low, high, value); + + SetRoot(RandomizedInsert(root_, n)); + + return n; + } + + void Remove(Node *n) { + // join left and right subtrees, assign new joined subtree to parent + Node *joined = Join(n->left, n->right); + + if (!n->parent) { + // removed node had no parent, must have been root + CHECK_EQ(root_, n); + SetRoot(joined); + } else if (n->parent->left == n) { + SetLeft(n->parent, joined); + } else { + SetRight(n->parent, joined); + } + + // fix up each node in the parent chain + Node *parent = n->parent; + while (parent) { + FixCounts(parent); + parent = parent->parent; + } + + // remove the node + delete n; + } + + void Clear() { + Clear(root_); + + SetRoot(nullptr); + } + + Node *Find(interval_type low, interval_type high) { + Node *n = root_; + + while (n) { + if (high >= n->low && n->high >= low) { + return n; + } else if (!n->left || n->left->max < low) { + n = n->right; + } else { + n = n->left; + } + } + + return nullptr; + } + + void Iterate(interval_type low, interval_type high, iterate_cb cb) { + Iterate(root_, low, high, cb); + } + + private: + int Size(Node *n) { return n ? n->num : 0; } + + int Height(Node *n) { + return n ? 1 + std::max(Height(n->left), Height(n->right)) : 0; + } + + // + // insertion + // + Node *RootInsert(Node *root, Node *n) { + if (!root) { + return n; + } + + if (*n < *root) { + SetLeft(root, RootInsert(root->left, n)); + root = RotateRight(root); + } else { + SetRight(root, RootInsert(root->right, n)); + root = RotateLeft(root); + } + + return root; + } + + Node *RandomizedInsert(Node *root, Node *n) { + if (!root) { + return n; + } + + // make new node the root with uniform probability + if (rand() % (Size(root) + 1) == 0) { + return RootInsert(root, n); + } + + if (*n < *root) { + SetLeft(root, RandomizedInsert(root->left, n)); + } else { + SetRight(root, RandomizedInsert(root->right, n)); + } + + return root; + } + + // + // removal + // + void Clear(Node *n) { + if (!n) { + return; + } + + Clear(n->left); + Clear(n->right); + + delete n; + } + + // + // iteration + // + bool Iterate(Node *n, interval_type low, interval_type high, iterate_cb cb) { + if (!n) { + return false; + } + + bool found1 = false; + bool found2 = false; + bool found3 = false; + + if (high >= n->low && n->high >= low) { + cb(*this, n); + found1 = true; + } + + if (n->left && n->left->max >= low) { + found2 = Iterate(n->left, low, high, cb); + } + + if (found2 || !n->left || n->left->max < low) { + found3 = Iterate(n->right, low, high, cb); + } + + return found1 || found2 || found3; + } + + // + // helper methods + // + void SetRoot(Node *n) { + root_ = n; + + if (root_) { + root_->parent = nullptr; + } + } + + void SetLeft(Node *parent, Node *n) { + parent->left = n; + + if (parent->left) { + parent->left->parent = parent; + } + + FixCounts(parent); + } + + void SetRight(Node *parent, Node *n) { + parent->right = n; + + if (parent->right) { + parent->right->parent = parent; + } + + FixCounts(parent); + } + + void FixCounts(Node *n) { + if (!n) { + return; + } + + n->num = 1 + Size(n->left) + Size(n->right); + n->max = n->high; + if (n->left) { + n->max = std::max(n->max, n->left->max); + } + if (n->right) { + n->max = std::max(n->max, n->right->max); + } + } + + Node *RotateRight(Node *root) { + Node *parent = root->parent; + Node *n = root->left; + + SetLeft(root, n->right); + SetRight(n, root); + + if (parent) { + if (parent->left == root) { + SetLeft(parent, n); + } else { + SetRight(parent, n); + } + } + + return n; + } + + Node *RotateLeft(Node *root) { + Node *parent = root->parent; + Node *n = root->right; + + SetRight(root, n->left); + SetLeft(n, root); + + if (parent) { + if (parent->left == root) { + SetLeft(parent, n); + } else { + SetRight(parent, n); + } + } + + return n; + } + + Node *Join(Node *a, Node *b) { + if (!a) { + return b; + } else if (!b) { + return a; + } + + if ((rand() % (Size(a) + Size(b))) < Size(a)) { + SetRight(a, Join(a->right, b)); + return a; + } else { + SetLeft(b, Join(a, b->left)); + return b; + } + } + + Node *root_; +}; +} + +#endif diff --git a/src/hw/dreamcast.cc b/src/hw/dreamcast.cc index b794e636..fd1c3bcf 100644 --- a/src/hw/dreamcast.cc +++ b/src/hw/dreamcast.cc @@ -16,6 +16,7 @@ using namespace dreavm::jit::backend::interpreter; using namespace dreavm::jit::backend::x64; using namespace dreavm::jit::frontend::sh4; using namespace dreavm::renderer; +using namespace dreavm::sigsegv; using namespace dreavm::trace; Dreamcast::Dreamcast() @@ -43,16 +44,16 @@ Dreamcast::Dreamcast() #include "hw/holly/pvr2_regs.inc" #undef PVR_REG + aica_regs_ = new uint8_t[AICA_REG_SIZE](); bios_ = new uint8_t[BIOS_SIZE](); + expdev_mem_ = new uint8_t[EXPDEV_SIZE](); flash_ = new uint8_t[FLASH_SIZE](); + modem_mem_ = new uint8_t[MODEM_REG_SIZE](); + palette_ram_ = new uint8_t[PVR_PALETTE_SIZE](); ram_ = new uint8_t[MAIN_RAM_SIZE](); unassigned_ = new uint8_t[UNASSIGNED_SIZE](); - modem_mem_ = new uint8_t[MODEM_REG_SIZE](); - aica_regs_ = new uint8_t[AICA_REG_SIZE](); - wave_ram_ = new uint8_t[WAVE_RAM_SIZE](); - expdev_mem_ = new uint8_t[EXPDEV_SIZE](); video_ram_ = new uint8_t[PVR_VRAM32_SIZE](); - palette_ram_ = new uint8_t[PVR_PALETTE_SIZE](); + wave_ram_ = new uint8_t[WAVE_RAM_SIZE](); scheduler_ = new Scheduler(); memory_ = new Memory(); @@ -98,6 +99,10 @@ Dreamcast::~Dreamcast() { } bool Dreamcast::Init() { + if (!(sigsegv_ = SIGSEGVHandler::Install())) { + return false; + } + MapMemory(); if (!aica_->Init()) { diff --git a/src/hw/dreamcast.h b/src/hw/dreamcast.h index 42ffa81f..0c92fbeb 100644 --- a/src/hw/dreamcast.h +++ b/src/hw/dreamcast.h @@ -14,6 +14,7 @@ #include "jit/frontend/frontend.h" #include "jit/runtime.h" #include "renderer/backend.h" +#include "sigsegv/sigsegv_handler.h" #include "trace/trace.h" namespace dreavm { @@ -116,6 +117,8 @@ class Dreamcast { hw::sh4::SH4 *sh4() { return sh4_; } hw::holly::TileAccelerator *ta() { return ta_; } + sigsegv::SIGSEGVHandler *sigsegv() { return sigsegv_; } + renderer::Backend *rb() { return rb_; } void set_rb(renderer::Backend *rb) { rb_ = rb; } @@ -148,16 +151,16 @@ class Dreamcast { private: void MapMemory(); + uint8_t *aica_regs_; uint8_t *bios_; + uint8_t *expdev_mem_; uint8_t *flash_; + uint8_t *modem_mem_; + uint8_t *palette_ram_; uint8_t *ram_; uint8_t *unassigned_; - uint8_t *modem_mem_; - uint8_t *aica_regs_; - uint8_t *wave_ram_; - uint8_t *expdev_mem_; uint8_t *video_ram_; - uint8_t *palette_ram_; + uint8_t *wave_ram_; hw::Memory *memory_; hw::Scheduler *scheduler_; @@ -173,6 +176,7 @@ class Dreamcast { hw::holly::TileAccelerator *ta_; // not owned by us + sigsegv::SIGSEGVHandler *sigsegv_; renderer::Backend *rb_; trace::TraceWriter *trace_writer_; }; diff --git a/src/hw/holly/tile_accelerator.cc b/src/hw/holly/tile_accelerator.cc index e5d9ca2c..6cb7d714 100644 --- a/src/hw/holly/tile_accelerator.cc +++ b/src/hw/holly/tile_accelerator.cc @@ -164,95 +164,6 @@ static int GetVertexType_raw(const PCW &pcw) { return 0; } -TileTextureCache::TileTextureCache(Dreamcast *dc) - : dc_(dc), trace_writer_(nullptr) {} - -void TileTextureCache::Clear() { - for (auto it : textures_) { - if (!it.second) { - continue; - } - - dc_->rb()->FreeTexture(it.second); - } - - textures_.clear(); -} - -// void TileTextureCache::RemoveTexture(uint32_t addr) { -// auto it = textures_.find(addr); -// if (it == textures_.end()) { -// return; -// } - -// TextureHandle handle = it->second; -// dc_->rb()->FreeTexture(handle); -// textures_.erase(it); -// } - -TextureHandle TileTextureCache::GetTexture( - const TSP &tsp, const TCW &tcw, RegisterTextureCallback register_cb) { - TextureKey texture_key = TextureCache::GetTextureKey(tsp, tcw); - - // if the trace writer has changed, clear the cache to force insert events - if (dc_->trace_writer() != trace_writer_) { - Clear(); - trace_writer_ = dc_->trace_writer(); - } - - // see if we already have an entry - auto it = textures_.find(texture_key); - if (it != textures_.end()) { - return it->second; - } - - // TCW texture_addr field is in 64-bit units - uint32_t texture_addr = tcw.texture_addr << 3; - - // get the texture data - const uint8_t *video_ram = dc_->video_ram(); - const uint8_t *texture = &video_ram[texture_addr]; - int width = 8 << tsp.texture_u_size; - int height = 8 << tsp.texture_v_size; - int element_size_bits = tcw.pixel_format == TA_PIXEL_8BPP - ? 8 - : tcw.pixel_format == TA_PIXEL_4BPP ? 4 : 16; - int texture_size = (width * height * element_size_bits) >> 3; - - // get the palette data - const uint8_t *palette = dc_->palette_ram(); - int palette_size = 0; - - if (tcw.pixel_format == TA_PIXEL_4BPP || tcw.pixel_format == TA_PIXEL_8BPP) { - // palette ram is 4096 bytes, with each palette entry being 4 bytes each, - // resulting in 1 << 10 indexes - if (tcw.pixel_format == TA_PIXEL_4BPP) { - // in 4bpp mode, the palette selector represents the upper 6 bits of the - // palette index, with the remaining 4 bits being filled in by the texture - palette += (tcw.p.palette_selector << 4) * 4; - palette_size = (1 << 4) * 4; - } else if (tcw.pixel_format == TA_PIXEL_8BPP) { - // in 4bpp mode, the palette selector represents the upper 2 bits of the - // palette index, with the remaining 8 bits being filled in by the texture - palette += ((tcw.p.palette_selector & 0x30) << 4) * 4; - palette_size = (1 << 8) * 4; - } - } - - // register and insert into the cache - TextureHandle handle = register_cb(palette, texture); - auto result = textures_.insert(std::make_pair(texture_key, handle)); - CHECK(result.second, "Texture already in the map?"); - - // add insert to trace - if (trace_writer_) { - trace_writer_->WriteInsertTexture(tsp, tcw, palette, palette_size, texture, - texture_size); - } - - return result.first->second; -} - int TileAccelerator::GetParamSize(const PCW &pcw, int vertex_type) { int size = param_size_lookup[pcw.obj_control * TA_NUM_PARAMS * TA_NUM_VERT_TYPES + @@ -399,9 +310,6 @@ void TileAccelerator::WriteCommand32(uint32_t addr, uint32_t value) { void TileAccelerator::WriteTexture32(uint32_t addr, uint32_t value) { addr &= 0xeeffffff; - // // FIXME this is terrible - // texcache_.RemoveTexture(addr); - *reinterpret_cast(&video_ram_[addr]) = value; } diff --git a/src/hw/holly/tile_accelerator.h b/src/hw/holly/tile_accelerator.h index 70e9fb51..139e7ead 100644 --- a/src/hw/holly/tile_accelerator.h +++ b/src/hw/holly/tile_accelerator.h @@ -4,6 +4,7 @@ #include #include #include "hw/holly/tile_renderer.h" +#include "hw/holly/tile_texture_cache.h" #include "renderer/backend.h" namespace dreavm { @@ -474,22 +475,6 @@ struct TileContext { int vertex_type; }; -class TileTextureCache : public TextureCache { - public: - TileTextureCache(hw::Dreamcast *dc); - - void Clear(); - // void RemoveTexture(uint32_t addr); - renderer::TextureHandle GetTexture(const TSP &tsp, const TCW &tcw, - RegisterTextureCallback register_cb); - - private: - hw::Dreamcast *dc_; - - trace::TraceWriter *trace_writer_; - std::unordered_map textures_; -}; - typedef std::unordered_map TileContextMap; typedef TileContextMap::iterator TileContextIterator; diff --git a/src/hw/holly/tile_texture_cache.cc b/src/hw/holly/tile_texture_cache.cc new file mode 100644 index 00000000..0283384a --- /dev/null +++ b/src/hw/holly/tile_texture_cache.cc @@ -0,0 +1,127 @@ +#include "core/core.h" +#include "hw/dreamcast.h" + +using namespace dreavm::hw; +using namespace dreavm::hw::holly; +using namespace dreavm::renderer; + +TileTextureCache::TileTextureCache(Dreamcast *dc) + : dc_(dc), trace_writer_(nullptr) {} + +TextureHandle TileTextureCache::GetTexture( + const TSP &tsp, const TCW &tcw, RegisterTextureCallback register_cb) { + TextureKey texture_key = TextureCache::GetTextureKey(tsp, tcw); + + // if there are any pending removals, do so at this time + if (pending_invalidations_.size()) { + ClearPending(); + } + + // if the trace writer has changed, clear the cache to force insert events + if (dc_->trace_writer() != trace_writer_) { + ClearAll(); + trace_writer_ = dc_->trace_writer(); + } + + // see if an an entry already exists + auto it = textures_.find(texture_key); + if (it != textures_.end()) { + return it->second; + } + + // TCW texture_addr field is in 64-bit units + uint32_t texture_addr = tcw.texture_addr << 3; + + // get the texture data + uint8_t *video_ram = dc_->video_ram(); + uint8_t *texture = &video_ram[texture_addr]; + int width = 8 << tsp.texture_u_size; + int height = 8 << tsp.texture_v_size; + int element_size_bits = tcw.pixel_format == TA_PIXEL_8BPP + ? 8 + : tcw.pixel_format == TA_PIXEL_4BPP ? 4 : 16; + int texture_size = (width * height * element_size_bits) >> 3; + + // get the palette data + uint8_t *palette = nullptr; + int palette_size = 0; + + if (tcw.pixel_format == TA_PIXEL_4BPP || tcw.pixel_format == TA_PIXEL_8BPP) { + // palette ram is 4096 bytes, with each palette entry being 4 bytes each, + // resulting in 1 << 10 indexes + if (tcw.pixel_format == TA_PIXEL_4BPP) { + // in 4bpp mode, the palette selector represents the upper 6 bits of the + // palette index, with the remaining 4 bits being filled in by the texture + palette = dc_->palette_ram() + (tcw.p.palette_selector << 4) * 4; + palette_size = (1 << 4) * 4; + } else if (tcw.pixel_format == TA_PIXEL_8BPP) { + // in 4bpp mode, the palette selector represents the upper 2 bits of the + // palette index, with the remaining 8 bits being filled in by the texture + palette = dc_->palette_ram() + ((tcw.p.palette_selector & 0x30) << 4) * 4; + palette_size = (1 << 8) * 4; + } + } + + // register and insert into the cache + TextureHandle handle = register_cb(palette, texture); + auto result = textures_.insert(std::make_pair(texture_key, handle)); + CHECK(result.second, "Texture already in the map?"); + + // add write callback in order to invalidate on future writes. the callback + // address will be page aligned, therefore it will be triggered falsely in + // some cases. over invalidate in these cases + TileTextureCacheMap::value_type *map_entry = &(*result.first); + + auto callback = [](void *ctx, void *data) { + TileTextureCache *texcache = reinterpret_cast(ctx); + TileTextureCacheMap::value_type *map_entry = + reinterpret_cast(data); + TextureKey texture_key = map_entry->first; + + // add to pending invalidation list (can't remove inside of signal handler) + texcache->pending_invalidations_.insert(texture_key); + }; + + dc_->sigsegv()->AddWriteWatch(texture, texture_size, callback, this, + map_entry); + + // TODO generate id for watch, so it can be cleared by both callbacks + // if (palette) { + // dc_->mmio()->AddWriteWatch(palette, palette_size, callback, this, + // map_entry); + // } + + // add insert to trace + if (trace_writer_) { + trace_writer_->WriteInsertTexture(tsp, tcw, palette, palette_size, texture, + texture_size); + } + + return handle; +} + +void TileTextureCache::ClearPending() { + for (auto texture_key : pending_invalidations_) { + auto it = textures_.find(texture_key); + CHECK_NE(it, textures_.end()); + + TextureHandle handle = it->second; + textures_.erase(it); + + dc_->rb()->FreeTexture(handle); + } + + pending_invalidations_.clear(); +} + +void TileTextureCache::ClearAll() { + for (auto it : textures_) { + TextureHandle handle = it.second; + + dc_->rb()->FreeTexture(handle); + } + + textures_.clear(); + + pending_invalidations_.clear(); +} diff --git a/src/hw/holly/tile_texture_cache.h b/src/hw/holly/tile_texture_cache.h new file mode 100644 index 00000000..1f2e261b --- /dev/null +++ b/src/hw/holly/tile_texture_cache.h @@ -0,0 +1,41 @@ +#ifndef TILE_TEXTURE_CACHE_H +#define TILE_TEXTURE_CACHE_H + +#include +#include "hw/holly/tile_renderer.h" +#include "renderer/backend.h" + +namespace dreavm { +namespace trace { +class TraceWriter; +} + +namespace hw { +class Dreamcast; + +namespace holly { + +typedef std::unordered_map + TileTextureCacheMap; + +class TileTextureCache : public TextureCache { + public: + TileTextureCache(hw::Dreamcast *dc); + + renderer::TextureHandle GetTexture(const TSP &tsp, const TCW &tcw, + RegisterTextureCallback register_cb); + + private: + void ClearPending(); + void ClearAll(); + + hw::Dreamcast *dc_; + trace::TraceWriter *trace_writer_; + TileTextureCacheMap textures_; + std::set pending_invalidations_; +}; +} +} +} + +#endif diff --git a/src/jit/ir/ir_builder.h b/src/jit/ir/ir_builder.h index 0275685e..27acde3a 100644 --- a/src/jit/ir/ir_builder.h +++ b/src/jit/ir/ir_builder.h @@ -3,6 +3,7 @@ #include #include "core/core.h" +#include "core/intrusive_list.h" namespace dreavm { namespace jit { diff --git a/src/sigsegv/sigsegv_handler.cc b/src/sigsegv/sigsegv_handler.cc new file mode 100644 index 00000000..67503bf7 --- /dev/null +++ b/src/sigsegv/sigsegv_handler.cc @@ -0,0 +1,70 @@ +#include "core/core.h" +#include "core/interval_tree.h" +#include "sigsegv/sigsegv_handler.h" + +using namespace dreavm::sigsegv; + +SIGSEGVHandler *dreavm::sigsegv::SIGSEGVHandler::global_handler_ = nullptr; + +SIGSEGVHandler *SIGSEGVHandler::Install() { + if (global_handler_) { + return global_handler_; + } + + global_handler_ = CreateSIGSEGVHandler(); + + if (!global_handler_->Init()) { + LOG_WARNING("Failed to install SIGSEGV handler"); + + delete global_handler_; + global_handler_ = nullptr; + } + + return global_handler_; +} + +SIGSEGVHandler::~SIGSEGVHandler() { global_handler_ = nullptr; } + +// TODO track handler counts with microprofile + +void SIGSEGVHandler::AddWriteWatch(void *ptr, int size, + WriteWatchHandler handler, void *ctx, + void *data) { + int page_size = GetPageSize(); + uintptr_t physical_start = dreavm::align(reinterpret_cast(ptr), + static_cast(page_size)); + uintptr_t physical_end = + dreavm::align(reinterpret_cast(ptr) + size, + static_cast(page_size)); + + // write-protect the pages + CHECK(Protect(reinterpret_cast(physical_start), + static_cast(physical_end - physical_start), ACC_READONLY)); + + write_watches_.Insert( + physical_start, physical_end - 1, + WriteWatch(handler, ctx, data, physical_start, physical_end)); +} + +bool SIGSEGVHandler::HandleAccessFault(uintptr_t rip, uintptr_t fault_addr) { + WatchTree::Node *node = write_watches_.Find(fault_addr, fault_addr); + + bool handled = node != nullptr; + + while (node) { + WriteWatch &watch = node->value; + + watch.handler(watch.ctx, watch.data); + + // remove write-protection + CHECK(Protect(reinterpret_cast(watch.physical_start), + static_cast(watch.physical_end - watch.physical_start), + ACC_READWRITE)); + + write_watches_.Remove(node); + + node = write_watches_.Find(fault_addr, fault_addr); + } + + return handled; +} diff --git a/src/sigsegv/sigsegv_handler.h b/src/sigsegv/sigsegv_handler.h new file mode 100644 index 00000000..5c6738b7 --- /dev/null +++ b/src/sigsegv/sigsegv_handler.h @@ -0,0 +1,61 @@ +#ifndef SIGSEGV_HANDLER_H +#define SIGSEGV_HANDLER_H + +#include +#include +#include "core/interval_tree.h" + +namespace dreavm { +namespace sigsegv { + +// implemented in the platform specific souce file +class SIGSEGVHandler; +extern SIGSEGVHandler *CreateSIGSEGVHandler(); + +enum PageAccess { ACC_READONLY, ACC_READWRITE }; + +typedef std::function WriteWatchHandler; + +struct WriteWatch { + WriteWatch(WriteWatchHandler handler, void *ctx, void *data, + uintptr_t physical_start, uintptr_t physical_end) + : handler(handler), + ctx(ctx), + data(data), + physical_start(physical_start), + physical_end(physical_end) {} + + WriteWatchHandler handler; + void *ctx; + void *data; + uintptr_t physical_start; + uintptr_t physical_end; +}; + +typedef IntervalTree WatchTree; + +class SIGSEGVHandler { + public: + static SIGSEGVHandler *global_handler() { return global_handler_; } + + static SIGSEGVHandler *Install(); + + virtual ~SIGSEGVHandler(); + + void AddWriteWatch(void *ptr, int size, WriteWatchHandler handler, void *ctx, + void *data); + bool HandleAccessFault(uintptr_t rip, uintptr_t fault_addr); + + protected: + static SIGSEGVHandler *global_handler_; + + virtual bool Init() = 0; + virtual int GetPageSize() = 0; + virtual bool Protect(void *ptr, int size, PageAccess access) = 0; + + WatchTree write_watches_; +}; +} +} + +#endif diff --git a/src/sigsegv/sigsegv_handler_linux.cc b/src/sigsegv/sigsegv_handler_linux.cc new file mode 100644 index 00000000..5de1bc42 --- /dev/null +++ b/src/sigsegv/sigsegv_handler_linux.cc @@ -0,0 +1,61 @@ +#include +#include +#include +#include "core/core.h" +#include "sigsegv/sigsegv_handler_linux.h" + +using namespace dreavm::sigsegv; + +SIGSEGVHandler *dreavm::sigsegv::CreateSIGSEGVHandler() { + return new SIGSEGVHandlerLinux(); +} + +static struct sigaction old_sa; + +static void SignalHandler(int signo, siginfo_t *info, void *ctx) { + ucontext_t *uctx = reinterpret_cast(ctx); + + uintptr_t rip = uctx->uc_mcontext.gregs[REG_RIP]; + uintptr_t fault_addr = reinterpret_cast(info->si_addr); + bool handled = + SIGSEGVHandler::global_handler()->HandleAccessFault(rip, fault_addr); + + if (!handled) { + // call into the original handler if the installed handler fails to handle + // the signal + (*old_sa.sa_sigaction)(signo, info, ctx); + } +} + +SIGSEGVHandlerLinux::~SIGSEGVHandlerLinux() { + sigaction(SIGSEGV, &old_sa, nullptr); +} + +bool SIGSEGVHandlerLinux::Init() { + struct sigaction new_sa; + new_sa.sa_flags = SA_SIGINFO; + sigemptyset(&new_sa.sa_mask); + new_sa.sa_sigaction = &SignalHandler; + + if (sigaction(SIGSEGV, &new_sa, &old_sa) != 0) { + return false; + } + + return true; +} + +int SIGSEGVHandlerLinux::GetPageSize() { return getpagesize(); } + +bool SIGSEGVHandlerLinux::Protect(void *ptr, int size, PageAccess access) { + int prot = PROT_NONE; + switch (access) { + case ACC_READONLY: + prot = PROT_READ; + break; + case ACC_READWRITE: + prot = PROT_READ | PROT_WRITE; + break; + } + + return mprotect(ptr, size, prot) == 0; +} diff --git a/src/sigsegv/sigsegv_handler_linux.h b/src/sigsegv/sigsegv_handler_linux.h new file mode 100644 index 00000000..0e4377d7 --- /dev/null +++ b/src/sigsegv/sigsegv_handler_linux.h @@ -0,0 +1,24 @@ +#ifndef SIGSEGV_HANDLER_LINUX +#define SIGSEGV_HANDLER_LINUX + +#include +#include "sigsegv/sigsegv_handler.h" + +namespace dreavm { +namespace sigsegv { + +class SIGSEGVHandlerLinux : public SIGSEGVHandler { + public: + ~SIGSEGVHandlerLinux(); + + protected: + bool Init(); + int GetPageSize(); + bool Protect(void *ptr, int size, PageAccess access); + + private: +}; +} +} + +#endif diff --git a/src/sigsegv/sigsegv_handler_mac.cc b/src/sigsegv/sigsegv_handler_mac.cc new file mode 100644 index 00000000..b959260a --- /dev/null +++ b/src/sigsegv/sigsegv_handler_mac.cc @@ -0,0 +1,141 @@ +#include +#include +#include +#include "core/core.h" +#include "sigsegv/sigsegv_handler_mac.h" + +using namespace dreavm::sigsegv; + +SIGSEGVHandler *dreavm::sigsegv::CreateSIGSEGVHandler() { + return new SIGSEGVHandlerMac(); +} + +// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/exc_server.html +extern "C" boolean_t exc_server(mach_msg_header_t *request_msg, + mach_msg_header_t *reply_msg); + +// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/catch_exception_raise.html +extern "C" kern_return_t catch_exception_raise( + mach_port_t exception_port, mach_port_t thread, mach_port_t task, + exception_type_t exception, exception_data_t code, + mach_msg_type_number_t code_count) { + // get exception state + mach_msg_type_number_t state_count = x86_EXCEPTION_STATE64_COUNT; + x86_exception_state64_t exc_state; + if (thread_get_state(thread, x86_EXCEPTION_STATE64, + reinterpret_cast(&exc_state), + &state_count) != KERN_SUCCESS) { + return KERN_FAILURE; + } + + // get thread state + state_count = x86_THREAD_STATE64_COUNT; + x86_thread_state64_t thread_state; + if (thread_get_state(thread, x86_THREAD_STATE64, + reinterpret_cast(&thread_state), + &state_count) != KERN_SUCCESS) { + return KERN_FAILURE; + } + + uintptr_t rip = thread_state.__rip; + uintptr_t fault_addr = exc_state.__faultvaddr; + bool handled = + SIGSEGVHandler::global_handler()->HandleAccessFault(rip, fault_addr); + if (!handled) { + return KERN_FAILURE; + } + + // reset thread state + if (thread_set_state(thread, x86_THREAD_STATE64, + reinterpret_cast(&thread_state), + state_count) != KERN_SUCCESS) { + return KERN_FAILURE; + } + + return KERN_SUCCESS; +} + +SIGSEGVHandlerMac::~SIGSEGVHandlerMac() { + task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, 0, + EXCEPTION_DEFAULT, 0); + mach_port_deallocate(mach_task_self(), listen_port_); +} + +bool SIGSEGVHandlerMac::Init() { + // allocate port to listen for exceptions + if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, + &listen_port_) != KERN_SUCCESS) { + return false; + } + + // http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_port_insert_right.html + if (mach_port_insert_right(mach_task_self(), listen_port_, listen_port_, + MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS) { + return false; + } + + // filter out any exception other than EXC_BAD_ACCESS + // http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/task_set_exception_ports.html + if (task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, + listen_port_, EXCEPTION_DEFAULT, + MACHINE_THREAD_STATE) != KERN_SUCCESS) { + return false; + } + + // launch thread to listen for exceptions + thread_ = std::thread([this] { ThreadEntry(); }); + thread_.detach(); + + return true; +} + +int SIGSEGVHandlerMac::GetPageSize() { return getpagesize(); } + +bool SIGSEGVHandlerMac::Protect(void *ptr, int size, PageAccess access) { + int prot = PROT_NONE; + switch (access) { + case ACC_READONLY: + prot = PROT_READ; + break; + case ACC_READWRITE: + prot = PROT_READ | PROT_WRITE; + break; + } + + return mprotect(ptr, size, prot) == 0; +} + +void SIGSEGVHandlerMac::ThreadEntry() { + while (true) { + struct { + mach_msg_header_t head; + mach_msg_body_t msgh_body; + char data[1024]; + } msg; + struct { + mach_msg_header_t head; + char data[1024]; + } reply; + + // wait for a message on the exception port + mach_msg_return_t ret = + mach_msg(&msg.head, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(msg), + listen_port_, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (ret != MACH_MSG_SUCCESS) { + LOG_INFO("mach_msg receive failed with %d %s", ret, + mach_error_string(ret)); + break; + } + + // call exc_server, which will call back into catch_exception_raise + exc_server(&msg.head, &reply.head); + + // send the reply + ret = mach_msg(&reply.head, MACH_SEND_MSG, reply.head.msgh_size, 0, + MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (ret != MACH_MSG_SUCCESS) { + LOG_INFO("mach_msg send failed with %d %s", ret, mach_error_string(ret)); + break; + } + } +} diff --git a/src/sigsegv/sigsegv_handler_mac.h b/src/sigsegv/sigsegv_handler_mac.h new file mode 100644 index 00000000..617c9b22 --- /dev/null +++ b/src/sigsegv/sigsegv_handler_mac.h @@ -0,0 +1,28 @@ +#ifndef SIGSEGV_HANDLER_MAC +#define SIGSEGV_HANDLER_MAC + +#include +#include "sigsegv/sigsegv_handler.h" + +namespace dreavm { +namespace sigsegv { + +class SIGSEGVHandlerMac : public SIGSEGVHandler { + public: + ~SIGSEGVHandlerMac(); + + protected: + bool Init(); + int GetPageSize(); + bool Protect(void *ptr, int size, PageAccess access); + + private: + void ThreadEntry(); + + mach_port_t listen_port_; + std::thread thread_; +}; +} +} + +#endif diff --git a/src/sigsegv/sigsegv_handler_win.cc b/src/sigsegv/sigsegv_handler_win.cc new file mode 100644 index 00000000..e30217b0 --- /dev/null +++ b/src/sigsegv/sigsegv_handler_win.cc @@ -0,0 +1,56 @@ +#include +#include "core/core.h" +#include "sigsegv/sigsegv_handler_win.h" + +using namespace dreavm::sigsegv; + +SIGSEGVHandler *dreavm::sigsegv::CreateSIGSEGVHandler() { + return new SIGSEGVHandlerWin(); +} + +static LONG CALLBACK ExceptionHandler(PEXCEPTION_POINTERS ex_info) { + auto code = ex_info->ExceptionRecord->ExceptionCode; + if (code != STATUS_ACCESS_VIOLATION) { + return EXCEPTION_CONTINUE_SEARCH; + } + + uintptr_t rip = ex_info->ContextRecord->Rip; + uintptr_t fault_addr = ex_info->ExceptionRecord->ExceptionInformation[1]; + bool handled = + SIGSEGVHandler::global_handler()->HandleAccessFault(rip, fault_addr); + + if (!handled) { + return EXCEPTION_CONTINUE_SEARCH; + } + + return EXCEPTION_CONTINUE_EXECUTION; +} + +SIGSEGVHandlerWin::~SIGSEGVHandlerWin() { + RemoveVectoredExceptionHandler(ExceptionHandler); +} + +bool SIGSEGVHandlerWin::Init() { + return AddVectoredExceptionHandler(1, ExceptionHandler) != nullptr; +} + +int SIGSEGVHandlerWin::GetPageSize() { + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + return system_info.dwPageSize; +} + +bool SIGSEGVHandlerWin::Protect(void *ptr, int size, PageAccess access) { + DWORD new_protect = PAGE_NOACCESS; + DWORD old_protect; + switch (access) { + case ACC_READONLY: + new_protect = PAGE_READONLY; + break; + case ACC_READWRITE: + new_protect = PAGE_READWRITE; + break; + } + + return VirtualProtect(ptr, size, new_protect, &old_protect); +} diff --git a/src/sigsegv/sigsegv_handler_win.h b/src/sigsegv/sigsegv_handler_win.h new file mode 100644 index 00000000..7763c9f2 --- /dev/null +++ b/src/sigsegv/sigsegv_handler_win.h @@ -0,0 +1,24 @@ +#ifndef SIGSEGV_HANDLER_WIN +#define SIGSEGV_HANDLER_WIN + +#include +#include "sigsegv/sigsegv_handler.h" + +namespace dreavm { +namespace sigsegv { + +class SIGSEGVHandlerWin : public SIGSEGVHandler { + public: + ~SIGSEGVHandlerWin(); + + protected: + bool Init(); + int GetPageSize(); + bool Protect(void *ptr, int size, PageAccess access); + + private: +}; +} +} + +#endif diff --git a/test/test_interval_tree.cc b/test/test_interval_tree.cc new file mode 100644 index 00000000..d54c8712 --- /dev/null +++ b/test/test_interval_tree.cc @@ -0,0 +1,150 @@ +#include +#include +#include "gtest/gtest.h" +#include "core/core.h" +#include "core/interval_tree.h" + +using namespace dreavm; + +typedef IntervalTree TestTree; + +class IntervalTreeTest : public ::testing::Test { + public: + IntervalTreeTest() {} + + static const int LOW = 0x0; + static const int HIGH = 0x10000; + static const int INTERVAL = 0x2000; + + void SetUp() { + // insert dummy intervals + for (int i = 0; i < 0x10000; i++) { + uint32_t low = 0; + uint32_t high = 0; + + while (high <= low) { + low = LOW + (rand() % (HIGH - LOW)); + high = low + INTERVAL; + } + + TestTree::Node *n = intervals.Insert(low, high, nullptr); + nodes.insert(n); + } + } + + TestTree intervals; + std::set nodes; +}; + +TEST_F(IntervalTreeTest, ValidateRelations) { + // make sure all children parent pointers match + for (auto n : nodes) { + if (n->left) { + ASSERT_EQ(n->left->parent, n); + } + if (n->right) { + ASSERT_EQ(n->right->parent, n); + } + } +} + +TEST_F(IntervalTreeTest, Size) { + ASSERT_EQ(intervals.Size(), (int)nodes.size()); +} + +TEST_F(IntervalTreeTest, Height) { + // make sure height is within expected range of ~2*log2(n) + int height = intervals.Height(); + int size = intervals.Size(); + ASSERT_TRUE(height >= log2(size) && height < (3 * log2(size))); +} + +TEST_F(IntervalTreeTest, Remove) { + // remove all results and ensure size is updated in the process + int size = nodes.size(); + + for (auto n : nodes) { + intervals.Remove(n); + + ASSERT_EQ(intervals.Size(), --size); + } +} + +TEST_F(IntervalTreeTest, Clear) { + int original_size = intervals.Size(); + + intervals.Clear(); + + ASSERT_NE(original_size, intervals.Size()); + ASSERT_EQ(0, intervals.Size()); +} + +TEST_F(IntervalTreeTest, Find) { + for (uint32_t i = 0; i < HIGH; i += 0x1000) { + // manually generate a list of results + std::set expected; + + for (auto n : nodes) { + if (i < n->low || i > n->high) { + continue; + } + + expected.insert(n); + } + + // query the tree for nodes and compare with the expected results + int found = 0; + TestTree::Node *n = intervals.Find(i, i); + + while (n) { + // validate that it's in the expected set + auto it = expected.find(n); + ASSERT_NE(it, expected.end()); + found++; + + // remove from nodes so the node isn't expected by the next loop iteration + auto it2 = nodes.find(n); + ASSERT_NE(it2, nodes.end()); + nodes.erase(it2); + + // remove from intervals so Find() can locate the next node + intervals.Remove(n); + + // locate the next node + n = intervals.Find(i, i); + } + + // validate that the same number of nodes were matched + ASSERT_EQ(found, (int)expected.size()); + } +} + +TEST_F(IntervalTreeTest, Iterate) { + for (uint32_t i = 0; i < HIGH; i += 0x1000) { + // manually generate a list of expected nodes + std::set expected; + + for (auto n : nodes) { + if (i < n->low || i > n->high) { + continue; + } + + expected.insert(n); + } + + // query the tree for nodes + std::set results; + + intervals.Iterate(i, i, [&](const TestTree &tree, TestTree::Node *node) { + results.insert(node); + }); + + // compare the results + ASSERT_EQ(expected.size(), results.size()); + + for (auto n : results) { + auto it = expected.find(n); + ASSERT_NE(it, expected.end()); + } + } +} diff --git a/test/test_intrusive_list.cc b/test/test_intrusive_list.cc index f23f3bea..8d9da7d2 100644 --- a/test/test_intrusive_list.cc +++ b/test/test_intrusive_list.cc @@ -1,5 +1,6 @@ #include "gtest/gtest.h" #include "core/core.h" +#include "core/intrusive_list.h" using namespace dreavm; diff --git a/test/test_ring_buffer.cc b/test/test_ring_buffer.cc index d31ed091..d65b5e5f 100644 --- a/test/test_ring_buffer.cc +++ b/test/test_ring_buffer.cc @@ -1,5 +1,6 @@ #include "gtest/gtest.h" #include "core/core.h" +#include "core/ring_buffer.h" using namespace dreavm;