Skip to content

Commit 8ae7c22

Browse files
wkspeterzhu2118
authored andcommitted
Annotate anonymous mmap
Use PR_SET_VMA_ANON_NAME to set human-readable names for anonymous virtual memory areas mapped by `mmap()` when compiled and run on Linux 5.17 or higher. This makes it convenient for developers to debug mmap.
1 parent 640bacc commit 8ae7c22

10 files changed

+100
-5
lines changed

common.mk

+15
Original file line numberDiff line numberDiff line change
@@ -8631,19 +8631,25 @@ io_buffer.$(OBJEXT): $(CCAN_DIR)/str/str.h
86318631
io_buffer.$(OBJEXT): $(hdrdir)/ruby/ruby.h
86328632
io_buffer.$(OBJEXT): $(hdrdir)/ruby/version.h
86338633
io_buffer.$(OBJEXT): $(top_srcdir)/internal/array.h
8634+
io_buffer.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
86348635
io_buffer.$(OBJEXT): $(top_srcdir)/internal/bignum.h
86358636
io_buffer.$(OBJEXT): $(top_srcdir)/internal/bits.h
86368637
io_buffer.$(OBJEXT): $(top_srcdir)/internal/compilers.h
86378638
io_buffer.$(OBJEXT): $(top_srcdir)/internal/error.h
86388639
io_buffer.$(OBJEXT): $(top_srcdir)/internal/fixnum.h
8640+
io_buffer.$(OBJEXT): $(top_srcdir)/internal/gc.h
8641+
io_buffer.$(OBJEXT): $(top_srcdir)/internal/imemo.h
86398642
io_buffer.$(OBJEXT): $(top_srcdir)/internal/io.h
86408643
io_buffer.$(OBJEXT): $(top_srcdir)/internal/numeric.h
8644+
io_buffer.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h
86418645
io_buffer.$(OBJEXT): $(top_srcdir)/internal/serial.h
86428646
io_buffer.$(OBJEXT): $(top_srcdir)/internal/static_assert.h
86438647
io_buffer.$(OBJEXT): $(top_srcdir)/internal/string.h
86448648
io_buffer.$(OBJEXT): $(top_srcdir)/internal/thread.h
86458649
io_buffer.$(OBJEXT): $(top_srcdir)/internal/vm.h
8650+
io_buffer.$(OBJEXT): $(top_srcdir)/internal/warnings.h
86468651
io_buffer.$(OBJEXT): {$(VPATH)}assert.h
8652+
io_buffer.$(OBJEXT): {$(VPATH)}atomic.h
86478653
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/assume.h
86488654
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/attributes.h
86498655
io_buffer.$(OBJEXT): {$(VPATH)}backward/2/bool.h
@@ -8657,6 +8663,7 @@ io_buffer.$(OBJEXT): {$(VPATH)}config.h
86578663
io_buffer.$(OBJEXT): {$(VPATH)}defines.h
86588664
io_buffer.$(OBJEXT): {$(VPATH)}encoding.h
86598665
io_buffer.$(OBJEXT): {$(VPATH)}fiber/scheduler.h
8666+
io_buffer.$(OBJEXT): {$(VPATH)}id.h
86608667
io_buffer.$(OBJEXT): {$(VPATH)}intern.h
86618668
io_buffer.$(OBJEXT): {$(VPATH)}internal.h
86628669
io_buffer.$(OBJEXT): {$(VPATH)}internal/abi.h
@@ -8811,13 +8818,21 @@ io_buffer.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
88118818
io_buffer.$(OBJEXT): {$(VPATH)}io.h
88128819
io_buffer.$(OBJEXT): {$(VPATH)}io/buffer.h
88138820
io_buffer.$(OBJEXT): {$(VPATH)}io_buffer.c
8821+
io_buffer.$(OBJEXT): {$(VPATH)}method.h
88148822
io_buffer.$(OBJEXT): {$(VPATH)}missing.h
8823+
io_buffer.$(OBJEXT): {$(VPATH)}node.h
88158824
io_buffer.$(OBJEXT): {$(VPATH)}onigmo.h
88168825
io_buffer.$(OBJEXT): {$(VPATH)}oniguruma.h
8826+
io_buffer.$(OBJEXT): {$(VPATH)}ruby_assert.h
8827+
io_buffer.$(OBJEXT): {$(VPATH)}ruby_atomic.h
8828+
io_buffer.$(OBJEXT): {$(VPATH)}rubyparser.h
88178829
io_buffer.$(OBJEXT): {$(VPATH)}st.h
88188830
io_buffer.$(OBJEXT): {$(VPATH)}subst.h
88198831
io_buffer.$(OBJEXT): {$(VPATH)}thread.h
8832+
io_buffer.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h
88208833
io_buffer.$(OBJEXT): {$(VPATH)}thread_native.h
8834+
io_buffer.$(OBJEXT): {$(VPATH)}vm_core.h
8835+
io_buffer.$(OBJEXT): {$(VPATH)}vm_opts.h
88218836
iseq.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
88228837
iseq.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
88238838
iseq.$(OBJEXT): $(CCAN_DIR)/list/list.h

