All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.shark.HprofDeobfuscator.kt Maven / Gradle / Ivy

package shark

import shark.HprofHeader.Companion.parseHeaderOf
import shark.HprofRecord.HeapDumpEndRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord
import shark.HprofRecord.LoadClassRecord
import shark.HprofRecord.StackFrameRecord
import shark.HprofRecord.StringRecord
import shark.StreamingRecordReaderAdapter.Companion.asStreamingRecordReader
import java.io.File

/**
 * Converts a Hprof file to another file with deobfuscated class and field names.
 */
class HprofDeobfuscator {

  /**
   * @see HprofDeobfuscator
   */
  fun deobfuscate(
    proguardMapping: ProguardMapping,
    inputHprofFile: File,
    /**
     * Optional output file. Defaults to a file in the same directory as [inputHprofFile], with
     * the same name and "-deobfuscated" prepended before the ".hprof" extension. If the file extension
     * is not ".hprof", then "-deobfuscated" is added at the end of the file.
     */
    outputHprofFile: File = File(
      inputHprofFile.parent, inputHprofFile.name.replace(
      ".hprof", "-deobfuscated.hprof"
    ).let { if (it != inputHprofFile.name) it else inputHprofFile.name + "-deobfuscated" })
  ): File {
    val (hprofStringCache, classNames, maxId) = readHprofRecords(inputHprofFile)

    return writeHprofRecords(
      inputHprofFile,
      outputHprofFile,
      proguardMapping,
      hprofStringCache,
      classNames,
      maxId + 1
    )
  }

  /**
   * Reads StringRecords and LoadClassRecord from an Hprof file and tracks maximum HprofRecord id
   * value.
   *
   * @return a Triple of: hprofStringCache map, classNames map and maxId value
   */
  private fun readHprofRecords(
    inputHprofFile: File
  ): Triple, Map, Long> {
    val hprofStringCache = mutableMapOf()
    val classNames = mutableMapOf()

    var maxId: Long = 0

    val reader = StreamingHprofReader.readerFor(inputHprofFile).asStreamingRecordReader()
    reader.readRecords(setOf(HprofRecord::class)
    ) { _, record ->
      when (record) {
        is StringRecord -> {
          maxId = maxId.coerceAtLeast(record.id)
          hprofStringCache[record.id] = record.string
        }
        is LoadClassRecord -> {
          maxId = maxId.coerceAtLeast(record.id)
          classNames[record.id] = record.classNameStringId
        }
        is StackFrameRecord -> maxId = maxId.coerceAtLeast(record.id)
        is ObjectRecord -> {
          maxId = when (record) {
            is ClassDumpRecord -> maxId.coerceAtLeast(record.id)
            is InstanceDumpRecord -> maxId.coerceAtLeast(record.id)
            is ObjectArrayDumpRecord -> maxId.coerceAtLeast(record.id)
            is PrimitiveArrayDumpRecord -> maxId.coerceAtLeast(record.id)
          }
        }
        else -> {
          // Don't care.
        }
      }
    }
    return Triple(hprofStringCache, classNames, maxId)
  }

  @Suppress("LongParameterList")
  private fun writeHprofRecords(
    inputHprofFile: File,
    outputHprofFile: File,
    proguardMapping: ProguardMapping,
    hprofStringCache: Map,
    classNames: Map,
    firstId: Long
  ): File {
    var id = firstId

    val hprofHeader = parseHeaderOf(inputHprofFile)
    val reader =
      StreamingHprofReader.readerFor(inputHprofFile, hprofHeader).asStreamingRecordReader()
    HprofWriter.openWriterFor(
      outputHprofFile,
      hprofHeader = hprofHeader
    ).use { writer ->
      reader.readRecords(setOf(HprofRecord::class),
        OnHprofRecordListener { _,
          record ->
          // HprofWriter automatically emits HeapDumpEndRecord, because it flushes
          // all continuous heap dump sub records as one heap dump record.
          if (record is HeapDumpEndRecord) {
            return@OnHprofRecordListener
          }

          when (record) {
            is StringRecord -> {
              writer.write(
                createDeobfuscatedStringRecord(record, proguardMapping, hprofStringCache)
              )
            }
            is ClassDumpRecord -> {
              val (recordsToWrite, maxId) = createDeobfuscatedClassDumpRecord(
                record, proguardMapping, hprofStringCache, classNames, id
              )
              id = maxId
              recordsToWrite.forEach {
                writer.write(it)
              }
            }
            else -> writer.write(record)
          }
        })
    }

    return outputHprofFile
  }

  private fun createDeobfuscatedStringRecord(
    record: StringRecord,
    proguardMapping: ProguardMapping,
    hprofStringCache: Map
  ): StringRecord {
    val obfuscatedName = hprofStringCache[record.id]!!
    return StringRecord(
      record.id, proguardMapping.deobfuscateClassName(obfuscatedName)
    )
  }

  /**
   * Deobfuscated ClassDumpRecord's field names. Different classes can have fields with the same
   * names. We need to generate new StringRecords in such cases.
   *
   * @return a Pair of: list of HprofRecords to write and new maxId value
   */
  private fun createDeobfuscatedClassDumpRecord(
    record: ClassDumpRecord,
    proguardMapping: ProguardMapping,
    hprofStringCache: Map,
    classNames: Map,
    maxId: Long
  ): Pair, Long> {
    val recordsToWrite = mutableListOf()

    var id = maxId

    val newFields = record.fields.map { field ->
      val className = hprofStringCache[classNames[record.id]]!!
      val fieldName = hprofStringCache[field.nameStringId]!!
      val deobfuscatedName =
        proguardMapping.deobfuscateFieldName(className, fieldName)

      val newStringRecord = StringRecord(id++, deobfuscatedName)
      recordsToWrite.add(newStringRecord)

      FieldRecord(newStringRecord.id, field.type)
    }
    val newStaticFields = record.staticFields.map { field ->
      val className = hprofStringCache[classNames[record.id]]!!
      val fieldName = hprofStringCache[field.nameStringId]!!
      val deobfuscatedName =
        proguardMapping.deobfuscateFieldName(className, fieldName)

      val newStringRecord = StringRecord(id++, deobfuscatedName)
      recordsToWrite.add(newStringRecord)

      StaticFieldRecord(newStringRecord.id, field.type, field.value)
    }

    recordsToWrite.add(
      ClassDumpRecord(
        record.id,
        record.stackTraceSerialNumber,
        record.superclassId,
        record.classLoaderId,
        record.signersId,
        record.protectionDomainId,
        record.instanceSize,
        newStaticFields,
        newFields
      )
    )

    return Pair(recordsToWrite, id)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy