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
4 changes: 2 additions & 2 deletions .github/workflows/compat-libc-shim.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ jobs:
build/compat_pthread_lifecycle_shim_integration_test build-fixtures/libc-ms/liba32android_pthread_lifecycle_consumer.so build-fixtures/libc-ms/libc.so |
tee build-fixtures/libc-ms-pthread-lifecycle-evidence.txt
grep -F 'fixture.pthread_lifecycle.object_count=2' build-fixtures/libc-ms-pthread-lifecycle-evidence.txt
grep -F 'fixture.pthread_lifecycle.required_jump_slots=32' build-fixtures/libc-ms-pthread-lifecycle-evidence.txt
grep -F 'fixture.pthread_lifecycle.wrapper_calls=39' build-fixtures/libc-ms-pthread-lifecycle-evidence.txt
grep -F 'fixture.pthread_lifecycle.required_jump_slots=35' build-fixtures/libc-ms-pthread-lifecycle-evidence.txt
grep -F 'fixture.pthread_lifecycle.wrapper_calls=42' build-fixtures/libc-ms-pthread-lifecycle-evidence.txt
grep -F 'fixture.pthread_lifecycle.thread_exits=2' build-fixtures/libc-ms-pthread-lifecycle-evidence.txt
grep -F 'fixture.pthread_lifecycle.status=PASS' build-fixtures/libc-ms-pthread-lifecycle-evidence.txt

Expand Down
18 changes: 18 additions & 0 deletions docs/architecture/a32-libc-memory-string-shim.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,21 @@ and exercises concurrent readers followed by a suspended/resumed writer.
The try-rwlock stubs are retained as bounded coherent companions required by
the accepted issue contract, but supplied-binary evidence is not claimed for
those two symbols.

## Pthread utility extension

The partial libc shim additionally exports three direct private-SVC wrappers:
`pthread_getschedparam`, `pthread_setschedparam`, and `pthread_setname_np`.
These are the only new utility symbols directly evidenced by the supplied
ARM32 libraries after scanning every `armeabi-v7a` object in the VLC APK.

The dedicated ARM32 pthread consumer now requires 35 eager JUMP_SLOT imports
and executes the three wrappers against bounded logical lifecycle state. The
scheduler surface is deliberately synthetic: SCHED_OTHER/0 is queryable and
settable, while broader host scheduler policy is not exposed. Thread names are
bounded logical metadata and are not forwarded to host pthreads.

The supplied `libemu32.so` is AArch64, so its semaphore and pthread-attribute
utility imports are not used to broaden this AArch32 shim. The original
45-wrapper base libc consumer remains backward compatible.

36 changes: 33 additions & 3 deletions docs/architecture/a32-pthread-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,40 @@ Timed rwlocks and rwlock attrs are not imported by the supplied ARM32 evidence
and remain outside this slice despite the clock seam already existing for
condition variables.

## Thread scheduling and naming utilities

A full scan of the supplied VLC APK's ARMv7 libraries finds three additional
pthread utility imports in `libvlc.so`: `pthread_getschedparam`,
`pthread_setschedparam`, and `pthread_setname_np`. They are implemented by
the existing bounded lifecycle service through private SVCs `0x122` through
`0x124`; they do not create or address host pthreads.

Each logical thread stores synthetic scheduler metadata. New logical threads
begin at `SCHED_OTHER` with priority zero. `pthread_getschedparam` reports that
logical state. `pthread_setschedparam` accepts the evidenced safe operation
`SCHED_OTHER/0`; a nonzero SCHED_OTHER priority returns EINVAL, while policy
changes outside the accepted logical surface return EPERM rather than mutating
an unrelated host scheduler. Unknown logical pthread identities return ESRCH.

`pthread_setname_np` stores a bounded logical name in the thread record. The
accepted Android/Bionic limit is 16 bytes including the terminating NUL, so a
name without a NUL in the first 16 guest bytes returns ERANGE. Guest-address
faults remain host-service failures rather than being converted into invented
pthread errors. The logical name is metadata only and is never forwarded to a
host thread.

