VM BYTECODE DECODE - dm1.OooOO0O AND o0O00OOO.OooO
Analysis only. No code changed. Used the existing spec: dispatch_64.json,
handler_class.json, op_jni.json, member_slots.tsv, isa_table.h, and the
CMasterCloner docs/sdk-audit notes.
WHAT THE TWO PROGRAMS ACTUALLY DO
Both are DEX/VM bytecode, not native code. The operands are member-slot offsets into the
descriptor the VM hands them; the real class/method names are resolved at runtime by
cacheInitial/getCacheClass from the DEX. The static spec gives the JNI call type and the
slot offset, not the literal name.
dm1.OooOO0O(Intent, ActivityInfo) - 187 bytes at 0x31ebe
// context: dm1 Binder at +0x30, Intent at +0x38, ActivityInfo at +0x40
GetObjectField(slot+0xd8) // 0x9e
load/compare // 0x9b x2, 0x59
GetIntField(slot+0x08) // 0xd5
load constant / compare // 0x92
invoke // 0x84 x2
move // 0x44 x2
SetBooleanField(slot+0xdf) // 0x8f
move // 0x44
GetBooleanField(slot+0xdf) // 0xfa
branch // 0x78
return/branch // 0x8a
GetObjectField(slot+0x0f) // 0x9e
SetObjectField(slot+0xd7) // 0xc8
GetObjectField(slot+0x06) // 0x9e
GetIntField(slot+0x1e) // 0xd5
GetObjectField(slot+0x0e) // 0x9e
branch // 0x78
JNI call (type slot 0x62, index 0x07) // 0x04
JNI call (type slot 0x63, index 0x07) // 0x04
alloc/call (slot 0x25) // 0x34
JNI call (type slot 0x52, index 0x70) // 0xad
JNI call (type slot 0x72, index 0x30) // 0x04
JNI call (type slot 0x73, index 0x30) // 0x04
NewLocalRef // 0x95
SetObjectField(slot+0xd8) // 0xc8
JNI call (type slot 0x5a, index 0x07) // 0x04
move // 0x44
NewLocalRef // 0x95
SetObjectField(slot+0xd9) // 0xc8
SetObjectField(slot+0xd4) // 0xc8
move // 0x44
alloc/call (slot 0x20) // 0x34
GetObjectField(slot+0x0b) // 0x9e
GetObjectField(slot+0x0e) // 0x9e
JNI call (type slot 0x41, index 0x540) // 0xad
branch // 0x78
alloc/call (slot 0x25) // 0x34
JNI call (type slot 0x52) // 0xad (truncated tail)
o0O00OOO.OooO(dm1) - 130 bytes at 0x32636
GetObjectField(slot+0x26f) // 0x9e
branch // 0x78
GetObjectField(slot+0xde) // 0x9e
JNI call (type slot 0x5a4, idx 0x51) // 0x04
JNI call (type slot 0x5a0, idx 0x01) // 0x04
load // 0x64
branch // 0x78
GetBooleanField(slot+0xe1) // 0xfa
compare // 0x4e
move // 0x44
JNI call (type slot 0x1bd) // 0x04
load // 0x64
JNI call (type slot 0x59e, idx 0x21) // 0x04
load // 0xc0
call/instanceof (slot 0x165) // 0xbe
JNI call (type slot 0x1c4, idx 0x10) // 0x04
load // 0x9b
branch // 0x78
GetObjectField(slot+0x26f) // 0x9e
IsSameObject(slot+0x49) // 0xb2
GetObjectField(slot+0xd6) // 0x9e
GetObjectField(slot+0xde) // 0x9e
branch // 0x78
load // 0x9b
JNI call (slot 0x27a) // 0xe2
JNI call (slot 0x41, 0x00) // 0xb6
compare/branch // 0xc1
JNI call (slot 0x27a) // 0xe2
JNI call (slot 0x42, 0x00) // 0xb6
JNI call (slot 0x494, idx 0x43) // 0x94
GetObjectField(slot+0x285) // 0x9e
JNI call (type slot 0x20) // 0x04
REAL CLASS/METHOD NAMES
The literal names are NOT in the static spec. The operands are slot offsets, and the VM
resolves them at runtime through cacheInitial/getCacheClass against the DEX descriptor.
What libcm.so strings prove:
- startActivity -> Context.startActivity(Intent) / Activity.startActivity(Intent)
- android/app/Activity, android/view/View, android/view/ViewGroup, android/view/Window
- android/os/Binder, android/os/Parcel
Likely calls:
1. Intent / ActivityInfo field reads (component, flags, etc.).
2. startActivity(intent) -> asks the system server to create the ActivityRecord and
return a window token.
3. Binder transaction on dm1 -> binds the clone identity to the activity record.
4. Activity.setContentView(view) / ViewGroup.addView(view) -> attaches the target's view
to the FIEL$A* proxy's android.R.id.content.
Marked as runtime-resolved: the exact class/method name for every 0x04/0xad/0xe2/0xb6/0x94
call. The static spec has only the JNI type and slot offset.
DOES OUR VM ENGINE ALREADY RUN THESE TWO PROGRAMS?
All opcodes in both programs are implemented. None of 0x11, 0x33, 0xa5, 0xe8 appears in
either program. unimplemented_opcodes.txt says 232/256 implemented; the "raised" list
(0x0e, 0x17, 0x18, 0x35, 0x63, 0x69, 0x8f, 0x9e, 0xc7, 0xd5) only throws on a null
operand, which is correct behavior with a real context.
But:
- The dcvm engine is a reimplementation, not the original libcmvm.so.
- These two specific programs have NOT been run/verified; traced_opcodes.txt has 18
opcodes, not these streams.
- The JNI calls need the DEX class/method cache and a real Android runtime to resolve
names; the dcvm host must supply that.
Blocking opcodes: none of the four "reported unimplemented" ones. The only risk is the JNI
host resolution, not the opcode coverage.
HOW THIS CHANGES CLONERUNNER
Must be replaced:
- CloneRunner.startActivity0 - the newActivity + attach + makeVisible path.
- SandboxLaunchTransactionSwap, SandboxActivityThreadHook,
SandboxClientTransactionReplayer, SandboxTransactionItems - the manual-hosting workaround
layer.
What stays:
- CloneClassLoader - still loads the target DEX.
- CloneContext, CloneApplicationInfo, CloneComponentHost.
- ProxyHosts slot mapping and SandboxEngineOrchestrator native storage sandbox.
- CloneActivity as the proxy, but it must be launched by the system (startActivity), not
newActivity.
- The Compose host UI and VirtualSpaceEngine slot selection.
Replacement flow:
1. startActivity(proxyIntent) -> real ActivityRecord + window token.
2. Bind the clone/dm1 identity to that record.
3. Attach the target's view to the proxy's android.R.id.content (not a second window).
4. Drive lifecycle from the real Activity lifecycle.
That is the same model the original uses, and it removes the white-screen/kick-out root
cause.