diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 888b680..ad2a0e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -593,6 +593,19 @@ jobs: # compile. It is run as well as built so that something links afterwards, # which is what distinguishes a source that compiles from a package that # works. + # ⭐ A THREAD CREATED FROM C++ IS THE THREAD THAT IS JOINED. + # + # musl's C++ `pthread_t` was `unsigned long`, which is thirty-two bits on + # Windows, so every std::thread there faulted when joined. The probe's + # static_assert is the criterion and the run shows the value surviving a + # started context. musl/PATCHES.md, `include/alltypes.h.in`. + - name: A thread created from C++ is the thread that is joined + env: + MCPP_TARGET: ${{ matrix.target }} + run: | + bash tools/run-probe.sh examples/threads-cxx threads-cxx + grep -q 'value 42, result is the argument' examples/threads-cxx/run.log + - name: A program may use the names the internal overlay defines env: MCPP_TARGET: ${{ matrix.target }} diff --git a/README.md b/README.md index 28a950e..db50e65 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ musl reaches its kernel through seven inline functions declared once per architecture. Replacing that one header is the whole of the redirection; the 1345 sources that make up the library are compiled unmodified. -`musl/PATCHES.md` lists the whole of what is not unmodified: **four patched +`musl/PATCHES.md` lists the whole of what is not unmodified: **five patched lines**, all of one kind — a machine word carried through a variable declared `long`, which is not a machine word on one of the three targets — and **eleven replaced sources**. Five of the eleven are replaced for the same reason: each diff --git a/examples/threads-cxx/mcpp.toml b/examples/threads-cxx/mcpp.toml new file mode 100644 index 0000000..874e55b --- /dev/null +++ b/examples/threads-cxx/mcpp.toml @@ -0,0 +1,13 @@ +[package] +name = "threads-cxx" +version = "0.1.0" + +[dependencies] +openkal-musl = { path = "../.." } + +[targets.threads-cxx] +kind = "bin" +main = "src/main.cpp" + +[build] +cxx_runtime = "host-coupled" diff --git a/examples/threads-cxx/src/main.cpp b/examples/threads-cxx/src/main.cpp new file mode 100644 index 0000000..e8f86cf --- /dev/null +++ b/examples/threads-cxx/src/main.cpp @@ -0,0 +1,42 @@ +/* A thread created from C++ is the thread that is joined. + * + * musl declares `pthread_t' twice: a pointer for C, and `unsigned long' for C++. + * The two agree wherever a long holds a pointer, which is every architecture + * musl was written for. Windows is LLP64 --- a long is thirty-two bits --- so a + * C++ program kept the lower half of the thread's address, and pthread_join read + * through the truncated value. libc++'s std::thread stores exactly this type, + * so every std::thread on that system ended in an access violation when joined. + * + * ⭐ THE static_assert IS THE CRITERION, and it is a compile-time one: before the + * change this file does not compile for x86_64-windows-gnu. The run afterwards + * shows that the value survives the round trip through a started context. + */ +#include +#include + +static_assert(sizeof(pthread_t) >= sizeof(void*), "pthread_t must hold the address musl stores in it"); + +static void* work(void* arg) +{ + *static_cast(arg) = 42; + return arg; +} + +int main() +{ + int failures = 0; + int value = 0; + pthread_t thread; + if (pthread_create(&thread, nullptr, work, &value) != 0) { + puts("pthread_create failed"); + return 1; + } + void* result = nullptr; + const int joined = pthread_join(thread, &result); + printf("joined: %d, value %d, result %s\n", joined, value, result == &value ? "is the argument" : "is not the argument"); + if (joined != 0) ++failures; + if (value != 42) ++failures; + if (result != &value) ++failures; + printf("-- failures: %d --\n", failures); + return failures == 0 ? 0 : 1; +} diff --git a/mcpp.toml b/mcpp.toml index 8b69eac..19c362e 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-musl" -version = "0.13.1" +version = "0.13.2" 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" diff --git a/musl-generated/aarch64-macos/bits/alltypes.h b/musl-generated/aarch64-macos/bits/alltypes.h index 6b23e44..42c06d4 100644 --- a/musl-generated/aarch64-macos/bits/alltypes.h +++ b/musl-generated/aarch64-macos/bits/alltypes.h @@ -272,7 +272,7 @@ typedef unsigned useconds_t; #ifdef __cplusplus #if defined(__NEED_pthread_t) && !defined(__DEFINED_pthread_t) -typedef unsigned long pthread_t; +typedef unsigned _Addr pthread_t; #define __DEFINED_pthread_t #endif diff --git a/musl-generated/aarch64/bits/alltypes.h b/musl-generated/aarch64/bits/alltypes.h index 04d8c0b..59d3585 100644 --- a/musl-generated/aarch64/bits/alltypes.h +++ b/musl-generated/aarch64/bits/alltypes.h @@ -272,7 +272,7 @@ typedef unsigned useconds_t; #ifdef __cplusplus #if defined(__NEED_pthread_t) && !defined(__DEFINED_pthread_t) -typedef unsigned long pthread_t; +typedef unsigned _Addr pthread_t; #define __DEFINED_pthread_t #endif diff --git a/musl-generated/riscv64/bits/alltypes.h b/musl-generated/riscv64/bits/alltypes.h index 18a151d..e40b0d0 100644 --- a/musl-generated/riscv64/bits/alltypes.h +++ b/musl-generated/riscv64/bits/alltypes.h @@ -262,7 +262,7 @@ typedef unsigned useconds_t; #ifdef __cplusplus #if defined(__NEED_pthread_t) && !defined(__DEFINED_pthread_t) -typedef unsigned long pthread_t; +typedef unsigned _Addr pthread_t; #define __DEFINED_pthread_t #endif diff --git a/musl-generated/x86_64-windows/bits/alltypes.h b/musl-generated/x86_64-windows/bits/alltypes.h index b93c551..97e0b5d 100644 --- a/musl-generated/x86_64-windows/bits/alltypes.h +++ b/musl-generated/x86_64-windows/bits/alltypes.h @@ -264,7 +264,7 @@ typedef unsigned useconds_t; #ifdef __cplusplus #if defined(__NEED_pthread_t) && !defined(__DEFINED_pthread_t) -typedef unsigned long pthread_t; +typedef unsigned _Addr pthread_t; #define __DEFINED_pthread_t #endif diff --git a/musl-generated/x86_64/bits/alltypes.h b/musl-generated/x86_64/bits/alltypes.h index b5d6f52..a6343ca 100644 --- a/musl-generated/x86_64/bits/alltypes.h +++ b/musl-generated/x86_64/bits/alltypes.h @@ -264,7 +264,7 @@ typedef unsigned useconds_t; #ifdef __cplusplus #if defined(__NEED_pthread_t) && !defined(__DEFINED_pthread_t) -typedef unsigned long pthread_t; +typedef unsigned _Addr pthread_t; #define __DEFINED_pthread_t #endif diff --git a/musl/PATCHES.md b/musl/PATCHES.md index 6274f27..15c637f 100644 --- a/musl/PATCHES.md +++ b/musl/PATCHES.md @@ -1,6 +1,6 @@ # What was changed in musl, and why -musl 1.2.5 is vendored here unmodified except for the four lines below, and the +musl 1.2.5 is vendored here unmodified except for the five lines below, and the list is exhaustive: `git log -p -- musl/` shows every one of them. Each is recorded with the reason, because a port that silently edits the library it ports is a port nobody can check. @@ -31,6 +31,28 @@ offset beyond two gigabytes would be truncated, and both would happen silently. already widen it because their arguments are wider than a `long`. On every target musl supports, the changed line says what the original said. +## `include/alltypes.h.in`, one declaration + +```diff +-TYPEDEF unsigned long pthread_t; ++TYPEDEF unsigned _Addr pthread_t; +``` + +musl declares `pthread_t` twice: as a pointer for C, and as an integer for C++, +because C++ needs a type it can compare and hash. The integer was written +`unsigned long`, which holds a pointer on every architecture musl supports. + +Windows is LLP64, so a C++ program above this library kept the lower half of the +thread's address. `pthread_join` then read through the truncated value: libc++'s +`std::thread` stores exactly this type, and every `std::thread` on that system +ended in an access violation when it was joined (`0xC0000005`, measured on +windows-2022 and under Wine). `_Addr` is the type musl already uses for +`size_t`, `uintptr_t` and `ptrdiff_t`; it is `long` on every other target here, +so the changed line says what the original said there. + +The five generated `alltypes.h` headers carry the same one-line change. +`examples/threads-cxx` does not compile for `x86_64-windows-gnu` without it. + ## `src/stdio/vfwscanf.c`, `src/stdlib/wcstol.c`, `src/stdlib/wcstod.c` ```diff diff --git a/musl/include/alltypes.h.in b/musl/include/alltypes.h.in index d47aeea..29efc48 100644 --- a/musl/include/alltypes.h.in +++ b/musl/include/alltypes.h.in @@ -51,7 +51,7 @@ TYPEDEF int key_t; TYPEDEF unsigned useconds_t; #ifdef __cplusplus -TYPEDEF unsigned long pthread_t; +TYPEDEF unsigned _Addr pthread_t; #else TYPEDEF struct __pthread * pthread_t; #endif