wrenpp/src/dynafunc_maker.cpp
2022-04-28 22:56:11 +02:00

126 lines
4.1 KiB
C++

/* Copyright 2020-2022, Michele Santullo
* This file is part of wrenpp.
*
* Wrenpp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Wrenpp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with wrenpp. If not, see <http://www.gnu.org/licenses/>.
*/
#if !defined(_GNU_SOURCE)
//for sys/mman.h
# define _GNU_SOURCE
#endif
#include "dynafunc_maker.hpp"
#include "config.h"
#include <algorithm>
#include <cassert>
#include <unistd.h> //for sysconf()
#include <stdexcept>
#include <string>
#include <cstdlib> //for aligned_alloc()
#include <sys/mman.h> //for mprotect()
#include <memory>
#include <new>
namespace wren {
namespace {
struct MemFree { void operator()(void* ptr) { std::free(ptr); }};
typedef std::unique_ptr<void, MemFree> unique_ptr_free_t;
//see src/dynafunc_amd64_gnu.asm
extern "C" const char g_dynafunc[];
extern "C" const char g_dynafunc_end[];
const unsigned int g_dynafunc_len = g_dynafunc_end - g_dynafunc;
constexpr unsigned int g_dynafunc_ptr1_size = ASM_PTR_SIZE;
constexpr unsigned int g_dynafunc_ptr2_size = ASM_FUNC_PTR_SIZE;
constexpr unsigned char g_dynafunc_ptr1[] = {0xef, 0xbe, 0xad, 0xde, 0xef, 0xbe, 0xad, 0xde};
constexpr unsigned char g_dynafunc_ptr2[] = {0x0d, 0xf0, 0xdd, 0xe0, 0xfe, 0x0f, 0xdc, 0xba};
static_assert(sizeof(g_dynafunc_ptr1) / sizeof(g_dynafunc_ptr1[0]) >= g_dynafunc_ptr1_size);
static_assert(sizeof(g_dynafunc_ptr2) / sizeof(g_dynafunc_ptr2[0]) >= g_dynafunc_ptr2_size);
template <typename T, unsigned int S>
void replace_ptr(
unsigned char* beg,
unsigned char* end,
const unsigned char (&search)[S],
T replace
) {
static_assert(sizeof(T) <= S, "Unexpected pointer size");
const auto subseq = std::search(beg, end, search, search + sizeof(T));
assert(subseq != end);
const auto ptr = reinterpret_cast<unsigned char*>(&replace);
std::copy(ptr, ptr + sizeof(T), subseq);
}
void make_dynafunc_asm(unsigned char* out, VM* ptr1, foreign_method_t ptr2) {
std::copy(g_dynafunc, g_dynafunc_end, out);
replace_ptr(out, out + g_dynafunc_len, g_dynafunc_ptr1, ptr1);
replace_ptr(out, out + g_dynafunc_len, g_dynafunc_ptr2, ptr2);
}
} //unnamed namespace
DynafuncMaker::DynafuncMaker() :
m_page_size(sysconf(_SC_PAGE_SIZE)),
m_used_bytes(m_page_size)
{
if (m_page_size <= 0)
throw std::runtime_error("Invalid page size " + std::to_string(m_page_size));
if (m_page_size < g_dynafunc_len)
throw std::runtime_error("Page size " + std::to_string(m_page_size) + " is too small to accomodate assembly the whose size is " + std::to_string(g_dynafunc_len));
assert(m_page_size == m_used_bytes);
}
DynafuncMaker::~DynafuncMaker() noexcept {
clear();
}
void* DynafuncMaker::make (VM* vm, foreign_method_t cb) {
unsigned char* const mem = allocate_chunk();
make_dynafunc_asm(mem, vm, cb);
return mem;
}
void DynafuncMaker::clear() noexcept {
for (auto page : m_pages) {
std::free(page);
}
m_pages.clear();
m_used_bytes = m_page_size;
}
std::size_t DynafuncMaker::total_memory() const {
return m_pages.size() * m_page_size +
m_pages.capacity() * sizeof(decltype(m_pages)::value_type) +
0;
}
unsigned char* DynafuncMaker::allocate_chunk() {
if (m_page_size - m_used_bytes < g_dynafunc_len) {
unique_ptr_free_t page(std::aligned_alloc(m_page_size, m_page_size));
if (not page)
throw std::bad_alloc();
mprotect(page.get(), m_page_size, PROT_EXEC | PROT_READ | PROT_WRITE);
m_pages.push_back(page.get());
page.release();
m_used_bytes = 0;
}
assert(not m_pages.empty());
const auto ret = static_cast<unsigned char*>(m_pages.back()) + m_used_bytes;
m_used_bytes += g_dynafunc_len;
return ret;
}
} //namespace wren