Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,19 @@ jobs:
bash tools/run-probe.sh examples/threads-detached threads-detached
grep -q 'detached: 8 started, 8 ended' examples/threads-detached/run.log

# ⭐ A LARGE ALLOCATION IS A MAPPING, AND A MAPPING IS WHOLE PAGES.
#
# musl's allocator uses a mapping up to the end of its last page; the port
# obtained only the length asked for, and on Windows the rest of that page
# was the next heap block. port/src/okm_syscall.c, `do_mmap`.
- name: A large allocation is a mapping and a mapping is whole pages
env:
MCPP_TARGET: ${{ matrix.target }}
run: |
bash tools/run-probe.sh examples/malloc-large malloc-large
grep -q 'growth: 24 blocks up to 8390650 bytes' examples/malloc-large/run.log
grep -q -- '-- failures: 0 --' examples/malloc-large/run.log

- name: A program may use the names the internal overlay defines
env:
MCPP_TARGET: ${{ matrix.target }}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ consumer needs to answer it without asking.

| this package | is carried by |
| --- | --- |
| 0.13.5 | `openkal-llvm-runtime = "0.9.6"` |
| 0.13.4 | `openkal-llvm-runtime = "0.9.5"` |
| 0.13.3 | `openkal-llvm-runtime = "0.9.4"` |
| 0.13.2 | `openkal-llvm-runtime = "0.9.3"` |
Expand Down
13 changes: 13 additions & 0 deletions examples/malloc-large/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "malloc-large"
version = "0.1.0"

[dependencies]
openkal-musl = { path = "../.." }

[targets.malloc-large]
kind = "bin"
main = "src/main.c"

[build]
cxx_runtime = "host-coupled"
126 changes: 126 additions & 0 deletions examples/malloc-large/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/* A large allocation is a mapping, and a mapping is whole pages.
*
* musl's allocator obtains an allocation of MMAP_THRESHOLD (131,052) bytes or
* more as a mapping of its own, and uses the mapping up to the end of its last
* page: the block may start up to a page into it, and the slot's footer sits
* just below that end. The port obtained exactly the length asked for, so on
* Windows, where that length comes from the process heap, the footer and up to
* a page of the block lay past the end of what was obtained --- over the next
* heap block's header. The program went on until the heap next walked there.
*
* ⭐ WHAT IS OBSERVED.
* (1) An anonymous mapping of a length that ends inside a page: the rest of
* that page reads as zero and can be written, and memory allocated beside
* it keeps its contents.
* (2) Blocks from half the threshold to eight megabytes, each grown from the
* one before the way a string grows, filled end to end, checked, and
* released while the next is held --- lengths on a page and inside one.
* (3) realloc across the threshold in both directions.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

enum { PAGE = 4096 };

static int failures;

static void fail(const char* what, size_t n)
{
printf("FAIL: %s (%zu)\n", what, n);
++failures;
}

static int filled_with(const unsigned char* p, size_t n, unsigned char value)
{
for (size_t i = 0; i < n; ++i)
if (p[i] != value) return 0;
return 1;
}

static void mapping_tail(void)
{
const size_t len = PAGE + 904; /* ends inside the second page */
unsigned char* neighbours[16];
for (int i = 0; i < 16; ++i) {
neighbours[i] = malloc(64);
if (neighbours[i]) memset(neighbours[i], 0x5a, 64);
}
unsigned char* m = mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
if (m == MAP_FAILED) { fail("mmap", len); return; }
for (int i = 0; i < 16; ++i) {
unsigned char* after = malloc(64);
if (after) { memset(after, 0x5a, 64); free(after); }
}
if (!filled_with(m, 2 * PAGE, 0)) fail("the last page of a mapping reads as zero", 2 * PAGE);
memset(m, 0xa5, 2 * PAGE);
for (int i = 0; i < 16; ++i)
if (neighbours[i] && !filled_with(neighbours[i], 64, 0x5a)) fail("a block allocated before the mapping kept its contents", 64);
if (munmap(m, len) != 0) fail("munmap", len);
for (int i = 0; i < 16; ++i) free(neighbours[i]);
printf("mapping: %zu bytes asked, %d readable and writable\n", len, 2 * PAGE);
}

