Xiangxiang's Personal Site

Machine Learning & Security Engineer
生命不息,折腾不止,留下一点活着的记录.

View on GitHub
13 November 2026

ArkTS Runtime & HookKit

by xiangxiang

ArkTS Runtime & HookKit

0x00 Overview

┌────────────────────────────────────────────────────────────────┐
│                        ArkTS Source (.ets)                     │
└─────────────────────────────────┬──────────────────────────────┘
                                  │  es2abc (es2panda Frontend)
                                  ▼
┌────────────────────────────────────────────────────────────────┐
│                        Ark Bytecode (.abc)                     │
│                 (Panda Bytecode: Compact Reg-Acc ISA)          │
└─────────────────┬───────────────────────────────┬──────────────┘
                  │ (+ .ap PGO Profile)           │
        ark_aot_compiler                  JIT / Interpreter
                  ▼                               ▼
┌───────────────────────────┐   ┌────────────────────────────────┐
│  Ark Native AOT (.an/.ai) │   │  Ark Runtime (ETS VM)          │
│  Optimized Machine Code   │   │  - ASM Interpreter (stub.an)   │
└─────────────────┬─────────┘   │  - Inline Caches (IC) & PGO    │
                  │             │  - Regional / Shared / CMC-GC  │
                  ▼             └─────────────────┬──────────────┘
┌─────────────────────────────────────────────────▼──────────────┐
│                      HarmonyOS Microkernel                     │
└────────────────────────────────────────────────────────────────┘

0x01 Source Code to Bytecode

# Compile ArkTS (.ets) to Ark Bytecode (.abc) as an ES Module
es2abc --module --merge-abc input.ets --output input.abc

# Disassemble .abc into Panda Assembly (.pa) with verbose metadata
ark_disasm --verbose input.abc input.pa

0x02 ArkCompiler ETS Runtime

Sub System Source Directory Core Responsibilities
Common Components common_components Unified runtime foundation (objects/, heap/, mutator/): CMC-GC (Concurrent Mark-Compact), Regional Heap, Write Barriers, Lock-Free String Table, and Mutator/Safepoint management
ECMAScript / ArkTS Engine ecmascript Object model (JSObject/JSHClass); ASM (stub.an) & C++ Bytecode Interpreter; Builtins; @Sendable Shared Heap; Inline Caches (IC) & PGO Profiler
Compiler Infrastructure ecmascript/compiler Circuit IR (Sea-of-Nodes), AOT (ark_aot_compiler), JIT, and Stub compiler (ark_stub_compiler) with LLVM and LiteCG backends
AOT (Ahead-of-Time) compilation compiler_service System Ability (aot_compiler_service) IPC wrapper for thermal- and idle-aware on-device AOT compilation scheduling

2.1 JSTaggedValue

//  NaN-boxing pointer is used and the upper 16 bits (two bytes) act as the primary tag:
//    Object:             [0x0000] [48-bit 8B-aligned direct pointer]
//    WeakRef:            [0x0000] [47-bit direct pointer | 0x1]
//                  /     [0x0001] [48-bit payload]
//    TaggedDouble:       ......   (IEEE-754 double + 0x0001000000000000ULL offset)
//                  \     [0xFFFE] [48-bit payload]
//    TaggedInt:          [0xFFFF] [0x0000] [32-bit signed integer]
//
//  Immediate Special Markers (Top 48 bits == 0):
//    Undefined:   [56 bits 0] | 0x02          // 0000_0010
//    Null:        [56 bits 0] | 0x03          // 0000_0011
//    Hole:        [56 bits 0] | 0x05          // 0000_0101 (Engine-internal empty array/property slot)
//    False:       [56 bits 0] | 0x06          // 0000_0110
//    True:        [56 bits 0] | 0x07          // 0000_0111
//    Exception:   [56 bits 0] | 0x0A          // 0000_1010
//    OptimizedOut:[56 bits 0] | 0x12          // 0001_0010

