Tencent/tinker · error · IOException
Bad oat magic: %x %x %x %x
Error message
Bad oat magic: %x %x %x %x
What it means
After locating the .rodata section of an odex, ShareOatUtil reads 8 bytes of oat magic and version and requires the first four bytes to be 'o','a','t','\n'. If they differ it throws IOException formatted as "Bad oat magic: %x %x %x %x". The .rodata content is not a real OAT header, so the instruction set cannot be extracted from it.
Source
Thrown at tinker-android/tinker-android-loader/src/main/java/com/tencent/tinker/loader/shareutil/ShareOatUtil.java:68
String result = "";
try {
elfFile = new ShareElfFile(oatFile);
final ShareElfFile.SectionHeader roDataHdr = elfFile.getSectionHeaderByName(".rodata");
if (roDataHdr == null) {
throw new IOException("Unable to find .rodata section.");
}
final FileChannel channel = elfFile.getChannel();
channel.position(roDataHdr.shOffset);
final byte[] oatMagicAndVersion = new byte[8];
ShareElfFile.readUntilLimit(channel, ByteBuffer.wrap(oatMagicAndVersion), "Failed to read oat magic and version.");
if (oatMagicAndVersion[0] != 'o'
|| oatMagicAndVersion[1] != 'a'
|| oatMagicAndVersion[2] != 't'
|| 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);
}View on GitHub (pinned to 1b7ea02c23)
Solutions
- Pull the odex and hex-dump the first bytes of .rodata to confirm whether it is a real oat header.
- Fall back to ShareTinkerInternals.getCurrentInstructionSet() (Build.CPU_ABI based) when oat parsing fails.
- Update Tinker to a version matching the target Android version's odex layout.
- Wrap the call in try-catch (it throws Throwable) and treat failure as 'isa unknown'.
Defensive patterns
Strategy: fallback
Validate before calling
// Cheap pre-check: peek 8 bytes at .rodata offset for 'oat\n' before full parse // (only if you already parse sections; otherwise rely on the try-catch fallback)
Try / catch
try {
isa = ShareOatUtil.getOatFileInstructionSet(odexFile);
} catch (IOException ioe) {
// 'Bad oat magic': unrecognized oat format, fall back to ABI-based ISA
isa = ShareTinkerInternals.getCurrentInstructionSet();
} Prevention
- Keep Tinker updated for new ART/oat layouts.
- Do not assume the odex format; always code an ISA fallback path.
- Hex-verify odex files when bringing up new Android versions.
When it happens
Trigger: getOatFileInstructionSet on an ELF whose .rodata begins with bytes other than the oat magic — wrong offset assumption for that ART version, a non-oat ELF, or a corrupted odex.
Common situations: Newer Android/ART versions changing the oat header placement; passing a stripped or unusual odex; patched/custom ROMs with non-standard dexopt output.
Related errors
- Unable to find .rodata section.
- bad elf magic: %x %x %x %x.
- Unable to find .rodata section.
- Bad oat version: {}
- Bad isa num: {}
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/be5761b0743f982a.
Report an issue: GitHub.