srctree

Robin Linden parent 2ea8e2da 42f4c8d5
os: Supporting creating executable memory

This is needed for some ongoing wasm jit-compilation experiments.

inlinesplit
os/linux.cpp added: 128, removed: 6, total 122
@@ -1,9 +1,11 @@
// SPDX-FileCopyrightText: 2021-2022 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "os/os.h"
 
#include <sys/mman.h>
 
#include <array>
#include <charconv>
#include <cstdlib>
@@ -51,4 +53,33 @@ unsigned active_window_scale_factor() {
 
// NOLINTEND(concurrency-mt-unsafe)
 
ExecutableMemory::~ExecutableMemory() {
if (memory_ != nullptr && munmap(memory_, size_) != 0) {
std::abort();
}
}
 
std::optional<ExecutableMemory> ExecutableMemory::allocate_containing(std::span<std::uint8_t const> data) {
if (data.empty()) {
return std::nullopt;
}
 
auto *memory = mmap(nullptr, data.size(), PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (memory == MAP_FAILED) {
return std::nullopt;
}
 
std::memcpy(memory, data.data(), data.size());
 
if (mprotect(memory, data.size(), PROT_EXEC) != 0) {
if (munmap(memory, data.size()) != 0) {
std::abort();
}
 
return std::nullopt;
}
 
return ExecutableMemory{memory, data.size()};
}
 
} // namespace os
 
os/os.h added: 128, removed: 6, total 122
@@ -1,11 +1,16 @@
// SPDX-FileCopyrightText: 2021-2022 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef OS_OS_H_
#define OS_OS_H_
 
#include <cstddef>
#include <cstdint>
#include <optional>
#include <span>
#include <string>
#include <utility>
#include <vector>
 
namespace os {
@@ -13,6 +18,33 @@ namespace os {
std::vector<std::string> font_paths();
unsigned active_window_scale_factor();
 
class ExecutableMemory {
public:
static std::optional<ExecutableMemory> allocate_containing(std::span<std::uint8_t const>);
~ExecutableMemory();
 
ExecutableMemory(ExecutableMemory const &) = delete;
ExecutableMemory &operator=(ExecutableMemory const &) = delete;
 
ExecutableMemory(ExecutableMemory &&other) noexcept {
std::swap(memory_, other.memory_);
std::swap(size_, other.size_);
}
 
ExecutableMemory &operator=(ExecutableMemory &&other) noexcept {
std::swap(memory_, other.memory_);
std::swap(size_, other.size_);
return *this;
}
 
void *ptr() { return memory_; }
 
private:
ExecutableMemory(void *memory, std::size_t size) : memory_{memory}, size_{size} {}
void *memory_{nullptr};
std::size_t size_{};
};
 
} // namespace os
 
#endif
 
os/os_test.cpp added: 128, removed: 6, total 122
@@ -6,6 +6,13 @@
 
#include "etest/etest2.h"
 
#include <cstdint>
 
// __amd64 => GNU C, _M_AMD64 => MSVC.
#if defined(__amd64) || defined(_M_AMD64)
#define AMD64
#endif
 
int main() {
etest::Suite s{"os"};
s.add_test("font_paths", [](etest::IActions &a) {
@@ -13,5 +20,21 @@ int main() {
a.expect(!font_paths.empty());
});
 
s.add_test("ExecutableMemory, normal use", [](etest::IActions &a) {
// MOV EAX, 42 ; b8 2a 0 0 0
// RET ; c3
auto exec_memory = os::ExecutableMemory::allocate_containing({{0xb8, 0x2a, 0, 0, 0, 0xc3}});
a.require(exec_memory.has_value());
#ifdef AMD64
using Fn = std::int32_t (*)();
auto get_42 = reinterpret_cast<Fn>(exec_memory->ptr());
a.expect_eq(get_42(), 42);
#endif
});
 
s.add_test("ExecutableMemory, empty data", [](etest::IActions &a) {
a.expect_eq(os::ExecutableMemory::allocate_containing({}), std::nullopt); //
});
 
return s.run();
}
 
os/windows.cpp added: 128, removed: 6, total 122
@@ -19,6 +19,7 @@
#include <Windows.h>
 
#include <Knownfolders.h>
#include <Memoryapi.h>
#include <Objbase.h>
#include <Shlobj.h>
#include <shellscalingapi.h>
@@ -60,4 +61,39 @@ unsigned active_window_scale_factor() {
return static_cast<unsigned>(std::lround(static_cast<float>(scale_factor) / 100.f));
}
 
// VirtualFree has a weird 2nd argument:
// [in] dwSize - The size of the region of memory to be freed, in bytes.
// If the dwFreeType parameter is MEM_RELEASE, this parameter must be 0 (zero).
// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree
ExecutableMemory::~ExecutableMemory() {
if (memory_ != nullptr && !VirtualFree(memory_, 0, MEM_RELEASE)) {
std::abort();
}
}
 
std::optional<ExecutableMemory> ExecutableMemory::allocate_containing(std::span<std::uint8_t const> data) {
if (data.empty()) {
return std::nullopt;
}
 
auto *memory = VirtualAlloc(nullptr, data.size(), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (memory == nullptr) {
return std::nullopt;
}
 
std::memcpy(memory, data.data(), data.size());
 
DWORD old_protect{};
if (!VirtualProtect(memory, data.size(), PAGE_EXECUTE, &old_protect)
|| !FlushInstructionCache(GetCurrentProcess(), memory, data.size())) {
if (!VirtualFree(memory, 0, MEM_RELEASE)) {
std::abort();
}
 
return std::nullopt;
}
 
return ExecutableMemory{memory, data.size()};
}
 
} // namespace os