Skip to content

Commit aa9419c

Browse files
authored
0.13.5 --- a large allocation is a mapping, and a mapping is whole pages (#34)
musl's allocator obtains an allocation of MMAP_THRESHOLD bytes or more as a mapping of n + IB + UNIT bytes and uses it up to the end of its last page: the block starts up to a page into the slot and the slot's footer is written just below the page's end. The port's mmap obtained exactly the length asked for, so where kal_alloc hands out that length and no more (the process heap on Windows) the footer and up to a page of the block lay over the next heap block's header. A std::string grown past 196,607 bytes ended the program under Wine; on windows-2022 lsp-mcpp's conformance runner stopped with an access violation reading a build tree. SYS_mmap and SYS_munmap round the length up to whole pages. examples/malloc-large observes the tail of a mapping, blocks grown from half the threshold to eight megabytes, and realloc across the threshold; CI runs it on every row.
1 parent e424c90 commit aa9419c

6 files changed

Lines changed: 180 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,19 @@ jobs:
619619
bash tools/run-probe.sh examples/threads-detached threads-detached
620620
grep -q 'detached: 8 started, 8 ended' examples/threads-detached/run.log
621621
622+
# ⭐ A LARGE ALLOCATION IS A MAPPING, AND A MAPPING IS WHOLE PAGES.
623+
#
624+
# musl's allocator uses a mapping up to the end of its last page; the port
625+
# obtained only the length asked for, and on Windows the rest of that page
626+
# was the next heap block. port/src/okm_syscall.c, `do_mmap`.
627+
- name: A large allocation is a mapping and a mapping is whole pages
628+
env:
629+
MCPP_TARGET: ${{ matrix.target }}
630+
run: |
631+
bash tools/run-probe.sh examples/malloc-large malloc-large
632+
grep -q 'growth: 24 blocks up to 8390650 bytes' examples/malloc-large/run.log
633+
grep -q -- '-- failures: 0 --' examples/malloc-large/run.log
634+
622635
- name: A program may use the names the internal overlay defines
623636
env:
624637
MCPP_TARGET: ${{ matrix.target }}

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ consumer needs to answer it without asking.
4141

4242
| this package | is carried by |
4343
| --- | --- |
44+
| 0.13.5 | `openkal-llvm-runtime = "0.9.6"` |
4445
| 0.13.4 | `openkal-llvm-runtime = "0.9.5"` |
4546
| 0.13.3 | `openkal-llvm-runtime = "0.9.4"` |
4647
| 0.13.2 | `openkal-llvm-runtime = "0.9.3"` |

examples/malloc-large/mcpp.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "malloc-large"
3+
version = "0.1.0"
4+
5+
[dependencies]
6+
openkal-musl = { path = "../.." }
7+
8+
[targets.malloc-large]
9+
kind = "bin"
10+
main = "src/main.c"
11+
12+
[build]
13+
cxx_runtime = "host-coupled"

examples/malloc-large/src/main.c

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/* A large allocation is a mapping, and a mapping is whole pages.
2+
*
3+
* musl's allocator obtains an allocation of MMAP_THRESHOLD (131,052) bytes or
4+
* more as a mapping of its own, and uses the mapping up to the end of its last
5+
* page: the block may start up to a page into it, and the slot's footer sits
6+
* just below that end. The port obtained exactly the length asked for, so on
7+
* Windows, where that length comes from the process heap, the footer and up to
8+
* a page of the block lay past the end of what was obtained --- over the next
9+
* heap block's header. The program went on until the heap next walked there.
10+
*
11+
* ⭐ WHAT IS OBSERVED.
12+
* (1) An anonymous mapping of a length that ends inside a page: the rest of
13+
* that page reads as zero and can be written, and memory allocated beside
14+
* it keeps its contents.
15+
* (2) Blocks from half the threshold to eight megabytes, each grown from the
16+
* one before the way a string grows, filled end to end, checked, and
17+
* released while the next is held --- lengths on a page and inside one.
18+
* (3) realloc across the threshold in both directions.
19+
*/
20+
#include <stdio.h>
21+
#include <stdlib.h>
22+
#include <string.h>
23+
#include <sys/mman.h>
24+
25+
enum { PAGE = 4096 };
26+
27+
static int failures;
28+
29+
static void fail(const char* what, size_t n)
30+
{
31+
printf("FAIL: %s (%zu)\n", what, n);
32+
++failures;
33+
}
34+
35+
static int filled_with(const unsigned char* p, size_t n, unsigned char value)
36+
{
37+
for (size_t i = 0; i < n; ++i)
38+
if (p[i] != value) return 0;
39+
return 1;
40+
}
41+
42+
static void mapping_tail(void)
43+
{
44+
const size_t len = PAGE + 904; /* ends inside the second page */
45+
unsigned char* neighbours[16];
46+
for (int i = 0; i < 16; ++i) {
47+
neighbours[i] = malloc(64);
48+
if (neighbours[i]) memset(neighbours[i], 0x5a, 64);
49+
}
50+
unsigned char* m = mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
51+
if (m == MAP_FAILED) { fail("mmap", len); return; }
52+
for (int i = 0; i < 16; ++i) {
53+
unsigned char* after = malloc(64);
54+
if (after) { memset(after, 0x5a, 64); free(after); }
55+
}
56+
if (!filled_with(m, 2 * PAGE, 0)) fail("the last page of a mapping reads as zero", 2 * PAGE);
57+
memset(m, 0xa5, 2 * PAGE);
58+
for (int i = 0; i < 16; ++i)
59+
if (neighbours[i] && !filled_with(neighbours[i], 64, 0x5a)) fail("a block allocated before the mapping kept its contents", 64);
60+
if (munmap(m, len) != 0) fail("munmap", len);
61+
for (int i = 0; i < 16; ++i) free(neighbours[i]);
62+
printf("mapping: %zu bytes asked, %d readable and writable\n", len, 2 * PAGE);
63+
}
64+
65+
static void growth(void)
66+
{
67+
unsigned char* held = 0;
68+
size_t held_size = 0;
69+
int blocks = 0;
70+
for (size_t size = 65536; size <= (size_t)8 << 20; size *= 2) {
71+
for (size_t inside = 0; inside < 3; ++inside) {
72+
const size_t n = size + inside * 1021;
73+
unsigned char* p = malloc(n);
74+
if (!p) { fail("malloc", n); continue; }
75+
const unsigned char value = (unsigned char)(n % 251);
76+
if (held) {
77+
if (!filled_with(held, held_size, (unsigned char)(held_size % 251))) fail("the held block kept its contents", held_size);
78+
memcpy(p, held, held_size);
79+
}
80+
memset(p + held_size, value, n - held_size);
81+
memset(p, value, held_size);
82+
/* Small allocations walk the heap beside the large ones. */
83+
for (int i = 0; i < 32; ++i) {
84+
unsigned char* small = malloc(24 + i);
85+
if (small) { memset(small, 0x33, 24 + i); free(small); }
86+
}
87+
if (!filled_with(p, n, value)) fail("a block kept its contents", n);
88+
free(held);
89+
held = p;
90+
held_size = n;
91+
++blocks;
92+
}
93+
}
94+
free(held);
95+
printf("growth: %d blocks up to %zu bytes\n", blocks, held_size);
96+
}
97+
98+
static void reallocation(void)
99+
{
100+
size_t n = 1000;
101+
unsigned char* p = malloc(n);
102+
if (!p) { fail("malloc", n); return; }
103+
memset(p, 0x11, n);
104+
const size_t sizes[] = { 140000, 600000, 131052, 131051, 2000, 300000, 4096 * 64 + 1, 100 };
105+
for (size_t i = 0; i < sizeof sizes / sizeof sizes[0]; ++i) {
106+
const size_t m = sizes[i];
107+
unsigned char* q = realloc(p, m);
108+
if (!q) { fail("realloc", m); free(p); return; }
109+
const size_t kept = n < m ? n : m;
110+
if (!filled_with(q, kept, 0x11)) fail("realloc kept the contents", m);
111+
memset(q, 0x11, m);
112+
p = q;
113+
n = m;
114+
}
115+
free(p);
116+
printf("realloc: %zu sizes across the threshold\n", sizeof sizes / sizeof sizes[0]);
117+
}
118+
119+
int main(void)
120+
{
121+
mapping_tail();
122+
growth();
123+
reallocation();
124+
printf("-- failures: %d --\n", failures);
125+
return failures == 0 ? 0 : 1;
126+
}

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-musl"
4-
version = "0.13.4"
4+
version = "0.13.5"
55
description = "musl 1.2.5 redirected onto openkal: one C library, ported once, above every implementation of the specification rather than above one kernel."
66
license = "Apache-2.0"
77

port/src/okm_syscall.c

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,12 +511,37 @@ static syscall_arg_t do_getdents(int fd, void* buf, size_t cap)
511511

512512
/* --- memory ---------------------------------------------------------------- */
513513

514+
/* ⚠️ A MAPPING IS WHOLE PAGES, AND THE CALLER USES ALL OF THEM.
515+
*
516+
* `mmap' maps every page the length touches, so the bytes from the length to
517+
* the end of its last page are the caller's too, and they read as zero. musl's
518+
* allocator counts on it: an allocation of MMAP_THRESHOLD bytes or more is a
519+
* mapping of `n + IB + UNIT' bytes whose slot is `pages * 4096 - UNIT' long, and
520+
* it places the block up to a page into that slot and writes the slot's footer
521+
* just below the end of the last page. This layer used to obtain exactly the
522+
* length asked for. Where kal_alloc hands out that length and no more --- the
523+
* process heap on Windows --- the footer and up to a page of the block were
524+
* written past the end of what was obtained, over the header of the heap's
525+
* next block, and the program stopped at a later free or allocation with an
526+
* access violation, or ended without a word.
527+
*
528+
* ⭐ Measured under Wine: a std::string grown by push_back past 196,607 bytes
529+
* ended the program, and it passes with the length rounded up here and at
530+
* SYS_munmap, whose length is the caller's as well. On windows-2022 the same
531+
* fault stopped lsp-mcpp's conformance runner while it read a build tree. */
532+
static size_t okm_whole_pages(size_t len)
533+
{
534+
return (len + OKM_PAGE - 1) & ~(size_t)(OKM_PAGE - 1);
535+
}
536+
514537
static syscall_arg_t do_mmap(void* addr, size_t len, int prot, int flags, int fd, off_t off)
515538
{
516539
(void)prot;
517540
if (addr != 0 || fd >= 0 || off != 0) return -ENOSYS;
518541
if (!(flags & MAP_ANON) || !(flags & MAP_PRIVATE)) return -ENOSYS;
519542
if (len == 0) return -EINVAL;
543+
if (len > (size_t)-1 - (OKM_PAGE - 1)) return -ENOMEM;
544+
len = okm_whole_pages(len);
520545
void* p = kal_alloc(len, OKM_PAGE);
521546
if (!p) return -ENOMEM;
522547
/* An anonymous mapping reads as zero, and a caller relies on it: musl's
@@ -1695,7 +1720,7 @@ syscall_arg_t __okm_syscall(syscall_arg_t n, syscall_arg_t a1, syscall_arg_t a2,
16951720
/* --- memory ----------------------------------------------------------- */
16961721
case SYS_mmap: return do_mmap((void*)a1, (size_t)a2, (int)a3, (int)a4, (int)a5, (off_t)a6);
16971722
case SYS_munmap:
1698-
kal_free((void*)a1, (size_t)a2, OKM_PAGE);
1723+
kal_free((void*)a1, okm_whole_pages((size_t)a2), OKM_PAGE);
16991724
return 0;
17001725
case SYS_mprotect:
17011726
/* openkal has no operation upon the protection of a mapping. Reporting

0 commit comments

Comments
 (0)