Skip to content

Commit d779980

Browse files
Gabriel Schulhofaddaleaxbnoordhuis
authored andcommitted
src: munmap(2) upon class instance destructor
Replace `OnScopeLeave` with a class whose instance destructor performs the munmap(2). Signed-off-by: Gabriel Schulhof <[email protected]> Fixes: #32532 PR-URL: #32570 Co-Authored-By: Anna Henningsen <[email protected]> Co-Authored-By: Ben Noordhuis <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: David Carlier <[email protected]>
1 parent 1c47bba commit d779980

File tree

1 file changed

+102
-72
lines changed

1 file changed

+102
-72
lines changed

src/large_pages/node_large_page.cc

+102-72
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,56 @@
2020
//
2121
// SPDX-License-Identifier: MIT
2222

23+
// The functions in this file map the .text section of Node.js into 2MB pages.
24+
// They perform the following steps:
25+
//
26+
// 1: Find the Node.js binary's `.text` section in memory. This is done below in
27+
// `FindNodeTextRegion`. It is accomplished in a platform-specific way. On
28+
// Linux and FreeBSD, `dl_iterate_phdr(3)` is used. When the region is found,
29+
// it is "trimmed" as follows:
30+
// * Modify the start to point to the very beginning of the Node.js `.text`
31+
// section (from symbol `__node_text_start` declared in node_text_start.S).
32+
// * Possibly modify the end to account for the `lpstub` section which
33+
// contains `MoveTextRegionToLargePages`, the function we do not wish to
34+
// move (see below).
35+
// * Align the address of the start to its nearest higher large page
36+
// boundary.
37+
// * Align the address of the end to its nearest lower large page boundary.
38+
//
39+
// 2: Move the text region to large pages. This is done below in
40+
// `MoveTextRegionToLargePages`. We need to be very careful:
41+
// a) `MoveTextRegionToLargePages` itself should not be moved.
42+
// We use gcc attributes
43+
// (__section__) to put it outside the `.text` section,
44+
// (__aligned__) to align it at the 2M boundary, and
45+
// (__noline__) to not inline this function.
46+
// b) `MoveTextRegionToLargePages` should not call any function(s) that might
47+
// be moved.
48+
// To move the .text section, perform the following steps:
49+
// * Map a new, temporary area and copy the original code there.
50+
// * Use mmap using the start address with MAP_FIXED so we get exactly the
51+
// same virtual address (except on OSX). On platforms other than Linux,
52+
// use mmap flags to request hugepages.
53+
// * On Linux use madvise with MADV_HUGEPAGE to use anonymous 2MB pages.
54+
// * If successful copy the code to the newly mapped area and protect it to
55+
// be readable and executable.
56+
// * Unmap the temporary area.
57+
2358
#include "node_large_page.h"
2459

2560
#include <cerrno> // NOLINT(build/include)
2661

2762
// Besides returning ENOTSUP at runtime we do nothing if this define is missing.
2863
#if defined(NODE_ENABLE_LARGE_CODE_PAGES) && NODE_ENABLE_LARGE_CODE_PAGES
2964
#include "debug_utils-inl.h"
30-
#include "util.h"
31-
#include "uv.h"
3265

3366
#if defined(__linux__) || defined(__FreeBSD__)
34-
#include <string.h>
3567
#if defined(__linux__)
3668
#ifndef _GNU_SOURCE
3769
#define _GNU_SOURCE
3870
#endif // ifndef _GNU_SOURCE
71+
#elif defined(__FreeBSD__)
72+
#include "uv.h" // uv_exepath
3973
#endif // defined(__linux__)
4074
#include <link.h>
4175
#endif // defined(__linux__) || defined(__FreeBSD__)
@@ -44,38 +78,16 @@
4478
#include <sys/mman.h>
4579
#if defined(__FreeBSD__)
4680
#include <sys/sysctl.h>
47-
#include <sys/user.h>
4881
#elif defined(__APPLE__)
4982
#include <mach/vm_map.h>
5083
#endif
51-
#include <unistd.h> // getpid
5284

