diff --git a/art/kotlin-explorer.png b/art/kotlin-explorer.png index 9c3ff449..6640984e 100644 Binary files a/art/kotlin-explorer.png and b/art/kotlin-explorer.png differ diff --git a/build.gradle.kts b/build.gradle.kts index ddfa32e6..2a9c4ebb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -68,7 +68,7 @@ compose.desktop { targetFormats(TargetFormat.Dmg) packageName = "Kotlin Explorer" - packageVersion = "1.0.1" + packageVersion = "1.1.0" description = "Kotlin Explorer" vendor = "Romain Guy" licenseFile = rootProject.file("LICENSE") diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/bytecode/ByteCodeParser.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/bytecode/ByteCodeParser.kt index 1b206964..b2063bfb 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/bytecode/ByteCodeParser.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/bytecode/ByteCodeParser.kt @@ -105,7 +105,7 @@ class ByteCodeParser { val instructions = readInstructions() val lineNumbers = readLineNumbers() - return Method(header, instructions.withLineNumbers(lineNumbers)) + return Method(header, InstructionSet(instructions.withLineNumbers(lineNumbers), ISA.ByteCode)) } private fun PeekingIterator.readInstructions(): List { diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/Code.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/Code.kt index 4a4893cf..0ecf2e5b 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/Code.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/Code.kt @@ -21,8 +21,8 @@ package dev.romainguy.kotlin.explorer.code * A data model representing disassembled code * * Given a list [Class]'s constructs a mode that provides: - * * Disassembled text with optional line number annotations - * * Jump information for branch instructions + * - Disassembled text with optional line number annotations + * - Jump information for branch instructions */ class Code( val text: String, @@ -43,7 +43,7 @@ class Code( startClass(clazz) clazz.methods.forEach { method -> startMethod(method) - method.instructions.forEach { instruction -> + method.instructionSet.instructions.forEach { instruction -> writeInstruction(instruction) } endMethod() diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/CodeBuilder.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/CodeBuilder.kt index 03fb9b52..0895efcc 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/CodeBuilder.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/CodeBuilder.kt @@ -46,7 +46,27 @@ class CodeBuilder(private val codeStyle: CodeStyle) { sb.append(" ".repeat(codeStyle.indent)) writeLine(method.header) sb.append(" ".repeat(codeStyle.indent)) - writeLine("-- ${method.instructions.size} instructions") + writeLine("-- ${method.instructionSet.instructions.size} instructions") + val branches = countBranches(method.instructionSet) + if (branches > 0) { + sb.append(" ".repeat(codeStyle.indent)) + writeLine("-- $branches branch${if (branches > 1) "es" else ""}") + } + } + + private fun countBranches(instructionSet: InstructionSet): Int { + var count = 0 + instructionSet.instructions.forEach { instruction -> + val code = instruction.code + val index = code.indexOf(": ") + instructionSet.isa.branchInstructions.forEach out@ { opCode -> + if (code.startsWith(opCode, index + 2)) { + count++ + return@out + } + } + } + return count } fun endMethod() { diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/CodeContent.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/CodeContent.kt index 05df5e5e..c766c44d 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/CodeContent.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/CodeContent.kt @@ -32,4 +32,4 @@ private fun Throwable.toFullString(): String { printStackTrace(PrintStream(it)) it.toString() } -} \ No newline at end of file +} diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/DataModels.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/DataModels.kt index c9db2809..e0c68b57 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/DataModels.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/code/DataModels.kt @@ -16,13 +16,46 @@ package dev.romainguy.kotlin.explorer.code +enum class ISA(val branchInstructions: Array) { + ByteCode(arrayOf("")), + Dex(arrayOf("if-")), + X86_64( + arrayOf( + "je", + "jz", + "jne", + "jnz", + "js", + "jns", + "jg", + "jnle", + "jge", + "jnl", + "jl", + "jnge", + "jle", + "jng", + "ja", + "jnbe", + "jae", + "jnb", + "jb", + "jnae", + "jbe", + "jna" + ) + ), + Arm64(arrayOf("b.", "b ", "bl", "cbz", "cbnz", "tbz", "tbnz")) +} + data class Class(val header: String, val methods: List) -data class Method(val header: String, val instructions: List) +data class Method(val header: String, val instructionSet: InstructionSet) + +data class InstructionSet(val instructions: List, val isa: ISA) data class Instruction(val address: Int, val code: String, val jumpAddress: Int?, val lineNumber: Int? = null) fun List.withLineNumbers(lineNumbers: Map): List { return map { it.copy(lineNumber = lineNumbers[it.address]) } - -} \ No newline at end of file +} diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/dex/DexDumpParser.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/dex/DexDumpParser.kt index eea5fcb4..4ed9bd1f 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/dex/DexDumpParser.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/dex/DexDumpParser.kt @@ -79,7 +79,10 @@ internal class DexDumpParser { val returnType = returnTypeFromType(type) val paramTypes = paramTypesFromType(type).joinToString(", ") - return Method("$returnType $className.$name($paramTypes)", instructions.withLineNumbers(positions)) + return Method( + "$returnType $className.$name($paramTypes)", + InstructionSet(instructions.withLineNumbers(positions), ISA.Dex) + ) } private fun Iterator.readInstructions(): List { diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/oat/OatDumpParser.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/oat/OatDumpParser.kt index fcf008b6..5812306f 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/oat/OatDumpParser.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/oat/OatDumpParser.kt @@ -17,28 +17,32 @@ package dev.romainguy.kotlin.explorer.oat import dev.romainguy.kotlin.explorer.* -import dev.romainguy.kotlin.explorer.code.Class -import dev.romainguy.kotlin.explorer.code.CodeContent +import dev.romainguy.kotlin.explorer.code.* import dev.romainguy.kotlin.explorer.code.CodeContent.Error import dev.romainguy.kotlin.explorer.code.CodeContent.Success -import dev.romainguy.kotlin.explorer.code.Instruction -import dev.romainguy.kotlin.explorer.code.Method private val ClassNameRegex = Regex("^\\d+: L(?[^;]+); \\(offset=0x$HexDigit+\\) \\(type_idx=\\d+\\).+") private val MethodRegex = Regex("^\\s+\\d+:\\s+(?.+)\\s+\\(dex_method_idx=\\d+\\)") private val CodeRegex = Regex("^\\s+0x(?
$HexDigit+):\\s+$HexDigit+\\s+(?.+)") -private val X86JumpRegex = Regex(".+ [+-]\\d+ \\(0x(?
$HexDigit{8})\\)\$") private val Arm64JumpRegex = Regex(".+ #[+-]0x$HexDigit+ \\(addr 0x(?
$HexDigit+)\\)\$") +private val X86JumpRegex = Regex(".+ [+-]\\d+ \\(0x(?
$HexDigit{8})\\)\$") internal class OatDumpParser { + private var isa = ISA.Arm64 + fun parse(text: String): CodeContent { return try { val lines = PeekingIterator(text.lineSequence().iterator()) - val jumpRegex = when (val set = lines.readInstructionSet()) { - "X86_64" -> X86JumpRegex - "Arm64" -> Arm64JumpRegex + val isa = when (val set = lines.readInstructionSet()) { + "Arm64" -> ISA.Arm64 + "X86_64" -> ISA.X86_64 else -> throw IllegalStateException("Unknown instruction set: $set") } + val jumpRegex = when (isa) { + ISA.Arm64 -> Arm64JumpRegex + ISA.X86_64 -> X86JumpRegex + else -> throw IllegalStateException("Incompatible ISA: $isa") + } val classes = buildList { while (lines.hasNext()) { val match = lines.consumeUntil(ClassNameRegex) ?: break @@ -81,7 +85,7 @@ internal class OatDumpParser { val method = match.getValue("method") consumeUntil("CODE:") val instructions = readInstructions(jumpRegex) - return Method(method, instructions) + return Method(method, InstructionSet(instructions, isa)) } private fun PeekingIterator.readInstructions(jumpRegex: Regex): List {