From cff741b6c7c115ac52e5d6f475e0b42858c2931b Mon Sep 17 00:00:00 2001 From: Kayllon Douglas <76278156+millesant@users.noreply.github.com> Date: Fri, 2 Oct 2026 07:06:46 -0300 Subject: [PATCH] Add evidence-backed pthread utility helpers --- .github/workflows/compat-libc-shim.yml | 4 +- .../a32-libc-memory-string-shim.md | 18 +++ docs/architecture/a32-pthread-sync.md | 36 ++++- docs/contracts/compat.md | 41 +++++- ...32-pthread-lifecycle-imports-2026-10-01.md | 36 +++-- src/compat/a32_pthread_lifecycle.cpp | 100 +++++++++++++ src/compat/a32_pthread_lifecycle.h | 14 ++ tests/compat/a32_pthread_lifecycle.cpp | 134 ++++++++++++++++++ tests/compat/a32_pthread_lifecycle_shim.cpp | 77 +++++++++- .../fixtures/a32_libc_memory_string_shim.S | 3 + .../fixtures/a32_pthread_lifecycle_consumer.c | 33 +++++ 11 files changed, 477 insertions(+), 19 deletions(-) diff --git a/.github/workflows/compat-libc-shim.yml b/.github/workflows/compat-libc-shim.yml index 38f26b2..40e0e47 100644 --- a/.github/workflows/compat-libc-shim.yml +++ b/.github/workflows/compat-libc-shim.yml @@ -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 diff --git a/docs/architecture/a32-libc-memory-string-shim.md b/docs/architecture/a32-libc-memory-string-shim.md index 708f505..02b89b1 100644 --- a/docs/architecture/a32-libc-memory-string-shim.md +++ b/docs/architecture/a32-libc-memory-string-shim.md @@ -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. + diff --git a/docs/architecture/a32-pthread-sync.md b/docs/architecture/a32-pthread-sync.md index 490dd05..cae6acf 100644 --- a/docs/architecture/a32-pthread-sync.md +++ b/docs/architecture/a32-pthread-sync.md @@ -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. diff --git a/docs/contracts/compat.md b/docs/contracts/compat.md index 03157e1..7945e79 100644 --- a/docs/contracts/compat.md +++ b/docs/contracts/compat.md @@ -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 @@ -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`. + diff --git a/docs/research/evidence/arm32-pthread-lifecycle-imports-2026-10-01.md b/docs/research/evidence/arm32-pthread-lifecycle-imports-2026-10-01.md index 266e0f9..eef4267 100644 --- a/docs/research/evidence/arm32-pthread-lifecycle-imports-2026-10-01.md +++ b/docs/research/evidence/arm32-pthread-lifecycle-imports-2026-10-01.md @@ -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 @@ -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` @@ -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 diff --git a/src/compat/a32_pthread_lifecycle.cpp b/src/compat/a32_pthread_lifecycle.cpp index 7517a2c..59a02f8 100644 --- a/src/compat/a32_pthread_lifecycle.cpp +++ b/src/compat/a32_pthread_lifecycle.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,9 @@ using runtime::A32HostServiceDisposition; case kA32PthreadExitSvcImmediate: case kA32PthreadJoinSvcImmediate: case kA32PthreadDetachSvcImmediate: + case kA32PthreadGetschedparamSvcImmediate: + case kA32PthreadSetschedparamSvcImmediate: + case kA32PthreadSetnameNpSvcImmediate: return true; default: return false; @@ -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 bytes{}; + if (!memory.read(address, bytes)) return false; + value = static_cast(bytes[0]) | + (static_cast(bytes[1]) << 8U) | + (static_cast(bytes[2]) << 16U) | + (static_cast(bytes[3]) << 24U); + return true; +} + [[nodiscard]] bool readable_u32( const memory::GuestMemory& memory, std::uint32_t address) { @@ -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(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(target_thread.sched_policy)) || + !write_u32_le( + memory, + regs[2], + std::bit_cast(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(regs[1]); + const std::int32_t priority = + std::bit_cast(priority_word); + if (policy == kA32SchedOther) { + if (priority != 0) { + regs[0] = static_cast(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(kA32AndroidEperm); + return A32HostServiceDisposition::Handled; + } + + if (regs[1] == 0U) { + return A32HostServiceDisposition::Failed; + } + std::array name{}; + bool terminated = false; + for (std::size_t index = 0; index < name.size(); ++index) { + if (regs[1] > + std::numeric_limits::max() - index) { + return A32HostServiceDisposition::Failed; + } + std::array byte{}; + if (!memory.read( + regs[1] + static_cast(index), byte)) { + return A32HostServiceDisposition::Failed; + } + name[index] = byte[0]; + if (byte[0] == 0U) { + terminated = true; + break; + } + } + if (!terminated) { + regs[0] = static_cast(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; diff --git a/src/compat/a32_pthread_lifecycle.h b/src/compat/a32_pthread_lifecycle.h index 170d684..d90a2f1 100644 --- a/src/compat/a32_pthread_lifecycle.h +++ b/src/compat/a32_pthread_lifecycle.h @@ -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 @@ -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{}; @@ -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 name{}; bool detached{}; bool owns_stack{}; bool initial_thread{}; diff --git a/tests/compat/a32_pthread_lifecycle.cpp b/tests/compat/a32_pthread_lifecycle.cpp index 75538f5..3ccc13c 100644 --- a/tests/compat/a32_pthread_lifecycle.cpp +++ b/tests/compat/a32_pthread_lifecycle.cpp @@ -17,6 +17,9 @@ using liba32android::compat::A32PthreadThreadPhase; using liba32android::compat::A32PthreadThreadState; using liba32android::compat::kA32AndroidEagain; using liba32android::compat::kA32AndroidEinval; +using liba32android::compat::kA32AndroidEperm; +using liba32android::compat::kA32AndroidErange; +using liba32android::compat::kA32AndroidEsrch; using liba32android::compat::kA32PthreadAttrDestroySvcImmediate; using liba32android::compat::kA32PthreadAttrGetdetachstateSvcImmediate; using liba32android::compat::kA32PthreadAttrGetstacksizeSvcImmediate; @@ -28,7 +31,11 @@ using liba32android::compat::kA32PthreadCreateJoinable; using liba32android::compat::kA32PthreadCreateSvcImmediate; using liba32android::compat::kA32PthreadEqualSvcImmediate; using liba32android::compat::kA32PthreadExitSvcImmediate; +using liba32android::compat::kA32PthreadGetschedparamSvcImmediate; +using liba32android::compat::kA32PthreadSetschedparamSvcImmediate; +using liba32android::compat::kA32PthreadSetnameNpSvcImmediate; using liba32android::compat::kA32PthreadSelfSvcImmediate; +using liba32android::compat::kA32SchedOther; using liba32android::memory::LinearGuestMemory; using liba32android::runtime::A32HostServiceDisposition; using liba32android::runtime::A32LogicalThreadId; @@ -48,6 +55,19 @@ std::int32_t signed_r0(std::uint32_t value) { return std::bit_cast(value); } +bool write_u32( + LinearGuestMemory& memory, + std::uint32_t address, + std::uint32_t value) { + const std::array bytes{{ + static_cast(value), + static_cast(value >> 8U), + static_cast(value >> 16U), + static_cast(value >> 24U), + }}; + return memory.write(address, bytes); +} + bool read_u32( const LinearGuestMemory& memory, std::uint32_t address, @@ -216,6 +236,117 @@ int test_identity_and_attrs() { return 0; } +int test_scheduler_and_name_helpers() { + Fixture fixture; + constexpr std::uint32_t kPolicyOut = 0x520U; + constexpr std::uint32_t kSchedParam = 0x524U; + constexpr std::uint32_t kName = 0x540U; + constexpr std::uint32_t kLongName = 0x560U; + std::array regs{}; + std::uint32_t value{}; + + if (!write_u32(fixture.memory, kSchedParam, 0U)) { + return fail("could not stage sched_param"); + } + regs[0] = 1U; + regs[1] = kPolicyOut; + regs[2] = kSchedParam; + if (call(fixture, kA32PthreadGetschedparamSvcImmediate, regs) != + A32HostServiceDisposition::Handled || + regs[0] != 0U || + !read_u32(fixture.memory, kPolicyOut, value) || + std::bit_cast(value) != kA32SchedOther || + !read_u32(fixture.memory, kSchedParam, value) || + std::bit_cast(value) != 0) { + return fail("pthread_getschedparam did not return logical SCHED_OTHER/0"); + } + + regs = {}; + regs[0] = 1U; + regs[1] = std::bit_cast(kA32SchedOther); + regs[2] = kSchedParam; + if (call(fixture, kA32PthreadSetschedparamSvcImmediate, regs) != + A32HostServiceDisposition::Handled || + regs[0] != 0U) { + return fail("pthread_setschedparam rejected logical SCHED_OTHER/0"); + } + + if (!write_u32(fixture.memory, kSchedParam, 1U)) { + return fail("could not stage invalid SCHED_OTHER priority"); + } + regs = {}; + regs[0] = 1U; + regs[1] = std::bit_cast(kA32SchedOther); + regs[2] = kSchedParam; + if (call(fixture, kA32PthreadSetschedparamSvcImmediate, regs) != + A32HostServiceDisposition::Handled || + signed_r0(regs[0]) != kA32AndroidEinval) { + return fail("pthread_setschedparam accepted nonzero SCHED_OTHER priority"); + } + + regs = {}; + regs[0] = 1U; + regs[1] = 1U; + regs[2] = kSchedParam; + if (call(fixture, kA32PthreadSetschedparamSvcImmediate, regs) != + A32HostServiceDisposition::Handled || + signed_r0(regs[0]) != kA32AndroidEperm) { + return fail("pthread_setschedparam faked unsupported policy success"); + } + + regs = {}; + regs[0] = 99U; + regs[1] = kPolicyOut; + regs[2] = kSchedParam; + if (call(fixture, kA32PthreadGetschedparamSvcImmediate, regs) != + A32HostServiceDisposition::Handled || + signed_r0(regs[0]) != kA32AndroidEsrch) { + return fail("pthread_getschedparam did not reject unknown pthread_t"); + } + + constexpr std::array name{{'m','a','i','n',0}}; + if (!fixture.memory.write(kName, name)) { + return fail("could not stage pthread name"); + } + regs = {}; + regs[0] = 1U; + regs[1] = kName; + if (call(fixture, kA32PthreadSetnameNpSvcImmediate, regs) != + A32HostServiceDisposition::Handled || + regs[0] != 0U || + fixture.threads[0].name[0] != 'm' || + fixture.threads[0].name[1] != 'a' || + fixture.threads[0].name[2] != 'i' || + fixture.threads[0].name[3] != 'n' || + fixture.threads[0].name[4] != 0U) { + return fail("pthread_setname_np did not store bounded logical name"); + } + + std::array long_name{}; + long_name.fill('x'); + if (!fixture.memory.write(kLongName, long_name)) { + return fail("could not stage overlong pthread name"); + } + regs = {}; + regs[0] = 1U; + regs[1] = kLongName; + if (call(fixture, kA32PthreadSetnameNpSvcImmediate, regs) != + A32HostServiceDisposition::Handled || + signed_r0(regs[0]) != kA32AndroidErange) { + return fail("pthread_setname_np did not return ERANGE for 16-byte name"); + } + + regs = {}; + regs[0] = 99U; + regs[1] = kName; + if (call(fixture, kA32PthreadSetnameNpSvcImmediate, regs) != + A32HostServiceDisposition::Handled || + signed_r0(regs[0]) != kA32AndroidEsrch) { + return fail("pthread_setname_np did not reject unknown pthread_t"); + } + return 0; +} + int test_create_return_and_explicit_exit() { Fixture fixture; std::array regs{}; @@ -403,6 +534,9 @@ int main() { if (const int status = test_identity_and_attrs(); status != 0) { return status; } + if (const int status = test_scheduler_and_name_helpers(); status != 0) { + return status; + } if (const int status = test_create_return_and_explicit_exit(); status != 0) { return status; } diff --git a/tests/compat/a32_pthread_lifecycle_shim.cpp b/tests/compat/a32_pthread_lifecycle_shim.cpp index 896123a..5e5f5c7 100644 --- a/tests/compat/a32_pthread_lifecycle_shim.cpp +++ b/tests/compat/a32_pthread_lifecycle_shim.cpp @@ -87,6 +87,9 @@ using liba32android::compat::kA32PthreadCreateSvcImmediate; using liba32android::compat::kA32PthreadDetachSvcImmediate; using liba32android::compat::kA32PthreadEqualSvcImmediate; using liba32android::compat::kA32PthreadExitSvcImmediate; +using liba32android::compat::kA32PthreadGetschedparamSvcImmediate; +using liba32android::compat::kA32PthreadSetschedparamSvcImmediate; +using liba32android::compat::kA32PthreadSetnameNpSvcImmediate; using liba32android::compat::kA32PthreadJoinSvcImmediate; using liba32android::compat::kA32PthreadSelfSvcImmediate; using liba32android::compat::make_a32_libc_memory_string_shim_catalog_entry; @@ -350,7 +353,7 @@ int main(int argc, char** argv) { return fail("pthread lifecycle consumer did not bind partial libc shim"); } - constexpr std::array shim_names{{ + constexpr std::array shim_names{{ "pthread_attr_init", "pthread_attr_destroy", "pthread_attr_getdetachstate", @@ -363,6 +366,9 @@ int main(int argc, char** argv) { "pthread_exit", "pthread_join", "pthread_detach", + "pthread_getschedparam", + "pthread_setschedparam", + "pthread_setname_np", "pthread_cond_init", "pthread_cond_destroy", "pthread_cond_wait", @@ -507,7 +513,7 @@ int main(int argc, char** argv) { return fail("could not register initial logical pthread"); } - const std::array services{{ + const std::array services{{ {kA32PthreadAttrInitSvcImmediate, &lifecycle}, {kA32PthreadAttrDestroySvcImmediate, &lifecycle}, {kA32PthreadAttrGetdetachstateSvcImmediate, &lifecycle}, @@ -520,6 +526,9 @@ int main(int argc, char** argv) { {kA32PthreadExitSvcImmediate, &lifecycle}, {kA32PthreadJoinSvcImmediate, &lifecycle}, {kA32PthreadDetachSvcImmediate, &lifecycle}, + {kA32PthreadGetschedparamSvcImmediate, &lifecycle}, + {kA32PthreadSetschedparamSvcImmediate, &lifecycle}, + {kA32PthreadSetnameNpSvcImmediate, &lifecycle}, {kA32PthreadCondInitSvcImmediate, &sync}, {kA32PthreadCondDestroySvcImmediate, &sync}, {kA32PthreadCondWaitSvcImmediate, &sync}, @@ -589,6 +598,9 @@ int main(int argc, char** argv) { const std::uint32_t attr_address = *data + 0x100U; const std::uint32_t detach_out = *data + 0x140U; const std::uint32_t stack_size_out = *data + 0x144U; + const std::uint32_t sched_policy_out = *data + 0x148U; + const std::uint32_t sched_param_address = *data + 0x14cU; + const std::uint32_t thread_name_address = *data + 0x150U; const std::uint32_t thread_out = *data + 0x180U; const std::uint32_t join_result_out = *data + 0x188U; const std::uint32_t cond_address = *data + 0x1a0U; @@ -641,6 +653,67 @@ int main(int argc, char** argv) { } ++wrapper_calls; + constexpr std::array default_sched_param{{0,0,0,0}}; + constexpr std::array logical_thread_name{{ + 'v','l','c','-','a','3','2',0, + }}; + if (!memory.write(sched_param_address, default_sched_param) || + !memory.write(thread_name_address, logical_thread_name)) { + return fail("could not stage pthread utility inputs"); + } + + result = run_wrapper( + memory, + graph_result.graph, + "fixture_pthread_getschedparam", + registry, + stack_top, + *stop, + 1U, + sched_policy_out, + sched_param_address); + if (!result || !*result || result->regs[0] != 0U || + read_u32(memory, sched_policy_out) != 0U || + read_u32(memory, sched_param_address) != 0U) { + return fail("real pthread_getschedparam wrapper failed"); + } + ++wrapper_calls; + + result = run_wrapper( + memory, + graph_result.graph, + "fixture_pthread_setschedparam", + registry, + stack_top, + *stop, + 1U, + 0U, + sched_param_address); + if (!result || !*result || result->regs[0] != 0U) { + return fail("real pthread_setschedparam wrapper failed"); + } + ++wrapper_calls; + + result = run_wrapper( + memory, + graph_result.graph, + "fixture_pthread_setname_np", + registry, + stack_top, + *stop, + 1U, + thread_name_address); + const auto* named_root = find_thread(std::span{threads}, 1U); + if (!result || !*result || result->regs[0] != 0U || + named_root == nullptr || + named_root->name[0] != 'v' || named_root->name[1] != 'l' || + named_root->name[2] != 'c' || named_root->name[3] != '-' || + named_root->name[4] != 'a' || named_root->name[5] != '3' || + named_root->name[6] != '2' || named_root->name[7] != 0U) { + return fail("real pthread_setname_np wrapper failed"); + } + ++wrapper_calls; + const std::uint32_t requested_stack = static_cast(memory.page_size() * 2U); result = run_wrapper( diff --git a/tests/compat/fixtures/a32_libc_memory_string_shim.S b/tests/compat/fixtures/a32_libc_memory_string_shim.S index 72a2370..2f318ef 100644 --- a/tests/compat/fixtures/a32_libc_memory_string_shim.S +++ b/tests/compat/fixtures/a32_libc_memory_string_shim.S @@ -64,6 +64,9 @@ LIBA32_STUB pthread_equal, LIBA32ANDROID_A32_PTHREAD_EQUAL_SVC LIBA32_STUB pthread_exit, LIBA32ANDROID_A32_PTHREAD_EXIT_SVC LIBA32_STUB pthread_join, LIBA32ANDROID_A32_PTHREAD_JOIN_SVC LIBA32_STUB pthread_detach, LIBA32ANDROID_A32_PTHREAD_DETACH_SVC +LIBA32_STUB pthread_getschedparam, LIBA32ANDROID_A32_PTHREAD_GETSCHEDPARAM_SVC +LIBA32_STUB pthread_setschedparam, LIBA32ANDROID_A32_PTHREAD_SETSCHEDPARAM_SVC +LIBA32_STUB pthread_setname_np, LIBA32ANDROID_A32_PTHREAD_SETNAME_NP_SVC LIBA32_STUB pthread_cond_init, LIBA32ANDROID_A32_PTHREAD_COND_INIT_SVC LIBA32_STUB pthread_cond_destroy, LIBA32ANDROID_A32_PTHREAD_COND_DESTROY_SVC LIBA32_STUB pthread_cond_wait, LIBA32ANDROID_A32_PTHREAD_COND_WAIT_SVC diff --git a/tests/compat/fixtures/a32_pthread_lifecycle_consumer.c b/tests/compat/fixtures/a32_pthread_lifecycle_consumer.c index cff2b78..ee9a34e 100644 --- a/tests/compat/fixtures/a32_pthread_lifecycle_consumer.c +++ b/tests/compat/fixtures/a32_pthread_lifecycle_consumer.c @@ -2,6 +2,9 @@ typedef __SIZE_TYPE__ fixture_size_t; typedef unsigned int fixture_pthread_t; +typedef struct fixture_sched_param { + int sched_priority; +} fixture_sched_param; __attribute__((visibility("default"))) int pthread_attr_init(void* attr); @@ -32,6 +35,18 @@ int pthread_join(fixture_pthread_t thread, void** value); __attribute__((visibility("default"))) int pthread_detach(fixture_pthread_t thread); __attribute__((visibility("default"))) +int pthread_getschedparam( + fixture_pthread_t thread, + int* policy, + fixture_sched_param* param); +__attribute__((visibility("default"))) +int pthread_setschedparam( + fixture_pthread_t thread, + int policy, + const fixture_sched_param* param); +__attribute__((visibility("default"))) +int pthread_setname_np(fixture_pthread_t thread, const char* name); +__attribute__((visibility("default"))) int pthread_cond_init(void* cond, const void* attr); __attribute__((visibility("default"))) int pthread_cond_destroy(void* cond); @@ -125,6 +140,24 @@ int fixture_pthread_detach(fixture_pthread_t thread) { return pthread_detach(thread); } __attribute__((visibility("default"), noinline)) +int fixture_pthread_getschedparam( + fixture_pthread_t thread, + int* policy, + fixture_sched_param* param) { + return pthread_getschedparam(thread, policy, param); +} +__attribute__((visibility("default"), noinline)) +int fixture_pthread_setschedparam( + fixture_pthread_t thread, + int policy, + const fixture_sched_param* param) { + return pthread_setschedparam(thread, policy, param); +} +__attribute__((visibility("default"), noinline)) +int fixture_pthread_setname_np(fixture_pthread_t thread, const char* name) { + return pthread_setname_np(thread, name); +} +__attribute__((visibility("default"), noinline)) int fixture_pthread_cond_init(void* cond, const void* attr) { return pthread_cond_init(cond, attr); }