cont.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -475,18 +475,20 @@ fiber_pool_allocate_memory(size_t * count, size_t stride)
475475
}
476476
#else
477477
errno = 0;
478-
void * base = mmap(NULL, (*count)*stride, PROT_READ | PROT_WRITE, FIBER_STACK_FLAGS, -1, 0);
478+
size_t mmap_size = (*count)*stride;
479+
void * base = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, FIBER_STACK_FLAGS, -1, 0);
479480

480481
if (base == MAP_FAILED) {
481482
// If the allocation fails, count = count / 2, and try again.
482483
*count = (*count) >> 1;
483484
}
484485
else {
486+
ruby_annotate_mmap(base, mmap_size, "Ruby:fiber_pool_allocate_memory");
485487
#if defined(MADV_FREE_REUSE)
486488
// On Mac MADV_FREE_REUSE is necessary for the task_info api
487489
// to keep the accounting accurate as possible when a page is marked as reusable
488490
// it can possibly not occurring at first call thus re-iterating if necessary.
489-
while (madvise(base, (*count)*stride, MADV_FREE_REUSE) == -1 && errno == EAGAIN);
491+
while (madvise(base, mmap_size, MADV_FREE_REUSE) == -1 && errno == EAGAIN);
490492
#endif
491493
return base;
492494
}

gc.c

+39
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@
7474
#include <emscripten.h>
7575
#endif
7676

77+
/* For ruby_annotate_mmap */
78+
#ifdef __linux__
79+
#include <linux/prctl.h>
80+
#include <sys/prctl.h>
81+
#endif
82+
7783
#undef LIST_HEAD /* ccan/list conflicts with BSD-origin sys/queue.h. */
7884

7985
#include "constant.h"
@@ -4494,3 +4500,36 @@ Init_GC(void)
44944500

44954501
rb_gc_impl_init();
44964502
}
4503+
4504+
// Set a name for the anonymous virtual memory area. `addr` is the starting
4505+
// address of the area and `size` is its length in bytes. `name` is a
4506+
// NUL-terminated human-readable string.
4507+
//
4508+
// This function is usually called after calling `mmap()`. The human-readable
4509+
// annotation helps developers identify the call site of `mmap()` that created
4510+
// the memory mapping.
4511+
//
4512+
// This function currently only works on Linux 5.17 or higher. After calling
4513+
// this function, we can see annotations in the form of "[anon:...]" in
4514+
// `/proc/self/maps`, where `...` is the content of `name`. This function has
4515+
// no effect when called on other platforms.
4516+
void
4517+
ruby_annotate_mmap(const void *addr, unsigned long size, const char *name)
4518+
{
4519+
#if defined(__linux__) && defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME)
4520+
// The name length cannot exceed 80 (including the '\0').
4521+
RUBY_ASSERT(strlen(name) < 80);
4522+
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name);
4523+
// We ignore errors in prctl. prctl may set errno to EINVAL for several
4524+
// reasons.
4525+
// 1. The attr (PR_SET_VMA_ANON_NAME) is not a valid attribute.
4526+
// 2. addr is an invalid address.
4527+
// 3. The string pointed by name is too long.
4528+
// The first error indicates PR_SET_VMA_ANON_NAME is not available, and may
4529+
// happen if we run the compiled binary on an old kernel. In theory, all
4530+
// other errors should result in a failure. But since EINVAL cannot tell
4531+
// the first error from others, and this function is mainly used for
4532+
// debugging, we silently ignore the error.
4533+
errno = 0;
4534+
#endif
4535+
}

