官方文档对于dex中的class数据结构表示如下:
基本上就是这样了,再看
public DexBackedClassDef(@Nonnull DexBackedDexFile dexFile,
int classDefOffset) {
this.dexFile = dexFile;
//classDefOffset 是这个类结构体在dex文件中的偏移地址。
this.classDefOffset = classDefOffset;
//获取类的数据部分的偏移
int classDataOffset = dexFile.readSmallUint(classDefOffset + ClassDefItem.CLASS_DATA_OFFSET);
if (classDataOffset == 0) {
staticFieldsOffset = -1;
staticFieldCount = 0;
instanceFieldCount = 0;
directMethodCount = 0;
virtualMethodCount = 0;
} else {
//如果不等于0,则要读取各种变量,方法的个数 保存到这个类的私有成员变量中,等到实际解析的时候
//再来使用
DexReader reader = dexFile.readerAt(classDataOffset);
staticFieldCount = reader.readSmallUleb128();
instanceFieldCount = reader.readSmallUleb128();
directMethodCount = reader.readSmallUleb128();
virtualMethodCount = reader.readSmallUleb128();
staticFieldsOffset = reader.getOffset();
}
}
这里再列出来 dex文件关于 class类数据的格式说明,以方便读者对代码的理解
ok 我们再回到
List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses());
这条语句,通过对里面机制的了解,已经知道,其实这条语句完成以后,
List<? extends ClassDef> classDefs 这个变量已经保存了 dex文件中关于类的各种信息。
故事2
return disassembleClass(classDef, fileNameHandler, options);
private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler,
baksmaliOptions options) {
//获取类名
String classDescriptor = classDef.getType();
//validate that the descriptor is formatted like we expect
if (classDescriptor.charAt(0) != 'L' ||
classDescriptor.charAt(classDescriptor.length()-1) != ';') {
System.err.println("Unrecognized class descriptor - " + classDescriptor + " - skipping class");
return false;
}
//生成相应要输入smali文件的位置信息
File smaliFile = fileNameHandler.getUniqueFilenameForClass(classDescriptor);
//create and initialize the top level string template
ClassDefinition classDefinition = new ClassDefinition(options, classDef); // 重点1
//write the disassembly
Writer writer = null;
try
{
File smaliParent = smaliFile.getParentFile();
if (!smaliParent.exists()) {
if (!smaliParent.mkdirs()) {
// check again, it's likely it was created in a different thread
if (!smaliParent.exists()) {
System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class");
return false;
}
}
}
if (!smaliFile.exists()){
if (!smaliFile.createNewFile()) {
System.err.println("Unable to create file " + smaliFile.toString() + " - skipping class");
return false;
}
}
BufferedWriter bufWriter = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(smaliFile), "UTF8"));
writer = new IndentingWriter(bufWriter);
classDefinition.writeTo((IndentingWriter)writer); //重点2
} catch (Exception ex) {
System.err.println("\n\nError occurred while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class");
ex.printStackTrace();
// noinspection ResultOfMethodCallIgnored
smaliFile.delete();
return false;
}
finally
{
if (writer != null) {
try {
writer.close();
} catch (Throwable ex) {
System.err.println("\n\nError occurred while closing file " + smaliFile.toString());
ex.printStackTrace();
}
}
}
return true;
}
这个函数有两个重点
ClassDefinition classDefinition = new ClassDefinition(options, classDef); // 重点1
classDefinition.writeTo((IndentingWriter)writer); //重点2
其实这两个重点调用完成以后,整个smali文件就已经生成了,所以我们就顺着前面的脚步跟进去,看看这两个重点到底做了什么事情
1 构造函数将 classdef 传入到 ClassDefinition 这个类中
public ClassDefinition(@Nonnull baksmaliOptions options, @Nonnull ClassDef classDef) {
this.options = options;
this.classDef = classDef;
fieldsSetInStaticConstructor = findFieldsSetInStaticConstructor();
}
2 writeTo 将生成smali文件的各个元素给写入到 IndentingWriter writer 代表的smali文件中。
public void writeTo(IndentingWriter writer) throws IOException {
writeClass(writer);
writeSuper(writer);
writeSourceFile(writer);
writeInterfaces(writer);
writeAnnotations(writer);
Set<String> staticFields = writeStaticFields(writer);
writeInstanceFields(writer, staticFields);
Set<String> directMethods = writeDirectMethods(writer);
writeVirtualMethods(writer, directMethods);
}
到这里baksmali 源码的分析,大体框架已经完成。
当然还有很多细节了,其实主要涉及在 public void writeTo(IndentingWriter writer) 这个函数里面
我们举一个比较复杂的例子 Set<String> directMethods = writeDirectMethods(writer); 来代码跟踪一边,看看里面的做了什么,
基本上就搞清楚 里面做的事情了
private Set<String> writeDirectMethods(IndentingWriter writer) throws IOException {
boolean wroteHeader = false;
Set<String> writtenMethods = new HashSet<String>();
Iterable<? extends Method> directMethods;
if (classDef instanceof DexBackedClassDef) {
directMethods = ((DexBackedClassDef)classDef).getDirectMethods(false); //重点1
} else {
directMethods = classDef.getDirectMethods();
}
for (Method method: directMethods) {
if (!wroteHeader) {
writer.write("\n\n");
writer.write("# direct methods");
wroteHeader = true;
}
writer.write('\n');
...
MethodImplementation methodImpl = method.getImplementation();
if (methodImpl == null) {
MethodDefinition.writeEmptyMethodTo(methodWriter, method, options);
} else {
MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl); //重点2
methodDefinition.writeTo(methodWriter); //重点3
}
}
return writtenMethods;
}
这个函数有三个重点
directMethods = ((DexBackedClassDef)classDef).getDirectMethods(false); //重点1
public Iterable<? extends DexBackedMethod> getDirectMethods(final boolean skipDuplicates) {
if (directMethodCount > 0) {
//首先得到这个类中的 direct 方法的在dex文件中的偏移地址
DexReader reader = dexFile.readerAt(getDirectMethodsOffset());
final AnnotationsDirectory annotationsDirectory = getAnnotationsDirectory();
final int methodsStartOffset = reader.getOffset();
//返回 new Iterable<DexBackedMethod>()给上层的调用函数,并且继承实现了
//iterator() 这个函数
return new Iterable<DexBackedMethod>() {
@Nonnull
@Override
public Iterator<DexBackedMethod> iterator() {
final AnnotationsDirectory.AnnotationIterator methodAnnotationIterator =
annotationsDirectory.getMethodAnnotationIterator();
final AnnotationsDirectory.AnnotationIterator parameterAnnotationIterator =
annotationsDirectory.getParameterAnnotationIterator();
//返回了 new VariableSizeLookaheadIterator<DexBackedMethod>(dexFile, methodsStartOffset)
//这个对象,里面继承实现了 readNextItem 这个方法,这个方法通过传入的 方法开始偏移,从
//dex文件中 返回 DexBackedMethod 这个对象给上层
return new VariableSizeLookaheadIterator<DexBackedMethod>(dexFile, methodsStartOffset) {
private int count;
@Nullable private MethodReference previousMethod;
private int previousIndex;
@Nullable
@Override
protected DexBackedMethod readNextItem(@Nonnull DexReader reader) {
while (true) {
if (++count > directMethodCount) {
virtualMethodsOffset = reader.getOffset();
return null;
}
// 生成一个 method的对象
DexBackedMethod item = new DexBackedMethod(reader, DexBackedClassDef.this,
previousIndex, methodAnnotationIterator, parameterAnnotationIterator); //重点1
MethodReference currentMethod = previousMethod;
MethodReference nextMethod = ImmutableMethodReference.of(item);
previousMethod = nextMethod;
previousIndex = item.methodIndex;
if (skipDuplicates && currentMethod != null && currentMethod.equals(nextMethod)) {
continue;
}
return item;
}
}
};
}
};
} else {
if (directMethodsOffset > 0) {
virtualMethodsOffset = directMethodsOffset;
}
return ImmutableSet.of();
}
}
关于重点1
public DexBackedMethod(@Nonnull DexReader reader,
@Nonnull DexBackedClassDef classDef,
int previousMethodIndex,
@Nonnull AnnotationsDirectory.AnnotationIterator methodAnnotationIterator,
@Nonnull AnnotationsDirectory.AnnotationIterator paramaterAnnotationIterator) {
this.dexFile = reader.dexBuf;
this.classDef = classDef;
// large values may be used for the index delta, which cause the cumulative index to overflow upon
// addition, effectively allowing out of order entries.
int methodIndexDiff = reader.readLargeUleb128();
this.methodIndex = methodIndexDiff + previousMethodIndex;
this.accessFlags = reader.readSmallUleb128();
this.codeOffset = reader.readSmallUleb128();
this.methodAnnotationSetOffset = methodAnnotationIterator.seekTo(methodIndex);
this.parameterAnnotationSetListOffset = paramaterAnnotationIterator.seekTo(methodIndex);
}
根据官方文档,encoded_method Format 这种格式的数据结构
其实这个构造函数就是将 数据结构中要求的索引从dex文件中找到,保存到自己的私有成员变量当中
重点2
MethodImplementation methodImpl = method.getImplementation();
public DexBackedMethodImplementation getImplementation() {
if (codeOffset > 0) {
return new DexBackedMethodImplementation(dexFile, this, codeOffset);
}
return null;
}
重点3
MethodDefinition methodDefinition = new MethodDefinition(this, method, methodImpl);
public MethodDefinition(@Nonnull ClassDefinition classDef, @Nonnull Method method,
@Nonnull MethodImplementation methodImpl) {
this.classDef = classDef;
this.method = method;
this.methodImpl = methodImpl;
//这里传入的method其实是 DexBackedMethod
try {
//TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh.
//methodImpl.getInstructions() 其实是调用的是 public Iterable<? extends Instruction> getInstructions()
// 在 DexBackedMethodImplementation 这个类中实现的,主要是根据前面的偏移从dex文件中读取相应的指令数据
//放在指令列表中
instructions = ImmutableList.copyOf(methodImpl.getInstructions());
methodParameters = ImmutableList.copyOf(method.getParameters());
packedSwitchMap = new SparseIntArray(0);
sparseSwitchMap = new SparseIntArray(0);
instructionOffsetMap = new InstructionOffsetMap(instructions);
for (int i=0; i<instructions.size(); i++) {
Instruction instruction = instructions.get(i);
//处理switch case 指令
Opcode opcode = instruction.getOpcode();
if (opcode == Opcode.PACKED_SWITCH) {
boolean valid = true;
int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
try {
targetOffset = findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
} catch (InvalidSwitchPayload ex) {
valid = false;
}
if (valid) {
packedSwitchMap.append(targetOffset, codeOffset);
}
} else if (opcode == Opcode.SPARSE_SWITCH) {
boolean valid = true;
int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
try {
targetOffset = findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
} catch (InvalidSwitchPayload ex) {
valid = false;
// The offset to the payload instruction was invalid. Nothing to do, except that we won't
// add this instruction to the map.
}
if (valid) {
sparseSwitchMap.append(targetOffset, codeOffset);
}
}
}
}catch (Exception ex) {
String methodString;
try {
methodString = ReferenceUtil.getMethodDescriptor(method);
} catch (Exception ex2) {
throw ExceptionWithContext.withContext(ex, "Error while processing method");
}
throw ExceptionWithContext.withContext(ex, "Error while processing method %s", methodString);
}
}
重点4
methodDefinition.writeTo(methodWriter);
这个函数其实也是十分复杂的一个函数,但是总体的思路,其实也是根据前面传递过来的数据,主要是索引值和偏移地址,来
将method里面的数据写回到 smali文件中去
由于篇幅的关系,这里就不在那么细节的分析 method的writeTo了,在看 method的writeTo方法的时候,需要
仔细理解一下 parameterRegisterCount 这个局部变量的赋值情况。总体来说java代码中非静态方法会自动为该函数加入一个参数
其实这个参数就相当于 this指针的作用,由于dalvik虚拟机中的寄存器都是32位的,所以对于 J和D也就是 long和Double类型的
其实每个参数是用两个寄存器表示的。
从下面的代码也能看出来
for (MethodParameter parameter: methodParameters) {
String type = parameter.getType();
writer.write(type);
parameterRegisterCount++;
if (TypeUtils.isWideType(type)) {
parameterRegisterCount++;
}
}
理解参数占用的寄存器数量是如何计算出来以后,就能很好的理解smali代码中关于p寄存器和v寄存器表示的规则了,并且为后续编写dex文件为函数添加寄存器的功能打下基础。
总之,baksmali对于写方法来说,基本上是最复杂的操作了,在理解了写入方法的操作以后,前面的操作的理解基本上应该不成问题。
到这里,基本上已经将baksmali的框架分析完成了。下一步 我们需要分析 smali框架了源代码了