The supplied `libemu32.so` is AArch64 rather than AArch32. Its semaphore and
attribute-scheduling imports therefore do not justify expanding this AArch32
compatibility surface. No supplied ARM32 artifact currently imports
`sem_trywait`, `sem_getvalue`, `sem_timedwait`, `pthread_mutex_timedlock`,
`pthread_attr_setschedparam`, or `pthread_getattr_np`.

## Scope limits

Process-shared synchronization, cond/rwlock attributes, mutex protocol/
pshared attributes, timed rwlocks, signals/futex internals, cancellation,
robust mutex recovery, priority inheritance/protection, and scheduler policy
remain outside this bounded synchronization/TLS service. Join/detach ownership and thread-exit destructor
iteration are handled by the separate pthread lifecycle service.
robust mutex recovery, and priority inheritance/protection remain outside this
bounded synchronization/TLS service. The lifecycle service exposes only the
accepted logical SCHED_OTHER/0 query/set behavior above; broader scheduling
policy remains outside the compatibility contract. Join/detach ownership and
thread-exit destructor iteration are also handled by that lifecycle service.
41 changes: 39 additions & 2 deletions docs/contracts/compat.md
Original file line number Diff line number Diff line change
Expand Up @@ -1960,8 +1960,8 @@ the relevant Bionic behavior rather than inventing per-reader identity.

The issue contract also accepts bounded pthread_rwlock_tryrdlock and
pthread_rwlock_trywrlock, although the supplied ARM32 artifacts do not directly
import those two functions. Timed rwlocks, rwlock attrs, process-shared locks,
priority policy, cancellation, and robust recovery remain out of scope.
import those two functions. Timed rwlocks, rwlock attrs, process-shared locks, cancellation, and robust
recovery remain out of scope for this synchronization slice.

The prepared partial libc shim now contains 74 libc-compatible exports: the
original 45 base wrappers, twelve lifecycle functions, six condition-variable
Expand All @@ -1970,3 +1970,40 @@ exports one internal once-completion trampoline. The dedicated ARM32 pthread
consumer requires 32 eager JUMP_SLOT imports and executes recursive typed mutex,
pthread_once, concurrent rwlock readers, blocked writer wake, and post-SVC
writer resume end to end.

## L32-C066 — ARM32 pthread scheduling/name utility subset

The supplied VLC Android APK's ARMv7 `libvlc.so` imports
`pthread_getschedparam`, `pthread_setschedparam`, and `pthread_setname_np`.
Private SVC IDs `0x122`, `0x123`, and `0x124` extend the existing bounded
pthread lifecycle service with exactly those calls.

The lifecycle service keeps scheduler/name state in finite logical-thread
metadata; it never exposes or mutates a host pthread. Every running logical
thread begins with synthetic `SCHED_OTHER` policy and priority zero.
`pthread_getschedparam` returns that logical state. The accepted
`pthread_setschedparam` operation is `SCHED_OTHER` with priority zero and
returns success. A nonzero priority under SCHED_OTHER returns EINVAL; policy
changes outside the accepted logical surface return EPERM. Unknown/non-running
pthread identities return ESRCH. Invalid guest sched-param/output memory is a
host-service failure and does not publish partial compatibility success.

`pthread_setname_np` stores at most 15 guest name bytes plus the terminating
NUL in the logical thread record. A name with no NUL in the first 16 bytes
returns ERANGE; an unknown/non-running pthread identity returns ESRCH. Invalid
or unreadable guest name memory is a host-service failure. The stored name is
logical metadata only and is not forwarded to a host thread.

The prepared partial libc shim now contains 77 libc-compatible public exports:
the prior 74-function surface plus these three pthread utilities. It still
exports the internal pthread_once completion trampoline separately. The
freestanding ARM32 pthread consumer now requires 35 eager JUMP_SLOT imports
and executes all three utility wrappers end to end in addition to the prior
lifecycle/condition/synchronization coverage.