gc/default.c

+16-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
#ifndef _WIN32
66
# include <sys/mman.h>
77
# include <unistd.h>
8+
# ifdef __linux__
9+
# include <linux/prctl.h>
10+
# include <sys/prctl.h>
11+
# endif
812
#endif
913

1014
#if !defined(PAGE_SIZE) && defined(HAVE_SYS_USER_H)
@@ -1859,12 +1863,23 @@ heap_page_body_allocate(void)
18591863
#ifdef HAVE_MMAP
18601864
GC_ASSERT(HEAP_PAGE_ALIGN % sysconf(_SC_PAGE_SIZE) == 0);
18611865

1862-
char *ptr = mmap(NULL, HEAP_PAGE_ALIGN + HEAP_PAGE_SIZE,
1866+
size_t mmap_size = HEAP_PAGE_ALIGN + HEAP_PAGE_SIZE;
1867+
char *ptr = mmap(NULL, mmap_size,
18631868
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
18641869
if (ptr == MAP_FAILED) {
18651870
return NULL;
18661871
}
18671872

1873+
// If we are building `default.c` as part of the ruby executable, we
1874+
// may just call `ruby_annotate_mmap`. But if we are building
1875+
// `default.c` as a shared library, we will not have access to private
1876+
// symbols, and we have to either call prctl directly or make our own
1877+
// wrapper.
1878+
#if defined(__linux__) && defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME)
1879+
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ptr, mmap_size, "Ruby:GC:default:heap_page_body_allocate");
1880+
errno = 0;
1881+
#endif
1882+
18681883
char *aligned = ptr + HEAP_PAGE_ALIGN;
18691884
aligned -= ((VALUE)aligned & (HEAP_PAGE_ALIGN - 1));
18701885
GC_ASSERT(aligned > ptr);

internal/gc.h

+1
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ RUBY_SYMBOL_EXPORT_END
260260
int rb_ec_stack_check(struct rb_execution_context_struct *ec);
261261
void rb_gc_writebarrier_remember(VALUE obj);
262262
const char *rb_obj_info(VALUE obj);
263+
void ruby_annotate_mmap(const void *addr, unsigned long size, const char *name);
263264

264265
#if defined(HAVE_MALLOC_USABLE_SIZE) || defined(HAVE_MALLOC_SIZE) || defined(_WIN32)
265266

io_buffer.c

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "internal/array.h"
1717
#include "internal/bits.h"
1818
#include "internal/error.h"
19+
#include "internal/gc.h"
1920
#include "internal/numeric.h"
2021
#include "internal/string.h"
2122
#include "internal/io.h"
@@ -83,6 +84,8 @@ io_buffer_map_memory(size_t size, int flags)
8384
if (base == MAP_FAILED) {
8485
rb_sys_fail("io_buffer_map_memory:mmap");
8586
}
87+
88+
ruby_annotate_mmap(base, size, "Ruby:io_buffer_map_memory");
8689
#endif
8790

8891
return base;

rjit_c.c

+5
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ rjit_reserve_addr_space(uint32_t mem_size)
8585

8686
// If we succeeded, stop
8787
if (mem_block != MAP_FAILED) {
88+
ruby_annotate_mmap(mem_block, mem_size, "Ruby:rjit_reserve_addr_space");
8889
break;
8990
}
9091

@@ -116,6 +117,10 @@ rjit_reserve_addr_space(uint32_t mem_size)
116117
-1,
117118
0
118119
);
120+
121+
if (mem_block != MAP_FAILED) {
122+
ruby_annotate_mmap(mem_block, mem_size, "Ruby:rjit_reserve_addr_space:fallback");
123+
}
119124
}
120125

