blob: 3586482be381dc22b2966b85084ec5ef388393e7 [file] [log] [blame] [edit]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.profgen.cli
import com.android.tools.profgen.Apk
import com.android.tools.profgen.ArtProfile
import com.android.tools.profgen.ArtProfileSerializer
import com.android.tools.profgen.Diagnostics
import com.android.tools.profgen.HumanReadableProfile
import com.android.tools.profgen.ObfuscationMap
import com.android.tools.profgen.dumpProfile
import com.android.tools.profgen.extractProfileAsDm
import com.android.tools.profgen.readHumanReadableProfileOrExit
import kotlinx.cli.ArgType
import kotlinx.cli.ExperimentalCli
import kotlinx.cli.Subcommand
import kotlinx.cli.default
import kotlinx.cli.required
import kotlin.io.path.Path
@Suppress("unused") // Values are referenced by name as shell args
@ExperimentalCli
enum class ArtProfileFormat(internal val serializer: ArtProfileSerializer) {
V0_1_5_S(ArtProfileSerializer.V0_1_5_S), // targets S+
V0_1_0_P(ArtProfileSerializer.V0_1_0_P), // targets P -> R
V0_0_9_OMR1(ArtProfileSerializer.V0_0_9_OMR1), // targets Android O MR1
V0_0_5_O(ArtProfileSerializer.V0_0_5_O), // targets O
V0_0_1_N(ArtProfileSerializer.V0_0_1_N) // targets N
}
@ExperimentalCli
class BinCommand : Subcommand("bin", "Generate Binary Profile") {
val hrpPath by argument(ArgType.String, "profile", "File path to Human Readable profile")
val apkPath by option(ArgType.String, "apk", "a", "File path to apk").required()
val outPath by option(ArgType.String, "output", "o", "File path to generated binary profile").required()
val obfPath by option(ArgType.String, "map", "m", "File path to name obfuscation map")
val metaPath by option(ArgType.String, "output-meta", "om", "File path to generated metadata output")
val artProfileFormat by option(ArgType.Choice<ArtProfileFormat>(), "profile-format", "pf", "The ART profile format version").default(ArtProfileFormat.V0_1_0_P)
override fun execute() {
val hrpFile = Path(hrpPath).toFile()
require(hrpFile.exists()) { "File not found: $hrpPath" }
val apkFile = Path(apkPath).toFile()
require(apkFile.exists()) { "File not found: $apkPath" }
val obfFile = obfPath?.let { Path(it).toFile() }
require(obfFile?.exists() != false) { "File not found: $obfPath" }
val metaFile = metaPath?.let { Path(it).toFile() }
if (metaFile != null) {
require(metaFile.parentFile.exists()) {
"Directory does not exist: ${metaFile.parent}"
}
}
val outFile = Path(outPath).toFile()
require(outFile.parentFile.exists()) { "Directory does not exist: ${outFile.parent}" }
val hrp = readHumanReadableProfileOrExit(hrpFile, StdErrorDiagnostics)
val apk = Apk(apkFile)
val obf = if (obfFile != null) ObfuscationMap(obfFile) else ObfuscationMap.Empty
val profile = ArtProfile(hrp, obf, apk)
profile.save(outFile.outputStream(), artProfileFormat.serializer)
if (metaFile != null) {
profile.save(metaFile.outputStream(), ArtProfileSerializer.METADATA_0_0_2)
}
}
}
@ExperimentalCli
class ExtractProfileCommand : Subcommand("extractProfile", "Extract Binary Profile as versioned dex metadata") {
private val apkPath by option(ArgType.String, "apk", "a", "File path to apk").required()
private val outPath by option(ArgType.String, "output-dex-metadata", "odm", "File path to generated dex metadata output").required()
private val artProfileFormat by option(ArgType.Choice<ArtProfileFormat>(), "profile-format", "pf", "The ART profile format version").default(ArtProfileFormat.V0_1_0_P)
override fun execute() {
val apkFile = Path(apkPath).toFile()
require(apkFile.exists()) { "File not found: $apkPath" }
val outFile = Path(outPath).toFile()
require(outFile.parentFile.exists()) { "Directory does not exist: ${outFile.parent}" }
extractProfileAsDm(
apkFile = apkFile,
profileSerializer = artProfileFormat.serializer,
metadataSerializer = ArtProfileSerializer.METADATA_0_0_2,
outputStream = outFile.outputStream()
)
}
}
@ExperimentalCli
class ValidateCommand : Subcommand("validate", "Validate Profile") {
val hrpPath by argument(ArgType.String, "profile", "File path to Human Readable profile")
override fun execute() {
val hrpFile = Path(hrpPath).toFile()
require(hrpFile.exists()) { "File not found: $hrpPath" }
HumanReadableProfile(hrpFile, StdErrorDiagnostics)
}
}
@ExperimentalCli
class PrintCommand : Subcommand("print", "Print methods matching profile") {
val hrpPath by option(ArgType.String, "profile", "p","File path to Human Readable profile").required()
val apkPath by option(ArgType.String, "apk", "a", "File path to apk").required()
val obfPath by option(ArgType.String, "map", "m", "File path to name obfuscation map")
override fun execute() {
val hrpFile = Path(hrpPath).toFile()
require(hrpFile.exists()) { "File not found: $hrpPath" }
val apkFile = Path(apkPath).toFile()
require(apkFile.exists()) { "File not found: $apkPath" }
val obfFile = obfPath?.let { Path(it).toFile() }
require(obfFile?.exists() != false) { "File not found: $obfPath" }
val hrp = readHumanReadableProfileOrExit(hrpFile, StdErrorDiagnostics)
val apk = Apk(apkFile)
val obf = if (obfFile != null) ObfuscationMap(obfFile) else ObfuscationMap.Empty
val profile = ArtProfile(hrp, obf, apk)
profile.print(System.out, obf)
}
}
@ExperimentalCli
class ProfileDumpCommand: Subcommand("dumpProfile", "Dump a binary profile to a HRF") {
val binPath by option(ArgType.String, "profile", "p", "File path to the binary profile").required()
val apkPath by option(ArgType.String, "apk", "a", "File path to apk").required()
val obfPath by option(ArgType.String, "map", "m", "File path to name obfuscation map")
val strictMode by option(ArgType.Boolean, "strict", "s", "Strict mode").default(value = true)
val outPath by option(ArgType.String, "output", "o", "File path for the HRF").required()
override fun execute() {
val binFile = Path(binPath).toFile()
require(binFile.exists()) { "File not found: $binPath" }
val apkFile = Path(apkPath).toFile()
require(apkFile.exists()) { "File not found: $apkPath" }
val obfFile = obfPath?.let { Path(it).toFile() }
require(obfFile?.exists() != false) { "File not found: $obfPath" }
val outFile = Path(outPath).toFile()
require(outFile.parentFile.exists()) { "Directory does not exist: ${outFile.parent}" }
val profile = ArtProfile(binFile)!!
val apk = Apk(apkFile)
val obf = if (obfFile != null) ObfuscationMap(obfFile) else ObfuscationMap.Empty
dumpProfile(outFile, profile, apk, obf, strict = strictMode)
}
}
val StdErrorDiagnostics = Diagnostics { System.err.println(it) }