The supplied `libemu32.so` is AArch64. Its `sem_trywait`, `sem_getvalue`, and
`pthread_attr_setschedparam` imports are not AArch32 evidence and do not expand
this contract. No supplied ARM32 artifact currently justifies
`sem_trywait`, `sem_getvalue`, `sem_timedwait`, `pthread_mutex_timedlock`,
`pthread_attr_setschedparam`, or `pthread_getattr_np`.

Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,15 @@ The VLC libraries were extracted from the supplied
- `0x025fbd88 pthread_attr_getstacksize@LIBC`
- `0x025fbd8c pthread_attr_setstacksize@LIBC`
- `0x025fbd90 pthread_self@LIBC`
- `0x025fbd94 pthread_setname_np@LIBC`
- `0x025fbec4 pthread_rwlock_init@LIBC`
- `0x025fbec8 pthread_rwlock_wrlock@LIBC`
- `0x025fbecc pthread_rwlock_unlock@LIBC`
- `0x025fbed0 pthread_rwlock_destroy@LIBC`
- `0x025fbedc pthread_setschedparam@LIBC`
- `0x025fbee0 pthread_cond_timedwait@LIBC`
- `0x025fbf34 pthread_rwlock_rdlock@LIBC`
- `0x025fbf48 pthread_getschedparam@LIBC`
- `0x025fbf4c pthread_equal@LIBC`

### VLC libvlcjni
Expand All @@ -129,6 +132,9 @@ The supplied ARM32 artifacts directly require:
- `pthread_exit`
- `pthread_join`
- `pthread_detach`
- `pthread_getschedparam`
- `pthread_setschedparam`
- `pthread_setname_np`
- `pthread_cond_init`
- `pthread_cond_destroy`
- `pthread_cond_wait`
Expand Down Expand Up @@ -160,16 +166,26 @@ The bounded compatibility surface additionally includes
`pthread_attr_getdetachstate` as the read side of the accepted detach-state
attribute pair. `pthread_join` is directly imported by multiple supplied VLC
ARMv7 libraries; `pthread_detach` is directly imported by the shipped ARMv7
`libc++_shared.so`. Unrelated scheduling attributes, explicit-stack attrs, cancellation, cond
attributes, rwlock attrs, timed rwlocks, and the try-rwlock entry points remain
outside the direct supplied-binary evidence. The accepted #42 implementation
still provides bounded tryrdlock/trywrlock as coherent nonblocking companions
covered by focused tests.

A supplied AArch64 `libemu32.so`
(`sha256:a467c34bc1543a2a193191ad42c4ac3a8e4a00181e83fa223abf7d42bc421119`)
also imports `pthread_attr_getdetachstate`, which is supporting evidence only;
the primary acceptance evidence for this slice remains the ARM32 FMOD/VLC set.
`libc++_shared.so`. A complete scan of every `lib/armeabi-v7a/*.so` in the supplied VLC APK
found the new utility imports only in ARM32 `libvlc.so`. That file has sha256
`f92266dbe28b4e4e2477225cf6bbb56291c2e1ca7a72a4573013ebf9d52517d4`.
The supplied APK has sha256
`10da537a545d5c9aa111571bbab3d1aae4204d2c4a0d4a4cdc22efab6d71cbe5`.

Explicit-stack attrs, cancellation, cond attributes, rwlock attrs, timed
rwlocks, and pthread attribute scheduling functions remain outside the direct
supplied ARM32 evidence. The accepted #42 implementation still provides
bounded tryrdlock/trywrlock as coherent nonblocking companions covered by
focused tests.

The separately supplied `libemu32.so` is AArch64, not AArch32
(`sha256:a467c34bc1543a2a193191ad42c4ac3a8e4a00181e83fa223abf7d42bc421119`).
Its imports include `sem_trywait`, `sem_getvalue`,
`pthread_attr_getdetachstate`, `pthread_attr_setschedparam`, and
`pthread_setname_np`; those are architecture-specific supporting observations
only and do not justify new AArch32 exports. The supplied ARM32 FMOD library
(`sha256:982e994c46a7f797fbd6e10df31a98d544c2bf117fe33c2292f4d6cc454a6544`)
adds no new utility imports beyond the already accepted lifecycle/mutex set.

