Writing patches
API: Raw bytecode
The layer under targets and code blocks: methods and classes as objects, instructions as data, and registers as numbers. The guide page is Raw bytecode. The handles and helpers are in app.reseam.patch.dex, and the data types in app.reseam.patch.types.
Reach it from a target (target.method, target.classDef) or from bytecode. Handles are only valid while the patch that got them runs.
app.reseam.patch.dex
Method
A method in the app.
Reading
| Member | Description |
|---|---|
handle: UInt |
The engine's id for the method. |
info: MethodInfo |
Everything below in one record. |
classDef: DexClass |
The class that declares it. |
descriptor: String, name: String, owner: String, proto: String |
Its identity. |
returnType: String, parameterTypes: List<String> |
Its signature's types. |
isStatic: Boolean |
Whether it is static. |
instructions: List<Instruction> |
Its instructions, in order. Read an instruction's operands through the Instruction properties. |
instructionCount: Int |
How many instructions it has. |
registersSize: Int, insSize: Int, outsSize: Int |
Its frame: every register, the registers its parameters arrive in, and the most any call it makes passes. |
dexIndex: Int |
Which of the app's DEX files holds it. |
Searching
Each returns an instruction index, or null when nothing matches.
| Member | Description |
|---|---|
indexOfFirst(opcode: Opcode, start: Int = 0): Int? |
The first instruction with this opcode, from start. |
indexOfFirstReversed(opcode: Opcode, start: Int): Int? |
The last one at or before start. |
indexOfFirstLiteral(literal: Long): Int? |
The first instruction loading this number. |
indexOfFirstLiteralReversed(literal: Long): Int? |
The last one. |
containsLiteral(literal: Long): Boolean |
Whether the method loads this number. |
indexOfFirstString(value: String): Int? |
The first instruction loading this string. |
findAllIndices(opcode: Opcode): List<Int> |
Every instruction with this opcode. |
indexOfFirstMethodCall(owner: String, name: String, start: Int = 0): Int? |
The first call to owner.name. |
indexOfFirstFieldAccess(opcode: Opcode, fieldType: String? = null, owner: String? = null, start: Int = 0): Int? |
The first field access with this opcode, optionally of this type or on this class. |
indexOfOpcodeSequence(vararg opcodes: Opcode?, start: Int = 0): Int? |
Where these opcodes run back to back. null matches any one instruction. |
indexOfFirstInstruction(start: Int = 0, predicate: Instruction.() -> Boolean): Int? |
The first instruction your check accepts. |
indexOfFirstInstructionReversed(start: Int? = null, predicate: Instruction.() -> Boolean): Int? |
The last one, searching back from start or the end. |
findFreeRegister(atIndex: Int, exclude: List<Int> = emptyList()): Int |
A register that holds nothing live at atIndex, other than those in exclude. |
Changing
Indexes you saved go stale when you insert or remove instructions before them. Points don't have that problem.
| Member | Description |
|---|---|
insertInstructions(index: Int, insns: List<Instruction>) |
Inserts instructions before index. |
replaceInstruction(index: Int, insn: Instruction) |
Replaces one instruction. |
removeInstructions(index: Int, count: Int) |
Removes count instructions from index. |
replaceBody(registersSize: Int, outsSize: Int, insns: List<Instruction>) |
Replaces the whole body and frame. Points and locals in the method stop working. |
replaceAllStrings(old: String, new: String): Int |
Replaces every load of exactly old. Returns how many changed. |
replaceAllLiterals(old: Long, new: Long): Int |
Replaces every load of the number old. Returns how many changed. |
replaceMethodCall(index: Int, target: MethodRef): Boolean |
Makes the call at index call target instead. |
ensureOutsSize(minOutsSize: Int) |
Grows the frame so calls can pass this many registers. |
setAccessFlags(flags: Int) |
Sets its access flags. |
clone(newName: String? = null): Method |
A copy of the method in the same class, under newName. |
remove() |
Removes the method from its class. |
addAnnotation(annotation: AnnotationItem) |
Adds an annotation. |
DexClass
A class in the app.
Reading
| Member | Description |
|---|---|
handle: UInt |
The engine's id for the class. |
info: ClassInfo |
The class's summary in one record. |
descriptor: String |
Its descriptor. |
superclass: String? |
Its superclass's descriptor. |
interfaces: List<String> |
The interfaces it declares. |
sourceFile: String? |
The source file name the compiler recorded. |
isInterface: Boolean |
Whether it is an interface. |
methods: List<Method> |
All its methods. |
directMethods: List<Method>, virtualMethods: List<Method> |
Its static, private, and constructor methods, and the rest. |
fields: List<FieldInfo> |
All its fields. |
staticFields: List<FieldInfo>, instanceFields: List<FieldInfo> |
Its static and instance fields. |
superclassChain: List<DexClass> |
Its superclasses, nearest first, across every DEX file, up to where the platform's classes start. |
method(name: String, proto: String? = null): Method? |
A method by name, with proto to pick an overload. |
field(name: String): FieldRef? |
A field by name. |
Changing
| Member | Description |
|---|---|
setAccessFlags(flags: Int) |
Sets its access flags. |
definal() |
Removes final, so other classes can extend it. |
setSuperclass(superclass: String) |
Changes its superclass. |
addInterface(descriptor: String) |
Makes it implement an interface. |
addMethod(method: NewMethod): Method |
Adds a method from a NewMethod. For most uses, ClassTarget.addMethod with a code block is simpler. |
addField(field: NewField) |
Adds a field. |
removeField(name: String) |
Removes a field. |
setFieldAccessFlags(fieldName: String, flags: Int) |
Sets a field's access flags. |
setStaticFieldValue(fieldName: String, value: EncodedVal) |
Sets a static field's initial value. |
addAnnotation(annotation: AnnotationItem) |
Adds an annotation to the class. |
addFieldAnnotation(fieldName: String, annotation: AnnotationItem) |
Adds an annotation to a field. |
remove() |
Removes the class. |
Instruction properties
Extension properties that read an Instruction without unpacking its variant. Each is null when the instruction has no such operand.
| Property | Description |
|---|---|
opcode: Opcode? |
The opcode. |
regA: Int?, regB: Int?, regC: Int? |
The first, second, and third register operands. |
referencedRegisters: List<Int> |
Every register the instruction names. |
invokeRegisters: List<Int>? |
The registers a call passes. |
invokeArgumentTypes: List<String>? |
The types a call passes, the object first for an instance call. |
methodRef: MethodRef? |
The method a call names. |
fieldRef: FieldRef? |
The field an access names. |
stringValue: String? |
The string a const-string loads. |
typeRef: String? |
The type an instruction names, such as in new-instance or check-cast. |
literal: Long? |
The number an instruction loads. |
Descriptor helpers
| Member | Description |
|---|---|
MethodRef.returnType, MethodRef.parameterTypes, MethodRef.descriptor |
A reference's return type, parameter types, and full descriptor. |
MethodInfo.returnType, MethodInfo.parameterTypes, MethodInfo.descriptor, MethodInfo.isStatic |
The same for a method's info. |
FieldInfo.ref: FieldRef |
A field's reference. |
parseParameterTypes(proto: String): List<String> |
The parameter types of a prototype. A malformed one fails. |
registerWordCount(type: String): Int |
How many registers a value of this type takes: 2 for long and double, 0 for void, 1 otherwise. |
isReferenceType(type: String): Boolean |
Whether the type is an object or array type. |
Opcode
Every Dalvik opcode, such as Opcode.INVOKE_VIRTUAL or Opcode.IF_EQZ.
| Member | Description |
|---|---|
value: Int |
The opcode's byte. |
isInvoke: Boolean |
Whether it is a call. |
isReturn: Boolean |
Whether it is a return. |
isMoveResult: Boolean |
Whether it reads a call's result. |
endsFlow: Boolean |
Whether execution never falls through to the next instruction: a return, throw, or goto. |
Opcode.of(value: Int): Opcode? |
The opcode with this byte. |
AccessFlags
Flag constants: PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, SYNCHRONIZED, VOLATILE, BRIDGE, TRANSIENT, VARARGS, NATIVE, INTERFACE, ABSTRACT, STRICT, SYNTHETIC, ANNOTATION, ENUM, CONSTRUCTOR, and DECLARED_SYNCHRONIZED. Combine them with or.
| Function | Description |
|---|---|
Int.isSet(flags: Int), Int.isSet(flags: UInt): Boolean |
Whether flags has any bit of this mask. Use it with one constant: AccessFlags.STATIC.isSet(info.accessFlags). |
Int.allSet(flags: Int), Int.allSet(flags: UInt): Boolean |
Whether flags has every bit of this mask, so (PUBLIC or FINAL).allSet(flags) needs both. |
InstructionBuilder
The receiver of buildInstructions { }, which returns the instructions as a List<Instruction>. You choose every register. Where Dalvik has several encodings of one instruction, the builder picks one: constants and moves by register width, field accesses by the field's type, and moves and results by the value's type.
| Member | Instruction |
|---|---|
nop() |
nop |
constInt(dest: Int, value: Int), constLong(dest: Int, value: Long) |
a number constant |
constString(dest: Int, value: String) |
const-string |
constClass(dest: Int, descriptor: String) |
const-class |
move(dest: Int, src: Int, type: String) |
a move, for a value of type |
moveResult(dest: Int, type: String) |
reads the last call's result, of type |
moveException(dest: Int) |
move-exception |
invokeVirtual, invokeSuper, invokeDirect, invokeStatic, invokeInterface (owner: String, name: String, proto: String, vararg registers: Int) |
a call |
invoke(opcode: Opcode, method: MethodRef, registers: List<Int>) |
a call with any invoke opcode |
invokeRange(opcode: Opcode, method: MethodRef, startReg: Int, count: Int) |
a call passing count consecutive registers |
iget(dest: Int, obj: Int, field: FieldRef), iput(src: Int, obj: Int, field: FieldRef) |
read or write an instance field |
sget(dest: Int, field: FieldRef), sput(src: Int, field: FieldRef) |
read or write a static field |
newInstance(dest: Int, descriptor: String) |
new-instance |
newArray(dest: Int, size: Int, descriptor: String) |
new-array |
arrayLength(dest: Int, array: Int) |
array-length |
agetObject(dest: Int, array: Int, index: Int), aputObject(src: Int, array: Int, index: Int) |
read or write an object array element |
checkCast(reg: Int, descriptor: String) |
check-cast |
instanceOf(dest: Int, ref: Int, descriptor: String) |
instance-of |
goto(offset: Int) |
goto |
ifEqz, ifNez, ifLtz, ifGez, ifGtz, ifLez (reg: Int, offset: Int) |
a branch comparing with zero |
ifEq, ifNe, ifLt, ifGe, ifGt, ifLe (regA: Int, regB: Int, offset: Int) |
a branch comparing two registers |
throwValue(reg: Int) |
throw |
cmplFloat, cmpgFloat, cmplDouble, cmpgDouble, cmpLong (dest: Int, src1: Int, src2: Int) |
a comparison |
addInt, subInt, mulInt, divInt, remInt (dest: Int, left: Int, right: Int) |
int arithmetic |
returnVoid(), returnValue(reg: Int), returnWide(reg: Int), returnObject(reg: Int) |
a return |
reg1(opcode: Opcode, reg: Int), reg2(opcode: Opcode, a: Int, b: Int), reg3(opcode: Opcode, a: Int, b: Int, c: Int) |
any instruction with one to three register operands |
literal(opcode: Opcode, dest: Int, value: Long, src: Int = 0) |
any instruction with a number operand |
type(opcode: Opcode, a: Int, b: Int, descriptor: String) |
any instruction with a type operand |
field(opcode: Opcode, a: Int, b: Int, field: FieldRef) |
any field instruction |
add(insn: Instruction) |
an instruction you built yourself |
build(): List<Instruction> |
the instructions so far |
Branch offsets are in code units, counted from the branch. The engine widens a branch that ends up out of reach after other changes. A call whose registers neither fit the five-register form nor sit next to each other is refused. Write that call in a code block instead, where the engine chooses the registers.
app.reseam.patch.types
Data types generated from the engine. You read them from instructions, methods, and classes, and build them for raw edits.
Instruction
One instruction. Each variant holds its operands as field0, a record named after it: Reg1 holds a Reg1Insn, Invoke an InvokeInsn, and so on. Raw holds the encoded bytes of an instruction the engine passes through unchanged.
The variants: Simple, Reg1, Reg2, Reg3, RegLiteral, RegString, RegType, RegField, Invoke, InvokeRange, Polymorphic, PolymorphicRange, Custom, CustomRange, RegHandle, RegProto, Branch0, Branch, Branch2, FilledArray, FilledArrayRange, PackedSwitchData, SparseSwitchData, FillArrayData, and Raw.
Reading operands through the instruction properties is usually simpler than matching variants.
Instruction records
Every record has opcode, the opcode's byte, except the three data payloads.
| Record | Fields |
|---|---|
SimpleInsn |
opcode |
Reg1Insn |
opcode, regA |
Reg2Insn |
opcode, regA, regB |
Reg3Insn |
opcode, regA, regB, regC |
RegLiteralInsn |
opcode, regA, regB, literal |
RegStringInsn |
opcode, regA, value |
RegTypeInsn |
opcode, regA, regB, typeDescriptor |
RegFieldInsn |
opcode, regA, regB, field |
InvokeInsn |
opcode, method, registers |
InvokeRangeInsn |
opcode, method, startReg, regCount |
PolymorphicInsn |
opcode, method, proto, registers |
PolymorphicRangeInsn |
opcode, method, proto, startReg, regCount |
CustomInsn |
opcode, callSite, registers |
CustomRangeInsn |
opcode, callSite, startReg, regCount |
RegHandleInsn |
opcode, regA, handle |
RegProtoInsn |
opcode, regA, proto |
Branch0Insn |
opcode, offset |
BranchInsn |
opcode, regA, offset |
Branch2Insn |
opcode, regA, regB, offset |
FilledArrayInsn |
opcode, typeDescriptor, registers |
FilledArrayRangeInsn |
opcode, typeDescriptor, startReg, regCount |
PackedSwitchInsn |
firstKey, targets: a switch table over consecutive keys |
SparseSwitchInsn |
keys, targets: a switch table over listed keys |
FillArrayInsn |
elementWidth, data: the data of a fill-array-data |
MethodRef
A reference to a method: definingClass, name, and proto.
FieldRef
A reference to a field: definingClass, name, and fieldType.
MethodInfo
What Method.info returns: classDescriptor, methodName, proto, accessFlags, dexIndex, registerCount, insSize, outsSize, and instructionCount.
ClassInfo
What DexClass.info returns: descriptor, accessFlags, superclass, interfaces, sourceFile, dexIndex, directMethodCount, virtualMethodCount, staticFieldCount, and instanceFieldCount.
FieldInfo
One entry of DexClass.fields: classDescriptor, name, fieldType, accessFlags, and initialValue, the EncodedVal a static field starts with.
NewMethod
A method for DexClass.addMethod: name, proto, accessFlags, registersSize, insSize, outsSize, instructions, tries: List<TryItem>, and catchHandlers: List<CatchHandler>.
NewField
A field for DexClass.addField: name, fieldType, accessFlags, and initialValue.
TryItem
A range of a method's code covered by an exception handler: startAddr and insnCount in code units, and handlerIdx, the index of its CatchHandler.
CatchHandler
typedCatches: List<TypedCatch>, and catchAllAddr, where a catch of any exception goes, if there is one.
TypedCatch
One catch: exceptionType and addr, where it goes.
AnnotationItem
An annotation for addAnnotation: visibility, annotationType, and elements.
AnnotationElement
One element of an annotation: name and value, an EncodedVal.
AnnotationValue
An annotation nested in a value: annotationType and elements.
EncodedVal
A constant, as DEX files store them. Each variant holds its value as field0:
| Variant | Value |
|---|---|
Null |
none |
BoolVal, ByteVal, ShortVal, CharVal, IntVal, LongVal, FloatVal, DoubleVal |
the number or boolean |
StringVal |
a String |
TypeVal |
a type descriptor |
ProtoVal |
a method prototype |
FieldVal, EnumVal |
a FieldRef |
MethodVal |
a MethodRef |
HandleVal |
a HandleRef |
ArrayVal |
a List<EncodedVal> |
AnnotationVal |
an AnnotationValue |
HandleRef
A method handle: Field, holding a FieldHandleRef, or Method, holding a MethodHandleRef.
FieldHandleRef
kind, the handle's kind, field, and origin.
MethodHandleRef
kind, the handle's kind, method, and origin.
CallSiteRef
The call site of an invoke-custom: bootstrap, name, proto, arguments, and origin.
PoolOrigin
Where an item came from in the app: dexIndex, the DEX file, and index, its position in that file's table.