<lambda>null1 package shark
2
3 import shark.HprofHeader.Companion.parseHeaderOf
4 import shark.HprofRecord.HeapDumpEndRecord
5 import shark.HprofRecord.HeapDumpRecord.ObjectRecord
6 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
7 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
8 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
9 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
10 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
11 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord
12 import shark.HprofRecord.LoadClassRecord
13 import shark.HprofRecord.StackFrameRecord
14 import shark.HprofRecord.StringRecord
15 import shark.StreamingRecordReaderAdapter.Companion.asStreamingRecordReader
16 import java.io.File
17
18 /**
19 * Converts a Hprof file to another file with deobfuscated class and field names.
20 */
21 class HprofDeobfuscator {
22
23 /**
24 * @see HprofDeobfuscator
25 */
26 fun deobfuscate(
27 proguardMapping: ProguardMapping,
28 inputHprofFile: File,
29 /**
30 * Optional output file. Defaults to a file in the same directory as [inputHprofFile], with
31 * the same name and "-deobfuscated" prepended before the ".hprof" extension. If the file extension
32 * is not ".hprof", then "-deobfuscated" is added at the end of the file.
33 */
34 outputHprofFile: File = File(
35 inputHprofFile.parent, inputHprofFile.name.replace(
36 ".hprof", "-deobfuscated.hprof"
37 ).let { if (it != inputHprofFile.name) it else inputHprofFile.name + "-deobfuscated" })
38 ): File {
39 val (hprofStringCache, classNames, maxId) = readHprofRecords(inputHprofFile)
40
41 return writeHprofRecords(
42 inputHprofFile,
43 outputHprofFile,
44 proguardMapping,
45 hprofStringCache,
46 classNames,
47 maxId + 1
48 )
49 }
50
51 /**
52 * Reads StringRecords and LoadClassRecord from an Hprof file and tracks maximum HprofRecord id
53 * value.
54 *
55 * @return a Triple of: hprofStringCache map, classNames map and maxId value
56 */
57 private fun readHprofRecords(
58 inputHprofFile: File
59 ): Triple<Map<Long, String>, Map<Long, Long>, Long> {
60 val hprofStringCache = mutableMapOf<Long, String>()
61 val classNames = mutableMapOf<Long, Long>()
62
63 var maxId: Long = 0
64
65 val reader = StreamingHprofReader.readerFor(inputHprofFile).asStreamingRecordReader()
66 reader.readRecords(setOf(HprofRecord::class)
67 ) { _, record ->
68 when (record) {
69 is StringRecord -> {
70 maxId = maxId.coerceAtLeast(record.id)
71 hprofStringCache[record.id] = record.string
72 }
73 is LoadClassRecord -> {
74 maxId = maxId.coerceAtLeast(record.id)
75 classNames[record.id] = record.classNameStringId
76 }
77 is StackFrameRecord -> maxId = maxId.coerceAtLeast(record.id)
78 is ObjectRecord -> {
79 maxId = when (record) {
80 is ClassDumpRecord -> maxId.coerceAtLeast(record.id)
81 is InstanceDumpRecord -> maxId.coerceAtLeast(record.id)
82 is ObjectArrayDumpRecord -> maxId.coerceAtLeast(record.id)
83 is PrimitiveArrayDumpRecord -> maxId.coerceAtLeast(record.id)
84 }
85 }
86 }
87 }
88 return Triple(hprofStringCache, classNames, maxId)
89 }
90
91 @Suppress("LongParameterList")
92 private fun writeHprofRecords(
93 inputHprofFile: File,
94 outputHprofFile: File,
95 proguardMapping: ProguardMapping,
96 hprofStringCache: Map<Long, String>,
97 classNames: Map<Long, Long>,
98 firstId: Long
99 ): File {
100 var id = firstId
101
102 val hprofHeader = parseHeaderOf(inputHprofFile)
103 val reader =
104 StreamingHprofReader.readerFor(inputHprofFile, hprofHeader).asStreamingRecordReader()
105 HprofWriter.openWriterFor(
106 outputHprofFile,
107 hprofHeader = hprofHeader
108 ).use { writer ->
109 reader.readRecords(setOf(HprofRecord::class),
110 OnHprofRecordListener { _,
111 record ->
112 // HprofWriter automatically emits HeapDumpEndRecord, because it flushes
113 // all continuous heap dump sub records as one heap dump record.
114 if (record is HeapDumpEndRecord) {
115 return@OnHprofRecordListener
116 }
117
118 when (record) {
119 is StringRecord -> {
120 writer.write(
121 createDeobfuscatedStringRecord(record, proguardMapping, hprofStringCache)
122 )
123 }
124 is ClassDumpRecord -> {
125 val (recordsToWrite, maxId) = createDeobfuscatedClassDumpRecord(
126 record, proguardMapping, hprofStringCache, classNames, id
127 )
128 id = maxId
129 recordsToWrite.forEach {
130 writer.write(it)
131 }
132 }
133 else -> writer.write(record)
134 }
135 })
136 }
137
138 return outputHprofFile
139 }
140
141 private fun createDeobfuscatedStringRecord(
142 record: StringRecord,
143 proguardMapping: ProguardMapping,
144 hprofStringCache: Map<Long, String>
145 ): StringRecord {
146 val obfuscatedName = hprofStringCache[record.id]!!
147 return StringRecord(
148 record.id, proguardMapping.deobfuscateClassName(obfuscatedName)
149 )
150 }
151
152 /**
153 * Deobfuscated ClassDumpRecord's field names. Different classes can have fields with the same
154 * names. We need to generate new StringRecords in such cases.
155 *
156 * @return a Pair of: list of HprofRecords to write and new maxId value
157 */
158 private fun createDeobfuscatedClassDumpRecord(
159 record: ClassDumpRecord,
160 proguardMapping: ProguardMapping,
161 hprofStringCache: Map<Long, String>,
162 classNames: Map<Long, Long>,
163 maxId: Long
164 ): Pair<List<HprofRecord>, Long> {
165 val recordsToWrite = mutableListOf<HprofRecord>()
166
167 var id = maxId
168
169 val newFields = record.fields.map { field ->
170 val className = hprofStringCache[classNames[record.id]]!!
171 val fieldName = hprofStringCache[field.nameStringId]!!
172 val deobfuscatedName =
173 proguardMapping.deobfuscateFieldName(className, fieldName)
174
175 val newStringRecord = StringRecord(id++, deobfuscatedName)
176 recordsToWrite.add(newStringRecord)
177
178 FieldRecord(newStringRecord.id, field.type)
179 }
180 val newStaticFields = record.staticFields.map { field ->
181 val className = hprofStringCache[classNames[record.id]]!!
182 val fieldName = hprofStringCache[field.nameStringId]!!
183 val deobfuscatedName =
184 proguardMapping.deobfuscateFieldName(className, fieldName)
185
186 val newStringRecord = StringRecord(id++, deobfuscatedName)
187 recordsToWrite.add(newStringRecord)
188
189 StaticFieldRecord(newStringRecord.id, field.type, field.value)
190 }
191
192 recordsToWrite.add(
193 ClassDumpRecord(
194 record.id,
195 record.stackTraceSerialNumber,
196 record.superclassId,
197 record.classLoaderId,
198 record.signersId,
199 record.protectionDomainId,
200 record.instanceSize,
201 newStaticFields,
202 newFields
203 )
204 )
205
206 return Pair(recordsToWrite, id)
207 }
208 }
209