blob: 8dc6ae5dc11c1776eb081945e0cf0bdb4aa37a1c [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"
"strings"
"time"
"android.googlesource.com/platform/tools/gpu/client/adb"
"android.googlesource.com/platform/tools/gpu/client/android"
"android.googlesource.com/platform/tools/gpu/framework/device"
"android.googlesource.com/platform/tools/gpu/framework/file"
"android.googlesource.com/platform/tools/gpu/framework/log"
"android.googlesource.com/platform/tools/gpu/framework/task"
"android.googlesource.com/platform/tools/gpu/gapid/pkgdata"
)
const (
// SoInstallDirectory is the installation directory of the libraries on the
// Android device.
SoInstallDirectory = "/data/local/tmp/"
// getPidRetries is the number of retries for getting the pid of the process our newly-started activity runs in.
getPidRetries = 7
)
type LibraryID int
const (
LibInterceptor LibraryID = iota
LibGapii
LibVkLayer
)
// LibraryInfo holds information about a library needed on the device.
type LibraryInfo struct {
ID LibraryID // ID of the library
Name string // Name of the library
Source file.Path // Source of the library on the host
Path string // Path to the library in the temporary directory on the device
Size int64 // size of the library in bytes
}
type Libraries []LibraryInfo
func getSoPath(ctx log.Context, a *android.Action, lib string) (file.Path, error) {
abi := a.Package.ABI
if abi == device.UnknownABI {
abi = a.Package.Device.ABI()
}
if abi == device.UnknownABI {
return file.Path{}, fmt.Errorf("Cannot determine device abi")
}
return pkgdata.File(ctx, abi, lib)
}
// NewLibraries returns list of libraries needed to be installed and loaded for
// tracing. The declaration order is the order the libraries are loaded.
func NewLibraries() Libraries {
return []LibraryInfo{
{ID: LibInterceptor, Name: "libinterceptor.so"},
{ID: LibGapii, Name: "libgapii.so"},
{ID: LibVkLayer, Name: "libVkLayerGraphicsSpy.so"},
}
}
func (l Libraries) Find(ctx log.Context, a *android.Action) error {
for i := range l {
lib := &l[i]
if !lib.Source.IsEmpty() {
continue
}
ctx := ctx.S("lib", lib.Name)
path, err := getSoPath(ctx, a, lib.Name)
if err != nil {
return ctx.WrapError(err, "Finding library")
}
if !path.Exists() {
continue
}
lib.Source = path
}
return nil
}
func (l Libraries) Set(id LibraryID, source file.Path) {
if source.IsEmpty() {
return
}
for i := range l {
lib := &l[i]
if lib.ID == id {
lib.Source = source
return
}
}
}
func (l Libraries) Install(ctx log.Context, a *android.Action) error {
for i := range l {
lib := &l[i]
if lib.Source.IsEmpty() {
continue
}
lib.Size = lib.Source.Info().Size()
lib.Path = SoInstallDirectory + lib.Name
ctx.Notice().V("src", lib.Source).S("dst", lib.Path).V("size", lib.Size).Log("Pushing")
if err := a.Package.Device.Push(ctx, lib.Source.System(), lib.Path); err != nil {
return ctx.WrapError(err, "Pushing library")
}
}
return nil
}
// JdwpStart launches an activity on an android device with the gapii tracer
// enabled. Gapii will attempt to connect back on the returned host port to
// write the trace.
// libraries is the libraries to install and use.
func JdwpStart(ctx log.Context, libraries Libraries, a *android.Action) (port adb.TCPPort, cleanup task.Task, err error) {
p := a.Package
ctx = ctx.Enter("JdwpStart").S("to", SoInstallDirectory).S("activity", a.Activity).S("on", p.Name)
d := p.Device.(adb.Device)
ctx.Print("Turning device screen on")
if err := d.TurnScreenOn(ctx); err != nil {
return 0, nil, ctx.WrapError(err, "Couldn't turn device screen on")
}
ctx.Print("Checking for lockscreen")
locked, err := d.IsShowingLockscreen(ctx)
if err != nil {
ctx.Warning().Fail(err, "Couldn't determine lockscreen state")
}
if locked {
return 0, nil, ctx.AsError("Cannot trace app on locked device")
}
port, err = adb.LocalFreeTCPPort()
if err != nil {
return 0, nil, ctx.WrapError(err, "Finding free port")
}
if err := libraries.Install(ctx, a); err != nil {
return 0, nil, ctx.WrapError(err, "Pushing gapii")
}
ctx = ctx.I("port", int(port))
ctx.Print("Forwarding")
if err := d.Forward(ctx, adb.TCPPort(port), adb.NamedAbstractSocket("gapii")); err != nil {
return 0, nil, ctx.WrapError(err, "Setting up port forwarding")
}
filedir, err := a.Package.FileDir(ctx)
if err == nil {
// FileDir may fail here. This happens if/when the app is non-debuggable.
// Don't set up vulkan tracing here, since the loader will not try and load the layer
// if we aren't debuggable regardless.
if err := d.Command("shell", "setprop", "debug.vulkan.layers", "VkGraphicsSpy").Run(ctx); err != nil {
d.RemoveForward(ctx, adb.TCPPort(port))
return 0, nil, ctx.WrapError(err, "Setting up vulkan layer")
}
}
doCleanup := func(ctx log.Context) error {
d.Command("shell", "setprop", "debug.vulkan.layers", "\"\"").Run(ctx)
return d.RemoveForward(ctx, adb.TCPPort(port))
}
defer func() {
if err != nil {
doCleanup(ctx)
}
}()
ctx.Print("Starting activity in debug mode")
if err := d.StartActivityForDebug(ctx, *a); err != nil {
return 0, nil, ctx.WrapError(err, "Starting activity in debug mode")
}
var pid int
err = android.ErrProcessNotFound
for attempt := 0; attempt <= getPidRetries && err == android.ErrProcessNotFound; attempt++ {
time.Sleep(time.Duration(attempt*100) * time.Millisecond)
pid, err = p.Pid(ctx)
}
if err != nil {
return 0, nil, ctx.WrapError(err, "Getting pid")
}
ctx = ctx.I("pid", pid)
if err := loadLibrariesViaJDWP(ctx, libraries, pid, filedir, d); err != nil {
return 0, nil, err
}
return port, doCleanup, nil
}
// AdbStart launches an activity on an android device with the gapii tracer
// enabled. Gapii will attempt to connect back on the returned host port to
// write the trace.
func AdbStart(ctx log.Context, libraries Libraries, a *android.Action) (port adb.TCPPort, cleanup task.Task, err error) {
p := a.Package
ctx = ctx.Enter("AdbStart").S("to", SoInstallDirectory).S("activity", a.Activity).S("on", p.Name)
d := p.Device.(adb.Device)
enforced, err := d.SELinuxEnforcing(ctx)
if err != nil {
return 0, nil, ctx.WrapError(err, "Detecting SELinux enforcing state")
}
if enforced {
ctx.Print("Disabling SELinux enforcing")
if err := d.SetSELinuxEnforcing(ctx, false); err != nil {
return 0, nil, ctx.WrapError(err, "Disabling SELinux enforcing")
}
defer func() {
ctx.Print("Re-enabling SELinux enforcing")
if err := d.SetSELinuxEnforcing(ctx, true); err != nil {
ctx.Fail(err, "Re-enabling SELinux enforcing")
}
}()
}
if err := libraries.Install(ctx, a); err != nil {
return 0, nil, ctx.WrapError(err, "Pushing gapii")
}
libInstallPaths := make([]string, len(libraries))
for i, l := range libraries {
if l.Path == "" {
continue
}
libInstallPaths[i] = l.Path
}
ctx.Print("Setting LD_PRELOAD")
err = p.SetWrapProperties(ctx, `LD_PRELOAD="`+strings.Join(libInstallPaths, " ")+`"`)
if err != nil {
return 0, nil, ctx.WrapError(err, "Setting LD_PRELOAD")
}
defer func() {
ctx.Print("Clearing LD_PRELOAD")
if err := p.SetWrapProperties(ctx, `""`); err != nil {
ctx.Fail(err, "Clearing LD_PRELOAD")
}
}()
ctx.Print("Turning device screen on")
if err := d.TurnScreenOn(ctx); err != nil {
return 0, nil, ctx.WrapError(err, "Couldn't turn device screen on")
}
ctx.Print("Checking for lockscreen")
locked, err := d.IsShowingLockscreen(ctx)
if err != nil {
ctx.Warning().Fail(err, "Couldn't determine lockscreen state")
}
if locked {
return 0, nil, ctx.AsError("Cannot trace app on locked device")
}
port, err = adb.LocalFreeTCPPort()
if err != nil {
return 0, nil, err
}
ctx = ctx.I("port", int(port))
ctx.Print("Forwarding")
if err := d.Forward(ctx, adb.TCPPort(port), adb.NamedAbstractSocket("gapii")); err != nil {
return 0, nil, ctx.WrapError(err, "Setting up port forwarding")
}
doCleanup := func(ctx log.Context) error { return d.RemoveForward(ctx, adb.TCPPort(port)) }
defer func() {
if err != nil {
doCleanup(ctx)
}
}()
ctx.Print("Starting activity")
if err := d.StartActivity(ctx, *a); err != nil {
return 0, nil, ctx.WrapError(err, "Starting activity")
}
return port, doCleanup, nil
}