JSTaggedValue (64-bit uint64_t) [ecmascript/js_tagged_value_internals.h]
  |
  +---> [Immediate Value (Encoded in 64-bit register, No Heap Allocation)]
  |       |-- TaggedInt     : [0xFFFF][0x0000][32-bit signed int] (TAG_INT = 0xFFFF000000000000ULL)
  |       |-- TaggedDouble  : IEEE-754 bits + 0x0001000000000000ULL (Top 16 bits in 0x0001..0xFFFE)
  |       |-- Boolean       : 0x06 (VALUE_FALSE), 0x07 (VALUE_TRUE)
  |       |-- Undefined     : 0x02 (VALUE_UNDEFINED)
  |       |-- Null          : 0x03 (VALUE_NULL)
  |       +-- Engine Markers: 0x05 (VALUE_HOLE), 0x0A (VALUE_EXCEPTION), 0x12 (VALUE_OPTIMIZED_OUT)
  |
  +---> [Heap Pointer ((value & TAG_HEAPOBJECT_MASK) == 0, != 0x05 VALUE_HOLE, != 0x00)]
          |   // TAG_HEAPOBJECT_MASK = 0xFFFF000000000006ULL (Checks Top 16 bits == 0 and Bits[2:1] == 00).
          |   // Because 0x05 (0b0101) also has Bits[2:1] == 00, IsHeapObject() explicitly excludes 0x05 and 0x00.
          |-- Strong Ref : [0x0000][48-bit 8B-aligned address] (Bit 0 == 0)
          |-- Weak Ref   : [0x0000][47-bit address | 0x1]      (Bit 0 == 1, TAG_WEAK)
          |
          v
        common::BaseObject [common_components/objects/base_object.h & common/type_def.h]
          |
          +---> TaggedObject [ecmascript/mem/tagged_object.h]
                  |-- [Offset 0: 64-bit ClassWord / StateWord] --> JSHClass* [ecmascript/js_hclass.h#L380]
                  |                                                  +-- JSType [ecmascript/js_hclass.h#L360]
                  |
                  +-- Subtypes of TaggedObject:
                        |-- ECMAObject -> JSObject [ecmascript/js_object.h#L366]
                        |     (Standard JS/ArkTS objects, JSArray, JSFunction, JSSharedObject)
                        |-- EcmaString [ecmascript/ecma_string.h#L64]
                        |     (In-place overlay of BaseString [common_components/objects/base_string.h]:
                        |      LineString, SlicedString, TreeString, CachedExternalString)
                        |-- BigInt     [ecmascript/js_bigint.h#L32]
                        |-- JSSymbol   [ecmascript/js_symbol.h#L28]
                        +-- JSHClass   [ecmascript/js_hclass.h#L380]

2.2 JS Object and JS HClass Memory Layout

        JSObject and JSHClass Layout (v6.1-LTS)

Properties                         JS Object                    JS HClass (72B)
+------------+                     +------------+               +--------------------+
|arrayHClass | <---------+         | ClassWord  | ------------> |    Meta HClass     | (0)
+------------+           |         +------------+               +--------------------+
| property 0 |           |         | Hash       |               |    Proto           | (8)
+------------+           |         +------------+               +--------------------+
| property 1 |           +-------- | Properties |               |    Layout          | (16)
+------------+                     +------------+               +--------------------+
| ...        |           +-------- | Elements   |               |    Transitions     | (24)
+------------+           |         +------------+               +--------------------+
                         |         | inl-prop-0 |               |    Parent          | (32)
Elements                 |         +------------+               +--------------------+
+------------+           |         | inl-prop-1 |               | ProtoChangeMarker  | (40)
|arrayHClass | <---------+         +------------+               +--------------------+
+------------+                     | ...        |               | ProtoChangeDetails | (48)
| value 0    |                     +------------+               +--------------------+
+------------+                                                  |    EnumCache       | (56)
| value 1    |                                                  +--------------------+
+------------+                                                  | BitField/BitField1 | (64..72)
| ...        |                                                  +--------------------+
+------------+

Proto: [[Prototype]] in ECMA spec
Layout: LayoutInfo recording property keys and attributes
ProtoChangeMarker / ProtoChangeDetails: monitor [[Prototype]] chain mutations for IC invalidation
EnumCache: cached enumerable keys for for-in / Object.keys

0x03 Hook Framework

3.1 How it works

3.2 What is not working

3.3 Implementation (Simplified)

type OriginalFn = (...args: Object[]) => Object;

type InstanceProxy = (original: OriginalFn, self: Object, args: Object[]) => Object;

function asRecord(v: Object): Record<string, Object> {
  return v as unknown as Record<string, Object>;
}

function resolveMethodOnTarget(target: Object, method: string): OriginalFn | undefined {
  try {
    const direct = asRecord(target)[method];
    if (typeof direct === 'function') {
      return direct as OriginalFn;
    }
  } catch (e) {

  }
  return undefined;
}

function hookInstanceMethod(cls: Object, method: string, proxy: InstanceProxy): boolean {
  const proto = asRecord(cls)['prototype'];
  const original = asRecord(target)[method]

  const wrapper = function (this: Object, ...args: Object[]): Object {
    const self = this;
    const boundOriginal: OriginalFn = function (this: Object, ...callArgs: Object[]): Object {
      const targetThis = (this !== undefined && this !== null) ? this : self;
      return Reflect.apply(original, targetThis, callArgs);
    } as OriginalFn;
    return proxy(boundOriginal, self, args);
  };

  // Override the original function to our wrapper
  // Static method - Override proto instead of asRecord(proto)
  Reflect.defineProperty(asRecord(proto), method, {
      value: wrapper,
      writable: true,
      enumerable: true,
      configurable: true
    });
  return true;
}
tags: HarmonyNext ArkTS security