Static imports prove required symbol resolution and ABI reachability. The
repository's generated ARM32 lifecycle fixture separately exercises blocked
Expand Down
100 changes: 100 additions & 0 deletions src/compat/a32_pthread_lifecycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <algorithm>
#include <array>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <limits>
Expand All @@ -28,6 +29,9 @@ using runtime::A32HostServiceDisposition;
case kA32PthreadExitSvcImmediate:
case kA32PthreadJoinSvcImmediate:
case kA32PthreadDetachSvcImmediate:
case kA32PthreadGetschedparamSvcImmediate:
case kA32PthreadSetschedparamSvcImmediate:
case kA32PthreadSetnameNpSvcImmediate:
return true;
default:
return false;
Expand All @@ -47,6 +51,19 @@ using runtime::A32HostServiceDisposition;
return memory.write(address, bytes);
}

[[nodiscard]] bool read_u32_le(
const memory::GuestMemory& memory,
std::uint32_t address,
std::uint32_t& value) {
std::array<std::uint8_t, 4> bytes{};
if (!memory.read(address, bytes)) return false;
value = static_cast<std::uint32_t>(bytes[0]) |
(static_cast<std::uint32_t>(bytes[1]) << 8U) |
(static_cast<std::uint32_t>(bytes[2]) << 16U) |
(static_cast<std::uint32_t>(bytes[3]) << 24U);
return true;
}