static void growth(void)
{
unsigned char* held = 0;
size_t held_size = 0;
int blocks = 0;
for (size_t size = 65536; size <= (size_t)8 << 20; size *= 2) {
for (size_t inside = 0; inside < 3; ++inside) {
const size_t n = size + inside * 1021;
unsigned char* p = malloc(n);
if (!p) { fail("malloc", n); continue; }
const unsigned char value = (unsigned char)(n % 251);
if (held) {
if (!filled_with(held, held_size, (unsigned char)(held_size % 251))) fail("the held block kept its contents", held_size);
memcpy(p, held, held_size);
}
memset(p + held_size, value, n - held_size);
memset(p, value, held_size);
/* Small allocations walk the heap beside the large ones. */
for (int i = 0; i < 32; ++i) {
unsigned char* small = malloc(24 + i);
if (small) { memset(small, 0x33, 24 + i); free(small); }
}
if (!filled_with(p, n, value)) fail("a block kept its contents", n);
free(held);
held = p;
held_size = n;
++blocks;
}
}
free(held);
printf("growth: %d blocks up to %zu bytes\n", blocks, held_size);
}

static void reallocation(void)
{
size_t n = 1000;
unsigned char* p = malloc(n);
if (!p) { fail("malloc", n); return; }
memset(p, 0x11, n);
const size_t sizes[] = { 140000, 600000, 131052, 131051, 2000, 300000, 4096 * 64 + 1, 100 };
for (size_t i = 0; i < sizeof sizes / sizeof sizes[0]; ++i) {
const size_t m = sizes[i];
unsigned char* q = realloc(p, m);
if (!q) { fail("realloc", m); free(p); return; }
const size_t kept = n < m ? n : m;
if (!filled_with(q, kept, 0x11)) fail("realloc kept the contents", m);
memset(q, 0x11, m);
p = q;
n = m;
}
free(p);
printf("realloc: %zu sizes across the threshold\n", sizeof sizes / sizeof sizes[0]);
}

int main(void)
{
mapping_tail();
growth();
reallocation();
printf("-- failures: %d --\n", failures);
return failures == 0 ? 0 : 1;
}
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-musl"
version = "0.13.4"
version = "0.13.5"
description = "musl 1.2.5 redirected onto openkal: one C library, ported once, above every implementation of the specification rather than above one kernel."
license = "Apache-2.0"

Expand Down
27 changes: 26 additions & 1 deletion port/src/okm_syscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -511,12 +511,37 @@ static syscall_arg_t do_getdents(int fd, void* buf, size_t cap)

/* --- memory ---------------------------------------------------------------- */

/* ⚠️ A MAPPING IS WHOLE PAGES, AND THE CALLER USES ALL OF THEM.
*
* `mmap' maps every page the length touches, so the bytes from the length to
* the end of its last page are the caller's too, and they read as zero. musl's
* allocator counts on it: an allocation of MMAP_THRESHOLD bytes or more is a
* mapping of `n + IB + UNIT' bytes whose slot is `pages * 4096 - UNIT' long, and
* it places the block up to a page into that slot and writes the slot's footer
* just below the end of the last page. This layer used to obtain exactly the
* length asked for. 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 were
* written past the end of what was obtained, over the header of the heap's
* next block, and the program stopped at a later free or allocation with an
* access violation, or ended without a word.
*
* ⭐ Measured under Wine: a std::string grown by push_back past 196,607 bytes
* ended the program, and it passes with the length rounded up here and at
* SYS_munmap, whose length is the caller's as well. On windows-2022 the same
* fault stopped lsp-mcpp's conformance runner while it read a build tree. */
static size_t okm_whole_pages(size_t len)
{
return (len + OKM_PAGE - 1) & ~(size_t)(OKM_PAGE - 1);
}

static syscall_arg_t do_mmap(void* addr, size_t len, int prot, int flags, int fd, off_t off)
{
(void)prot;
if (addr != 0 || fd >= 0 || off != 0) return -ENOSYS;
if (!(flags & MAP_ANON) || !(flags & MAP_PRIVATE)) return -ENOSYS;
if (len == 0) return -EINVAL;
if (len > (size_t)-1 - (OKM_PAGE - 1)) return -ENOMEM;
len = okm_whole_pages(len);
void* p = kal_alloc(len, OKM_PAGE);
if (!p) return -ENOMEM;
/* An anonymous mapping reads as zero, and a caller relies on it: musl's
Expand Down Expand Up @@ -1695,7 +1720,7 @@ syscall_arg_t __okm_syscall(syscall_arg_t n, syscall_arg_t a1, syscall_arg_t a2,
/* --- memory ----------------------------------------------------------- */
case SYS_mmap: return do_mmap((void*)a1, (size_t)a2, (int)a3, (int)a4, (int)a5, (off_t)a6);
case SYS_munmap:
kal_free((void*)a1, (size_t)a2, OKM_PAGE);
kal_free((void*)a1, okm_whole_pages((size_t)a2), OKM_PAGE);
return 0;
case SYS_mprotect:
/* openkal has no operation upon the protection of a mapping. Reporting
Expand Down
Loading