blob: f3b7c8e3640e59c639878bec89540937358b9cae [file] [log] [blame]
// Copyright (C) 2016 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 gapii
import (
"fmt"
"io"
"net"
"reflect"
"time"
"android.googlesource.com/platform/tools/gpu/client/adb"
"android.googlesource.com/platform/tools/gpu/framework/java/jdbg"
"android.googlesource.com/platform/tools/gpu/framework/java/jdwp"
"android.googlesource.com/platform/tools/gpu/framework/log"
"android.googlesource.com/platform/tools/gpu/framework/task"
)
func expect(r io.Reader, expected []byte) error {
got := make([]byte, len(expected))
if _, err := io.ReadFull(r, got); err != nil {
return err
}
if !reflect.DeepEqual(expected, got) {
return fmt.Errorf("Expected %v, got %v", expected, got)
}
return nil
}
// waitForOnCreate waits for android.app.Application.onCreate to be called, and
// then suspends the thread.
func waitForOnCreate(ctx log.Context, conn *jdwp.Connection, wakeup jdwp.ThreadID) (*jdwp.EventMethodEntry, error) {
app, err := conn.GetClassBySignature("Landroid/app/Application;")
if err != nil {
return nil, err
}
onCreate, err := conn.GetClassMethod(app.ClassID(), "onCreate", "()V")
if err != nil {
return nil, err
}
return conn.WaitForMethodEntry(ctx, app.ClassID(), onCreate.ID, wakeup)
}
// waitForVulkanLoad for android.app.ApplicationLoaders.getClassLoader to be called,
// and then suspends the thread.
// This function is what is used to tell the vulkan loader where to search for
// layers.
func waitForVulkanLoad(ctx log.Context, conn *jdwp.Connection) (*jdwp.EventMethodEntry, error) {
loaders, err := conn.GetClassBySignature("Landroid/app/ApplicationLoaders;")
if err != nil {
return nil, err
}
getClassLoader, err := conn.GetClassMethod(loaders.ClassID(), "getClassLoader",
"(Ljava/lang/String;IZLjava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/ClassLoader;")
if err != nil {
return nil, err
}
return conn.WaitForMethodEntry(ctx, loaders.ClassID(), getClassLoader.ID, 0)
}
// loadLibrariesViaJDWP connects to the application waiting for a JDWP
// connection with the specified process id, sends a number of JDWP commands to
// load the list of libraries.
func loadLibrariesViaJDWP(ctx log.Context, libraries Libraries, pid int, filedir string, device adb.Device) error {
const (
reconnectAttempts = 10
reconnectDelay = time.Second
)
ctx, stop := task.WithCancel(ctx)
defer stop()
jdwpPort, err := adb.LocalFreeTCPPort()
if err != nil {
return ctx.WrapError(err, "Finding free port")
}
ctx = ctx.I("jdwpPort", int(jdwpPort))
ctx.Print("Forwarding JDWP port")
if err := device.Forward(ctx, adb.TCPPort(jdwpPort), adb.Jdwp(pid)); err != nil {
return ctx.WrapError(err, "Setting up JDWP port forwarding")
}
defer device.RemoveForward(ctx, adb.TCPPort(jdwpPort))
ctx.Print("Connecting to JDWP")
// Create a JDWP connection with the application.
var sock net.Conn
var conn *jdwp.Connection
for i := 0; i < reconnectAttempts; i++ {
if sock, err = net.Dial("tcp", fmt.Sprintf("localhost:%v", jdwpPort)); err == nil {
if conn, err = jdwp.Open(ctx, sock); err == nil {
break
}
sock.Close()
}
ctx.Printf("Failed to connect: %v", err)
time.Sleep(reconnectDelay)
}
if err != nil {
return ctx.WrapError(err, "Connecting to JDWP")
}
defer sock.Close()
classLoaderThread := jdwp.ThreadID(0)
load_gapii := func(j *jdbg.JDbg, filesDir jdbg.Value) error {
j.Class("java.io.File").New(filesDir).Call("mkdir")
for _, library := range libraries {
if library.Path == "" {
continue
}
ctx := ctx.S("library", library.Name).I("size", int(library.Size))
srcFile := j.Class("java.io.File").New(library.Path)
dstFile := j.Class("java.io.File").New(filesDir, library.Name)
srcChannel := j.Class("java.io.FileInputStream").New(srcFile).Call("getChannel")
dstChannel := j.Class("java.io.FileOutputStream").New(dstFile).Call("getChannel")
for remaining := library.Size; remaining > 0; {
remaining -= dstChannel.Call("transferFrom", srcChannel, library.Size-remaining, remaining).Get().(int64)
ctx.Printf("Copied %d bytes, %d remaining", library.Size-remaining, remaining)
}
srcChannel.Call("close")
dstChannel.Call("close")
// Load the library.
ctx.Print("Loading library...")
ctx.Print("Library loaded")
// Work around for loading libraries in the N previews. See http://b/29441142.
j.Class("java.lang.Runtime").Call("getRuntime").Call("doLoad", dstFile.Call("toString"), nil)
}
return nil
}
if len(filedir) != 0 {
// If filedir not empty, that means we could find the filedir through normal means.
// It also means we have a shot at tracing vulkan applications. So try that now.
ctx.Print("Waiting for ApplicationLoaders.getClassLoader()")
getClassLoader, err := waitForVulkanLoad(ctx, conn)
// If err != nil that means we could not find or break in getClassLoader
// so we have no vulkan support.
if err == nil {
classLoaderThread = getClassLoader.Thread
err = jdbg.Do(conn, getClassLoader.Thread, func(j *jdbg.JDbg) error {
newLibraryPath := j.String(":" + filedir)
obj := j.GetStackObject("librarySearchPath").Call("concat", newLibraryPath)
j.SetStackObject("librarySearchPath", obj)
fileDir := j.String(filedir)
// If successfully loaded vulkan support, then we should be good to go
// load libgapii and friends here.
return load_gapii(j, fileDir)
})
if err != nil {
return ctx.WrapError(err, "JDWP failure")
}
}
}
// If we did not have vulkan support, then we should try to load with
// Application.onCreate().
if classLoaderThread == jdwp.ThreadID(0) {
// Wait for Application.onCreate to be called.
ctx.Print("Waiting for Application.onCreate()")
onCreate, err := waitForOnCreate(ctx, conn, classLoaderThread)
if err != nil {
return ctx.WrapError(err, "Waiting for Application.OnCreate")
}
// Create a JDbg session to install and load the libraries.
ctx.Print("Installing interceptor libraries")
err = jdbg.Do(conn, onCreate.Thread, func(j *jdbg.JDbg) error {
// This is the /data/data/com.foo.bar/files directory where we're going to
// place the libraries.
filesDir := j.This().Call("getFilesDir")
return load_gapii(j, filesDir)
})
}
if err != nil {
return ctx.WrapError(err, "JDWP failure")
}
return nil
}