[[nodiscard]] bool readable_u32(
const memory::GuestMemory& memory,
std::uint32_t address) {
Expand Down Expand Up @@ -635,6 +652,89 @@ runtime::A32HostServiceDisposition A32PthreadLifecycleService::handle(
return A32HostServiceDisposition::Failed;
}

if (svc_immediate == kA32PthreadGetschedparamSvcImmediate ||
svc_immediate == kA32PthreadSetschedparamSvcImmediate ||
svc_immediate == kA32PthreadSetnameNpSvcImmediate) {
const std::size_t target = find_thread(regs[0]);
if (target >= threads_.size() ||
threads_[target].phase != A32PthreadThreadPhase::Running) {
regs[0] = static_cast<std::uint32_t>(kA32AndroidEsrch);
return A32HostServiceDisposition::Handled;
}
auto& target_thread = threads_[target];

if (svc_immediate == kA32PthreadGetschedparamSvcImmediate) {
if (regs[1] == 0U || regs[2] == 0U ||
!write_u32_le(
memory,
regs[1],
std::bit_cast<std::uint32_t>(target_thread.sched_policy)) ||
!write_u32_le(
memory,
regs[2],
std::bit_cast<std::uint32_t>(target_thread.sched_priority))) {
return A32HostServiceDisposition::Failed;
}
regs[0] = 0U;
return A32HostServiceDisposition::Handled;
}

if (svc_immediate == kA32PthreadSetschedparamSvcImmediate) {
std::uint32_t priority_word{};
if (regs[2] == 0U ||
!read_u32_le(memory, regs[2], priority_word)) {
return A32HostServiceDisposition::Failed;
}
const std::int32_t policy = std::bit_cast<std::int32_t>(regs[1]);
const std::int32_t priority =
std::bit_cast<std::int32_t>(priority_word);
if (policy == kA32SchedOther) {
if (priority != 0) {
regs[0] = static_cast<std::uint32_t>(kA32AndroidEinval);
return A32HostServiceDisposition::Handled;
}
target_thread.sched_policy = policy;
target_thread.sched_priority = priority;
regs[0] = 0U;
return A32HostServiceDisposition::Handled;
}
// The cooperative runtime has no host scheduler identity. Keep the
// accepted surface at SCHED_OTHER/0 rather than faking policy
// changes against an unrelated host thread.
regs[0] = static_cast<std::uint32_t>(kA32AndroidEperm);
return A32HostServiceDisposition::Handled;
}

if (regs[1] == 0U) {
return A32HostServiceDisposition::Failed;
}
std::array<std::uint8_t, kA32PthreadNameBytes> name{};
bool terminated = false;
for (std::size_t index = 0; index < name.size(); ++index) {
if (regs[1] >
std::numeric_limits<std::uint32_t>::max() - index) {
return A32HostServiceDisposition::Failed;
}
std::array<std::uint8_t, 1> byte{};
if (!memory.read(
regs[1] + static_cast<std::uint32_t>(index), byte)) {
return A32HostServiceDisposition::Failed;
}
name[index] = byte[0];
if (byte[0] == 0U) {
terminated = true;
break;
}
}
if (!terminated) {
regs[0] = static_cast<std::uint32_t>(kA32AndroidErange);
return A32HostServiceDisposition::Handled;
}
target_thread.name = name;
regs[0] = 0U;
return A32HostServiceDisposition::Handled;
}

if (svc_immediate == kA32PthreadSelfSvcImmediate) {
regs[0] = current_thread_id_.value();
return A32HostServiceDisposition::Handled;
Expand Down
14 changes: 14 additions & 0 deletions src/compat/a32_pthread_lifecycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
#define LIBA32ANDROID_A32_PTHREAD_EXIT_SVC 0x10D
#define LIBA32ANDROID_A32_PTHREAD_JOIN_SVC 0x10E
#define LIBA32ANDROID_A32_PTHREAD_DETACH_SVC 0x10F
#define LIBA32ANDROID_A32_PTHREAD_GETSCHEDPARAM_SVC 0x122
#define LIBA32ANDROID_A32_PTHREAD_SETSCHEDPARAM_SVC 0x123
#define LIBA32ANDROID_A32_PTHREAD_SETNAME_NP_SVC 0x124

#ifdef __cplusplus

Expand Down Expand Up @@ -52,11 +55,19 @@ inline constexpr std::uint32_t kA32PthreadJoinSvcImmediate =
LIBA32ANDROID_A32_PTHREAD_JOIN_SVC;
inline constexpr std::uint32_t kA32PthreadDetachSvcImmediate =
LIBA32ANDROID_A32_PTHREAD_DETACH_SVC;
inline constexpr std::uint32_t kA32PthreadGetschedparamSvcImmediate =
LIBA32ANDROID_A32_PTHREAD_GETSCHEDPARAM_SVC;
inline constexpr std::uint32_t kA32PthreadSetschedparamSvcImmediate =
LIBA32ANDROID_A32_PTHREAD_SETSCHEDPARAM_SVC;
inline constexpr std::uint32_t kA32PthreadSetnameNpSvcImmediate =
LIBA32ANDROID_A32_PTHREAD_SETNAME_NP_SVC;

inline constexpr std::uint32_t kA32PthreadCreateJoinable = 0U;
inline constexpr std::uint32_t kA32PthreadCreateDetached = 1U;
inline constexpr std::uint32_t kA32PthreadDefaultStackSize = 1024U * 1024U;
inline constexpr std::uint32_t kA32PthreadDestructorIterations = 4U;
inline constexpr std::int32_t kA32SchedOther = 0;
inline constexpr std::size_t kA32PthreadNameBytes = 16U;

struct A32PthreadAttrState {
std::uint32_t address{};
Expand All @@ -82,6 +93,9 @@ struct A32PthreadThreadState {
std::uint32_t return_value{};
std::uint32_t joiner_thread_id{};
std::uint32_t join_result_address{};
std::int32_t sched_policy{kA32SchedOther};
std::int32_t sched_priority{};
std::array<std::uint8_t, kA32PthreadNameBytes> name{};
bool detached{};
bool owns_stack{};
bool initial_thread{};
Expand Down
Loading
Loading