blob: 8eff75a08ef8ec3b2c0aa81cde5782f9f42621b4 [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/ioutil"
"net"
"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"
)
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
)
// libraries is the list of libraries needed to be installed and loaded for
// tracing. The declaration order is the order the libraries are loaded.
var libraries = []string{"libgapii.so"}
var abiToSo = map[string]string{
"armeabi": "android-armv7a",
"armeabi-v7a": "android-armv7a",
"arm64-v8a": "android-armv8a",
"x86": "android-x86",
}
func getSoDirectory(ctx log.Context, a *android.Action) (string, error) {
abi := a.Package.ABI
if abi == device.UnknownABI {
abi = a.Package.Device.ABI()
}
if abi == device.UnknownABI {
return "", fmt.Errorf("Cannot determine device abi")
}
so, ok := abiToSo[abi.Name]
if !ok {
return "", fmt.Errorf("Unknown device abi: %+v", abi)
}
return so, nil
}
func getSoPath(ctx log.Context, binDir file.Path, a *android.Action, lib string) (file.Path, error) {
soDirectory, err := getSoDirectory(ctx, a)
if err != nil {
return file.Path{}, err
}
return binDir.Join(soDirectory, lib), nil
}
func installLibraries(ctx log.Context, binDir file.Path, a *android.Action) ([]string, error) {
installPaths := make([]string, len(libraries))
for i, lib := range libraries {
ctx := ctx.S("lib", lib)
installPath := SoInstallDirectory + lib
path, err := getSoPath(ctx, binDir, a, lib)
if err != nil {
return nil, ctx.WrapError(err, "Finding library")
}
ctx.Info().Log("Pushing")
err = a.Package.Device.Push(ctx, path.System(), installPath)
if err != nil {
return nil, ctx.WrapError(err, "Pushing library")
}
installPaths[i] = installPath
}
return installPaths, 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. binDir is the path to the directory holding the GAPID
// executable binaries.
func JdwpStart(ctx log.Context, binDir file.Path, a *android.Action) (port int, 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 = findFreePort()
if err != nil {
return 0, nil, ctx.WrapError(err, "Finding free port")
}
ctx = ctx.I("port", 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 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)
ctx.Print("Reading libraries")
files := []stash{}
for _, name := range libraries {
ctx = ctx.S("name", name)
path, err := getSoPath(ctx, binDir, a, name)
if err != nil {
return 0, nil, ctx.WrapError(err, "Failed to get so path")
}
ctx = ctx.V("path", path)
name := path.Basename()
data, err := ioutil.ReadFile(path.System())
if err != nil {
return 0, nil, ctx.WrapError(err, "Failed to read file")
}
files = append(files, stash{name: name, data: data})
}
ctx.Print("Installing libraries on device")
if err := loadLibrariesViaJDWP(ctx, files, pid, 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. binDir is the path to the directory holding the GAPID
// executable binaries.
func AdbStart(ctx log.Context, binDir file.Path, a *android.Action) (port int, 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")
}
}()
}
libInstallPaths, err := installLibraries(ctx, binDir, a)
if err != nil {
return 0, nil, ctx.WrapError(err, "Pushing gapii")
}
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 = findFreePort()
if err != nil {
return 0, nil, err
}
ctx = ctx.I("port", 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
}
// findFreePort returns a currently free TCP port on the localhost.
// There are two potential issues with using this for ADB port forwarding:
// * There is the potential for the port to be taken between the function
// returning and the port being used by ADB.
// * The system _may_ hold on to the socket after it has been told to close.
// Because of these issues, there is a potential for flakiness.
func findFreePort() (int, error) {
dummy, err := net.Listen("tcp", ":0")
if err != nil {
return 0, err
}
defer dummy.Close()
return dummy.Addr().(*net.TCPAddr).Port, nil
}