Tencent/tinker · error · IOException

Bad isa num: {}

Error message

Bad isa num: {}

What it means

ShareOatUtil reads a 4-byte int at fixed offset 12 from the OAT header (the instruction-set enum index) and requires 0 <= isaNum < InstructionSet.values().length. An out-of-range value means the header layout does not match the expected OAT format (the in-code TODO explicitly flags this fixed offset as a risk across oat versions), so an IOException('Bad isa num') is thrown instead of indexing the enum with garbage.

Source

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

            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]) {
                case kArm:
                case kThumb2:
                    result = "arm";
                    break;
                case kArm64:
                    result = "arm64";
                    break;
                case kX86:
                    result = "x86";
                    break;
                case kX86_64:
                    result = "x86_64";
                    break;
                case kMips:
                    result = "mips";

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Delete the odex and retry patch install so the on-device dex2oat regenerates a matching file.
  2. Fall back to ShareTinkerInternals.getCurrentInstructionSet() (Build.CPU_ABI) when the oat-based parse fails, instead of propagating the error.
  3. Upgrade tinker to a version whose InstructionSet table and offset handling cover the target Android release.
  4. Verify the patch is built for the same ABI as the running process (no 32/64-bit odex mixing).

Example fix

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

// after
int isaNum = buffer.getInt();
if (isaNum < 0 || isaNum >= InstructionSet.values().length) {
    ShareTinkerLog.w(TAG, "Bad isa num " + isaNum + ", fall back to abi-based isa.");
    return ShareTinkerInternals.getCurrentInstructionSet();
}
Defensive patterns

Strategy: fallback

Try / catch

try { isa = ShareOatUtil.readOatInstructionSet(f); } catch (IOException e) { isa = ShareTinkerInternals.getCurrentInstructionSet(); }

Prevention

When it happens

Trigger: Parsing an oat file produced by an ART version that places different data at offset 12; a corrupt or truncated odex where the isa field lands on arbitrary bytes; an odex built for an ABI whose enum index is unknown to this tinker version.

Common situations: New Android versions changing the OAT header layout after this code shipped; unusual ABIs (mips-era or new ISAs) not present in the embedded InstructionSet enum; vendor-modified ART.

Related errors


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