121126
// Check that the memory mapping was successful

shape.c

+10-2
Original file line numberDiff line numberDiff line change
@@ -1232,11 +1232,15 @@ Init_default_shapes(void)
12321232
rb_shape_tree_ptr = xcalloc(1, sizeof(rb_shape_tree_t));
12331233

12341234
#ifdef HAVE_MMAP
1235-
rb_shape_tree_ptr->shape_list = (rb_shape_t *)mmap(NULL, rb_size_mul_or_raise(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t), rb_eRuntimeError),
1235+
size_t shape_list_mmap_size = rb_size_mul_or_raise(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t), rb_eRuntimeError);
1236+
rb_shape_tree_ptr->shape_list = (rb_shape_t *)mmap(NULL, shape_list_mmap_size,
12361237
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
12371238
if (GET_SHAPE_TREE()->shape_list == MAP_FAILED) {
12381239
GET_SHAPE_TREE()->shape_list = 0;
12391240
}
1241+
else {
1242+
ruby_annotate_mmap(rb_shape_tree_ptr->shape_list, shape_list_mmap_size, "Ruby:Init_default_shapes:shape_list");
1243+
}
12401244
#else
12411245
GET_SHAPE_TREE()->shape_list = xcalloc(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t));
12421246
#endif
@@ -1249,7 +1253,8 @@ Init_default_shapes(void)
12491253
id_t_object = rb_make_internal_id();
12501254

12511255
#ifdef HAVE_MMAP
1252-
rb_shape_tree_ptr->shape_cache = (redblack_node_t *)mmap(NULL, rb_size_mul_or_raise(REDBLACK_CACHE_SIZE, sizeof(redblack_node_t), rb_eRuntimeError),
1256+
size_t shape_cache_mmap_size = rb_size_mul_or_raise(REDBLACK_CACHE_SIZE, sizeof(redblack_node_t), rb_eRuntimeError);
1257+
rb_shape_tree_ptr->shape_cache = (redblack_node_t *)mmap(NULL, shape_cache_mmap_size,
12531258
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
12541259
rb_shape_tree_ptr->cache_size = 0;
12551260

@@ -1260,6 +1265,9 @@ Init_default_shapes(void)
12601265
GET_SHAPE_TREE()->shape_cache = 0;
12611266
GET_SHAPE_TREE()->cache_size = REDBLACK_CACHE_SIZE;
12621267
}
1268+
else {
1269+
ruby_annotate_mmap(rb_shape_tree_ptr->shape_cache, shape_cache_mmap_size, "Ruby:Init_default_shapes:shape_cache");
1270+
}
12631271
#endif
12641272

12651273
// Root shape

thread_pthread_mn.c

+2
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ nt_alloc_thread_stack_chunk(void)
194194
return NULL;
195195
}
196196

197+
ruby_annotate_mmap(m, MSTACK_CHUNK_SIZE, "Ruby:nt_alloc_thread_stack_chunk");
198+
197199
size_t msz = nt_thread_stack_size();
198200
int header_page_cnt = 1;
199201
int stack_count = ((MSTACK_CHUNK_PAGE_NUM - header_page_cnt) * MSTACK_PAGE_SIZE) / msz;

yjit.c

+5
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ rb_yjit_reserve_addr_space(uint32_t mem_size)
291291

292292
// If we succeeded, stop
293293
if (mem_block != MAP_FAILED) {
294+
ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_yjit_reserve_addr_space");
294295
break;
295296
}
296297

@@ -325,6 +326,10 @@ rb_yjit_reserve_addr_space(uint32_t mem_size)
325326
-1,
326327
0
327328
);
329+
330+
if (mem_block != MAP_FAILED) {
331+
ruby_annotate_mmap(mem_block, mem_size, "Ruby:rb_yjit_reserve_addr_space:fallback");
332+
}
328333
}
329334

330335
// Check that the memory mapping was successful

0 commit comments

Comments
 (0)