5385
#include <climits> // PATH_MAX
54-
#include <clocale>
55-
#include <csignal>
5686
#include <cstdlib>
5787
#include <cstdint>
5888
#include <cstring>
5989
#include <string>
6090
#include <fstream>
61-
#include <iostream>
62-
#include <vector>
63-
64-
// The functions in this file map the text segment of node into 2M pages.
65-
// The algorithm is simple
66-
// Find the text region of node binary in memory
67-
// 1: Examine the /proc/self/maps to determine the currently mapped text
68-
// region and obtain the start and end
69-
// Modify the start to point to the very beginning of node text segment
70-
// (from variable nodetext setup in ld.script)
71-
// Align the address of start and end to Large Page Boundaries
72-
//
73-
// 2: Move the text region to large pages
74-
// Map a new area and copy the original code there
75-
// Use mmap using the start address with MAP_FIXED so we get exactly the
76-
// same virtual address
77-
// Use madvise with MADV_HUGEPAGE to use Anonymous 2M Pages
78-
// If successful copy the code there and unmap the original region.
7991

8092
#if defined(__linux__) || defined(__FreeBSD__)
8193
extern "C" {
@@ -282,20 +294,44 @@ bool IsSuperPagesEnabled() {
282294
}
283295
#endif
284296

297+
// Functions in this class must always be inlined because they must end up in
298+
// the `lpstub` section rather than the `.text` section.
299+
class MemoryMapPointer {
300+
public:
301+
FORCE_INLINE explicit MemoryMapPointer() {}
302+
FORCE_INLINE bool operator==(void* rhs) const { return mem_ == rhs; }
303+
FORCE_INLINE void* mem() const { return mem_; }
304+
MemoryMapPointer(const MemoryMapPointer&) = delete;
305+
MemoryMapPointer(MemoryMapPointer&&) = delete;
306+
void operator= (const MemoryMapPointer&) = delete;
307+
void operator= (const MemoryMapPointer&&) = delete;
308+
FORCE_INLINE void Reset(void* start,
309+
size_t size,
310+
int prot,
311+
int flags,
312+
int fd = -1,
313+
size_t offset = 0) {
314+
mem_ = mmap(start, size, prot, flags, fd, offset);
315+
size_ = size;
316+
}
317+
FORCE_INLINE void Reset() {
318+
mem_ = nullptr;
319+
size_ = 0;
320+
}
321+
FORCE_INLINE ~MemoryMapPointer() {
322+
if (mem_ == nullptr) return;
323+
if (mem_ == MAP_FAILED) return;
324+
if (munmap(mem_, size_) == 0) return;
325+
PrintSystemError(errno);
326+
}
327+
328+
private:
329+
size_t size_ = 0;
330+
void* mem_ = nullptr;
331+
};
332+
285333
} // End of anonymous namespace
286334

