12.8. OAT containers: what’s actually AOT-compiled to native code, and where¶
Preinstalled apps (Boox, GMS, AOSP) catalogues the 124 preinstalled APKs but explicitly does
not open their compiled Java. This page does: it looks inside the 308
.odex files under oat/<arch>/ (each with a matching .vdex sitting
right there) plus 60 more boot-*.vdex files that live directly in
system/framework/ rather than under any oat/ subdirectory — 368
.vdex files total — across system_a, vendor_a, product_a and
system_ext_a, establishes
what container format they actually are on this device, and disassembles the
handful of places that hold genuine AArch64 machine code rather than
interpreted DEX bytecode.
Key finding: almost none of it is native code. Of 308 .odex
files, only 28 (9.1%) contain any AOT-compiled machine code at all — the
rest are ELF containers whose “executable” region is literally absent,
because ART’s build-time compiler filter for this image is verify for
the overwhelming majority of apps (including every first-party Boox/Onyx app
checked). The exceptions are System Server and a cluster of Mainline APEX
system services, which are speed/speed-profile compiled and do hold
megabytes of real compiled code — plus one vendor outlier,
TrustZoneAccessService, whose only compiled code is six JNI trampolines
bridging into Qualcomm’s Mink/QSEE IPC layer.
12.8.1. Container format, confirmed against raw bytes¶
An .odex under oat/<arch>/ is a real ET_DYN ELF shared object.
Confirmed directly on this dump (not assumed from AOSP documentation):
$ xxd artifacts/system_a/system/app/knote2-release/oat/arm64/knote2-release.odex | head -1
00000000: 7f45 4c46 0201 0103 0000 0000 0000 0000 .ELF............
00000010: 0300 b700 0100 0000 0000 0000 0000 0000 ................
e_ident = 7f 45 4c 46 02 01 01 03 (ELF64, little-endian, EI_OSABI
= 3/Linux); e_type = 3 (ET_DYN); e_machine = 0x00b7 = 183 =
EM_AARCH64. Every oat/arm64/*.odex on this device (216 of the 308) is
this format. The other 92 — the oat/arm/ 32-bit compatibility copies —
are the ELF32/EM_ARM (40) equivalent of the same container; confirmed by
extending the same parser to 32-bit Elf32_Phdr/Elf32_Sym layout (see
Provenance).
Section layout (services.odex, the system_server OAT file, as a
representative example — llvm-objdump -h):
Idx Name Size VMA Type
1 .note.gnu.build-id 00000024 00000000000003c0 DATA
2 .rodata 00163000 0000000000001000 DATA
3 .text 003c9968 0000000000164000 TEXT
4 .data.img.rel.ro 000031cc 000000000052e000 DATA
5 .bss 0000cd30 0000000000532000 BSS
6 .dex 00048990 000000000053f000 BSS
7 .dynstr / .dynsym / .hash / .dynamic (standard dynamic-linking sections)
and its dynamic symbol table (no .symtab — these are stripped, but the
dynsym table is intact and readable with llvm-objdump -T without needing
to hand-walk PT_DYNAMIC):
0000000000001000 g DO .rodata 0000000000163000 oatdata
0000000000164000 g DO .text 0000000000000000 oatexec
000000000052d964 g DO .text 0000000000000004 oatlastword
000000000052e000 g DO .data.img.rel.ro 00000000000031cc oatdataimgrelro
0000000000532000 g DO .bss 0000000000007ce8 oatbss
000000000053f000 g DO .dex 0000000000000000 oatdex
000000000058798c g DO .dex 0000000000000004 oatdexlastword
oatdata marks the start of .rodata: the OAT header itself, the
key/value metadata store, and per-dex-file tables. oatexec/oatlastword
bound the actual compiled-native-code region — note oatexec’s own
symbol size is always reported as 0 by the linker (it’s a boundary marker,
not a sized object); the real size of the executable region has to be
computed as oatlastword.value + oatlastword.size - oatexec.value. For
services.odex that is 0x52d964 + 4 - 0x164000 = 0x3c9968 bytes
(3,971,432 bytes) — matching the .text section size exactly. When an
OAT file has zero compiled methods, the ``oatexec``/``oatlastword`` symbols
are absent from the dynsym table entirely (not present-with-zero-size) —
confirmed on every “verify”-filter file checked (see next section). oatdex
marks the embedded/quickened DEX blob (.dex section, mapped SHT_NOBITS
in some files — see the system_server table above where it’s typed
BSS, meaning the on-disk .odex doesn’t even carry those bytes; they
come from the paired .vdex at load time via mmap).
OAT header magic, confirmed in every file checked:
00001000: 6f61 740a 3234 3400 ... oat\n244\0 ...
oat\n (4 bytes) + a NUL-terminated ASCII version string. Every file in
this dump reports version 244 except four vendor-partition apps
(IWlanService, TimeService, TrustZoneAccessService,
pasrservice — all under artifacts/vendor_a/app/), which report
version 183. This tracks the same Android-version split documented in
The Onyx/Boox platform layer and Security and DRM userspace: the vendor partition is frozen
on an older Android/ART release than system, and these four vendor apps
were compiled by that older toolchain’s dex2oat. We did not locate or
verify an authoritative OAT-version-to-AOSP-tag mapping for 183 vs. 244 —
both numbers are read directly from the header bytes, not looked
up.
Following the fixed header fields is a flat key/value store of
NUL-terminated key\0value\0 pairs (apex-versions, bootclasspath,
classpath-dir, compiler-filter, debuggable, …). We did not
reverse-engineer the exact fixed-width OatHeader struct that precedes
this store (checksum/instruction-set/trampoline-offset fields) — that layout
is known to have changed repeatedly across AOSP art/runtime/oat.h
revisions and pinning it for version 244 specifically was out of scope here.
What we did instead, robustly and without needing the struct layout, is a
direct byte search for the compiler-filter\0 key and read its value —
see oat_scan.py in Provenance.
12.8.2. The compiler-filter reality¶
This is the load-bearing finding. compiler-filter is an ART build-time
setting that controls how much of an APK’s DEX bytecode dex2oat actually
turns into native machine code at build time, versus leaving to be
interpreted (or quickened — bytecode rewritten in place with faster
virtual-dispatch opcodes, still not native) at runtime. Scanning all 308
.odex files (script: oat_scan.py, see Provenance):
compiler-filter distribution (308 files):
verify 277 (89.9%) -- zero compiled methods, no oatexec symbol at all
speed 24 ( 7.8%) -- full AOT compilation, large oatexec
quicken 4 ( 1.3%) -- only JNI-native-method trampolines compiled
speed-profile 3 ( 1.0%) -- profile-guided partial AOT compilation
oatexec symbol present: 28 / 308 (9.1%)
verify means dex2oat did class/method verification only — no
compilation whatsoever, not even the small JNI entry stubs. Every single one
of the 277 verify-filter files we checked has no oatexec symbol in
its dynamic symbol table (confirmed both by llvm-objdump -T spot checks
and by the bulk scanner): the .text section, if a section-header table is
even present, is size 0. This includes every first-party Onyx/Boox app in
the dump that was checked: knote2-release (Notes), kreader2-release
(Reader), kcb-release, ksync-release, kmail-release,
floatingbutton-release, dict-release, ai-assistant-release,
OnyxLatinIME-release, and SystemUI itself. None of these apps have a
single AOT-compiled method on this device image — everything in them runs
interpreted or quickened-interpreted at runtime, compiled on-device later
(if at all) by the JIT/profile-guided background dex2oat pass that
Android normally runs after enough real-world usage, which isn’t captured
in a factory image dump.
speed / speed-profile (27 files total) is where the real compiled
code lives, and it is concentrated almost entirely in system framework
and Mainline APEX service jars — i.e., the platform’s own core services, not
any app:
oatexec size filter file
9,352,200 speed apex@com.android.wifi@...@service-wifi.jar@classes.odex
3,971,432 speed-profile system/framework/oat/arm64/services.odex (system_server)
3,676,216 speed system/app/KeyChain/oat/arm64/KeyChain.odex
2,873,976 speed apex@com.android.tethering@...@service-connectivity.jar@classes.odex
1,747,192 speed apex@com.android.appsearch@...@service-appsearch.jar@classes.odex
1,692,168 speed apex@com.android.uwb@...@service-uwb.jar@classes.odex
1,232,920 speed apex@com.android.permission@...@service-permission.jar@classes.odex
684,792 speed priv-app/SettingsProvider/oat/arm64/SettingsProvider.odex
... (19 more apex service-*.jar entries, 9.2 KB - 918 KB each)
36 speed priv-app/InputDevices/oat/arm64/InputDevices.odex
(Full 28-row table with exact byte counts: /tmp/oat_scan_all.csv from the
--find run described in Provenance — not checked into the repo since
it’s a full re-derivable dump, but the command to reproduce it is given
there.) Note the long tail down to InputDevices.odex at a mere 36 bytes —
a single trivial compiled method (see below) sitting alongside gigabytes of
interpreted-only DEX. KeyChain.odex is the one exception among apps
(not framework/APEX services) with full speed compilation — plausible
given it’s a boot-critical, security-sensitive system app, which (per
general, unverified-for-this-device knowledge of Android’s compilation
policy) tends to be prioritized for AOT compilation.
12.8.3. Disassembly: three real oatexec regions¶
All addresses below are file-relative (this ELF’s PT_LOAD segments are a
1:1 file-offset-equals-vaddr mapping, verified with elfdis.py --segs on
each file — no relocation/rebasing needed). Disassembled with
elfdis.py (capstone 5.0.7, AArch64) from artifacts/re_static/.
One artifact worth flagging up front: a raw linear disassembly starting
exactly at the oatexec symbol’s address routinely decodes a few
garbage/udf instructions before it locks onto real code. This isn’t a
bug in the tooling — it’s a small non-instruction header (ART’s per-method
OatQuickMethodHeader: a packed, variable-length-encoded record of
vmap-table offset, CFI offset and frame info that precedes every compiled
method’s entry point) sitting in the byte stream. capstone’s linear
disassembler has no way to know to skip it, so it either decodes it as
nonsense (udf #0 happens to be the AArch64 encoding for four zero
bytes) or, worse, desyncs and stops entirely at the first byte sequence with
no valid encoding. We did not reverse the exact OatQuickMethodHeader byte
layout for OAT version 244; the
practical workaround used throughout this section was to nudge the start
address forward by 0x10–0x20 bytes past a run of zeros/garbage until capstone
locks onto a recognizable AArch64 function prologue.
12.8.3.1. 1. services.odex (system_server, speed-profile) — first compiled method¶
Raw bytes at oatexec (0x164000):
00164000: 0000 0000 0000 0000 0000 0000 4ff4 0e00 ................
12 zero bytes (the tail of the method header) then non-instruction bytes,
then at 0x164010 a completely ordinary ART “quick”-ABI method prologue:
0x164010 sub sp, sp, #0xb0
0x164014 stp x19, x20, [sp, #0x50]
0x164018 stp x21, x22, [sp, #0x60]
0x16401c stp x23, x24, [sp, #0x70]
0x164020 stp x25, x26, [sp, #0x80]
0x164024 stp x27, x28, [sp, #0x90]
0x164028 stp x29, x30, [sp, #0xa0]
0x16402c stp d8, d9, [sp, #0x10]
0x164030 stp d10, d11, [sp, #0x20]
0x164034 stp d12, d13, [sp, #0x30]
0x164038 stp d14, d15, [sp, #0x40]
0x16403c str x0, [sp]
...
0x16404c mov x16, sp
0x164050 str x16, [x19, #0xa8]
0x164054 ldxr w16, [x19]
0x164058 mov w17, #0x5c000000
0x16405c cbnz w16, #0x164110
0x164060 stlxr w16, w17, [x19]
0x164064 cbnz w16, #0x164054
...
0x164098 mov x0, x22
0x16409c ldr x30, [x25, #0x10]
0x1640a0 blr x30
0x1640a4 ldaxr w16, [x19]
0x1640a8 mov w17, #0x5c000000
0x1640ac cmp w16, w17
0x1640b0 b.ne #0x16411c
0x1640b4 stxr w16, wzr, [x19]
0x1640b8 cbnz w16, #0x1640a4
...
0x164104 ldr w20, [x19, #0x20]
0x164108 add sp, sp, #0xb0
0x16410c ret
Register x19 is ART’s reserved “Thread self” pointer in the AAPCS64
“quick” calling convention (never used as a general-purpose scratch
register by dex2oat-generated code). The body is an ldxr/stlxr
compare-and-swap loop against [x19] (the Thread::tls32_.state_and_flags
word) toggling between a runnable value and 0x5c000000 before and after
an indirect call through x30 loaded from [x25, #0x10] — i.e. this is
a thread-state transition to a “suspended/native” state, call out, then
transition back. That is the textbook shape of a compiled JNI entry
stub (the same pattern ART emits for GenericJNI/@FastNative
native-method trampolines — see the TrustZoneAccessService example
below for an unambiguous, name-attributed instance of the identical
pattern). We cannot name which System Server method this specific
instance belongs to without parsing the OAT method-offset table (not done
— see Honest limits), but the instruction-level identity of
the pattern itself is not in doubt.
12.8.3.2. 2. KeyChain.odex (speed) — GC write-barrier (“card mark”) idiom¶
At oatexec + 0x20000 (file offset 0x109010), a run of 5 back-to-back
occurrences of the same 12-instruction idiom, each following a different
object’s field store:
0x109040 adrp x1, #0x472000
0x109044 ldr w1, [x1, #0xc8c]
0x109048 cbz w1, #0x109174
0x10904c mov x23, x0
0x109050 mov x0, x1
0x109054 ldr x30, [x19, #0x1d8]
0x109058 blr x30
0x10905c str w24, [x0, #8]
0x109060 ldr x16, [x19, #0x90]
0x109064 lsr w17, w0, #0xa
0x109068 strb w16, [x16, x17]
0x10906c dmb ishst
Two recognizable ART codegen idioms stacked here:
adrp x1,#0x472000/ldr w1,[x1,#0xc8c]/cbz w1,<slow-path>is the standard “resolve-if-not-already-resolved” guard ART emits before touching aClass/String/static-field target: the pointer lives in the.data.img.rel.roboot-image-relocation section (this device’s.data.img.rel.roforKeyChain.odexspans0x46b000–0x46d384, comfortably containing0x472000… actually this particular target falls just past that window, meaning it resolves into the boot image’s own relocatable data rather than this OAT file’s private table — consistent with referencing a boot-classpath class/string, not an app-private one); on a cache miss it calls a runtime helper through[x19, #0x1d8](aQuickEntryPointsslot in theThreadstruct) to resolve it the slow way.str w24,[x0,#8](a plain object-reference field store) immediately followed byldr x16,[x19,#0x90](load the GC card-table base pointer, cached inThread),lsr w17,w0,#0xa(divide the stored-to address by 1024 to get a card index) andstrb w16,[x16,x17](mark that card dirty), then admb ishststore-store barrier. This is ART’s mandatory write barrier: every compiled store of a heap-reference-typed field has to dirty the corresponding card in the concurrent-GC’s card table, and the compiler always emits it inline rather than calling out. This exact five-times-in-a-row shape (5 consecutive field-store + card-mark blocks) is consistent with a compiled constructor or areadFields-style deserialization method initializing several reference fields in sequence, though which one specifically was not determined (same OAT-method-table caveat as above).
12.8.3.3. 3. service-wifi apex jar (speed) — a single trivial compiled method¶
At the very start of oatexec (0x213010, past the same header-garbage
gap seen in example 1):
0x213010 str w2, [x1, #0x10]
0x213014 cbz w2, #0x213024
0x213018 ldr x16, [x19, #0x90]
0x21301c lsr w17, w1, #0xa
0x213020 strb w16, [x16, x17]
0x213024 dmb ishst
0x213028 ret
The same card-mark idiom as example 2, but with the compiler’s
null-check optimization visible: cbz w2, #0x213024 skips the card-mark
entirely when the value just stored is null (a null reference can never
point at a young/uncollected object, so marking the card would be wasted
work) — a 7-instruction compiled setter, and (per the byte-range check
against oatlastword) one of only a literal handful of compiled methods
in this 12 MB APEX jar’s OAT file relative to its multi-megabyte DEX
payload.
12.8.4. 4. TrustZoneAccessService.odex (vendor, quicken) — six JNI stubs, name-attributed¶
This is the one file in the whole scan where we could tie compiled code to
named Java methods with confidence, because the paired .apk is small
enough to extract its classes.dex directly and read the method table.
artifacts/vendor_a/app/TrustZoneAccessService/TrustZoneAccessService.odex
reports compiler-filter=quicken (not speed) yet still has a nonzero
oatexec — 1,752 bytes, from 0x2000 to 0x26d4. Under quicken,
dex2oat compiles no regular Java-bytecode methods but does still
compile a small, fixed-shape entry trampoline for every native-declared
Java method, because there is no other way to call into JNI without one.
Pulling classes.dex straight out of the APK (7z e ... classes.dex)
and grepping its string pool confirms the class and its six native methods:
Lcom/qualcomm/qti/qms/service/trustzoneaccess/TZAccessService;
nativeGetEnvHandle
nativeGetOpenerHandle
nativeInit
nativeInvoke
nativeIsNull
nativeRelease
— six methods, and the disassembly of oatexec is exactly six
back-to-back trampolines of near-identical shape (at 0x2000, 0x21a0,
0x22a0, 0x2390, 0x2480, 0x25e0 — each preceded by the same
kind of header-garbage gap as example 1, each ending in a ret/slow-path
pair). The count matching exactly is strong circumstantial evidence for
“these six stubs are these six methods, in some order”; we did not
establish the exact stub-to-name mapping (that needs the OAT method-offset
table, not done here), but the structural finding — this file’s entire
compiled-code footprint is JNI glue for a Mink/TrustZone IPC class, nothing
else — stands on its own. One representative stub (nativeInit, most
likely, given it takes the most arguments of the six — 6 registers pushed
into the JNI local-ref frame vs. 1–5 for the others):
0x2010 sub sp, sp, #0xd0
0x2014 stp x19, x20, [sp, #0x70]
... ; full quick-ABI register save
0x203c str x0, [sp] ; this/jclass
0x2040 str w1, [sp, #0xd8] ; spill JNIEnv-visible args
0x2044 stur x2, [sp, #0xdc]
0x2048 str w3, [sp, #0xe4]
0x204c str w4, [sp, #0xe8]
0x2050 str w5, [sp, #0xec]
0x2054 str w6, [sp, #0xf0]
0x2058 str w7, [sp, #0xf4]
0x205c mov x16, #6 ; JNI local-ref-table "6 refs" marker
0x2060 str w16, [sp, #0x10]
0x2064 ldr x16, [x19, #0x128] ; Thread::tlsPtr_.jni_env / top handle scope
0x2068 str x16, [sp, #8]
0x206c add x16, sp, #8
0x2070 str x16, [x19, #0x128] ; push new HandleScope/local-ref frame
... ; per-arg: null stays null, else &stack-slot
0x20b0 mov x0, x19
0x20b4 ldr x16, [x0, #0x338] ; QuickEntryPoints: JNI method-start helper
0x20b8 blr x16
...
0x2120 ldr x0, [x19, #0xd8] ; ArtMethod* / down-call target
0x2124 ldr x16, [sp, #0x10]
0x2128 ldr x16, [x16, #0x18] ; dlsym'd native fn pointer
0x212c blr x16 ; call into libnative-api.so
0x2130 ldr w0, [sp, #0x3c]
0x2134 mov x1, x19
0x2138 ldr x16, [x1, #0x350] ; QuickEntryPoints: JNI method-end helper
0x213c blr x16
0x2144 ldr x16, [x19, #0xa8] ; check pending suspend/exception
0x2148 cbnz x16, #0x2180 ; slow path -> brk #0 (deopt/exception trap)
0x214c ldp x19, x20, [sp, #0x70]
... ; full register restore
0x217c ret
This is the ART “GenericJNI” argument-marshalling/handle-scope pattern:
push a JNI local-reference-table frame recorded in Thread ([x19,
#0x128]), replace each non-null reference argument with a pointer to its
own stack slot (a lightweight indirect reference so the native side can’t
outlive the frame), call the JNI method-entry QuickEntryPoint
([x19,#0x338]), blr the actual native function pointer, call the
JNI method-exit QuickEntryPoint ([x19,#0x350]/[x19,#0x368] — two
different exit helpers appear across the six stubs, unconfirmed whether this
is return-type dependent (reference-returning vs. primitive-returning)), then
check for a pending suspend request/exception before restoring registers.
The dex string pool also contains the literal string native-api
positioned immediately before the six nativeXxx method-name strings —
consistent with being the argument to System.loadLibrary("native-api").
libnative-api.so does exist on this device, at
artifacts/vendor_a/lib64/libnative-api.so (and a 32-bit copy in
lib/) — confirming the target of these trampolines’ final blr, though
that library’s own internals were not examined here (out of scope —
this page is about the OAT container, not the Mink native implementation).
The dex string pool additionally references com.qualcomm.qti.qms.api.mink.
{CMinkObject,IMinkObject,JMinkObject} and
com.qualcomm.qti.qms.api.minksocket.IMinkSocketFd — Qualcomm’s “Mink” IDL
IPC framework, which is how privileged Android userspace processes invoke
QSEE trustlets on this SoC family. This is the userspace-JNI edge of the same
QSEECOM bridge Security and DRM userspace documents from the HAL/daemon side
(qseecomd / libQSEEComAPI.so / vendor.qti.hardware.qseecom@1.0);
TZAccessService is a second, separate (Mink-based rather than
QSEECOM-ioctl-based) path into the same secure world, not previously
catalogued on this page’s sibling doc.
12.8.5. The .vdex container (lower priority, not fully resolved)¶
.vdex files hold the original/quickened DEX bytecode plus a small
quickening-info table — never native code. Header, confirmed on
services.vdex (297,360 bytes):
00000000: 7664 6578 3032 3700 0400 0000 0000 0000 vdex027.........
00000010: 3c00 0000 0c00 0000 0100 0000 0000 0000 <...............
00000020: 0000 0000 0200 0000 4800 0000 3b49 0200 ........H...;I..
Magic is vdex027\0 (8 bytes) followed by several 32-bit fields we did
not map to named VdexFile::VdexFileHeader struct members with
confidence (same reasoning as the OAT header: the layout has shifted across
AOSP versions and we had no source tree to check it against for “027”
specifically). Structural confirmation that this is a DEX-bytecode
container rather than something else: a plain byte search for
Landroid/Ljava class-descriptor prefixes finds real hits starting
around file offset 0x101** (e.g. ``Landroid/view/accessibility/
AccessibilityRecord; at 0x101x4), and immediately before that region
is a table of steadily-increasing 4-byte little-endian offsets — the shape
of a DEX string_ids table pointing forward into a string-data section.
Unexplained: a plain search for the literal bytes dex\n (the standalone-.dex
file magic + version marker, e.g. dex\n035\0) returns zero matches
anywhere in services.vdex, despite the file demonstrably containing real
DEX structural data (string tables, type descriptors). Either this vdex
format variant elides the per-embedded-dex magic/checksum/signature header
(storing dex payloads as a shared, header-less blob addressed purely by the
vdex header’s own offset/size fields) or the embedded dex bodies are processed in some way not identified here.
The vdex section table was not fully parsed to resolve this.
12.8.6. Honest limits¶
No OAT method-offset table (the per-
OatDexFile/per-class/per-method structures insideoatdatathat map a DEXmethod_idto a code offset) was parsed. Every disassembly excerpt above is address-only — real, verified machine code at a real, verified file offset — but method name attribution was only possible forTrustZoneAccessService(by the 6-methods/6-stubs count match against its APK’s DEX, not by parsing OAT metadata), and even there the specific stub-to-method order is not established. A fullOatQuickMethodHeader/OatMethodOffsetsparser would be needed for exact per-method boundaries.The fixed-width portion of
OatHeaderpreceding the key/value store (checksum, instruction-set-features bitmap, trampoline offsets, etc.) was not mapped field-by-field; only the leading magic/version and the key/value store (found by direct string search, not by walking the struct) were used.The
.vdexper-dex-file section layout was not resolved (see above).libnative-api.so(the JNI target of theTrustZoneAccessServicestubs) was located but not analyzed — out of scope for an OAT-container page.92 of the 308
.odexfiles are theoat/arm/32-bit copies; the bulk scan covers them (ELF32 support was added tooat_scan.py), but no 32-bit file’soatexecwas individually hand-disassembled — spot checks of a few (org.apache.http.legacy.odex,com.android.location. provider.odex) via the compiler-filter/size columns alone show the same filter/size pattern as their arm64 siblings, so no separate 32-bit disassembly examples are included here.
12.8.7. Provenance¶
- Source:
All 308
.odexfiles (each with a matching.vdex) underartifacts/{system_a,vendor_a,product_a,system_ext_a}/**/oat/**/, plus the 60boot-*.vdexfiles directly undersystem_a/system/framework/(368.vdextotal). Specific files quoted above:artifacts/system_a/system/framework/oat/arm64/services.odex,artifacts/system_a/system/app/KeyChain/oat/arm64/KeyChain.odex,artifacts/system_a/system/framework/oat/arm64/apex@com.android.wifi@ javalib@service-wifi.jar@classes.odex,artifacts/vendor_a/app/TrustZoneAccessService/oat/arm64/ TrustZoneAccessService.odex(+ its sibling.apkand.vdexin the same directory),artifacts/system_a/system/app/knote2-release/oat/ arm64/knote2-release.odexand the other Onyx-app odex files named in The compiler-filter reality, andartifacts/system_a/system/framework/oat/arm64/services.vdex.- Method:
Container identification: raw
xxd/Pythonstructreads ofe_ident/e_machine/e_typeand theoat\nmagic; cross- checked againstllvm-objdump -h/-T(pixi LLVM 23.1.1) which independently confirms the section table and dynamic symbol table.Bulk survey of all 308 files:
artifacts/re_static/oat_scan.py(written for this task, saved for reuse) — walksPT_DYNAMIC->DT_SYMTAB/DT_STRTAB/DT_HASHby hand (no reliance on a section-header table, works on stripped ELFs) to recoveroatdata/oatexec/oatlastwordand computes realoatexecsize from symbol values; separately byte-searches the first 32 KiB for thecompiler-filter\0key. Handles both ELF64/EM_AARCH64(oat/arm64/) and ELF32/EM_ARM(oat/arm/). Reproduce with:python3 artifacts/re_static/oat_scan.py --csv --find artifacts > /tmp/oat_scan_all.csv(takes a few seconds; no external dependencies beyond the standard library).Disassembly:
artifacts/re_static/elfdis.py(pre-existing tool, reused as-is) with system/usr/bin/python3+ capstone 5.0.7 (the pixi python lacks capstone, so the system interpreter was used instead).TrustZoneAccessServicemethod-name attribution:7z e TrustZoneAccessService.apk classes.dex, thenstrings classes.dexgrepped for the class’s method-name string-pool entries (no DEX decompiler used — this was a raw string-pool read, not a jadx-style decompile).No code was executed; no OAT/DEX/VDEX file was mutated. All figures (counts, sizes, offsets) are re-derivable from the commands given above against the artifacts as they sit in this checkout.
- Cross-refs:
Preinstalled apps (Boox, GMS, AOSP) (the app catalogue this page opens up), The Onyx/Boox platform layer (the Android-version split between
systemandvendorthat explains the OAT-version-183-vs-244 divide), Security and DRM userspace (the QSEECOM/Mink bridge to TrustZone thatTrustZoneAccessServiceis a second instance of), Userspace boot — init, SELinux, A/B updates (OnyxOtaService, decompiled separately by DEX source rather than by this page’s OAT-container approach — its own.odex/.vdexAOT copy was not separately examined there, and is covered by this page’s bulk scan:compiler-filter=verify, no compiled code, like the other Onyx apps above).