Project 9: Native ARM64 System Probe
Build and explain a small Bionic-native diagnostic whose binary format, runtime dependencies, measurements, and failure behavior are all observable on a real Android phone.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 3 - Advanced |
| Time estimate | 18-30 hours |
| Main language | C |
| Alternatives | Rust, C++, Zig |
| Prerequisite projects | P01 Pocket Runtime Cartographer; P02 storage boundary; P08 recommended for operational integration |
| Primary concepts | ABI, ELF, Bionic, dynamic loading, procfs, native measurement, memory-safe parsing |
| Main tools | Termux clang, readelf, file, lldb or gdb where available, strace where supported |
| Observable outcome | An inspected aarch64 Android ELF probe and reproducible comparison report against the interpreted doctor adapter |
1. Learning Objectives
By completing this project, you will be able to:
- Explain why “ARM64 Linux” is not a sufficient binary compatibility claim.
- Separate the CPU instruction set, ABI, ELF metadata, dynamic interpreter, libc, Android API level, and PREFIX-dependent library contract.
- Trace source through compilation, assembly, linking, loading, relocation, and program startup.
- Inspect an artifact before running it and turn inspection results into automated release assertions.
- Gather process and platform facts through supported libc/syscall interfaces and selected procfs files without assuming every field is readable.
- Parse kernel-provided text with explicit byte, line, field, and numeric bounds.
- Compare native and interpreted tools using equivalent output contracts, controlled samples, and distribution statistics.
- Diagnose wrong-architecture, wrong-loader, missing-library, unavailable-API, and malformed-input failures from evidence rather than guesswork.
2. Theoretical Foundation
2.1 Core Concepts
Architecture is only the first compatibility layer. A phone reported as aarch64 executes the AArch64 instruction set, but a usable program also needs the expected calling convention, data layout, relocation model, object format, dynamic loader, libraries, and platform APIs. A desktop aarch64 binary linked against glibc can be perfectly valid ELF and still fail on Android because Android supplies Bionic and /system/bin/linker64, not the desktop loader recorded in that artifact.
An ABI is a binary contract. It covers register use, stack alignment, argument and return-value passing, scalar sizes, structure layout, symbol naming, and more. Source compatibility means code can be rebuilt for another target. Binary compatibility means an existing artifact can be loaded and called correctly. The project must never use these phrases interchangeably.
ELF makes the contract inspectable. The ELF header identifies class, byte order, machine, file type, and entry point. Program headers describe loadable segments and may name the interpreter. The dynamic section lists needed libraries and search-related metadata. Symbol and relocation tables explain names that must be resolved. Inspection does not prove correctness, but it rejects many invalid artifacts before deployment.
source
|
v
compiler target rules ---- Android API availability
|
v
object files -------------- machine code + symbols + relocations
|
v
linker -------------------- Bionic libraries + interpreter contract
|
v
Android ELF artifact
|
+--> static inspection: class, machine, interpreter, NEEDED, symbols
|
v
/system/bin/linker64 ------ maps segments and resolves dependencies
|
v
process startup ----------- libc initialization -> main -> exit
Bionic is Android’s C library environment. It provides familiar ISO C and POSIX-shaped interfaces plus Android-specific behavior, but it is not glibc. A configure test that passed on Ubuntu does not prove Android support. Some interfaces differ, some appeared at particular Android API levels, and some Linux facilities are hidden or restricted by Android policy. Prefer documented interfaces and feature detection over libc-name heuristics.
Termux adds a relocated userspace. The binary runs under Android’s app UID and kernel while Termux packages live beneath $PREFIX, normally inside the app-private directory. A program that embeds /usr/lib, /bin/sh, or a desktop loader has crossed the wrong boundary. Package-time paths should derive from the Termux build environment; user state belongs beneath private HOME, not inside package-owned directories or shared storage.
Compile-time, load-time, and run-time facts differ. A compile-time macro can describe the build target. ELF inspection describes the produced artifact. libc calls and system properties describe the executing environment. procfs describes kernel-exposed state subject to namespace and Android access policy. The report should preserve each fact’s source so contradictory evidence can be diagnosed.
procfs is an external data source, not a struct ABI. Files under /proc are generated by the kernel and often look regular, but fields vary across kernel and Android versions and access may be restricted. Reads can be partial; a process may disappear; a line can be longer than expected; numeric conversion can overflow. The correct result for a restricted optional field is UNAVAILABLE with evidence—not a crash and not a fabricated zero.
Measurement requires a protocol. Native startup often has less interpreter overhead, but a fair claim requires equivalent work and output. Cold and warm conditions, filesystem cache, process spawning, thermal throttling, CPU frequency, background activity, sample count, and ordering all influence results. Report distributions such as median and a tail percentile, retain raw observations, and avoid claiming causal superiority from one handset.
2.2 Why This Matters
Native diagnostics are useful when startup latency, resource footprint, direct system interfaces, or packaging constraints matter. More importantly, this project makes the invisible runtime contract visible. The same skills explain why prebuilt command-line tools fail in Termux, why a package works only on the developer phone, why a shared library update breaks a partial upgrade, and why a seemingly simple port needs Termux-specific patches.
The probe also creates a small, bounded native component for PocketOps. It is intentionally not a rewrite of the Python core. The learner must justify each boundary: use native code for facts or costs it can measure, normalize the result into the same schema used elsewhere, and preserve the interpreted implementation as a readable reference and fallback where appropriate.
2.3 Historical Context / Background
Unix systems standardized source-level interfaces through C and POSIX, but binary compatibility remained platform-specific. ELF became the dominant object format on Linux-family systems, while each architecture and platform defined its own ABI and loader conventions. Android adopted the Linux kernel but built an application platform around Bionic, APK-managed app identities, SELinux, API-level compatibility, and Android’s own dynamic linker.
Termux then created a Unix-style package environment inside an ordinary Android application sandbox. Because an unrooted app cannot populate global /usr, Termux relocates its packages beneath PREFIX and patches software that assumes a conventional filesystem hierarchy or glibc. Modern Termux package engineering therefore combines familiar Unix compilation with Android NDK/API and PREFIX constraints.
2.4 Common Misconceptions
- “It is ARM64, so any ARM64 Linux binary works.” The loader, libc ABI, API level, libraries, and paths must also match.
- “A syscall and a libc call are the same thing.” libc is an interface and adaptation layer; direct syscall use is architecture- and platform-sensitive.
- “Static linking makes portability automatic.” It changes dependency placement but not architecture, API, licensing, resolver, kernel, or Android policy constraints.
- “If
/procexists, all Linux observability is available.” Android may hide, filter, or deny fields, and kernel formats evolve. - “One timing run proves performance.” It mostly proves that one uncontrolled run produced one number.
- “Native means better.” Native code increases memory-safety and release complexity; use it only when the demonstrated tradeoff warrants it.
3. Project Specification
3.1 What You Will Build
Build pocketprobe, a native command with human and JSON renderers that reports:
- build identity, schema version, target ABI, and declared minimum Android API;
- runtime ABI observation, pointer width, byte order, and page size;
- monotonic and real-time clock capability plus resolution;
- process identity, thread count if observable, resource-limit summaries, and selected memory facts;
- selected procfs capability states for CPU, process memory, and mounts;
- explicit
READY,RESTRICTED,UNAVAILABLE,MALFORMED, orINCONSISTENTstates; - provenance for each fact: build, ELF inspection, libc/syscall, Android property, or procfs;
- benchmark evidence comparing its normalized doctor subset with the interpreted adapter.
Also produce an artifact-inspection record and a benchmark report. The binary alone is not the project deliverable.
3.2 Functional Requirements
- Support
humanand versionedjsonoutput with equivalent facts. - Keep stdout clean: one selected result; diagnostics go to stderr.
- Expose a stable exit taxonomy for success, degraded optional observations, incompatible runtime, invalid usage, malformed input, and internal fault.
- Prove ELF class, machine, interpreter, and NEEDED libraries during acceptance.
- Detect contradictions between declared target, ELF identity, and runtime observation.
- Bound every procfs read by total bytes, line length, field count, and numeric range.
- Continue with partial results when an optional procfs field is restricted.
- Include build/compiler identity without leaking unique device identifiers or absolute developer paths.
- Emit the same normalized subset as the Project 1 doctor adapter for comparison.
- Demonstrate a wrong-architecture or deliberately incompatible fixture without executing untrusted artifacts.
3.3 Non-Functional Requirements
- No network access is needed for normal probe execution.
- Ordinary execution performs no mutation beyond optional private benchmark evidence.
- Memory ownership and cleanup rules are documented for every dynamically sized buffer.
- Parsing fixtures run independently of live
/proc. - Results are deterministic for fixed input fixtures; naturally changing fields carry timestamps or are excluded from equality checks.
- The benchmark caps samples, output size, duration, and device heat budget.
- Executable and test fixtures remain in Termux-private storage, never shared storage.
3.4 Example Usage / Output
$ pocketprobe --format human
PocketProbe 1.0.0 schema=pocketops.probe.v1
Build target=aarch64-linux-android api>=24 READY
ELF class=64 machine=AArch64 READY
Loader /system/bin/linker64 READY
libc Android Bionic READY
Pointer 64 bit READY
Page size 4096 bytes READY
Clock monotonic,realtime READY
procfs.cpu readable READY
procfs.mem fields filtered RESTRICTED
procfs.mount readable READY
Result: DEGRADED optional_fields=1
$ pocketprobe inspect-evidence --format human
Artifact pocketprobe-1.0.0-aarch64
SHA-256 1b80...c4d2
ELF machine AArch64
Interpreter /system/bin/linker64
NEEDED libc.so, libdl.so
Build path redacted
Assessment COMPATIBLE_WITH_DECLARED_TARGET
The transcript is a contract target. Your values will vary by device and build.
3.5 Real World Outcome
On a supported aarch64 phone, a clean Termux session runs the inspected binary and receives a useful report even when one procfs view is restricted. A release evidence bundle connects the source revision and compiler identity to a SHA-256 digest, ELF facts, live output, and malformed-input tests. A controlled incompatible artifact is rejected during inspection or package installation with the exact failed layer named. An interleaved benchmark of at least 50 bounded samples reports native and interpreted median and tail startup costs for the same normalized fields; it does not claim more than the evidence supports.
4. Solution Architecture
4.1 High-Level Design
pocketprobe invocation
|
+------------v-------------+
| argument + contract layer |
+------------+-------------+
|
immutable probe request/budget
|
+-----------------------+------------------------+
| | |
+-------v--------+ +-------v--------+ +-------v--------+
| platform facts | | procfs readers | | build identity |
| libc/syscalls | | bounded parser | | generated data |
+-------+--------+ +-------+--------+ +-------+--------+
| | |
+-----------------------+------------------------+
|
normalized observation set
|
+----------------+----------------+
| |
+------v-------+ +------v-------+
| human render | | JSON render |
+--------------+ +--------------+
Separate release path:
source -> controlled build -> ELF inspection -> digest -> device acceptance
The live probe never parses readelf text about itself to discover runtime truth. Artifact inspection is a release-stage adapter; runtime observations use supported interfaces. The evidence bundle later reconciles both sources.
4.2 Key Components
| Component | Responsibility | Must not do |
|---|---|---|
| Contract parser | Validate operation, format, budget, and sample options | Probe the platform while parsing |
| Platform adapter | Gather bounded libc/syscall facts | Invent values for unavailable fields |
| Procfs adapter | Open named files and return bounded bytes plus read status | Scan arbitrary paths or follow caller-controlled names |
| Typed parser | Convert fixture/live bytes to observations | Depend on global mutable buffers |
| Consistency evaluator | Compare build, artifact, and runtime declarations | Treat optional restriction as fatal incompatibility |
| Renderer | Emit one human or JSON contract | Print logs or benchmark progress to stdout |
| Artifact inspector | Record ELF and dependency metadata during release | Execute the inspected artifact |
| Benchmark harness | Interleave equivalent invocations and retain raw samples | Run indefinitely or ignore thermal state |
4.3 Data Structures
Use language-level structures equivalent to the following pseudocode:
ProbeObservation = {
name: StableFieldId,
source: BUILD | ELF | LIBC | SYSCALL | ANDROID_PROPERTY | PROCFS,
state: READY | RESTRICTED | UNAVAILABLE | MALFORMED | INCONSISTENT,
value: BoundedTypedValue?,
evidence_code: StableCode,
observed_at: Timestamp?,
safe_detail: BoundedString?
}
ArtifactIdentity = {
sha256, elf_class, machine, interpreter,
needed_libraries[], declared_min_api,
compiler_identity, source_revision
}
BenchmarkRun = {
protocol_version, device_state_summary,
workload_schema, tool_identity,
ordered_samples_ns[], median_ns, tail_ns,
failures[], thermal_stop_reason?
}
Never place raw /proc files, environment dumps, or full device properties in the normal result.
4.4 Algorithm Overview
PROBE(request):
validate request and construct total resource budget
append immutable build observations
gather each independent runtime observation through a bounded adapter
parse selected procfs fixtures or live files using explicit limits
classify unavailable facts without aborting independent observations
evaluate internal consistency
normalize and render exactly once
INSPECT(artifact):
hash without execution
parse trusted tool output under size bound
compare ELF properties with release policy
reject mismatch before installation
BENCHMARK(tool_a, tool_b):
verify equivalent schemas and fixed workload
interleave bounded samples to reduce ordering bias
retain raw durations and failures
stop on heat/resource policy
summarize distributions, not only averages
For live probe work, runtime cost is linear in the bounded bytes read: O(B), where B is capped. Memory use is O(L) for the largest permitted line or O(B) only if your design justifies one bounded whole-file buffer. Benchmark work is O(S) samples with an explicit ceiling.
5. Implementation Guide
5.1 Development Environment Setup
Record the Project 1 doctor report before installing build dependencies. Keep source and build outputs under private HOME. Install the Termux compiler and ELF inspection tools from the same supported package ecosystem as the current Termux installation. Do not copy a desktop compiler artifact into shared storage and call it an Android build.
Capture these setup facts in the project log:
- Termux distribution track and app/package source;
- Android version/API and device ABI;
- compiler target triple, version, and default API assumptions;
- PREFIX and build/output directories;
- available debugger/tracer and known Android restrictions;
- baseline battery, temperature policy, and free space for measurements.
For a release-quality multi-ABI path, use the official termux-packages build environment rather than treating an on-device build as proof of portability. The on-device build remains the learning and immediate-feedback path.
5.2 Project Structure
pocketprobe/
|-- docs/
| |-- runtime-contract.md
| |-- benchmark-protocol.md
| `-- compatibility-matrix.md
|-- include/
| `-- probe-types.h
|-- src/
| |-- contract/
| |-- platform/
| |-- procfs/
| |-- render/
| `-- main-entry/
|-- tests/
| |-- fixtures/procfs/
| |-- malformed/
| `-- contract/
|-- release/
| |-- artifact-policy.md
| `-- evidence-schema.md
`-- benchmarks/
|-- protocol.md
`-- observations/
Directory names are a design suggestion, not a runnable implementation.
5.3 The Core Question You Are Answering
Why does this exact binary run on this Android phone, and which compile, link, loader, libc, API, architecture, and data-parsing contracts make that true?
Your final explanation should trace one successful launch and one rejected artifact through every layer. “Because clang built it” is not an acceptable answer.
5.4 Concepts You Must Understand First
- ABI versus API: binary calling/data contract versus callable source/platform surface.
- ELF loading: headers, segments, interpreter, dynamic dependencies, symbols, and relocations.
- Bionic versus glibc: source familiarity does not imply binary compatibility.
- Termux PREFIX relocation: package paths differ from conventional FHS locations.
- procfs uncertainty: kernel-generated text is versioned, mutable, and permission-sensitive input.
- C memory safety: every buffer, conversion, pointer, lifetime, and cleanup path needs an invariant.
- Experimental method: comparable workloads, interleaving, distributions, and honest limits.
5.5 Questions to Guide Your Design
- Which observation is authoritative when compile-time and runtime facts conflict?
- Which facts are required for compatibility and which are optional diagnostics?
- What exact byte and numeric limits apply to every parser?
- What happens when a procfs file disappears between discovery and read?
- Does JSON preserve integers without overflow or accidental string changes?
- How will the artifact evidence avoid absolute developer paths and timestamps that defeat reproducibility?
- When does a native optimization repay its additional security and distribution cost?
5.6 Thinking Exercise
Trace this sequence on paper:
source -> compiler target -> object -> linker -> ELF -> Android loader
-> shared libraries -> process initialization -> probe -> renderer
At every arrow, name one failure that can occur while the CPU architecture remains correct. Then decide whether it should be detected at build, inspection, install, load, or runtime.
5.7 The Interview Questions They Will Ask
- What does an ABI include that an instruction-set name does not?
- Why can an aarch64 glibc ELF fail on an aarch64 Android phone?
- What information is stored in ELF program headers and the dynamic section?
- How does a dynamic loader resolve shared-library dependencies?
- Why should procfs parsers be fuzzed or fixture-tested?
- How would you determine a binary’s minimum supported Android API?
- What makes a cold-start benchmark fair?
- When would Rust be a better boundary than C for this probe?
5.8 Hints in Layers
Hint 1: Establish identity first
Produce ABI, pointer width, byte order, and page size before adding any procfs parser.
Hint 2: Treat inspection as a test
Write the allowed ELF class, machine, interpreter, dependency set, and path policy before inspecting the first artifact.
Hint 3: Parse immutable fixtures
Copy representative, redacted procfs samples into test fixtures and make live reading a thin adapter.
Hint 4: Make restrictions visible
Return a typed restricted observation with the operation and safe cause category; never substitute zero.
Hint 5: Interleave measurements
Alternate native and interpreted runs so device warming does not systematically favor the second tool.
5.9 Books That Will Help
| Topic | Book | Suggested chapters |
|---|---|---|
| Linking and object files | “Computer Systems: A Programmer’s Perspective” | Ch. 7 |
| Processes and measurement | “Computer Systems: A Programmer’s Perspective” | Ch. 8-9 |
| Loaders and relocation | “Linkers and Loaders” | Ch. 1-3, 9-10 |
| C object and pointer discipline | “Effective C” | Object, pointer, array, and error-handling chapters |
| Linux process/files interfaces | “The Linux Programming Interface” | Ch. 6, 14, 24-28 |
5.10 Implementation Phases
Phase 1: Compatibility contract
- Write the supported ABI/API matrix and stable output fields.
- Define required versus optional observations and exit statuses.
- Capture the baseline doctor and compiler identity.
Phase 2: Minimal native identity
- Produce build identity, pointer width, byte order, and page-size observations.
- Separate typed core results from renderers.
- Verify clean human/JSON channel behavior.
Phase 3: Artifact inspection
- Define an ELF release policy.
- Capture digest, class, machine, interpreter, and dependencies.
- Compare against a known Termux binary and a controlled incompatible fixture.
Phase 4: Bounded procfs adapters
- Add one parser at a time using redacted fixtures.
- Test missing, denied, truncated, oversized, duplicate, and malformed fields.
- Integrate live reads only after parser tests pass.
Phase 5: Consistency and diagnostics
- Reconcile build, artifact, and runtime facts.
- Add safe error evidence and debugger/tracer observations for one failure.
- Ensure optional restrictions produce degraded output.
Phase 6: Measurement
- Freeze the benchmark protocol and equivalent workload.
- Run bounded interleaved cold/warm experiments.
- Summarize distributions and retain raw observations.
Phase 7: Release evidence
- Assemble source revision, toolchain, digest, ELF report, tests, and benchmark.
- Re-run on a clean Termux environment.
- Document what the evidence does and does not prove.
5.11 Key Design Decisions
| Decision | Recommended default | Reason |
|---|---|---|
| Language | C for the exercise, Rust as a justified extension | C exposes the ABI and memory model directly |
| Linking | Minimal dynamic dependencies | Fits Termux rolling packages and keeps artifact inspectable |
| Procfs scope | Fixed allowlist of named files/fields | Prevents arbitrary file inspection and format sprawl |
| Output | Shared normalized schema with Project 1 | Makes performance comparison meaningful |
| Optional restriction | Degraded result | Android policy differences are expected |
| Benchmark statistic | Median plus tail and raw samples | Shows distribution without hiding outliers |
| Build claims | On-device for learning; official environment for release/multi-ABI | One phone cannot prove ecosystem portability |
6. Testing Strategy
6.1 Test Categories
- Contract tests: human/JSON parity, stable field names, stdout/stderr, and exit codes.
- Parser tests: valid, absent, truncated, oversized, duplicate, reordered, negative, and overflowing fields.
- Memory-safety tests: sanitizers where supported, guard fixtures, repeated failure cleanup, and fuzzed parser inputs.
- Artifact tests: allowed ELF properties, forbidden desktop loader, unexpected dependency, and embedded-path policy.
- Device tests: aarch64 phone, restricted procfs behavior, real clocks, and resource limits.
- Compatibility tests: wrong architecture, missing dependency, incompatible loader metadata, and minimum-API policy.
- Benchmark tests: warm/cold protocol, interleaving, failure accounting, and thermal stop.
6.2 Critical Test Cases
| Case | Expected result |
|---|---|
| Optional procfs file denied | Independent facts remain; overall state is degraded |
| Required identity contradicts build target | Incompatible-runtime error; no misleading normal report |
| Numeric field exceeds supported range | MALFORMED observation; no wraparound |
| Line exceeds parser cap | Bounded rejection with safe evidence |
| ELF names desktop glibc loader | Release inspection fails before installation |
| NEEDED library absent | Compatibility rejection identifies dependency layer |
| Wrong-architecture fixture | Inspection/package layer rejects it; tool is not executed |
| JSON mode plus warning | JSON remains valid; warning is stderr/log only |
| Benchmark sample fails | Failure is retained, not silently dropped from statistics |
| Device heats beyond protocol limit | Run stops and records an inconclusive result |
6.3 Test Data
Create small redacted fixtures representing at least two Android/kernel layouts, an empty file, denied-open simulation, a disappearing process, a very long line, maximum valid integers, overflow integers, duplicate labels, unknown fields, and invalid UTF-8 if your parser accepts bytes. Preserve expected normalized observations beside fixtures, not live device identifiers.
The incompatible ELF fixture should come from a controlled build description or metadata sample. Do not download and execute arbitrary binaries merely to create a failure.
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
- Hard-coding
/usr,/bin, or a desktop ELF interpreter. - Treating
uname -mas the full compatibility answer. - Using compile-time macros as proof of the executing artifact.
- Reading unlimited procfs data into a fixed or unchecked buffer.
- Converting failure to zero, making “unavailable memory” look like no memory.
- Printing debug messages into JSON stdout.
- Measuring different work in native and interpreted versions.
- Reporting only the fastest sample.
- Shipping build paths, timestamps, or device identifiers as reproducibility evidence.
7.2 Debugging Strategies
- Start with
fileand ELF inspection before stepping through source. - Compare interpreter and NEEDED entries with a known working Termux binary.
- Capture exit status and loader diagnostics separately from program stderr.
- Replace live procfs reads with the exact failing redacted bytes.
- Reduce the parser to one field and reintroduce bounds incrementally.
- Use a debugger or tracer only after recording the contract-level failure.
- For performance anomalies, inspect raw sample order, failures, device state, and thermal stop before changing code.
7.3 Performance Traps
- Reopening and reparsing large kernel views for every small field.
- Allocating one object per token in a tiny probe.
- Doing artifact inspection during every normal invocation.
- Flushing benchmark evidence after every sample rather than committing bounded batches.
- Adding concurrency to a short CPU-light probe and measuring scheduler overhead.
- Optimizing parser speed before proving output equivalence and memory safety.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a
--explain FIELDview that names the observation source and limitation. - Compare monotonic and real-time clocks during a short sleep without claiming wall-clock stability.
- Render a compatibility checklist for a selected artifact without executing it.
8.2 Intermediate Extensions
- Add a Rust parser for the same fixture corpus and compare error modeling and binary dependencies.
- Produce architecture-independent JSON Schemas for native/interpreted output.
- Cross-build in the official Termux package environment and verify the artifact on a second phone.
- Add a bounded
/proc/self/mapsanalysis that redacts paths and documents Android restrictions.
8.3 Advanced Extensions
- Build a multi-ABI matrix for aarch64, arm, i686, and x86_64 using official infrastructure and available emulation/real devices.
- Fuzz every text parser with a reproducible seed corpus.
- Investigate reproducible-build differences across two clean builders.
- Package the probe through Project 10 and make ELF policy a release gate.
- Extract the native core into a narrowly versioned library only after proving a stable FFI ownership contract.
9. Real-World Connections
9.1 Industry Applications
The same techniques appear in endpoint agents, crash reporters, package builders, container runtime diagnostics, embedded Linux tooling, Android NDK libraries, and supply-chain scanners. Teams routinely need to answer “what was built, for which target, with which dependencies, and why did it fail to load?”
9.2 Related Open-Source Projects
- Termux packages demonstrates Android/Termux patching and multi-architecture builds.
- Android NDK documents supported native APIs and build concepts.
- LLVM supplies the compiler/linker toolchain concepts used by Termux clang.
- ELF Tool Chain and GNU binutils documentation provide object inspection context.
9.3 Interview Relevance
This project supports systems, Android native, embedded, performance, and release-engineering interviews. A strong portfolio explanation connects one actual loader failure to ABI evidence, shows a bounded parser test, and explains why the benchmark conclusion is deliberately narrow.
10. Resources
10.1 Essential Reading
- Termux execution environment
- Termux filesystem layout
- Termux build environment
- Building Termux packages
- Android NDK guides
- Android application sandbox
10.2 Video Resources
- Android Developers talks on native development and NDK compatibility.
- LLVM conference talks on linking, object files, and sanitizers.
- Systems conference talks that demonstrate measurement methodology rather than benchmark marketing.
Use videos as explanatory companions; verify current platform claims in the official documents above.
10.3 Tools & Documentation
- Termux
clangand LLVM tooling from the supported package repository. readelf/LLVM object inspection documentation.- Debugger documentation for the tool available on your supported Termux device.
- POSIX Issue 8 via The Open Group.
10.4 Related Projects in This Series
- P01 defines the normalized doctor contract.
- P08 can supervise scheduled probe collection.
- P10 packages and distributes this artifact.
- P11 audits writable executables and release trust.
- P12 integrates the probe into acceptance evidence.
11. Self-Assessment Checklist
11.1 Understanding
- I can distinguish ISA, ABI, API, ELF, loader, libc, and syscall.
- I can explain why an aarch64 glibc program may fail on Android.
- I can identify package-prefix assumptions in a native port.
- I can explain why procfs must be parsed as untrusted variable input.
- I can defend the benchmark protocol and its limits.
11.2 Implementation
- Human and JSON output expose the same normalized observations.
- Every external read and parser dimension has a bound.
- Restricted optional facts produce explicit degradation.
- ELF class, machine, interpreter, and dependencies are recorded.
- Wrong-architecture and missing-dependency paths are demonstrated safely.
- Raw benchmark samples, failures, and device-state notes are retained.
11.3 Growth
- I can decide whether native code is justified for a new component.
- I can propose a Rust boundary with explicit ownership and error semantics.
- I can describe how to move from an on-device build to official multi-ABI packaging.
- I can explain what my release evidence does not prove.
12. Submission / Completion Criteria
Minimum completion
- A Bionic-native aarch64 probe reports ABI, pointer width, page size, clocks, and at least two bounded procfs capability states.
- Human and JSON contracts are documented and tested.
- An ELF inspection record names class, machine, interpreter, dependencies, and digest.
- One missing/restricted field and one malformed fixture fail safely.
Full completion
- All functional and non-functional requirements are satisfied.
- Build, artifact, and runtime facts are reconciled in an evidence bundle.
- Wrong architecture, wrong loader or missing dependency, restricted procfs, overflow, and output-channel cases are demonstrated.
- Native/interpreted comparisons use equivalent contracts, interleaved bounded samples, median/tail results, and raw observations.
- A clean supported Termux environment reproduces the acceptance transcript.
Excellence criteria
- A second controlled build is compared for reproducibility.
- The parser corpus is fuzzed or property-tested with reproducible seeds.
- An official Termux build-environment artifact is verified on a second device.
- The learner writes a concise decision record explaining what remains in C, what remains in Python, and why.
Generated from the Termux Android Apps and Tools Mastery parent guide and its expanded project index. This guide intentionally provides architecture, pseudocode, metadata, tests, and observable contracts rather than complete runnable source code.