Tencent/tinker · error · DexException

Unexpected header: 0x${headerSize}

Error message

Unexpected header: 0x${headerSize}

What it means

Thrown by TableOfContents.readHeader when header_size (offset 36) does not equal SizeOf.HEADER_ITEM (0x70). The dex spec fixes the header item at 112 bytes; a different value means the layout this parser assumes does not match the file, so every subsequent fixed-offset read would be wrong.

Source

Thrown at third-party/aosp-dexutils/src/main/java/com/tencent/tinker/android/dex/TableOfContents.java:178

        // an {@code java.nio.BufferUnderflowException} will be thrown.
        readMap(dex.openSection(mapList.off));
        computeSizesFromOffsets();
    }

    private void readHeader(Dex.Section headerIn) throws UnsupportedEncodingException {
        byte[] magic = headerIn.readByteArray(8);
        api = DexFormat.magicToApi(magic);

        if (api == -1) {
            throw new DexException("Unexpected magic: " + Arrays.toString(magic));
        }

        checksum = headerIn.readInt();
        signature = headerIn.readByteArray(20);
        fileSize = headerIn.readInt();
        int headerSize = headerIn.readInt();
        if (headerSize != SizeOf.HEADER_ITEM) {
            throw new DexException("Unexpected header: 0x" + Integer.toHexString(headerSize));
        }
        int endianTag = headerIn.readInt();
        if (endianTag != DexFormat.ENDIAN_TAG) {
            throw new DexException("Unexpected endian tag: 0x" + Integer.toHexString(endianTag));
        }
        linkSize = headerIn.readInt();
        linkOff = headerIn.readInt();
        mapList.off = headerIn.readInt();
        if (mapList.off == 0) {
            throw new DexException("Cannot merge dex files that do not contain a map");
        }
        stringIds.size = headerIn.readInt();
        stringIds.off = headerIn.readInt();
        typeIds.size = headerIn.readInt();
        typeIds.off = headerIn.readInt();
        protoIds.size = headerIn.readInt();
        protoIds.off = headerIn.readInt();
        fieldIds.size = headerIn.readInt();

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Verify integrity via the header checksum/SHA-1; a mismatch confirms corruption — restore from a clean source.
  2. Rebuild the dex from sources/classes with a standard toolchain (d8) rather than repairing the binary.
  3. Reject such files early in pipelines instead of trying to merge them.
Defensive patterns

Strategy: try-catch

Validate before calling

static boolean plausibleDexHeader(ByteBuffer b) {
    return b.getInt(36) == 0x70; // header_size
}

Try / catch

catch (DexException e) -> treat as corrupt input; re-obtain from source of truth, never patch header_size to pass

Prevention

When it happens

Trigger: Reading a file that passed the magic check but has a nonstandard header — patched binaries where someone rewrote the magic, experimental formats, or a header-size field corrupted by partial writes.

Common situations: Rare in practice; most often seen after binary-level tampering, badly applied hot-patch tooling that edits headers in place, or when a zip-local-file artifact coincidentally starts with dex-like bytes.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/a96050a45cd30f7c. Report an issue: GitHub.