├── .gitignore ├── README.md ├── build.gradle ├── settings.gradle └── src └── main ├── example └── me │ └── xdark │ └── shell │ ├── Debugger.java │ └── VMHang.java └── java ├── me └── xdark │ └── shell │ ├── JVMUtil.java │ ├── NativeLibrary.java │ └── ShellcodeRunner.java └── one └── helfy ├── Field.java ├── JVM.java ├── JVMException.java └── Type.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build/ 3 | .classpath 4 | .project 5 | out 6 | .gradle 7 | .vscode 8 | .settings 9 | gradle 10 | gradlew 11 | gradlew.bat -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Credits**: https://github.com/apangin/helfy 2 | 3 | **Example(s)**: https://github.com/xxDark/JavaShellcodeInjector/tree/master/src/main/example/me/xdark/shell 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | group 'me.xdark' 4 | version '1.0' 5 | 6 | tasks.withType(JavaCompile) { 7 | options.encoding = 'UTF-8' 8 | sourceCompatibility = 1.8 9 | targetCompatibility = 1.8 10 | } 11 | 12 | sourceSets { 13 | main { 14 | java { 15 | srcDirs = ['src/main/java', 'src/main/example'] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'shellrunner' 2 | 3 | -------------------------------------------------------------------------------- /src/main/example/me/xdark/shell/Debugger.java: -------------------------------------------------------------------------------- 1 | package me.xdark.shell; 2 | 3 | public class Debugger { 4 | 5 | private static int x1; 6 | 7 | public static void main(String[] args) { 8 | int i = 20000; 9 | while (i-- > 0) test(); 10 | ShellcodeRunner.inject(Debugger.class, "test", "()V", new byte[]{(byte) 0xCC}); 11 | test(); 12 | } 13 | 14 | public static void test() { 15 | x1++; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/example/me/xdark/shell/VMHang.java: -------------------------------------------------------------------------------- 1 | package me.xdark.shell; 2 | 3 | public class VMHang { 4 | private static int x1; 5 | 6 | public static void main(String[] args) { 7 | int i = 20000; 8 | while (i-- > 0) test(); 9 | ShellcodeRunner.inject(VMHang.class, "test", "()V", new byte[]{(byte) 0x90, (byte) 0xeb, (byte) 0xfd}); 10 | test(); 11 | } 12 | 13 | public static void test() { 14 | x1++; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/xdark/shell/JVMUtil.java: -------------------------------------------------------------------------------- 1 | package me.xdark.shell; 2 | 3 | import sun.misc.Unsafe; 4 | 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodHandles; 7 | import java.lang.invoke.MethodType; 8 | import java.lang.reflect.Field; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import java.util.Arrays; 13 | 14 | public final class JVMUtil { 15 | 16 | public static final Unsafe UNSAFE; 17 | public static final MethodHandles.Lookup LOOKUP; 18 | private static final NativeLibraryLoader NATIVE_LIBRARY_LOADER; 19 | 20 | private JVMUtil() { 21 | } 22 | 23 | public static NativeLibrary findJvm() throws Throwable { 24 | Path jvmDir = Paths.get(System.getProperty("java.home")); 25 | Path maybeJre = jvmDir.resolve("jre"); 26 | if (Files.isDirectory(maybeJre)) { 27 | jvmDir = maybeJre; 28 | } 29 | jvmDir = jvmDir.resolve("bin"); 30 | String os = System.getProperty("os.name").toLowerCase(); 31 | Path pathToJvm; 32 | if (os.contains("win")) { 33 | pathToJvm = findFirstFile(jvmDir, "server/jvm.dll", "client/jvm.dll"); 34 | } else if (os.contains("nix") || os.contains("nux")) { 35 | pathToJvm = findFirstFile(jvmDir, "lib/amd64/server/libjvm.so", "lib/i386/server/libjvm.so"); 36 | } else { 37 | throw new RuntimeException("Unsupported OS (probably MacOS X): " + os); 38 | } 39 | return NATIVE_LIBRARY_LOADER.loadLibrary(pathToJvm.normalize().toString()); 40 | } 41 | 42 | private static Path findFirstFile(Path directory, String... files) { 43 | for (String file : files) { 44 | Path path = directory.resolve(file); 45 | if (Files.exists(path)) return path; 46 | } 47 | throw new RuntimeException("Failed to find one of the required paths!: " + Arrays.toString(files)); 48 | } 49 | 50 | static { 51 | try { 52 | Field field = Unsafe.class.getDeclaredField("theUnsafe"); 53 | field.setAccessible(true); 54 | Unsafe unsafe = UNSAFE = (Unsafe) field.get(null); 55 | MethodHandles.publicLookup(); 56 | field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); 57 | LOOKUP = (MethodHandles.Lookup) unsafe.getObject(unsafe.staticFieldBase(field), 58 | unsafe.staticFieldOffset(field)); 59 | NATIVE_LIBRARY_LOADER = Float.parseFloat(System.getProperty("java.class.version")) - 44 > 8 ? new Java9LibraryLoader() : new Java8LibraryLoader(); 60 | } catch (Throwable t) { 61 | throw new ExceptionInInitializerError(t); 62 | } 63 | } 64 | 65 | private static abstract class NativeLibraryLoader { 66 | 67 | protected static final Class CL_NATIVE_LIBRARY; 68 | protected static final MethodHandle CNSTR_NATIVE_LIBRARY; 69 | 70 | abstract NativeLibrary loadLibrary(String path) throws Throwable; 71 | 72 | static { 73 | try { 74 | CL_NATIVE_LIBRARY = Class.forName("java.lang.ClassLoader$NativeLibrary", true, null); 75 | CNSTR_NATIVE_LIBRARY = LOOKUP.findConstructor(CL_NATIVE_LIBRARY, MethodType.methodType(Void.TYPE, Class.class, String.class, Boolean.TYPE)); 76 | } catch (Throwable t) { 77 | throw new ExceptionInInitializerError(t); 78 | } 79 | } 80 | } 81 | 82 | private static class Java8LibraryLoader extends NativeLibraryLoader { 83 | 84 | private static final MethodHandle MH_NATIVE_LOAD; 85 | private static final MethodHandle MH_NATIVE_FIND; 86 | private static final MethodHandle MH_NATIVE_LOADED_SET; 87 | 88 | @Override 89 | NativeLibrary loadLibrary(String path) throws Throwable { 90 | Object library = CNSTR_NATIVE_LIBRARY.invoke(JVMUtil.class, path, false); 91 | MH_NATIVE_LOAD.invoke(library, path, false); 92 | MH_NATIVE_LOADED_SET.invoke(library, true); 93 | return new NativeLibrary() { 94 | @Override 95 | public long findEntry(String entry) { 96 | try { 97 | return (long) MH_NATIVE_FIND.invoke(library, entry); 98 | } catch (Throwable t) { 99 | throw new InternalError(t); 100 | } 101 | } 102 | }; 103 | } 104 | 105 | static { 106 | MethodHandles.Lookup lookup = LOOKUP; 107 | Class cl = CL_NATIVE_LIBRARY; 108 | try { 109 | MH_NATIVE_LOAD = lookup.findVirtual(cl, "load", MethodType.methodType(Void.TYPE, String.class, Boolean.TYPE)); 110 | MH_NATIVE_FIND = lookup.findVirtual(cl, "find", MethodType.methodType(Long.TYPE, String.class)); 111 | MH_NATIVE_LOADED_SET = lookup.findSetter(cl, "loaded", Boolean.TYPE); 112 | } catch (Throwable t) { 113 | throw new ExceptionInInitializerError(t); 114 | } 115 | 116 | } 117 | } 118 | 119 | private static class Java9LibraryLoader extends NativeLibraryLoader { 120 | 121 | private static final MethodHandle MH_NATIVE_LOAD; 122 | private static final MethodHandle MH_NATIVE_FIND; 123 | 124 | @Override 125 | NativeLibrary loadLibrary(String path) throws Throwable { 126 | Object library = CNSTR_NATIVE_LIBRARY.invoke(JVMUtil.class, path, false); 127 | MH_NATIVE_LOAD.invoke(library, path, false); 128 | return new NativeLibrary() { 129 | @Override 130 | public long findEntry(String entry) { 131 | try { 132 | return (long) MH_NATIVE_FIND.invoke(library, entry); 133 | } catch (Throwable t) { 134 | throw new InternalError(t); 135 | } 136 | } 137 | }; 138 | } 139 | 140 | static { 141 | MethodHandles.Lookup lookup = LOOKUP; 142 | Class cl = CL_NATIVE_LIBRARY; 143 | try { 144 | MH_NATIVE_LOAD = lookup.findVirtual(cl, "load0", MethodType.methodType(Boolean.TYPE, String.class, Boolean.TYPE)); 145 | MH_NATIVE_FIND = lookup.findVirtual(cl, "findEntry", MethodType.methodType(Long.TYPE, String.class)); 146 | } catch (Throwable t) { 147 | throw new ExceptionInInitializerError(t); 148 | } 149 | 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/me/xdark/shell/NativeLibrary.java: -------------------------------------------------------------------------------- 1 | package me.xdark.shell; 2 | 3 | public interface NativeLibrary { 4 | 5 | long findEntry(String entry); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/me/xdark/shell/ShellcodeRunner.java: -------------------------------------------------------------------------------- 1 | package me.xdark.shell; 2 | 3 | import one.helfy.JVM; 4 | import one.helfy.Type; 5 | import sun.misc.Unsafe; 6 | 7 | import java.nio.charset.StandardCharsets; 8 | 9 | public final class ShellcodeRunner { 10 | 11 | private static final JVM jvm = new JVM(); 12 | 13 | private ShellcodeRunner() { 14 | } 15 | 16 | public static void inject(Class target, String name, String descriptor, byte[] payload) { 17 | // Before execution: prepare the method to match the exact size of payload you want to execute 18 | // execute it ~~20000 times let JIT do it's work 19 | // After injection call the method again 20 | Unsafe unsafe = JVMUtil.UNSAFE; 21 | JVM jvm = ShellcodeRunner.jvm; 22 | int oopSize = jvm.intConstant("oopSize"); 23 | long klassOffset = jvm.getInt(jvm.type("java_lang_Class").global("_klass_offset")); 24 | long klass = oopSize == 8 25 | ? unsafe.getLong(target, klassOffset) 26 | : unsafe.getInt(target, klassOffset) & 0xffffffffL; 27 | 28 | long methodArray = jvm.getAddress(klass + jvm.type("InstanceKlass").offset("_methods")); 29 | int methodCount = jvm.getInt(methodArray); 30 | long methods = methodArray + jvm.type("Array").offset("_data"); 31 | 32 | long constMethodOffset = jvm.type("Method").offset("_constMethod"); 33 | Type constMethodType = jvm.type("ConstMethod"); 34 | Type constantPoolType = jvm.type("ConstantPool"); 35 | long constantPoolOffset = constMethodType.offset("_constants"); 36 | long nameIndexOffset = constMethodType.offset("_name_index"); 37 | long signatureIndexOffset = constMethodType.offset("_signature_index"); 38 | long _from_compiled_entry = jvm.type("Method").offset("_from_compiled_entry"); 39 | 40 | for (int i = 0; i < methodCount; i++) { 41 | long method = jvm.getAddress(methods + i * oopSize); 42 | long constMethod = jvm.getAddress(method + constMethodOffset); 43 | 44 | long constantPool = jvm.getAddress(constMethod + constantPoolOffset); 45 | int nameIndex = jvm.getShort(constMethod + nameIndexOffset) & 0xffff; 46 | int signatureIndex = jvm.getShort(constMethod + signatureIndexOffset) & 0xffff; 47 | 48 | if (name.equals(getSymbol(constantPool + constantPoolType.size + nameIndex * oopSize)) 49 | && descriptor.equals(getSymbol( 50 | constantPool + constantPoolType.size + signatureIndex * oopSize))) { 51 | long address = jvm.getAddress(method + _from_compiled_entry); 52 | // let's rock! 53 | for (int j = 0, k = payload.length; j < k; j++) { 54 | unsafe.putByte(address+j, payload[j]); 55 | } 56 | return; 57 | } 58 | } 59 | throw new InternalError(target + "." + name + descriptor); 60 | } 61 | 62 | private static String getSymbol(long symbolAddress) { 63 | Type symbolType = jvm.type("Symbol"); 64 | long symbol = jvm.getAddress(symbolAddress); 65 | long body = symbol + symbolType.offset("_body"); 66 | int length = jvm.getShort(symbol + symbolType.offset("_length")) & 0xffff; 67 | 68 | byte[] b = new byte[length]; 69 | for (int i = 0; i < length; i++) { 70 | b[i] = jvm.getByte(body + i); 71 | } 72 | return new String(b, StandardCharsets.UTF_8); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/one/helfy/Field.java: -------------------------------------------------------------------------------- 1 | package one.helfy; 2 | 3 | public final class Field implements Comparable { 4 | public final String name; 5 | public final String typeName; 6 | public final long offset; 7 | public final boolean isStatic; 8 | 9 | Field(String name, String typeName, long offset, boolean isStatic) { 10 | this.name = name; 11 | this.typeName = typeName; 12 | this.offset = offset; 13 | this.isStatic = isStatic; 14 | } 15 | 16 | @Override 17 | public int compareTo(Field o) { 18 | if (isStatic != o.isStatic) { 19 | return isStatic ? -1 : 1; 20 | } 21 | return Long.compare(offset, o.offset); 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | if (isStatic) { 27 | return "static " + typeName + ' ' + name + " @ 0x" + Long.toHexString(offset); 28 | } else { 29 | return typeName + ' ' + name + " @ " + offset; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/one/helfy/JVM.java: -------------------------------------------------------------------------------- 1 | package one.helfy; 2 | 3 | import me.xdark.shell.JVMUtil; 4 | import me.xdark.shell.NativeLibrary; 5 | import sun.misc.Unsafe; 6 | 7 | import java.util.Arrays; 8 | import java.util.HashMap; 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | import java.util.NoSuchElementException; 12 | import java.util.Set; 13 | import java.util.TreeSet; 14 | 15 | public final class JVM { 16 | private static final Unsafe unsafe = JVMUtil.UNSAFE; 17 | private static final NativeLibrary JVM; 18 | 19 | private final Map types = new LinkedHashMap<>(); 20 | private final Map constants = new LinkedHashMap<>(); 21 | 22 | public JVM() { 23 | readVmTypes(readVmStructs()); 24 | readVmIntConstants(); 25 | readVmLongConstants(); 26 | } 27 | 28 | private Map> readVmStructs() { 29 | long entry = getSymbol("gHotSpotVMStructs"); 30 | long typeNameOffset = getSymbol("gHotSpotVMStructEntryTypeNameOffset"); 31 | long fieldNameOffset = getSymbol("gHotSpotVMStructEntryFieldNameOffset"); 32 | long typeStringOffset = getSymbol("gHotSpotVMStructEntryTypeStringOffset"); 33 | long isStaticOffset = getSymbol("gHotSpotVMStructEntryIsStaticOffset"); 34 | long offsetOffset = getSymbol("gHotSpotVMStructEntryOffsetOffset"); 35 | long addressOffset = getSymbol("gHotSpotVMStructEntryAddressOffset"); 36 | long arrayStride = getSymbol("gHotSpotVMStructEntryArrayStride"); 37 | 38 | Map> structs = new HashMap<>(); 39 | 40 | for (;; entry += arrayStride) { 41 | String typeName = getStringRef(entry + typeNameOffset); 42 | String fieldName = getStringRef(entry + fieldNameOffset); 43 | if (fieldName == null) break; 44 | 45 | String typeString = getStringRef(entry + typeStringOffset); 46 | boolean isStatic = getInt(entry + isStaticOffset) != 0; 47 | long offset = getLong(entry + (isStatic ? addressOffset : offsetOffset)); 48 | 49 | Set fields = structs.computeIfAbsent(typeName, k -> new TreeSet<>()); 50 | fields.add(new Field(fieldName, typeString, offset, isStatic)); 51 | } 52 | 53 | return structs; 54 | } 55 | 56 | private void readVmTypes(Map> structs) { 57 | long entry = getSymbol("gHotSpotVMTypes"); 58 | long typeNameOffset = getSymbol("gHotSpotVMTypeEntryTypeNameOffset"); 59 | long superclassNameOffset = getSymbol("gHotSpotVMTypeEntrySuperclassNameOffset"); 60 | long isOopTypeOffset = getSymbol("gHotSpotVMTypeEntryIsOopTypeOffset"); 61 | long isIntegerTypeOffset = getSymbol("gHotSpotVMTypeEntryIsIntegerTypeOffset"); 62 | long isUnsignedOffset = getSymbol("gHotSpotVMTypeEntryIsUnsignedOffset"); 63 | long sizeOffset = getSymbol("gHotSpotVMTypeEntrySizeOffset"); 64 | long arrayStride = getSymbol("gHotSpotVMTypeEntryArrayStride"); 65 | 66 | for (;; entry += arrayStride) { 67 | String typeName = getStringRef(entry + typeNameOffset); 68 | if (typeName == null) break; 69 | 70 | String superclassName = getStringRef(entry + superclassNameOffset); 71 | boolean isOop = getInt(entry + isOopTypeOffset) != 0; 72 | boolean isInt = getInt(entry + isIntegerTypeOffset) != 0; 73 | boolean isUnsigned = getInt(entry + isUnsignedOffset) != 0; 74 | int size = getInt(entry + sizeOffset); 75 | 76 | Set fields = structs.get(typeName); 77 | types.put(typeName, new Type(typeName, superclassName, size, isOop, isInt, isUnsigned, fields)); 78 | } 79 | } 80 | 81 | private void readVmIntConstants() { 82 | long entry = getSymbol("gHotSpotVMIntConstants"); 83 | long nameOffset = getSymbol("gHotSpotVMIntConstantEntryNameOffset"); 84 | long valueOffset = getSymbol("gHotSpotVMIntConstantEntryValueOffset"); 85 | long arrayStride = getSymbol("gHotSpotVMIntConstantEntryArrayStride"); 86 | 87 | for (;; entry += arrayStride) { 88 | String name = getStringRef(entry + nameOffset); 89 | if (name == null) break; 90 | 91 | int value = getInt(entry + valueOffset); 92 | constants.put(name, value); 93 | } 94 | } 95 | 96 | private void readVmLongConstants() { 97 | long entry = getSymbol("gHotSpotVMLongConstants"); 98 | long nameOffset = getSymbol("gHotSpotVMLongConstantEntryNameOffset"); 99 | long valueOffset = getSymbol("gHotSpotVMLongConstantEntryValueOffset"); 100 | long arrayStride = getSymbol("gHotSpotVMLongConstantEntryArrayStride"); 101 | 102 | for (;; entry += arrayStride) { 103 | String name = getStringRef(entry + nameOffset); 104 | if (name == null) break; 105 | 106 | long value = getLong(entry + valueOffset); 107 | constants.put(name, value); 108 | } 109 | } 110 | 111 | public byte getByte(long addr) { 112 | return unsafe.getByte(addr); 113 | } 114 | 115 | public void putByte(long addr, byte val) { 116 | unsafe.putByte(addr, val); 117 | } 118 | 119 | public short getShort(long addr) { 120 | return unsafe.getShort(addr); 121 | } 122 | 123 | public void putShort(long addr, short val) { 124 | unsafe.putShort(addr, val); 125 | } 126 | 127 | public int getInt(long addr) { 128 | return unsafe.getInt(addr); 129 | } 130 | 131 | public void putInt(long addr, int val) { 132 | unsafe.putInt(addr, val); 133 | } 134 | 135 | public long getLong(long addr) { 136 | return unsafe.getLong(addr); 137 | } 138 | 139 | public void putLong(long addr, long val) { 140 | unsafe.putLong(addr, val); 141 | } 142 | 143 | public long getAddress(long addr) { 144 | return unsafe.getAddress(addr); 145 | } 146 | 147 | public void putAddress(long addr, long val) { 148 | unsafe.putAddress(addr, val); 149 | } 150 | 151 | public String getString(long addr) { 152 | if (addr == 0) { 153 | return null; 154 | } 155 | 156 | char[] chars = new char[40]; 157 | int offset = 0; 158 | for (byte b; (b = getByte(addr + offset)) != 0; ) { 159 | if (offset >= chars.length) chars = Arrays.copyOf(chars, offset * 2); 160 | chars[offset++] = (char) b; 161 | } 162 | return new String(chars, 0, offset); 163 | } 164 | 165 | public String getStringRef(long addr) { 166 | return getString(getAddress(addr)); 167 | } 168 | 169 | public long getSymbol(String name) { 170 | long address = JVM.findEntry(name); 171 | if (address == 0) { 172 | throw new NoSuchElementException("No such symbol: " + name); 173 | } 174 | return getLong(address); 175 | } 176 | 177 | public Type type(String name) { 178 | Type type = types.get(name); 179 | if (type == null) { 180 | throw new NoSuchElementException("No such type: " + name); 181 | } 182 | return type; 183 | } 184 | 185 | public Number constant(String name) { 186 | Number constant = constants.get(name); 187 | if (constant == null) { 188 | throw new NoSuchElementException("No such constant: " + name); 189 | } 190 | return constant; 191 | } 192 | 193 | public int intConstant(String name) { 194 | return constant(name).intValue(); 195 | } 196 | 197 | public long longConstant(String name) { 198 | return constant(name).longValue(); 199 | } 200 | 201 | static { 202 | try { 203 | JVM = JVMUtil.findJvm(); 204 | } catch (Throwable t) { 205 | throw new ExceptionInInitializerError(t); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/one/helfy/JVMException.java: -------------------------------------------------------------------------------- 1 | package one.helfy; 2 | 3 | public final class JVMException extends RuntimeException { 4 | 5 | public JVMException(String message) { 6 | super(message); 7 | } 8 | 9 | public JVMException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | 13 | public JVMException(Throwable cause) { 14 | super(cause); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/one/helfy/Type.java: -------------------------------------------------------------------------------- 1 | package one.helfy; 2 | 3 | import java.util.NoSuchElementException; 4 | import java.util.Set; 5 | 6 | public final class Type { 7 | private static final Field[] NO_FIELDS = new Field[0]; 8 | 9 | public final String name; 10 | public final String superName; 11 | public final int size; 12 | public final boolean isOop; 13 | public final boolean isInt; 14 | public final boolean isUnsigned; 15 | public final Field[] fields; 16 | 17 | Type(String name, String superName, int size, boolean isOop, boolean isInt, boolean isUnsigned, Set fields) { 18 | this.name = name; 19 | this.superName = superName; 20 | this.size = size; 21 | this.isOop = isOop; 22 | this.isInt = isInt; 23 | this.isUnsigned = isUnsigned; 24 | this.fields = fields == null ? NO_FIELDS : fields.toArray(new Field[0]); 25 | } 26 | 27 | public Field field(String name) { 28 | for (Field field : fields) { 29 | if (field.name.equals(name)) { 30 | return field; 31 | } 32 | } 33 | throw new NoSuchElementException("No such field: " + name); 34 | } 35 | 36 | public long global(String name) { 37 | Field field = field(name); 38 | if (field.isStatic) { 39 | return field.offset; 40 | } 41 | throw new IllegalArgumentException("Static field expected"); 42 | } 43 | 44 | public long offset(String name) { 45 | Field field = field(name); 46 | if (!field.isStatic) { 47 | return field.offset; 48 | } 49 | throw new IllegalArgumentException("Instance field expected"); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | StringBuilder sb = new StringBuilder(name); 55 | if (superName != null) sb.append(" extends ").append(superName); 56 | sb.append(" @ ").append(size).append('\n'); 57 | for (Field field : fields) { 58 | sb.append(" ").append(field).append('\n'); 59 | } 60 | return sb.toString(); 61 | } 62 | } 63 | --------------------------------------------------------------------------------