| // 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 |
| } |