1.2.2. The EL3 “Sec” monitor and the EL1 hand-off

PH12 of xbl_a.bin is not ordinary code. It loads at 0x14950000 with the distinctive PT_LOAD flags 0x05000005, and its entry at 0x14953000 installs an EL3 exception environment. This is the same feature set — vaddr, flags, entry offset, SCR_EL3/VBAR_EL3 install — as the Firehose programmer’s nested EL3 monitor that Firehose fuse read and secure-boot confirmation proves is signature-enforced unconditionally. The segment also carries the XBL Sec Attestation Root CA 4 string (file 0x2beb87). The consequence for replacing XBL (you can substitute the EL1/UEFI payload but not clean-room this EL3 root) is analysed in Boot chain and exception-level hand-off (PBL → XBL → ABL).

The ELF e_entry (0x14817908, in PH2) is not this monitor — it is the EL1 XBLCore reset stub the monitor erets down to. On this SoC XBLCore executes at EL1 and reaches EL3 through a single smc #0 at 0x14699024 (PH7).

1.2.2.1. EL3 “Sec” monitor — entry disassembly

PH12 is not laid out like the other code segments: its first ``0x3000`` bytes are a literal/data header, and executable code only begins at 0x14953000 (= 0x14950000 + 0x3000, file 0x2c17a0). A naive linear disassembly of the segment from 0x14950000 desyncs on that header and never re-aligns to the real instructions — which is why a whole-image sweep can wrongly report “no scr_el3”. Disassembled from 0x14953000 the monitor’s EL3 set-up is explicit (all instructions below are Capstone CS_ARCH_ARM64 output):

0x14953000: mov   x9, x0            ; stash PBL boot-arg
0x14953004: adr   x0, #0x14953000   ; self-position check:
0x14953008: ldr   x1, #0x149531a8   ;   literal[0x149531a8] = 0x14953000
0x1495300c: cmp   x0, x1
0x14953010: b.ne  #0x14953010       ;   hang if not loaded where expected
0x14953014: mov   x0, #0xe00
0x14953018: msr   scr_el3, x0       ; SCR_EL3 = 0xE00  (RW|ST|SIF, still Secure)
0x1495301c: isb
0x14953020: ldr   x0, #0x149531b0   ;   literal = 0x14964800
0x14953024: msr   vbar_el3, x0      ; VBAR_EL3 = 0x14964800  (EL3 vectors)
0x14953028: mov   x0, #0x1c0
0x1495302c: msr   daif, x0          ; mask A/I/F
0x14953030: ldr   x10, #0x149531b8  ;   = 0x14967080
0x14953034: str   x9, [x10]         ; persist boot-arg
0x1495303c: movk  x0, #0x1495, lsl #16
0x14953044: bl    #0x14961488       ; relocate/copy (memcpy), sp = 0x14953000
...
0x14953070: bl    #0x1495313c       ; sanitise EL1 register file (see below)
0x14953074: bl    #0x149563d4       ; loader dispatch -> x0 = EL1 entry
0x14953078: msr   elr_el3, x0       ; ELR_EL3 = computed EL1 entry point
0x1495307c: mrs   x0, hcr_el2
0x14953084: orr   x0, x0, #0x80000000
0x14953088: msr   hcr_el2, x0       ; HCR_EL2.RW = 1  (EL1 is AArch64)
0x1495308c: mov   x0, #0x1c5
0x14953090: msr   spsr_el3, x0      ; SPSR_EL3 = 0x1C5  (return to EL1h, A/I/F masked)
0x14953094: mrs   x0, scr_el3
0x14953098: orr   x0, x0, #1
0x1495309c: msr   scr_el3, x0       ; SCR_EL3 |= NS  -> 0xE01  (Non-secure)
...                                 ; x0..x30 cleared to 0
0x14953128: ldr   x0, #0x149531d8   ;   = 0x14967088
0x1495312c: ldr   x0, [x0]          ; x0 = boot-info pointer for EL1
0x14953130: dsb   sy
0x14953134: isb
0x14953138: eret                    ; drop to EL1h at ELR_EL3

