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

Commit a7006818 authored by Allen Hair's avatar Allen Hair Committed by Gerrit Code Review
Browse files

Merge "Add tool for injecting tracing code into a method."

parents 04c1004e e0f2c62f
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