diff --git a/tools/traceinjection/Android.bp b/tools/traceinjection/Android.bp new file mode 100644 index 0000000000000..1395c5f2e6351 --- /dev/null +++ b/tools/traceinjection/Android.bp @@ -0,0 +1,49 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_binary_host { + name: "traceinjection", + manifest: "manifest.txt", + srcs: ["src/**/*.java"], + static_libs: [ + "asm-7.0", + "asm-commons-7.0", + "asm-tree-7.0", + "asm-analysis-7.0", + "guava-21.0", + ], +} + +java_library_host { + name: "TraceInjectionTests-Uninjected", + srcs: ["test/**/*.java"], + static_libs: [ + "junit", + ], +} + +java_genrule_host { + name: "TraceInjectionTests-Injected", + srcs: [":TraceInjectionTests-Uninjected"], + tools: ["traceinjection"], + cmd: "$(location traceinjection) " + + " --annotation \"com/android/traceinjection/Trace\"" + + " --start \"com/android/traceinjection/InjectionTests.traceStart\"" + + " --end \"com/android/traceinjection/InjectionTests.traceEnd\"" + + " -o $(out) " + + " -i $(in)", + out: ["TraceInjectionTests-Injected.jar"], +} + +java_test_host { + name: "TraceInjectionTests", + static_libs: [ + "TraceInjectionTests-Injected", + ], +} diff --git a/tools/traceinjection/manifest.txt b/tools/traceinjection/manifest.txt new file mode 100644 index 0000000000000..7f4ee1d617fa4 --- /dev/null +++ b/tools/traceinjection/manifest.txt @@ -0,0 +1 @@ +Main-Class: com.android.traceinjection.Main diff --git a/tools/traceinjection/src/com/android/traceinjection/Main.java b/tools/traceinjection/src/com/android/traceinjection/Main.java new file mode 100644 index 0000000000000..190df819dd64a --- /dev/null +++ b/tools/traceinjection/src/com/android/traceinjection/Main.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2022 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.traceinjection; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; + +import java.io.BufferedInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class Main { + public static void main(String[] args) throws IOException { + String inJar = null; + String outJar = null; + String annotation = null; + String traceStart = null; + String traceEnd = null; + + // All arguments require a value currently, so just make sure we have an even number and + // then process them all two at a time. + if (args.length % 2 != 0) { + throw new IllegalArgumentException("Argument is missing corresponding value"); + } + for (int i = 0; i < args.length - 1; i += 2) { + final String arg = args[i].trim(); + final String argValue = args[i + 1].trim(); + if ("-i".equals(arg)) { + inJar = argValue; + } else if ("-o".equals(arg)) { + outJar = argValue; + } else if ("--annotation".equals(arg)) { + annotation = argValue; + } else if ("--start".equals(arg)) { + traceStart = argValue; + } else if ("--end".equals(arg)) { + traceEnd = argValue; + } else { + throw new IllegalArgumentException("Unknown argument: " + arg); + } + } + + if (inJar == null) { + throw new IllegalArgumentException("input jar is required"); + } + + if (outJar == null) { + throw new IllegalArgumentException("output jar is required"); + } + + if (annotation == null) { + throw new IllegalArgumentException("trace annotation is required"); + } + + if (traceStart == null) { + throw new IllegalArgumentException("start trace method is required"); + } + + if (traceEnd == null) { + throw new IllegalArgumentException("end trace method is required"); + } + + TraceInjectionConfiguration params = + new TraceInjectionConfiguration(annotation, traceStart, traceEnd); + + try ( + ZipFile zipSrc = new ZipFile(inJar); + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outJar)); + ) { + Enumeration extends ZipEntry> srcEntries = zipSrc.entries(); + while (srcEntries.hasMoreElements()) { + ZipEntry entry = srcEntries.nextElement(); + ZipEntry newEntry = new ZipEntry(entry.getName()); + newEntry.setTime(entry.getTime()); + zos.putNextEntry(newEntry); + BufferedInputStream bis = new BufferedInputStream(zipSrc.getInputStream(entry)); + + if (entry.getName().endsWith(".class")) { + convert(bis, zos, params); + } else { + while (bis.available() > 0) { + zos.write(bis.read()); + } + zos.closeEntry(); + bis.close(); + } + } + zos.finish(); + } + } + + private static void convert(InputStream in, OutputStream out, + TraceInjectionConfiguration params) throws IOException { + ClassReader cr = new ClassReader(in); + ClassWriter cw = new ClassWriter(0); + TraceInjectionClassVisitor cv = new TraceInjectionClassVisitor(cw, params); + cr.accept(cv, ClassReader.EXPAND_FRAMES); + byte[] data = cw.toByteArray(); + out.write(data); + } +} diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java new file mode 100644 index 0000000000000..863f976b8aff4 --- /dev/null +++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 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.traceinjection; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * {@link ClassVisitor} that injects tracing code to methods annotated with the configured + * annotation. + */ +public class TraceInjectionClassVisitor extends ClassVisitor { + private final TraceInjectionConfiguration mParams; + public TraceInjectionClassVisitor(ClassVisitor classVisitor, + TraceInjectionConfiguration params) { + super(Opcodes.ASM7, classVisitor); + mParams = params; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + MethodVisitor chain = super.visitMethod(access, name, desc, signature, exceptions); + return new TraceInjectionMethodAdapter(chain, access, name, desc, mParams); + } +} diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionConfiguration.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionConfiguration.java new file mode 100644 index 0000000000000..f9595bdad9cfe --- /dev/null +++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 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.traceinjection; + +/** + * Configuration data for trace method injection. + */ +public class TraceInjectionConfiguration { + public final String annotation; + public final String startMethodClass; + public final String startMethodName; + public final String endMethodClass; + public final String endMethodName; + + public TraceInjectionConfiguration(String annotation, String startMethod, String endMethod) { + this.annotation = annotation; + String[] startMethodComponents = parseMethod(startMethod); + String[] endMethodComponents = parseMethod(endMethod); + startMethodClass = startMethodComponents[0]; + startMethodName = startMethodComponents[1]; + endMethodClass = endMethodComponents[0]; + endMethodName = endMethodComponents[1]; + } + + public String toString() { + return "TraceInjectionParams{annotation=" + annotation + + ", startMethod=" + startMethodClass + "." + startMethodName + + ", endMethod=" + endMethodClass + "." + endMethodName + "}"; + } + + private static String[] parseMethod(String method) { + String[] methodComponents = method.split("\\."); + if (methodComponents.length != 2) { + throw new IllegalArgumentException("Invalid method descriptor: " + method); + } + return methodComponents; + } +} diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java new file mode 100644 index 0000000000000..c2bbddcb56688 --- /dev/null +++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2022 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.traceinjection; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.AdviceAdapter; +import org.objectweb.asm.commons.Method; + +/** + * Adapter that injects tracing code to methods annotated with the configured annotation. + * + * Assuming the configured annotation is {@code @Trace} and the configured methods are + * {@code Tracing.begin()} and {@code Tracing.end()}, it effectively transforms: + * + *
{@code
+ * @Trace
+ * void method() {
+ * doStuff();
+ * }
+ * }
+ *
+ * into:
+ * {@code
+ * @Trace
+ * void method() {
+ * Tracing.begin();
+ * try {
+ * doStuff();
+ * } finally {
+ * Tracing.end();
+ * }
+ * }
+ * }
+ */
+public class TraceInjectionMethodAdapter extends AdviceAdapter {
+ private final TraceInjectionConfiguration mParams;
+ private final Label mStartFinally = newLabel();
+ private final boolean mIsConstructor;
+
+ private boolean mShouldTrace;
+ private long mTraceId;
+ private String mTraceLabel;
+
+ public TraceInjectionMethodAdapter(MethodVisitor methodVisitor, int access,
+ String name, String descriptor, TraceInjectionConfiguration params) {
+ super(Opcodes.ASM7, methodVisitor, access, name, descriptor);
+ mParams = params;
+ mIsConstructor = "