So the “Sec” monitor is the piece that owns EL3 and then hands control down to EL1: it installs VBAR_EL3, sets SCR_EL3 (Secure 0xE00 while it runs, then flips the NS bit to 0xE01 just before returning), masks exceptions via DAIF, then erets with SPSR_EL3 = 0x1C5 (EL1h) and HCR_EL2.RW = 1. The eret target (ELR_EL3) is computed at run time by the loader-dispatch routine 0x149563d4 (it walks two tables of loader callbacks at 0x149617e8 (6) and 0x149617c0 (5), tailing into 0x14958c00); the XBLCore EL1 entry 0x14817908 appears in the whole image only at file 0x18 (the ELF header e_entry field), never as an in-code constant, consistent with a computed jump. x0 is loaded with the boot-info pointer 0x14967088 per the AArch64 SMCCC/boot convention.

Before the eret the monitor scrubs the EL1 context it is about to enter (0x1495313c) — every EL1 system register is written with xzr:

0x1495313c: msr actlr_el1, xzr    0x14953148: msr contextidr_el1, xzr
0x1495314c: msr amair_el1, xzr    0x14953158: msr mair_el1, xzr
0x14953160: msr sctlr_el1, xzr    0x14953170: msr tcr_el1, xzr
0x14953178: msr ttbr1_el1, xzr    0x1495317c: msr ttbr0_el1, xzr
0x14953184: msr vbar_el1, xzr     0x14953190: msr elr_el1, xzr
0x14953194: msr sp_el1, xzr       0x1495319c: msr spsr_el1, xzr   ... (and afsr0/1,
far, csselr, esr, par, tpidr*, cntp_ctl/tval/cval, sp_el0)

EL3 state the monitor does establish (confirmed by disassembly from 0x14953000):

Register

Value / action

Where

SCR_EL3

0xE00 then |=NS0xE01

0x14953018 / 0x1495309c (6 writes total)

VBAR_EL3

0x14964800

0x14953024

SPSR_EL3

0x1C5 (EL1h, A/I/F masked)

0x14953090

ELR_EL3

computed EL1 entry

0x14953078

HCR_EL2.RW

1 (EL1 AArch64)

0x14953088

DAIF

0x1C0 (mask A/I/F)

0x1495302c

ESR_EL3 / TPIDR_EL3

read in the EL3 handlers

0x14961510 / 0x14959f2c

