Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e0f2c62f authored by Michael Wright's avatar Michael Wright Committed by Allen Hair
Browse files

Add tool for injecting tracing code into a method.

This tool rewrites the bytecode in the designated JAR files to produce
tracing calls on enter and exit, while making sure to close the tracing
span even on exceptions being thrown.

The idea is mostly to reduce the amount of noise within methods when
just trying to add some tracing.

Test: atest --host TraceInjectionTests
Change-Id: If6acb72f34cbb83d9b041a62ee3d8c2abf74b69e
Merged-In: If6acb72f34cbb83d9b041a62ee3d8c2abf74b69e
parent 5a5a4504
Loading
Loading
Loading
Loading
+49 −0
Original line number Diff line number Diff line
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",
    ],
}
+1 −0
Original line number Diff line number Diff line
Main-Class: com.android.traceinjection.Main
+121 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+41 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+52 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
Loading