Tencent/tinker · error · IOException

Bad oat version: {}

Error message

Bad oat version: {}

What it means

ShareOatUtil reads the 3 ASCII version bytes at offset 4 of the oat header and requires them to parse as an integer (e.g. '007', '064'). NumberFormatException triggers IOException("Bad oat version: <str>"). The oat header exists but its version field is not a recognizable OAT version.

Source

Thrown at tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareOatUtil.java:85

                    || oatMagicAndVersion[3] != '\n') {
                throw new IOException(
                        String.format("Bad oat magic: %x %x %x %x",
                                oatMagicAndVersion[0],
                                oatMagicAndVersion[1],
                                oatMagicAndVersion[2],
                                oatMagicAndVersion[3])
                );
            }

            final int versionOffsetFromOatBegin = 4;
            final int versionBytes = 3;

            final String oatVersion = new String(oatMagicAndVersion,
                    versionOffsetFromOatBegin, versionBytes, Charset.forName("ASCII"));
            try {
                Integer.parseInt(oatVersion);
            } catch (NumberFormatException e) {
                throw new IOException("Bad oat version: " + oatVersion);
            }

            ByteBuffer buffer = ByteBuffer.allocate(128);
            buffer.order(elfFile.getDataOrder());
            // TODO This is a risk point, since each oat version may use a different offset.
            // So far it's ok. Perhaps we should use oatVersionNum to judge the right offset in
            // the future.
            final int isaNumOffsetFromOatBegin = 12;
            channel.position(roDataHdr.shOffset + isaNumOffsetFromOatBegin);
            buffer.limit(4);
            ShareElfFile.readUntilLimit(channel, buffer, "Failed to read isa num.");

            int isaNum = buffer.getInt();
            if (isaNum < 0 || isaNum >= InstructionSet.values().length) {
                throw new IOException("Bad isa num: " + isaNum);
            }

            switch (InstructionSet.values()[isaNum]) {

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Verify the oat version bytes with a hex dump of .rodata offset 4..7.
  2. Fall back to Build.CPU_ABI-derived instruction set when parsing fails.
  3. Update Tinker to a release that supports the device's ART version.
  4. Catch Throwable from getOatFileInstructionSet and degrade gracefully.
Defensive patterns

Strategy: fallback

Try / catch

try {
    isa = ShareOatUtil.getOatFileInstructionSet(odexFile);
} catch (IOException ioe) {
    // 'Bad oat version': unsupported oat version, fall back to ABI-derived ISA
    isa = ShareTinkerInternals.getCurrentInstructionSet();
}

Prevention

When it happens

Trigger: getOatFileInstructionSet on an odex whose .rodata starts with 'oat\n' but whose following 3 bytes are not digits — format change across ART versions or corruption.

Common situations: ART version layout changes on newer Android releases (the code itself carries a TODO about version-dependent offsets); custom ROMs; partially corrupted odex files.

Related errors


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