287-
// Moving the text region to large pages. We need to be very careful.
288-
// 1: This function itself should not be moved.
289-
// We use a gcc attributes
290-
// (__section__) to put it outside the ".text" section
291-
// (__aligned__) to align it at 2M boundary
292-
// (__noline__) to not inline this function
293-
// 2: This function should not call any function(s) that might be moved.
294-
// a. map a new area and copy the original code there
295-
// b. mmap using the start address with MAP_FIXED so we get exactly
296-
// the same virtual address (except on macOS).
297-
// c. madvise with MADV_HUGEPAGE
298-
// d. If successful copy the code there and unmap the original region
299335
int
300336
#if !defined(__APPLE__)
301337
__attribute__((__section__("lpstub")))
@@ -305,62 +341,56 @@ __attribute__((__section__("__TEXT,__lpstub")))
305341
__attribute__((__aligned__(hps)))
306342
__attribute__((__noinline__))
307343
MoveTextRegionToLargePages(const text_region& r) {
308-
void* nmem = nullptr;
309-
void* tmem = nullptr;
344+
MemoryMapPointer nmem;
345+
MemoryMapPointer tmem;
310346
void* start = r.from;
311347
size_t size = r.to - r.from;
312348

313-
auto free_mems = OnScopeLeave([&nmem, &tmem, size]() {
314-
if (nmem != nullptr && nmem != MAP_FAILED && munmap(nmem, size) == -1)
315-
PrintSystemError(errno);
316-
if (tmem != nullptr && tmem != MAP_FAILED && munmap(tmem, size) == -1)
317-
PrintSystemError(errno);
318-
});
319-
320-
// Allocate temporary region and back up the code we will re-map.
321-
nmem = mmap(nullptr, size,
322-
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
323-
if (nmem == MAP_FAILED) goto fail;
324-
memcpy(nmem, r.from, size);
349+
// Allocate a temporary region and back up the code we will re-map.
350+
nmem.Reset(nullptr, size,
351+
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS);
352+
if (nmem.mem() == MAP_FAILED) goto fail;
353+
memcpy(nmem.mem(), r.from, size);
325354

326355
#if defined(__linux__)
327356
// We already know the original page is r-xp
328357
// (PROT_READ, PROT_EXEC, MAP_PRIVATE)
329358
// We want PROT_WRITE because we are writing into it.
330359
// We want it at the fixed address and we use MAP_FIXED.
331-
tmem = mmap(start, size,
332-
PROT_READ | PROT_WRITE | PROT_EXEC,
333-
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1 , 0);
334-
if (tmem == MAP_FAILED) goto fail;
335-
if (madvise(tmem, size, 14 /* MADV_HUGEPAGE */) == -1) goto fail;
336-
memcpy(start, nmem, size);
360+
tmem.Reset(start, size,
361+
PROT_READ | PROT_WRITE | PROT_EXEC,
362+
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED);
363+
if (tmem.mem() == MAP_FAILED) goto fail;
364+
if (madvise(tmem.mem(), size, 14 /* MADV_HUGEPAGE */) == -1) goto fail;
365+
memcpy(start, nmem.mem(), size);
337366
#elif defined(__FreeBSD__)
338-
tmem = mmap(start, size,
339-
PROT_READ | PROT_WRITE | PROT_EXEC,
340-
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED |
341-
MAP_ALIGNED_SUPER, -1 , 0);
342-
if (tmem == MAP_FAILED) goto fail;
343-
memcpy(start, nmem, size);
367+
tmem.Reset(start, size,
368+
PROT_READ | PROT_WRITE | PROT_EXEC,
369+
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED |
370+
MAP_ALIGNED_SUPER);
371+
if (tmem.mem() == MAP_FAILED) goto fail;
372+
memcpy(start, nmem.mem(), size);
344373
#elif defined(__APPLE__)
345374
// There is not enough room to reserve the mapping close
346375
// to the region address so we content to give a hint
347376
// without forcing the new address being closed to.
348377
// We explicitally gives all permission since we plan
349378
// to write into it.
350-
tmem = mmap(start, size,
351-
PROT_READ | PROT_WRITE | PROT_EXEC,
352-
MAP_PRIVATE | MAP_ANONYMOUS,
353-
VM_FLAGS_SUPERPAGE_SIZE_2MB, 0);
354-
if (tmem == MAP_FAILED) goto fail;
355-
memcpy(tmem, nmem, size);
379+
tmem.Reset(start, size,
380+
PROT_READ | PROT_WRITE | PROT_EXEC,
381+
MAP_PRIVATE | MAP_ANONYMOUS,
382+
VM_FLAGS_SUPERPAGE_SIZE_2MB);
383+
if (tmem.mem() == MAP_FAILED) goto fail;
384+
memcpy(tmem.mem(), nmem.mem(), size);
356385
if (mprotect(start, size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1)
357386
goto fail;
358-
memcpy(start, tmem, size);
387+
memcpy(start, tmem.mem(), size);
359388
#endif
360389

361390
if (mprotect(start, size, PROT_READ | PROT_EXEC) == -1) goto fail;
362-
// We need not `munmap(tmem, size)` in the above `OnScopeLeave` on success.
363-
tmem = nullptr;
391+
392+
// We need not `munmap(tmem, size)` on success.
393+
tmem.Reset();
364394
return 0;
365395
fail:
366396
PrintSystemError(errno);

0 commit comments

Comments
 (0)