diff options
Diffstat (limited to '')
-rw-r--r-- | bridges/source/cpp_uno/shared/vtablefactory.cxx | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/bridges/source/cpp_uno/shared/vtablefactory.cxx b/bridges/source/cpp_uno/shared/vtablefactory.cxx new file mode 100644 index 000000000..53b516cde --- /dev/null +++ b/bridges/source/cpp_uno/shared/vtablefactory.cxx @@ -0,0 +1,403 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you 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 . + */ + + +#include <vtablefactory.hxx> + +#include <vtables.hxx> + +#include <osl/thread.h> +#include <osl/security.hxx> +#include <osl/file.hxx> +#include <osl/mutex.hxx> +#include <rtl/alloc.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/types.h> +#include <typelib/typedescription.hxx> + +#include <memory> +#include <new> +#include <unordered_map> +#include <vector> + +#if defined SAL_UNX +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/mman.h> +#elif defined _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#else +#error Unsupported platform +#endif + +#if defined USE_DOUBLE_MMAP +#include <fcntl.h> +#endif + +#if defined MACOSX && defined __aarch64__ +#include <pthread.h> +#endif + +using bridges::cpp_uno::shared::VtableFactory; + +namespace { + +extern "C" void * allocExec( + SAL_UNUSED_PARAMETER rtl_arena_type *, sal_Size * size) +{ + std::size_t pagesize; +#if defined SAL_UNX +#if defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY || defined HAIKU + pagesize = getpagesize(); +#else + pagesize = sysconf(_SC_PAGESIZE); +#endif +#elif defined _WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + pagesize = info.dwPageSize; +#else +#error Unsupported platform +#endif + std::size_t n = (*size + (pagesize - 1)) & ~(pagesize - 1); + void * p; +#if defined SAL_UNX +#if defined MACOSX + p = mmap( + nullptr, n, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, + 0); + if (p != MAP_FAILED) { + goto done; + } + { + auto const e = errno; + SAL_INFO("bridges.osx", "mmap failed with " << e); + if (e != EINVAL) { + p = nullptr; + goto done; + } + } + // At least some macOS 10.13 machines are reported to fail the above mmap with EINVAL (see + // tdf#134754 "Crash on macOS 10.13 opening local HSQLDB-based odb file in Base on LibreOffice 7 + // rc1", so in that case retry with the "traditional" approach: +#endif + p = mmap( + nullptr, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, + 0); + if (p == MAP_FAILED) { + p = nullptr; + } + else if (mprotect (p, n, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) + { + munmap (p, n); + p = nullptr; + } +#if defined MACOSX +done: +#endif +#elif defined _WIN32 + p = VirtualAlloc(nullptr, n, MEM_COMMIT, PAGE_EXECUTE_READWRITE); +#endif + if (p != nullptr) { + *size = n; + } + return p; +} + +extern "C" void freeExec( + SAL_UNUSED_PARAMETER rtl_arena_type *, void * address, sal_Size size) +{ +#if defined SAL_UNX + munmap(address, size); +#elif defined _WIN32 + (void) size; // unused + VirtualFree(address, 0, MEM_RELEASE); +#endif +} + +#if defined MACOSX && defined __aarch64__ +struct JitMemoryProtectionGuard { + JitMemoryProtectionGuard() { pthread_jit_write_protect_np(0); } + ~JitMemoryProtectionGuard() { pthread_jit_write_protect_np(1); } +}; +#endif + +} + +class VtableFactory::GuardedBlocks: + public std::vector<Block> +{ +public: + GuardedBlocks(const GuardedBlocks&) = delete; + const GuardedBlocks& operator=(const GuardedBlocks&) = delete; + + explicit GuardedBlocks(VtableFactory const & factory): + m_factory(factory), m_guarded(true) {} + + ~GuardedBlocks(); + + void unguard() { m_guarded = false; } + +private: + VtableFactory const & m_factory; + bool m_guarded; +}; + +VtableFactory::GuardedBlocks::~GuardedBlocks() { + if (m_guarded) { + for (iterator i(begin()); i != end(); ++i) { + m_factory.freeBlock(*i); + } + } +} + +class VtableFactory::BaseOffset { +public: + explicit BaseOffset(typelib_InterfaceTypeDescription * type) { calculate(type, 0); } + + sal_Int32 getFunctionOffset(OUString const & name) const + { return m_map.find(name)->second; } + +private: + sal_Int32 calculate( + typelib_InterfaceTypeDescription * type, sal_Int32 offset); + + std::unordered_map< OUString, sal_Int32 > m_map; +}; + +sal_Int32 VtableFactory::BaseOffset::calculate( + typelib_InterfaceTypeDescription * type, sal_Int32 offset) +{ + OUString name(type->aBase.pTypeName); + if (m_map.find(name) == m_map.end()) { + for (sal_Int32 i = 0; i < type->nBaseTypes; ++i) { + offset = calculate(type->ppBaseTypes[i], offset); + } + m_map.insert({name, offset}); + typelib_typedescription_complete( + reinterpret_cast< typelib_TypeDescription ** >(&type)); + offset += bridges::cpp_uno::shared::getLocalFunctions(type); + } + return offset; +} + +VtableFactory::VtableFactory(): m_arena( + rtl_arena_create( + "bridges::cpp_uno::shared::VtableFactory", + sizeof (void *), // to satisfy alignment requirements + 0, nullptr, allocExec, freeExec, 0)) +{ + if (m_arena == nullptr) { + throw std::bad_alloc(); + } +} + +VtableFactory::~VtableFactory() { + { + std::scoped_lock guard(m_mutex); + for (const auto& rEntry : m_map) { + for (sal_Int32 j = 0; j < rEntry.second.count; ++j) { + freeBlock(rEntry.second.blocks[j]); + } + } + } + rtl_arena_destroy(m_arena); +} + +const VtableFactory::Vtables& VtableFactory::getVtables( + typelib_InterfaceTypeDescription * type) +{ + OUString name(type->aBase.pTypeName); + std::scoped_lock guard(m_mutex); + Map::iterator i(m_map.find(name)); + if (i == m_map.end()) { + GuardedBlocks blocks(*this); + createVtables(blocks, BaseOffset(type), type, 0, type, true); + Vtables vtables; + assert(blocks.size() <= SAL_MAX_INT32); + vtables.count = static_cast< sal_Int32 >(blocks.size()); + vtables.blocks.reset(new Block[vtables.count]); + for (sal_Int32 j = 0; j < vtables.count; ++j) { + vtables.blocks[j] = blocks[j]; + } + i = m_map.emplace(name, std::move(vtables)).first; + blocks.unguard(); + } + return i->second; +} + +#ifdef USE_DOUBLE_MMAP +bool VtableFactory::createBlock(Block &block, sal_Int32 slotCount) const +{ + std::size_t size = getBlockSize(slotCount); + std::size_t pagesize = sysconf(_SC_PAGESIZE); + block.size = (size + (pagesize - 1)) & ~(pagesize - 1); + block.fd = -1; + + // Try non-doublemmaped allocation first: + block.start = block.exec = rtl_arena_alloc(m_arena, &block.size); + if (block.start != nullptr) { + return true; + } + + osl::Security aSecurity; + OUString strDirectory; + OUString strURLDirectory; + if (aSecurity.getHomeDir(strURLDirectory)) + osl::File::getSystemPathFromFileURL(strURLDirectory, strDirectory); + + for (int i = strDirectory.isEmpty() ? 1 : 0; i < 2; ++i) + { + if (strDirectory.isEmpty()) + strDirectory = "/tmp"; + + strDirectory += "/.execoooXXXXXX"; + OString aTmpName = OUStringToOString(strDirectory, osl_getThreadTextEncoding()); + std::unique_ptr<char[]> tmpfname(new char[aTmpName.getLength()+1]); + strncpy(tmpfname.get(), aTmpName.getStr(), aTmpName.getLength()+1); + // coverity[secure_temp] - https://communities.coverity.com/thread/3179 + if ((block.fd = mkstemp(tmpfname.get())) == -1) + fprintf(stderr, "mkstemp(\"%s\") failed: %s\n", tmpfname.get(), strerror(errno)); + if (block.fd == -1) + { + break; + } + unlink(tmpfname.get()); + tmpfname.reset(); +#if defined(HAVE_POSIX_FALLOCATE) + int err = posix_fallocate(block.fd, 0, block.size); +#else + int err = ftruncate(block.fd, block.size); +#endif + if (err != 0) + { +#if defined(HAVE_POSIX_FALLOCATE) + SAL_WARN("bridges", "posix_fallocate failed with code " << err); +#else + SAL_WARN("bridges", "truncation of executable memory area failed with code " << err); +#endif + close(block.fd); + block.fd = -1; + break; + } + block.start = mmap(nullptr, block.size, PROT_READ | PROT_WRITE, MAP_SHARED, block.fd, 0); + if (block.start== MAP_FAILED) { + block.start = nullptr; + } + block.exec = mmap(nullptr, block.size, PROT_READ | PROT_EXEC, MAP_SHARED, block.fd, 0); + if (block.exec == MAP_FAILED) { + block.exec = nullptr; + } + + //All good + if (block.start && block.exec && block.fd != -1) + break; + + freeBlock(block); + + strDirectory.clear(); + } + return (block.start != nullptr && block.exec != nullptr); +} + +void VtableFactory::freeBlock(Block const & block) const { + //if the double-map failed we were allocated on the arena + if (block.fd == -1 && block.start == block.exec && block.start != nullptr) + rtl_arena_free(m_arena, block.start, block.size); + else + { + if (block.start) munmap(block.start, block.size); + if (block.exec) munmap(block.exec, block.size); + if (block.fd != -1) close(block.fd); + } +} +#else +bool VtableFactory::createBlock(Block &block, sal_Int32 slotCount) const +{ + block.size = getBlockSize(slotCount); + block.start = rtl_arena_alloc(m_arena, &block.size); + return block.start != nullptr; +} + +void VtableFactory::freeBlock(Block const & block) const { + rtl_arena_free(m_arena, block.start, block.size); +} +#endif + +sal_Int32 VtableFactory::createVtables( + GuardedBlocks & blocks, BaseOffset const & baseOffset, + typelib_InterfaceTypeDescription * type, sal_Int32 vtableNumber, + typelib_InterfaceTypeDescription * mostDerived, bool includePrimary) const +{ + { +#if defined MACOSX && defined __aarch64__ + JitMemoryProtectionGuard guard; +#endif + if (includePrimary) { + sal_Int32 slotCount + = bridges::cpp_uno::shared::getPrimaryFunctions(type); + Block block; + if (!createBlock(block, slotCount)) { + throw std::bad_alloc(); + } + try { + Slot * slots = initializeBlock( + block.start, slotCount, vtableNumber, mostDerived); + unsigned char * codeBegin = + reinterpret_cast< unsigned char * >(slots); + unsigned char * code = codeBegin; + sal_Int32 vtableOffset = blocks.size() * sizeof (Slot *); + for (typelib_InterfaceTypeDescription const * type2 = type; + type2 != nullptr; type2 = type2->pBaseTypeDescription) + { + code = addLocalFunctions( + &slots, code, +#ifdef USE_DOUBLE_MMAP + reinterpret_cast<sal_uIntPtr>(block.exec) - reinterpret_cast<sal_uIntPtr>(block.start), +#endif + type2, + baseOffset.getFunctionOffset(type2->aBase.pTypeName), + bridges::cpp_uno::shared::getLocalFunctions(type2), + vtableOffset); + } + flushCode(codeBegin, code); +#ifdef USE_DOUBLE_MMAP + //Finished generating block, swap writable pointer with executable + //pointer + std::swap(block.start, block.exec); +#endif + blocks.push_back(block); + } catch (...) { + freeBlock(block); + throw; + } + } + } + for (sal_Int32 i = 0; i < type->nBaseTypes; ++i) { + vtableNumber = createVtables( + blocks, baseOffset, type->ppBaseTypes[i], + vtableNumber + (i == 0 ? 0 : 1), mostDerived, i != 0); + } + return vtableNumber; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |