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
- Delete the odex and retry patch install so the on-device dex2oat regenerates a matching file.
- Fall back to ShareTinkerInternals.getCurrentInstructionSet() (Build.CPU_ABI) when the oat-based parse fails, instead of propagating the error.
- Upgrade tinker to a version whose InstructionSet table and offset handling cover the target Android release.
- 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
- Fall back to ABI-based instruction-set detection when the oat header cannot be trusted.
- Keep tinker updated so the fixed-offset parser matches the oat versions on your device matrix.
- Never parse an odex produced for a different Android/ART version.
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
- Bad isa num: {}
- Unable to find .rodata section.
- Unable to find .rodata section.
- Bad oat magic: %x %x %x %x
- Bad oat version: {}
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/0f488bf6e8790332.
Report an issue: GitHub.