1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <vtablefactory.hxx>
23 #include <vtables.hxx>
25 #include <osl/thread.h>
26 #include <osl/security.hxx>
27 #include <osl/file.hxx>
28 #include <rtl/alloc.h>
29 #include <rtl/ustring.hxx>
30 #include <sal/log.hxx>
31 #include <sal/types.h>
35 #include <unordered_map>
44 #define WIN32_LEAN_AND_MEAN
47 #error Unsupported platform
50 #if defined USE_DOUBLE_MMAP
54 #if defined MACOSX && defined __aarch64__
58 using bridges::cpp_uno::shared::VtableFactory
;
62 extern "C" void * allocExec(
63 SAL_UNUSED_PARAMETER rtl_arena_type
*, sal_Size
* size
)
67 #if defined FREEBSD || defined NETBSD || defined OPENBSD || defined DRAGONFLY || defined HAIKU
68 pagesize
= getpagesize();
70 // coverity[ tainted_data_return : FALSE ] version 2023.12.2
71 pagesize
= sysconf(_SC_PAGESIZE
);
76 pagesize
= info
.dwPageSize
;
78 #error Unsupported platform
80 std::size_t n
= (*size
+ (pagesize
- 1)) & ~(pagesize
- 1);
85 nullptr, n
, PROT_READ
| PROT_WRITE
| PROT_EXEC
, MAP_PRIVATE
| MAP_ANON
| MAP_JIT
, -1,
87 if (p
!= MAP_FAILED
) {
92 SAL_INFO("bridges.osx", "mmap failed with " << e
);
98 // At least some macOS 10.13 machines are reported to fail the above mmap with EINVAL (see
99 // tdf#134754 "Crash on macOS 10.13 opening local HSQLDB-based odb file in Base on LibreOffice 7
100 // rc1", so in that case retry with the "traditional" approach:
103 nullptr, n
, PROT_READ
| PROT_WRITE
, MAP_PRIVATE
| MAP_ANON
, -1,
105 if (p
== MAP_FAILED
) {
108 else if (mprotect (p
, n
, PROT_READ
| PROT_WRITE
| PROT_EXEC
) == -1)
117 p
= VirtualAlloc(nullptr, n
, MEM_COMMIT
, PAGE_EXECUTE_READWRITE
);
125 extern "C" void freeExec(
126 SAL_UNUSED_PARAMETER rtl_arena_type
*, void * address
, sal_Size size
)
129 munmap(address
, size
);
131 (void) size
; // unused
132 VirtualFree(address
, 0, MEM_RELEASE
);
136 #if defined MACOSX && defined __aarch64__
137 struct JitMemoryProtectionGuard
{
138 JitMemoryProtectionGuard() { pthread_jit_write_protect_np(0); }
139 ~JitMemoryProtectionGuard() { pthread_jit_write_protect_np(1); }
145 class VtableFactory::GuardedBlocks
:
146 public std::vector
<Block
>
149 GuardedBlocks(const GuardedBlocks
&) = delete;
150 const GuardedBlocks
& operator=(const GuardedBlocks
&) = delete;
152 explicit GuardedBlocks(VtableFactory
const & factory
):
153 m_factory(factory
), m_guarded(true) {}
157 void unguard() { m_guarded
= false; }
160 VtableFactory
const & m_factory
;
164 VtableFactory::GuardedBlocks::~GuardedBlocks() {
166 for (iterator
i(begin()); i
!= end(); ++i
) {
167 m_factory
.freeBlock(*i
);
172 class VtableFactory::BaseOffset
{
174 explicit BaseOffset(typelib_InterfaceTypeDescription
* type
) { calculate(type
, 0); }
176 sal_Int32
getFunctionOffset(OUString
const & name
) const
177 { return m_map
.find(name
)->second
; }
181 typelib_InterfaceTypeDescription
* type
, sal_Int32 offset
);
183 std::unordered_map
< OUString
, sal_Int32
> m_map
;
186 sal_Int32
VtableFactory::BaseOffset::calculate(
187 typelib_InterfaceTypeDescription
* type
, sal_Int32 offset
)
189 OUString
name(type
->aBase
.pTypeName
);
190 if (m_map
.find(name
) == m_map
.end()) {
191 for (sal_Int32 i
= 0; i
< type
->nBaseTypes
; ++i
) {
192 offset
= calculate(type
->ppBaseTypes
[i
], offset
);
194 m_map
.insert({name
, offset
});
195 typelib_typedescription_complete(
196 reinterpret_cast< typelib_TypeDescription
** >(&type
));
197 offset
+= bridges::cpp_uno::shared::getLocalFunctions(type
);
202 VtableFactory::VtableFactory(): m_arena(
204 "bridges::cpp_uno::shared::VtableFactory",
205 sizeof (void *), // to satisfy alignment requirements
206 0, nullptr, allocExec
, freeExec
, 0))
208 if (m_arena
== nullptr) {
209 throw std::bad_alloc();
213 VtableFactory::~VtableFactory() {
215 std::scoped_lock
guard(m_mutex
);
216 for (const auto& rEntry
: m_map
) {
217 for (sal_Int32 j
= 0; j
< rEntry
.second
.count
; ++j
) {
218 freeBlock(rEntry
.second
.blocks
[j
]);
222 rtl_arena_destroy(m_arena
);
225 const VtableFactory::Vtables
& VtableFactory::getVtables(
226 typelib_InterfaceTypeDescription
* type
)
228 OUString
name(type
->aBase
.pTypeName
);
229 std::scoped_lock
guard(m_mutex
);
230 Map::iterator
i(m_map
.find(name
));
231 if (i
== m_map
.end()) {
232 GuardedBlocks
blocks(*this);
233 createVtables(blocks
, BaseOffset(type
), type
, 0, type
, true);
235 assert(blocks
.size() <= SAL_MAX_INT32
);
236 vtables
.count
= static_cast< sal_Int32
>(blocks
.size());
237 vtables
.blocks
.reset(new Block
[vtables
.count
]);
238 for (sal_Int32 j
= 0; j
< vtables
.count
; ++j
) {
239 vtables
.blocks
[j
] = blocks
[j
];
241 i
= m_map
.emplace(name
, std::move(vtables
)).first
;
247 #ifdef USE_DOUBLE_MMAP
248 bool VtableFactory::createBlock(Block
&block
, sal_Int32 slotCount
) const
250 std::size_t size
= getBlockSize(slotCount
);
251 std::size_t pagesize
= sysconf(_SC_PAGESIZE
);
252 block
.size
= (size
+ (pagesize
- 1)) & ~(pagesize
- 1);
255 // Try non-doublemmaped allocation first:
256 block
.start
= block
.exec
= rtl_arena_alloc(m_arena
, &block
.size
);
257 if (block
.start
!= nullptr) {
261 osl::Security aSecurity
;
262 OUString strDirectory
;
263 OUString strURLDirectory
;
264 if (aSecurity
.getHomeDir(strURLDirectory
))
265 osl::File::getSystemPathFromFileURL(strURLDirectory
, strDirectory
);
267 for (int i
= strDirectory
.isEmpty() ? 1 : 0; i
< 2; ++i
)
269 if (strDirectory
.isEmpty())
270 strDirectory
= "/tmp";
272 strDirectory
+= "/.execoooXXXXXX";
273 OString aTmpName
= OUStringToOString(strDirectory
, osl_getThreadTextEncoding());
274 std::unique_ptr
<char[]> tmpfname(new char[aTmpName
.getLength()+1]);
275 strncpy(tmpfname
.get(), aTmpName
.getStr(), aTmpName
.getLength()+1);
276 // coverity[secure_temp] - https://communities.coverity.com/thread/3179
277 if ((block
.fd
= mkstemp(tmpfname
.get())) == -1)
278 fprintf(stderr
, "mkstemp(\"%s\") failed: %s\n", tmpfname
.get(), strerror(errno
));
283 unlink(tmpfname
.get());
285 #if defined(HAVE_POSIX_FALLOCATE)
286 int err
= posix_fallocate(block
.fd
, 0, block
.size
);
288 int err
= ftruncate(block
.fd
, block
.size
);
292 #if defined(HAVE_POSIX_FALLOCATE)
293 SAL_WARN("bridges", "posix_fallocate failed with code " << err
);
295 SAL_WARN("bridges", "truncation of executable memory area failed with code " << err
);
301 block
.start
= mmap(nullptr, block
.size
, PROT_READ
| PROT_WRITE
, MAP_SHARED
, block
.fd
, 0);
302 if (block
.start
== MAP_FAILED
) {
303 block
.start
= nullptr;
305 block
.exec
= mmap(nullptr, block
.size
, PROT_READ
| PROT_EXEC
, MAP_SHARED
, block
.fd
, 0);
306 if (block
.exec
== MAP_FAILED
) {
307 block
.exec
= nullptr;
311 if (block
.start
&& block
.exec
&& block
.fd
!= -1)
316 strDirectory
.clear();
318 return (block
.start
!= nullptr && block
.exec
!= nullptr);
321 void VtableFactory::freeBlock(Block
const & block
) const {
322 //if the double-map failed we were allocated on the arena
323 if (block
.fd
== -1 && block
.start
== block
.exec
&& block
.start
!= nullptr)
324 rtl_arena_free(m_arena
, block
.start
, block
.size
);
327 if (block
.start
) munmap(block
.start
, block
.size
);
328 if (block
.exec
) munmap(block
.exec
, block
.size
);
329 if (block
.fd
!= -1) close(block
.fd
);
333 bool VtableFactory::createBlock(Block
&block
, sal_Int32 slotCount
) const
335 block
.size
= getBlockSize(slotCount
);
336 block
.start
= rtl_arena_alloc(m_arena
, &block
.size
);
337 return block
.start
!= nullptr;
340 void VtableFactory::freeBlock(Block
const & block
) const {
341 rtl_arena_free(m_arena
, block
.start
, block
.size
);
345 sal_Int32
VtableFactory::createVtables(
346 GuardedBlocks
& blocks
, BaseOffset
const & baseOffset
,
347 typelib_InterfaceTypeDescription
* type
, sal_Int32 vtableNumber
,
348 typelib_InterfaceTypeDescription
* mostDerived
, bool includePrimary
) const
351 #if defined MACOSX && defined __aarch64__
352 JitMemoryProtectionGuard guard
;
354 if (includePrimary
) {
356 = bridges::cpp_uno::shared::getPrimaryFunctions(type
);
358 if (!createBlock(block
, slotCount
)) {
359 throw std::bad_alloc();
362 Slot
* slots
= initializeBlock(
363 block
.start
, slotCount
, vtableNumber
, mostDerived
);
364 unsigned char * codeBegin
=
365 reinterpret_cast< unsigned char * >(slots
);
366 unsigned char * code
= codeBegin
;
367 sal_Int32 vtableOffset
= blocks
.size() * sizeof (Slot
*);
368 for (typelib_InterfaceTypeDescription
const * type2
= type
;
369 type2
!= nullptr; type2
= type2
->pBaseTypeDescription
)
371 code
= addLocalFunctions(
373 #ifdef USE_DOUBLE_MMAP
374 reinterpret_cast<sal_uIntPtr
>(block
.exec
) - reinterpret_cast<sal_uIntPtr
>(block
.start
),
377 baseOffset
.getFunctionOffset(type2
->aBase
.pTypeName
),
378 bridges::cpp_uno::shared::getLocalFunctions(type2
),
381 flushCode(codeBegin
, code
);
382 #ifdef USE_DOUBLE_MMAP
383 //Finished generating block, swap writable pointer with executable
385 std::swap(block
.start
, block
.exec
);
387 blocks
.push_back(block
);
394 for (sal_Int32 i
= 0; i
< type
->nBaseTypes
; ++i
) {
395 vtableNumber
= createVtables(
396 blocks
, baseOffset
, type
->ppBaseTypes
[i
],
397 vtableNumber
+ (i
== 0 ? 0 : 1), mostDerived
, i
!= 0);
402 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */