android–翻译混淆日志
- 我们在发正式版时一般会开启混淆,但是一旦出现bug拿到的日志也是被混淆过的,这对我们的排查造成了极大的阻碍
- android sdk里有提供工具能翻译混淆过的文件,在Sdk\tools\proguard\bin目录下有proguardgui.bat这个文件,运行它即可
- 然后我们就可以进行利用左侧最下方的retrace功能进行翻译了
- 当然,如果你喜欢命令行,该目录下还有一个retrace.bat,你也可以利用它来进行翻译,参数说明如下
Usage: java proguard.ReTrace [-verbose] <mapping_file> [<stacktrace_file>]
抽取翻译(反混淆)库
- 上面之所以能帮助你翻译文件,主要是使用了Sdk\tools\proguard\lib目录下的库
- 那你一定在想,我是不是也可以利用这个库的api来做一些更好用的翻译工具,或者在android内部测试版本自动将日志翻译好呢
- 实际上这种想法是可行的
- 在网上找到了这个工程: Guardsquare/proguard: ProGuard, Java optimizer and obfuscator
- 我们完全可以抽取里面的部分源代码来进行翻译调用
- 经过本人实践,翻译的话只需要抽取这几个文件:ClassUtil,MappingProcessor,MappingReader,FrameInfo,FramePattern,FrameRemapper,Retrace
注意,随着Guardsquare/proguard工程的更新迭代,代码可能有所变化,到时随机应变
- 但你可能找遍了github上的Guardsquare/proguard这个工程也找不到ClassUtil,没关系,Sdk\tools\proguard\lib下的jar包里就有(事实上你如果直接使用这些jar包也是可以的,但是未免包含太多无关的类了),在jar包里找到它,拷贝出来,找到被依赖的方法,其他方法可以删除掉,或者你直接拷贝以下代码去使用
public class ClassUtil {
public static String internalClassName(String var0) {
return var0.replace('.', '/');
}
}
- 主要是FramePattern会依赖到它的internalClassName方法
- 这样以后抽取的这些代码已经可以跑起来了
- 但你在使用的时候可能会发现MappingReader和Retrace不太好用(毕竟人家主要是命令行工具)
- 因此本人改造了一下(主要采用继承方式)
package proguard.obfuscate;
import java.io.*;
/**
* 修改了{@link MappingReader}的部分方法的可见性范围
* 并提供了更多方便的方法
*/
public class MappingReaderExt extends MappingReader {
public MappingReaderExt() {
super(null);
}
/**
* 请勿使用
* @param mappingProcessor
* @throws IOException
*/
@Deprecated
@Override
public void pump(MappingProcessor mappingProcessor) throws IOException {
throw new IOException("Deprecated!!! Do not use any more!!!");
}
public void pump(File mappingFile, MappingProcessor mappingProcessor) throws IOException {
pump(new FileInputStream(mappingFile), mappingProcessor);
}
public void pump(InputStream mappingInputStream, MappingProcessor mappingProcessor) throws IOException {
LineNumberReader reader =
new LineNumberReader(
new BufferedReader(
new InputStreamReader(mappingInputStream, "UTF-8")));
try {
String[] className = new String[]{null};
// Read the subsequent class mappings and class member mappings.
while (true) {
String line = reader.readLine();
if (!pumpInternal(mappingProcessor, className, line)) break;
}
} catch (IOException ex) {
throw new IOException("Can't process mapping file (" + ex.getMessage() + ")");
} finally {
try {
reader.close();
} catch (IOException ex) {
// This shouldn't happen.
}
}
}
public void pump(String mappingString, MappingProcessor mappingProcessor) {
String[] mappingStringLines = mappingString.split("\n");
String[] className = new String[]{null};
// Read the subsequent class mappings and class member mappings.
for (int i = 0; i < mappingStringLines.length; i++) {
String line = mappingStringLines[i];
if (!pumpInternal(mappingProcessor, className, line)) break;
}
}
private boolean pumpInternal(MappingProcessor mappingProcessor, String[] className, String line) {
if (line == null) {
return false;
}
line = line.trim();
// Is it a non-comment line?
if (!line.startsWith("#")) {
// Is it a class mapping or a class member mapping?
if (line.endsWith(":")) {
// Process the class mapping and remember the class's
// old name.
className[0] = processClassMapping(line, mappingProcessor);
} else if (className[0] != null) {
// Process the class member mapping, in the context of
// the current old class name.
processClassMemberMapping(className[0], line, mappingProcessor);
}
}
return true;
}
}
- Retrace改造如下:
package proguard.retrace;
import proguard.obfuscate.MappingReaderExt;
import java.io.*;
/**
* 修改了{@link ReTrace}的部分方法的可见性范围
* 增加换行符
* 并提供了更多方便的方法
*/
public class ReTraceExt extends ReTrace {
private final FramePattern pattern1;
private final FramePattern pattern2;
public ReTraceExt() {
//第三个参数表示是否在不匹配正则的情况下只要匹配类的情况也进行反混淆,一般都是需要的,因此建议为true
this(REGULAR_EXPRESSION, REGULAR_EXPRESSION2, true, false);
}
/**
*
* @param verbose true--反混淆时输出更多信息,比如方法的入参和返回值,如果找不到则显示null
*/
public ReTraceExt(boolean verbose) {
this(REGULAR_EXPRESSION, REGULAR_EXPRESSION2, true, verbose);
}
public ReTraceExt(String regularExpression, String regularExpression2, boolean allClassNames, boolean verbose) {
super(regularExpression, regularExpression2, allClassNames, verbose, null);
pattern1 = new FramePattern(regularExpression, verbose);
pattern2 = new FramePattern(regularExpression2, verbose);
}
public String retrace(File mappingFile, File stackTraceFile) throws IOException {
return retrace(mappingFile, new FileInputStream(stackTraceFile));
}
public String retrace(File mappingFile, InputStream stackTraceInputStream) throws IOException {
LineNumberReader reader = getLineNumberReader(stackTraceInputStream);
return retrace(getFrameRemapper(mappingFile), reader);
}
public String retrace(File mappingFile, String stackTraceString) throws IOException {
return retrace(getFrameRemapper(mappingFile), stackTraceString);
}
public String retrace(InputStream mappingInputStream, File stackTraceFile) throws IOException {
return retrace(mappingInputStream, new FileInputStream(stackTraceFile));
}
public String retrace(InputStream mappingInputStream, InputStream stackTraceInputStream) throws IOException {
LineNumberReader reader = getLineNumberReader(stackTraceInputStream);
return retrace(getFrameRemapper(mappingInputStream), reader);
}
public String retrace(InputStream mappingInputStream, String stackTraceString) throws IOException {
return retrace(getFrameRemapper(mappingInputStream), stackTraceString);
}
public String retrace(String mappingString, File stackTraceFile) throws IOException {
return retrace(mappingString, new FileInputStream(stackTraceFile));
}
public String retrace(String mappingString, InputStream stackTraceInputStream) throws IOException {
LineNumberReader reader = getLineNumberReader(stackTraceInputStream);
return retrace(getFrameRemapper(mappingString), reader);
}
public String retrace(String mappingString, String stackTraceString) {
return retrace(getFrameRemapper(mappingString), stackTraceString);
}
/**
* 单行文本retrace
*
* @param obfuscatedLine 混淆过的单行文本
* @param mapper
* @return
*/
private String retraceLine(FrameRemapper mapper, String obfuscatedLine) {
if (obfuscatedLine == null || obfuscatedLine.trim().isEmpty()) {
return obfuscatedLine;
}
// Try to match it against the regular expression.
FrameInfo obfuscatedFrame1 = pattern1.parse(obfuscatedLine);
FrameInfo obfuscatedFrame2 = pattern2.parse(obfuscatedLine);
String deobf = handle(obfuscatedFrame1, mapper, pattern1, obfuscatedLine);
// DIRTY FIX:
// I have to execute it two times because recent Java stacktraces may have multiple fields/methods in the same line.
// For example: java.lang.NullPointerException: Cannot invoke "com.example.Foo.bar.foo(int)" because the return value of "com.example.Foo.bar.foo2()" is null
deobf = handle(obfuscatedFrame2, mapper, pattern2, deobf);
return deobf;
}
/**
* De-obfuscates a given stack trace
*
* @param stackTraceReader a reader for the obfuscated stack trace
* @return 翻译后的字符串
* @throws IOException
*/
public String retrace(FrameRemapper mapper, LineNumberReader stackTraceReader) throws IOException {
StringBuilder result = new StringBuilder();
// Read and process the lines of the stack trace.
while (true) {
// Read a line.
String obfuscatedLine = stackTraceReader.readLine();
if (obfuscatedLine == null) {
break;
}
result.append(retraceLine(mapper, obfuscatedLine + "\n"));
}
if (stackTraceReader != null) {
stackTraceReader.close();
}
return result.toString();
}
private String retrace(FrameRemapper mapper, String stackTraceString) {
StringBuilder result = new StringBuilder();
String[] stackTraceLines = stackTraceString.split("\n");
for (int i = 0; i < stackTraceLines.length; i++) {
String obfuscatedLine = stackTraceLines[i] + "\n";
result.append(retraceLine(mapper, obfuscatedLine));
}
return result.toString();
}
private FrameRemapper getFrameRemapper(File mappingFile) throws IOException {
MappingReaderExt mappingReader = new MappingReaderExt();
FrameRemapper mapper = new FrameRemapper();
mappingReader.pump(mappingFile, mapper);
return mapper;
}
private FrameRemapper getFrameRemapper(InputStream mappingInputStream) throws IOException {
MappingReaderExt mappingReader = new MappingReaderExt();
FrameRemapper mapper = new FrameRemapper();
mappingReader.pump(mappingInputStream, mapper);
return mapper;
}
private FrameRemapper getFrameRemapper(String mappingString) {
MappingReaderExt mappingReader = new MappingReaderExt();
FrameRemapper mapper = new FrameRemapper();
mappingReader.pump(mappingString, mapper);
return mapper;
}
private LineNumberReader getLineNumberReader(InputStream stackTraceInputStream) throws UnsupportedEncodingException {
return new LineNumberReader(
new InputStreamReader(stackTraceInputStream, "UTF-8"));
}
}
- 最后封装下提供个便捷使用的工具类
import proguard.retrace.ReTraceExt;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class RetraceUtil {
/**
* 翻译混淆后的文件
*
* @param mappingFile 映射文件
* @param stackTraceFile 待翻译的文件
* @return 翻译结果,null表示翻译失败
*/
public static String retrace(File mappingFile, File stackTraceFile) {
try {
String result = new ReTraceExt()
.retrace(mappingFile, stackTraceFile);
return result;
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
}
/**
* 翻译混淆后的文件
*
* @param mappingInputStream 映射文件
* @param stackTraceInputStream 待翻译的文件
* @return null表示翻译失败
*/
public static String retrace(InputStream mappingInputStream, InputStream stackTraceInputStream) {
try {
String result = new ReTraceExt().retrace(mappingInputStream, stackTraceInputStream);
return result;
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
}
/**
* 翻译混淆后的字符串
*
* @param mappingString 映射字符串
* @param stackTraceString 待翻译的字符串
* @return null表示翻译失败
*/
public static String retrace(String mappingString, String stackTraceString) {
String result = new ReTraceExt().retrace(mappingString, stackTraceString);
return result;
}
}
- 接下来你就可以愉快的使用这个工具类去进行翻译混淆文件了
- 另外,为了方便使用,已经制作成jar,欢迎使用:retrace: 混淆日志的翻译库
- 基于此jar,提供了一个后台翻译服务:RMS: 一个android日志retrace管理服务
- 配套有web项目,方便在线使用:junmeng/retrace-ui
其他
关于参数allClassNames的理解
- 原文解释为:specifies whether all words that match class names should be de-obfuscated, even if they aren’t matching the regular expression
- 翻译一下:该参数指定是否对所有与类名匹配的单词进行反混淆,即使它们不匹配正则表达式
- 这句话的意思是,该参数用于确定是否应对所有与类名匹配的单词进行反混淆,即使它们不符合正则表达式的匹配规则
- 换句话说,即使某个单词与类名模式不完全匹配,该参数也可以指定是否对其进行反混淆操作