Java class file¶
Level: 201 · for anyone with a hex editor open
One line: CA FE BA BE starts every Java class file and every Mach-O universal binary, and file(1) separates them with one comparison — the big-endian integer at offset 4 is a slice count below 25 in a fat binary and a version above 30 in a class — while inside the class every string is modified UTF-8 with a 16-bit byte count, so café, a NUL and an emoji cost 5, 2 and 6 bytes.
What the loader reads¶
A .class file is what javac writes and the JVM loads, specified in chapter 4 of the JVM Specification ↗. It is big-endian from end to end, with no field to say so, and it opens:
| offset | field | width | what it says |
|---|---|---|---|
| 0 | magic |
4 | ca fe ba be |
| 4 | minor_version |
2 | usually 0 |
| 6 | major_version |
2 | 45 for Java 1.1, 52 for 8, 61 for 17, 65 for 21, 69 for 25 |
| 8 | constant_pool_count |
2 | one more than the number of entries |
| 10 | constant_pool[] |
tagged entries: 1 Utf8, 3 Integer, 5 Long, 7 Class, 8 String, 10 Methodref, 12 NameAndType … |
|
access_flags, this_class, super_class |
2 each | indexes into the pool | |
| interfaces, fields, methods, attributes | each a 16-bit count and a list |
The constant pool is where the strings are, and a CONSTANT_Utf8_info is a tag byte, a 16-bit length in bytes, and that many bytes of modified UTF-8.
In Python¶
Verified output of java_class_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. MAGIC, THEN MINOR, THEN MAJOR, ALL BIG-ENDIAN
------------------------------------------------------------------------
ca fe ba be 00 00 00 45 magic 0xcafebabe minor 0 major 69 = Java 25
major 45 Java 1.1 bytes 00 2d
major 52 Java 8 bytes 00 34
major 61 Java 17 bytes 00 3d
major 65 Java 21 bytes 00 41
major 69 Java 25 bytes 00 45
The class file is big-endian from the first byte to the last, by
specification; no field says so and no field could be read before
deciding it. Read as little-endian the version is 0x4500 = 17664.
2. A CONSTANT_Utf8 IS A 16-BIT BYTE COUNT AND MODIFIED UTF-8
------------------------------------------------------------------------
the string 'café\x00😀' 6 code points, 10 bytes of UTF-8
built here 01 00 0d 63 61 66 c3 a9 c0 80 ed a0 bd ed b8 80
javac 25 wrote 01 00 0d 63 61 66 c3 a9 c0 80 ed a0 bd ed b8 80
identical True
tag 01, length 00 0d = 13 bytes: café is five, the NUL is c0 80,
and the emoji is two surrogates of three bytes each. The length
counts bytes of MUTF-8, not characters and not UTF-16 units -- a
third answer to 'how long is this string', beside DEX's and C's.
3. THE POOL IS COUNTED FROM 1, AND TWO KINDS TAKE TWO SLOTS
------------------------------------------------------------------------
constant_pool_count 00 07 = 7 (entries + 1, and a Long counts twice)
#1 Utf8 length 5 b'Hello'
#2 Class -> #1
#3 Long 1
#4 (unusable) the second half of the Long: no entry may use this index
#5 Utf8 length 1 b'x'
#6 String -> #4
#0 does not exist, and the count is one more than the last index.
The JVM specification calls the two-slot rule 'a poor choice' in
its own words, and keeps it, because every class file has it.
4. THE LENGTH FIELD IS 16 BITS OF BYTES
------------------------------------------------------------------------
'A' x 65535 65535 bytes fits
'é' x 32767 65534 bytes fits
'😀' x 10922 65532 bytes fits
'日' x 21845 65535 bytes fits
65,535 is the ceiling in BYTES, so how many characters fit depends
on which characters: 65,535 of A, 32,767 of é, 10,922 emoji. A
string constant longer than that is a compile error, not a runtime
one, and the limit is measured after encoding.
Measured: what javac wrote¶
Section 2 above compares its own encoder with a class compiled on this Mac, and the bytes are the same. The source held one constant, "caf\u00e9\u0000\uD83D\uDE00":
00000000: cafe babe 0000 0045 0020 0a00 0200 0307 .......E. ......
minor version: 0
major version: 69
#15 = String #16 // café\u0000😀
#16 = Utf8 café\u0000😀
the bytes of #16: 01 00 0d 63 61 66 c3 a9 c0 80 ed a0 bd ed b8 80
00 20 at offset 8 is the pool count, 32, and the first entry's tag 0a is a Methodref. Entry 16 is tag 01, length 00 0d, thirteen bytes — and javap prints the NUL as \u0000 because it cannot draw it, and the emoji as itself, having decoded the six bytes back to one character.
Modified UTF-8, and the length that counts it¶
The encoding is DEX's page too, because Dalvik took it from here: U+0000 becomes c0 80, so no string holds a zero byte, and a supplementary character becomes two surrogates of three bytes each. The class file adds a rule DEX does not have — the length field is bytes of MUTF-8, sixteen bits of them, so the longest string constant is 65,535 bytes and how many characters that is depends on the characters. Section 4 counts: 65,535 of A, 32,767 of é, 10,922 emoji. The kata below asks for the length field of five strings, and it equals the UTF-8 length for three of them.
Cafebabe, shared¶
The magic is Java's; Apple reused it for the Mach-O fat header some years later, and a fat binary and a class file are told apart by the next four bytes. Both read them big-endian: a class has minor_version then major_version, and no major has ever been below 45; a fat header has nfat_arch, and no binary has more than a couple of dozen slices. The rule in file(1)'s magic database is exactly that threshold, and its author's comment explains the number:
# ... Since there are only
# only 23 labeled Mach-O architectures at current, and the first released
# Java class format was version 43.0, we can safely choose any number
# between 23 and 39 to test the number of architectures against
0 belong 0xcafebabe
>4 ubelong >30 compiled Java class data,
And measured on both builds, on the class above and on /bin/ls:
Hello.class application/x-java-applet compiled Java class data, version 69.0 (both builds)
/bin/ls application/x-mach-binary Mach-O universal binary with 2 architectures (both builds)
The pool is counted from one¶
Section 3 builds a six-entry pool and reads it back. Two conventions bite: entries are numbered from 1, and constant_pool_count is one more than the last index; and a Long or Double occupies two slots, the second of which no entry may reference — the specification says, of its own rule, "In retrospect, making 8-byte constants take two constant pool entries was a poor choice." A String entry does not hold text; it holds the index of a Utf8 entry, which is why javap prints #15 = String #16.
What Ghidra checks¶
JavaLoader ↗ lives in the JVM processor module rather than with the other loaders, peeks a big-endian int at offset 0, refuses anything but 0xcafebabe, and loads under the fixed language JVM:BE:32:default — the byte order and width are in the language's name because the file has no field for either. Its name is Java Class File. It does not look at offset 4; a fat Mach-O offered to it would pass the magic test and fail while parsing the pool.
If you are coming from Python or ABAP¶
Python. struct.unpack('>IHH', data[:8]) is the head, and '>' is not optional. Walking the pool means reading a tag byte and knowing each tag's size — the program does it for five tags — and remembering to advance the index by two after a 5 or a 6. The MUTF-8 decoder is the same dozen lines as on the DEX page; bytes.decode('utf-8') raises on c0 80 and on the surrogates, which is correct of it, and errors='surrogatepass' gets the surrogates through but still refuses the overlong NUL.
ABAP. (Not machine-checked — CI cannot run ABAP.) Nothing on an ABAP stack reads a class file, but the SAP JVM writes them and the length rule is worth knowing when a Java constant travels into a string: the class file's 65,535 is a limit in UTF-8-ish bytes, ABAP's strlen counts UTF-16 units, and a Java String with 40,000 CJK characters fits a Java String and does not fit a class-file constant. Verify any code-page number against the system.
Try it¶
javacany file andxxd -l 8the result. Say the Java version from the last two bytes beforejavap -vconfirms it.javap -von the same class and find aUtf8entry with a non-ASCII character. Locate its bytes in the dump and check the length field against the byte count.- Add a
"\u0000"to a string constant, recompile, and grep the class for the two bytesc0 80underLC_ALL=C. - Write a string literal of 30,000
éand compile. Then 33,000. The second is a compiler error about a constant string, and the number it fails at is a byte count. filea class file and a fat Mach-O side by side, thenxxd -l 8both and find the numberfile's rule compared.
Practice¶
Five constants. For each string, say the two bytes javac writes as the length of its CONSTANT_Utf8_info:
Before running: which of the five have a length equal to their UTF-8 byte count, and why do the other two differ?
Answers
Verified output of java_class_kata_py.py — regenerated by tools/run_examples.py, never hand-typed.
string chars UTF-16 UTF-8 MUTF-8 the two length bytes
'A' 1 1 1 1 00 01
'café' 4 4 5 5 00 05
'Łódź' 4 4 7 7 00 07
'a\x00b' 3 3 3 4 00 04
'😀' 1 2 4 6 00 06
The field is the last column, big-endian. It equals the UTF-8 length
for three of the five and differs for the two the encoding exists
to change: the NUL costs one byte more, the emoji two. It never
equals the character count except for pure ASCII, and it equals
the UTF-16 count for nothing here but 'A'.
See also¶
- DEX — the same encoding with a different length field, and the byte-order tag this format lacks
- Mach-O — the fat header that shares the magic, and the
<25half of the rule quoted above - GZF — Java's
writeUTF, which is this entry's encoding and length rule used by Ghidra's own container - Overlong sequences —
c0 80, and the three formats that break the shortest-form rule on purpose runeis anint32— Go's answer to the same question, beside Java'schar- JVM Specification, chapter 4 ↗ — the
ClassFilestructure and §4.4.7, theCONSTANT_Utf8_infodefinition