What it does *not* touch: there is no write to CPTR_EL3, MDCR_EL3 or SCTLR_EL3 (and no MAIR_EL3/TCR_EL3/TTBR0_EL3) in PH12 — the EL3 monitor runs with the EL3 MMU off / flat and does not reprogram FP-trap or debug at EL3. (Those EL3-MMU registers do appear in the image, but only in the EL-agnostic boot-MMU helpers reached from the EL1 XBLCore path — see EL1 XBLCore reset stub.) This entry is byte-for-byte the same shape as the TZ tz_a EL3 stub at 0x14680000 (mov x0,#0xe00; msr scr_el3; mov x0,#0x1c0; msr daif) documented in Secure world and coprocessors (TZ, HYP, trustlets).

The EL3 vector table at VBAR_EL3 = 0x14964800 is a standard 16×``0x80`` AArch64 table. The Current-EL synchronous slots tail into a common handler at 0x149614a8 (which builds a 0x130-byte frame and saves ELR_EL3); the Lower-EL AArch64 synchronous slot (+0x400, 0x14964c00) is the SMC/lower-EL dispatcher — it re-asserts SCR_EL3 = 0xe00 (Secure) and builds a 0x110-byte frame including SP_EL0/SP_EL1 before branching to 0x14961558:

0x14964c00: stp   x0, x1, [sp, #-0x10]!
0x14964c04: mrs   x0, scr_el3
0x14964c08: mov   x1, #0xe00
0x14964c0c: msr   scr_el3, x1        ; handle call in Secure state
...
0x14964c70: b     #0x14961558

This is the EL3 end of the SMC path whose EL1 caller is the gateway below.

1.2.2.2. EL1 XBLCore reset stub

The monitor’s eret lands in the EL1 XBLCore reset stub — the ELF e_entry 0x14817908 (PH2, file 0x3908). It manipulates only the EL1 copies of the control registers:

0x14817908: b     #0x14817910       ; over a udf padding word
0x14817920: mov   x7, x0            ; save boot-arg
0x14817928: mov   sp, x0            ; sp = 0
0x14817934: tlbi  vmalle1
0x14817938: ic    iallu
0x14817944: mrs   x0, sctlr_el1
0x14817948: and   x0, x0, #~1       ; M=0 (MMU off)
0x1481794c: and   x0, x0, #~4       ; C=0 (D-cache off)
0x14817950: orr   x0, x0, #8        ; SA=1 (SP-align check)
0x14817954: msr   sctlr_el1, x0
0x14817960: ldr   x0, #0x148179c0   ;   literal = 0x14817000
0x14817964: msr   vbar_el1, x0      ; VBAR_EL1 = 0x14817000  (SBL1 vector table)
0x14817968: mrs   x0, isr_el1
0x1481796c: and   x0, x0, #0x100    ; pending SError?
0x14817970: cbnz  x0, #0x148179bc   ;   -> hang if so
0x14817974: msr   daifclr, #4       ; unmask SError (A)
0x1481797c: mov   x0, #0xb000
0x14817980: movk  x0, #0x1486, lsl #16
0x14817984: mov   sp, x0            ; sp = 0x1486b000
0x14817988: mov   x0, #0x8000       ; zero .bss 0x14868000..0x1486b000
0x1481798c: movk  x0, #0x1486, lsl #16
0x148179a0: stp   xzr, xzr, [x0], #0x10
0x148179ac: b.ne  #0x148179a0
0x148179b0: mov   w0, w7            ; boot-arg
0x148179b4: bl    #0x148222b0       ; XBLCore C loader (enables FP via CPACR_EL1)

Every control-register write here is the EL1 encoding (sctlr_el1, vbar_el1, isr_el1); there is no CurrentEL check and no EL3 write — the stub assumes it is already at EL1. VBAR_EL1 points at the SBL1 vector table at 0x14817000 (16×``0x80``); only the Current-EL-SPx slots are live (Synchronous +0x200 saves all GPRs then bl 0x1482bec0; IRQ +0x280, FIQ +0x300, SError +0x380), while the SP0 and Lower-EL slots are b . hangs. The synchronous handler’s C routine prints Exception caught by SBL1 vector table!! and dumps ELR-EL1/ESR-EL1 (strings at file 0x6c38/0x6c60) — i.e. EL1 exception reporting, corroborating that XBLCore runs at EL1.

A boot-MMU library on this EL1 path can program the EL3 MMU: 0x14818280 writes TTBR0_EL3+TTBR0_EL1, MAIR_EL3/MAIR_EL1 = 0xAAFFF4884A4F4400, TCR_EL3/TCR_EL1 = 0x30019 (T0SZ=25 → 39-bit VA, PS=3 → 42-bit PA), and lays a 4-entry 1 GB-block identity map over 0–4 GB — these six _el3/_el1 register pairs are written back-to-back, unconditionally, with no CurrentEL guard on the _el3 half (no direct bl caller for 0x14818280 was found inside PH2, so its actual call site — and thus whether it in fact runs at EL3 when it executes — is not independently confirmed here). Only the separate SCTLR accessor pair, 0x14817a08 (getter) / 0x14817a28 (setter), which the MMU library also calls to enable M|C|I, is CurrentEL-dispatched: each msr sctlr_el3 there is individually guarded by cmp x1,#3 ; b.ge on CurrentEL and only runs at EL3 — that guard does not extend to the TTBR0/MAIR/TCR writes above.

EL1 XBLCore C main. The reset stub’s bl 0x148179b4 reaches the C entry 0x148222b0 (in PH2), identified by its two panic sites logging the source file sbl1_mc.c (file 0x14818a2f) with a line number and error code through a function pointer at 0x1486c1b0, and by the SBL1, Start banner (file 0x14818a6e). It enables FP/SIMD (0x14818510), copies a 0x180-byte boot-info block from its argument, runs an init/milestone chain, and polls a debug cookie register before continuing — the SBL1 main-control routine of the EL1 loader.

1.2.2.3. EL1 → EL3 SMC gateway (PH7)

PH7 (0x14699000, 0x1230 bytes, R E) is the scm/SMC trampoline — and it carries the only smc instruction in the entire image (0x14699024; the whole-image scan finds smc = 1, hvc/eret outside PH12 = 0). This is how the dropped-to-EL1 XBLCore calls back up into the EL3 monitor (and TZ):

0x14699000: stp   x7, x8, [sp, #-0x10]!   ; save x7,x8 + x15..x30 (x0..x6 = SMCCC args)
0x14699020: stp   x15, x16, [sp, #-0x10]!
0x14699024: smc   #0                      ; trap to EL3 (SMC64)
0x14699028: cmp   x0, #1
0x1469902c: b.eq  #0x14699024             ; SMCCC busy/interrupted -> call again
...                                       ; restore x15..x30, x7, x8
0x14699054: cmp   x7, #0                  ; x7 = result-struct pointer
0x14699058: b.eq  #0x14699068
0x1469905c: str   x1, [x7]                ; write back return values x1..x3
0x14699064: str   x3, [x7, #0x10]
0x14699068: ret

Callers pass Qualcomm SiP function IDs in w0 (e.g. 0x82000117 built by the EL1 exception handler at 0x1482bf0c). The smc #0 lands in the EL3 monitor’s Lower-EL synchronous vector (0x14964c00 above). Together the two ends make the EL3/EL1 split concrete: XBLCore is EL1 code that reaches the resident EL3 “Sec” monitor exclusively through this gateway.

1.2.2.4. Function inventory and call graph

Beyond the entry/monitor stubs above, the EL3 “Sec” segment (PH12) is a small, self-contained runtime. Following the bl/blr targets out of the entry and the vector handlers yields the call graph below (all addresses are PH12 vaddrs; “file” offsets map through the ELF phdrs, 0x14950000 → file 0x2be7a0). Function roles are inferred from structure only — the image is symbol-free, so no Qualcomm names are assigned.

EL3 monitor support routines (reached from the entry 0x14953000):

0x14961488  DC-ZVA zero-fill: `lsr x1,x1,#6; dc zva,x0; add x0,#0x40; loop`.
            The monitor's memset-0. Clears the 0x14950000..0x14953000 EL3
            stack from the entry, and scrubs the stack in the SMC top-half.
0x14957558  stack-protected reader whose result is stored to global
            0x149660b0 (0xDEADDEAD sentinel in the file image); worker
            0x149574a4, logs "(%x)" on canary failure.
0x1495f528  memscpy(dst, dst_size, src, src_size): copies min(sizes) via the
            bytewise loop 0x149575d0. 0x1495f564 is the matching memset.
0x14965078  cache-info helper (reads ``CLIDR_EL1``); 0x14965178/0x14965190
            wrap an I-cache invalidate-all (``ic iallu``).
0x14956560  printf-style logger: parses ``%x``/``%X``/``%s`` using the digit
            table "0123456789ABCDEF" (file 0x14961819) into a 1 KiB character
            ring at 0x1496c0b8 (index masked ``& 0x3ff``). The monitor's
            console; called with a level in ``w0`` and a tag in ``w2``.

EL3 exception path. The VBAR_EL3 Current-EL synchronous slots inject a code (#1/#5) and fall into the common handler 0x149614a8, which saves a 0x130 frame (x0..x30, ELR_EL3, SPSR_EL3, SP_EL0/SP_EL1, ESR_EL3), forces SCR_EL3 = 0xE00 (saving the old value to 0x14966f04), then calls the dump routine 0x149546a4 and dead-loops. Any non-SMC EL3 exception therefore terminates here — this is the “Exception caught”-style panic sink at EL3.

EL3 SMC path. The Lower-EL AArch64 synchronous vector (0x14964c00) saves a 0x110 frame and branches to the top-half 0x14961558:

0x1496155c: mrs  x4, esr_el3
0x14961560: ubfx x4, x4, #0x1a, #6   ; EC = ESR_EL3[31:26]
0x14961564: cmp  x4, #0x17           ; 0x17 = SMC from AArch64
0x14961568: b.ne #0x14961604         ; not an SMC -> re-dispatch (code #9) to 0x149614a8
0x1496156c: mov  x0, sp              ; x0 = saved-register frame
0x14961570: bl   #0x14957614         ; SMC service dispatcher
...                                  ; restore frame; scrub stack (0x14961488)
0x149615f0: msr  scr_el3, x19        ; restore saved SCR_EL3 (from 0x14966f04)
0x14961600: eret                     ; return to the EL1 caller

The dispatcher 0x14957614 masks the function ID to & 0x3FFFFFFF, rejects IDs with bits [23:16] set, then linearly scans a handler table whose start and end pointers live at 0x149660b8/0x149660c0. Each table entry is 0x10 bytes — {u32 funcID; u32 arg_desc; u64 handler} — and on a match the handler pointer at entry+8 is called via blr with x0..x7 taken from the (validated, copied-in) argument buffer. Pointer arguments are bounds-checked against allowed-region tables by 0x14953bac and 0x14959208 before the call.

On this image the table is pre-populated in the file at 0x14962cf0..0x14962dd0 — 14 entries, all in the Qualcomm SiP 0x0200xxxx owner range (verified byte-for-byte through the phdr map):

funcID       arg_desc    handler       funcID       arg_desc    handler
0x02000114   0x00000022  0x149573a8    0x02000119   0x00000001  0x14957050
0x02000117   0x00000022  0x14953728    0x02000214   0x00008017  0x14955d68
0x02000310   0x00000000  0x14956e38    0x02000215   0x00000446  0x14956060
0x02000210   0x00021048  0x14954d0c    0x0200011a   0x00000001  0x1495475c
0x02000211   0x00000083  0x149558fc    0x02000115   0x00000000  0x14956454
0x02000212   0x00000043  0x14954868    0x02000116   0x00000000  0x14958f84
0x02000213   0x00000043  0x149548fc    0x02000118   0x00000001  0x149538a0

This closes the EL1→EL3 loop: the 0x82000117 SiP ID that the EL1 exception handler builds (0x1482bf0c, in EL1 → EL3 SMC gateway (PH7)) masks to 0x02000117 — the table’s second row, handler 0x14953728. Handler roles are only partially recoverable without symbols; structurally, handler 0x14956454 (ID 0x02000115) zero-fills the TZ base 0x14680000 (the tz_a EL3-stub base, Secure world and coprocessors (TZ, HYP, trustlets)) and a 0x1C000000 staging window — consistent with a secure-image handoff transfer.

Source:

_READONLY/lun1/xbl_a.bin — Capstone CS_ARCH_ARM64 disasm of the EL3 “Sec” monitor 0x14953000, EL1 XBLCore reset stub 0x14817908, EL3 vector table 0x14964800 and the smc gateway 0x14699000; full listings in artifacts/re_static/item3_xbl_disasm.txt.

Cross-refs:

XBL (segment layout table), Boot chain and exception-level hand-off (PBL → XBL → ABL), Secure world and coprocessors (TZ, HYP, trustlets), Firehose fuse read and secure-boot confirmation.