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