From e472288d284353813a171fa1857e801ce01bf623 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Mon, 21 Aug 2017 15:58:48 +0100 Subject: [PATCH 1/2] core/event/task: Add Retry() method. Attempting to retry tasks is quite a common thing to do, so make a helper for it. --- core/event/task/task.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/core/event/task/task.go b/core/event/task/task.go index e294150619..92016aa874 100644 --- a/core/event/task/task.go +++ b/core/event/task/task.go @@ -17,6 +17,7 @@ package task import ( "context" "sync" + "time" ) // Task is the unit of work used in the task system. @@ -33,3 +34,27 @@ func Once(task Task) Task { return err } } + +// Retry repeatedly calls task until task returns a nil error, the number +// attempts reaches maxAttempts or the context is cancelled. Retry will sleep +// for retryDelay between retry attempts. +// if maxAttempts <= 0, then there is no maximum limit to the number of times +// task will be called. +func Retry(ctx context.Context, maxAttempts int, retryDelay time.Duration, task Task) error { + var count int + for { + err := task(ctx) + if err == nil { + return nil + } + count++ + if maxAttempts > 0 && count >= maxAttempts { + return err + } + select { + case <-ShouldStop(ctx): + return StopReason(ctx) + case <-time.After(retryDelay): + } + } +} From 9012dba682b329b55def120fffd9ba9ee915e050 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Thu, 20 Jul 2017 18:00:33 +0100 Subject: [PATCH 2/2] gapii: Connect under thread suspension. I believe some traces were failing as we were attempting to connect after we had let threads continue execution. Instead connect while the debugger is still attached. There's no point dealing with versioning in the connection header at this point, so remove the conditional logic. --- cmd/gapit/trace.go | 61 +++++++++++-------- gapii/cc/connection_header.cpp | 32 +++------- gapii/cc/connection_header.h | 14 ++--- gapii/cc/spy.cpp | 1 + gapii/client/adb.go | 65 ++++++++++++-------- gapii/client/capture.go | 87 +++++++++++++++------------ gapii/client/header.go | 19 +++--- gapii/client/jdwp_loader.go | 105 ++++++++++++++++++--------------- 8 files changed, 212 insertions(+), 172 deletions(-) diff --git a/cmd/gapit/trace.go b/cmd/gapit/trace.go index 13b0e4c053..064503191a 100644 --- a/cmd/gapit/trace.go +++ b/cmd/gapit/trace.go @@ -101,11 +101,34 @@ func (verb *traceVerb) Run(ctx context.Context, flags flag.FlagSet) error { } } + ctx, start := verb.inputHandler(ctx, options.Flags&client.DeferStart != 0) + if verb.Local.Port != 0 { - return verb.captureLocal(ctx, flags, verb.Local.Port, options) + return verb.captureLocal(ctx, flags, verb.Local.Port, start, options) } - return verb.captureADB(ctx, flags, options) + return verb.captureADB(ctx, flags, start, options) +} + +func (verb *traceVerb) inputHandler(ctx context.Context, deferStart bool) (context.Context, task.Signal) { + if verb.For > 0 { + return ctx, task.FiredSignal + } + startSignal, start := task.NewSignal() + var cancel task.CancelFunc + ctx, cancel = task.WithCancel(ctx) + go func() { + reader := bufio.NewReader(os.Stdin) + if deferStart { + println("Press enter to start capturing...") + _, _ = reader.ReadString('\n') + start(ctx) + } + println("Press enter to stop capturing...") + _, _ = reader.ReadString('\n') + cancel() + }() + return ctx, startSignal } func (verb *traceVerb) startLocalApp(ctx context.Context) (func(), error) { @@ -138,15 +161,16 @@ func (verb *traceVerb) startLocalApp(ctx context.Context) (func(), error) { return func() { cancel(); cleanup() }, nil } -func (verb *traceVerb) captureLocal(ctx context.Context, flags flag.FlagSet, port int, options client.Options) error { +func (verb *traceVerb) captureLocal(ctx context.Context, flags flag.FlagSet, port int, start task.Signal, options client.Options) error { output := verb.Out if output == "" { output = "capture.gfxtrace" } - return doCapture(ctx, options, port, output, verb.For) + process := &client.Process{Port: port, Options: options} + return doCapture(ctx, process, output, start, verb.For) } -func (verb *traceVerb) captureADB(ctx context.Context, flags flag.FlagSet, options client.Options) error { +func (verb *traceVerb) captureADB(ctx context.Context, flags flag.FlagSet, start task.Signal, options client.Options) error { d, err := getADBDevice(ctx, verb.Gapii.Device) if err != nil { return err @@ -265,11 +289,10 @@ func (verb *traceVerb) captureADB(ctx context.Context, flags flag.FlagSet, optio defer d.TurnScreenOff(ctx) // Think green! } - port, cleanup, err := client.StartOrAttach(ctx, pkg, a) + process, err := client.StartOrAttach(ctx, pkg, a, options) if err != nil { return err } - defer cleanup(ctx) ctx, stop := task.WithCancel(ctx) if verb.Record.Inputs { @@ -286,10 +309,10 @@ func (verb *traceVerb) captureADB(ctx context.Context, flags flag.FlagSet, optio } } - return doCapture(ctx, options, int(port), output, verb.For) + return doCapture(ctx, process, output, start, verb.For) } -func doCapture(ctx context.Context, options client.Options, port int, out string, duration time.Duration) error { +func doCapture(ctx context.Context, process *client.Process, out string, start task.Signal, duration time.Duration) error { log.I(ctx, "Creating file '%v'", out) os.MkdirAll(filepath.Dir(out), 0755) file, err := os.Create(out) @@ -298,25 +321,11 @@ func doCapture(ctx context.Context, options client.Options, port int, out string } defer file.Close() - signal, fireSignal := task.NewSignal() - if duration == 0 { - var cancel task.CancelFunc - ctx, cancel = task.WithCancel(ctx) - go func() { - reader := bufio.NewReader(os.Stdin) - if (options.Flags & client.DeferStart) != 0 { - println("Press enter to start capturing...") - _, _ = reader.ReadString('\n') - fireSignal(ctx) - } - println("Press enter to stop capturing...") - _, _ = reader.ReadString('\n') - cancel() - }() - } else { + if duration > 0 { ctx, _ = task.WithTimeout(ctx, duration) } - _, err = client.Capture(ctx, port, signal, file, options) + + _, err = process.Capture(ctx, start, file) if err != nil { return err } diff --git a/gapii/cc/connection_header.cpp b/gapii/cc/connection_header.cpp index ff040cd253..016b9af1b5 100644 --- a/gapii/cc/connection_header.cpp +++ b/gapii/cc/connection_header.cpp @@ -49,35 +49,21 @@ bool ConnectionHeader::read(core::StreamReader* reader) { return false; } - const int kMinSupportedVersion = 2; - const int kMaxSupportedVersion = 5; + const int kMinSupportedVersion = 1; + const int kMaxSupportedVersion = 1; if (mVersion < kMinSupportedVersion || mVersion > kMaxSupportedVersion) { GAPID_WARNING("Unsupported ConnectionHeader version %d. Only understand [%d to %d].", mVersion, kMinSupportedVersion, kMaxSupportedVersion); return false; } - if (mVersion >= 2) { - if (!reader->read(mObserveFrameFrequency) || - !reader->read(mObserveDrawFrequency)) { - return false; - } - } - if (mVersion >= 4) { - if (!reader->read(mStartFrame) || - !reader->read(mNumFrames)) { - return false; - } - } - if (mVersion >= 5) { - if (!reader->read(mAPIs)) { - return false; - } - } - if (mVersion >= 3) { - if (!reader->read(mFlags)) { - return false; - } + if (!reader->read(mObserveFrameFrequency) || + !reader->read(mObserveDrawFrequency) || + !reader->read(mStartFrame) || + !reader->read(mNumFrames) || + !reader->read(mAPIs) || + !reader->read(mFlags)) { + return false; } // Insert new version handling here. Don't forget to bump kMaxSupportedVersion! diff --git a/gapii/cc/connection_header.h b/gapii/cc/connection_header.h index b24e0e412d..f484ebddf6 100644 --- a/gapii/cc/connection_header.h +++ b/gapii/cc/connection_header.h @@ -47,13 +47,13 @@ class ConnectionHeader { bool read(core::StreamReader* reader); uint8_t mMagic[4]; // 's', 'p', 'y', '0' - uint32_t mVersion; // 2 or 3 - uint32_t mObserveFrameFrequency; // non-zero == enabled. Version: 2+ - uint32_t mObserveDrawFrequency; // non-zero == enabled. Version: 2+ - uint32_t mStartFrame; // non-zero == Frame to start at. version 4+ - uint32_t mNumFrames; // non-zero == Number of frames to capture. version 4+ - uint32_t mAPIs; // Bitset of APIS to enable. version 5+ - uint32_t mFlags; // Combination of FLAG_XX bits. Version: 3+ + uint32_t mVersion; // 1 + uint32_t mObserveFrameFrequency; // non-zero == enabled. + uint32_t mObserveDrawFrequency; // non-zero == enabled. + uint32_t mStartFrame; // non-zero == Frame to start at. + uint32_t mNumFrames; // non-zero == Number of frames to capture. + uint32_t mAPIs; // Bitset of APIS to enable. + uint32_t mFlags; // Combination of FLAG_XX bits. }; } // namespace gapii diff --git a/gapii/cc/spy.cpp b/gapii/cc/spy.cpp index 3e8771987f..9d0175f536 100644 --- a/gapii/cc/spy.cpp +++ b/gapii/cc/spy.cpp @@ -51,6 +51,7 @@ extern "C" jint JNI_OnLoad(JavaVM *vm, void *reserved) { GAPID_INFO("JNI_OnLoad() was called. vm = %p", vm); gJavaVM = vm; + gapii::Spy::get(); // Construct the spy. return JNI_VERSION_1_6; } void* queryPlatformData() { diff --git a/gapii/client/adb.go b/gapii/client/adb.go index 7dc0da0f1f..454f6b2295 100644 --- a/gapii/client/adb.go +++ b/gapii/client/adb.go @@ -16,9 +16,11 @@ package client import ( "context" + "net" "time" - "github.com/google/gapid/core/event/task" + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/context/keys" "github.com/google/gapid/core/log" "github.com/google/gapid/core/os/android" "github.com/google/gapid/core/os/android/adb" @@ -33,11 +35,23 @@ const ( getPidRetries = 7 ) +// Process represents a running process to capture. +type Process struct { + // The local host port used to connect to GAPII. + Port int + + // The options used for the capture. + Options Options + + // The connection + conn net.Conn +} + // StartOrAttach launches an activity on an android device with the GAPII interceptor // enabled using the gapid.apk built for the ABI matching the specified action and device. // If there is no activity provided, it will try to attach to any already running one. // GAPII will attempt to connect back on the returned host port to write the trace. -func StartOrAttach(ctx context.Context, p *android.InstalledPackage, a *android.ActivityAction) (port adb.TCPPort, cleanup task.Task, err error) { +func StartOrAttach(ctx context.Context, p *android.InstalledPackage, a *android.ActivityAction, o Options) (*Process, error) { ctx = log.Enter(ctx, "start") if a != nil { ctx = log.V{"activity": a.Activity}.Bind(ctx) @@ -53,7 +67,7 @@ func StartOrAttach(ctx context.Context, p *android.InstalledPackage, a *android. log.I(ctx, "Turning device screen on") if err := d.TurnScreenOn(ctx); err != nil { - return 0, nil, log.Err(ctx, err, "Couldn't turn device screen on") + return nil, log.Err(ctx, err, "Couldn't turn device screen on") } log.I(ctx, "Checking for lockscreen") @@ -62,49 +76,50 @@ func StartOrAttach(ctx context.Context, p *android.InstalledPackage, a *android. log.W(ctx, "Couldn't determine lockscreen state: %v", err) } if locked { - return 0, nil, log.Err(ctx, nil, "Cannot trace app on locked device") + return nil, log.Err(ctx, nil, "Cannot trace app on locked device") } - port, err = adb.LocalFreeTCPPort() + port, err := adb.LocalFreeTCPPort() if err != nil { - return 0, nil, log.Err(ctx, err, "Finding free port") + return nil, log.Err(ctx, err, "Finding free port") } log.I(ctx, "Checking gapid.apk is installed") apk, err := gapidapk.EnsureInstalled(ctx, d, abi) if err != nil { - return 0, nil, log.Err(ctx, err, "Installing gapid.apk") + return nil, log.Err(ctx, err, "Installing gapid.apk") } ctx = log.V{"port": port}.Bind(ctx) log.I(ctx, "Forwarding") if err := d.Forward(ctx, adb.TCPPort(port), adb.NamedAbstractSocket("gapii")); err != nil { - return 0, nil, log.Err(ctx, err, "Setting up port forwarding") + return nil, log.Err(ctx, err, "Setting up port forwarding") + } + + removeForward := func(ctx context.Context) error { + // Clone context to ignore cancellation. + ctx = keys.Clone(context.Background(), ctx) + return d.RemoveForward(ctx, port) } // 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, log.Err(ctx, err, "Setting up vulkan layer") + removeForward(ctx) + return nil, log.Err(ctx, err, "Setting up vulkan layer") } - doCleanup := func(ctx context.Context) error { + app.AddCleanup(ctx, func() { d.Command("shell", "setprop", "debug.vulkan.layers", "\"\"").Run(ctx) - return d.RemoveForward(ctx, adb.TCPPort(port)) - } - defer func() { - if err != nil { - doCleanup(ctx) - } - }() + removeForward(ctx) + }) if a != nil { log.I(ctx, "Starting activity in debug mode") if err := d.StartActivityForDebug(ctx, *a); err != nil { - return 0, nil, log.Err(ctx, err, "Starting activity in debug mode") + return nil, log.Err(ctx, err, "Starting activity in debug mode") } } else { log.I(ctx, "No start activity selected - trying to attach...") @@ -117,13 +132,17 @@ func StartOrAttach(ctx context.Context, p *android.InstalledPackage, a *android. pid, err = p.Pid(ctx) } if err != nil { - return 0, nil, log.Err(ctx, err, "Getting pid") + return nil, log.Err(ctx, err, "Getting pid") } ctx = log.V{"pid": pid}.Bind(ctx) - if err := loadLibrariesViaJDWP(ctx, apk, pid, d); err != nil { - return 0, nil, err + process := &Process{ + Port: int(port), + Options: o, + } + if err := process.loadAndConnectViaJDWP(ctx, apk, pid, d); err != nil { + return nil, err } - return port, doCleanup, nil + return process, nil } diff --git a/gapii/client/capture.go b/gapii/client/capture.go index f11e495f6a..e41407ff10 100644 --- a/gapii/client/capture.go +++ b/gapii/client/capture.go @@ -15,6 +15,7 @@ package client import ( + "bufio" "context" "fmt" "io" @@ -90,18 +91,56 @@ func (s siSize) String() string { return fmt.Sprintf(f, v) } -func capture(ctx context.Context, port int, s task.Signal, w io.Writer, o Options) (int64, error) { - if task.Stopped(ctx) { - return 0, nil - } - conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) - if err != nil { - return 0, nil // Treat failure-to-connect as target-not-ready instead of an error. +func (p *Process) connect(ctx context.Context) error { + log.I(ctx, "Waiting for connection to localhost:%d...", p.Port) + + // ADB has an annoying tendancy to insta-close forwarded sockets when + // there's no application waiting for the connection. Treat errors as + // another waiting-for-connection case. + return task.Retry(ctx, 0, 500*time.Millisecond, func(ctx context.Context) error { + conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", p.Port)) + if err != nil { + log.D(ctx, "Dial failed: %v", err) + return err + } + if err := sendHeader(conn, p.Options); err != nil { + log.D(ctx, "Failed to send header: %v", err) + conn.Close() + return err + } + r := bufio.NewReader(conn) + conn.SetReadDeadline(time.Now().Add(time.Millisecond * 500)) + if _, err := r.Peek(4); err != nil { + log.D(ctx, "Failed to read data: %v", err) + conn.Close() + return err + } + p.conn = bufConn{conn, r} + return nil + }) +} + +type bufConn struct { + net.Conn + r io.Reader +} + +func (c bufConn) Read(b []byte) (n int, err error) { return c.r.Read(b) } + +// Capture opens up the specified port and then waits for a capture to be +// delivered using the specified capture options o. +// It copies the capture into the supplied writer. +// If the process was started with the DeferStart flag, then tracing will wait +// until s is fired. +func (p *Process) Capture(ctx context.Context, s task.Signal, w io.Writer) (int64, error) { + if p.conn == nil { + if err := p.connect(ctx); err != nil { + return 0, err + } } + + conn := p.conn defer conn.Close() - if err := sendHeader(conn, o); err != nil { - return 0, log.Err(ctx, err, "Header send failed") - } var count, nextSize siSize startTime := time.Now() @@ -112,7 +151,7 @@ func capture(ctx context.Context, port int, s task.Signal, w io.Writer, o Option log.I(ctx, "Stop: %v", count) break } - if (o.Flags & DeferStart) != 0 { + if (p.Options.Flags & DeferStart) != 0 { if !started && s.Fired() { started = true w := endian.Writer(conn, device.LittleEndian) @@ -149,29 +188,3 @@ func capture(ctx context.Context, port int, s task.Signal, w io.Writer, o Option } return int64(count), nil } - -// Capture opens up the specified port and then waits for a capture to be -// delivered using the specified capture options. -// It copies the capture into the supplied writer. -func Capture(ctx context.Context, port int, s task.Signal, w io.Writer, options Options) (int64, error) { - log.I(ctx, "Waiting for connection to localhost:%d...", port) - for { - count, err := capture(ctx, port, s, w, options) - if err != nil { - return count, err - } - if count != 0 { - return count, nil - } - // ADB has an annoying tendancy to insta-close forwarded sockets when - // there's no application waiting for the connection. Treat this as - // another waiting-for-connection case. - select { - case <-task.ShouldStop(ctx): - log.I(ctx, "Aborted.") - return 0, nil - case <-time.After(500 * time.Millisecond): - log.I(ctx, "Retry...") - } - } -} diff --git a/gapii/client/header.go b/gapii/client/header.go index d4244cfa58..0ff4e8b901 100644 --- a/gapii/client/header.go +++ b/gapii/client/header.go @@ -23,19 +23,19 @@ import ( var magic = [4]byte{'s', 'p', 'y', '0'} -const version = 5 +const version = 1 // The GAPII header is defined as: // // struct ConnectionHeader { -// uint8_t mMagic[4]; // 's', 'p', 'y', '0' -// uint32_t mVersion; // 2+ -// uint32_t mObserveFrameFrequency; // non-zero == enabled. Version: 2+ -// uint32_t mObserveDrawFrequency; // non-zero == enabled. Version: 2+ -// uint32_t mStartFrame; // non-zero == Frame to start at. version 4+ -// uint32_t mNumFrames; // non-zero == Number of frames to capture. version 4+ -// uint32_t mAPIs; // Bitset of APIS to enable. version 5+ -// uint32_t mFlags; // Combination of FLAG_XX bits. Version: 3+ +// uint8_t mMagic[4]; // 's', 'p', 'y', '0' +// uint32_t mVersion; // 1 +// uint32_t mObserveFrameFrequency; // non-zero == enabled. +// uint32_t mObserveDrawFrequency; // non-zero == enabled. +// uint32_t mStartFrame; // non-zero == Frame to start at. +// uint32_t mNumFrames; // non-zero == Number of frames to capture. +// uint32_t mAPIs; // Bitset of APIS to enable. +// uint32_t mFlags; // Combination of FLAG_XX bits. // }; // // All fields are encoded little-endian with no compression, regardless of @@ -54,5 +54,6 @@ func sendHeader(out io.Writer, options Options) error { w.Uint32(options.FramesToCapture) w.Uint32(options.APIs) w.Uint32(uint32(options.Flags)) + return w.Error() } diff --git a/gapii/client/jdwp_loader.go b/gapii/client/jdwp_loader.go index 4530283430..d830da5692 100644 --- a/gapii/client/jdwp_loader.go +++ b/gapii/client/jdwp_loader.go @@ -22,6 +22,7 @@ import ( "reflect" "time" + "github.com/google/gapid/core/context/keys" "github.com/google/gapid/core/event/task" "github.com/google/gapid/core/java/jdbg" "github.com/google/gapid/core/java/jdwp" @@ -77,10 +78,15 @@ func waitForVulkanLoad(ctx context.Context, conn *jdwp.Connection) (*jdwp.EventM return conn.WaitForMethodEntry(ctx, loaders.ClassID(), getClassLoader.ID, 0) } -// loadLibrariesViaJDWP connects to the application waiting for a JDWP +// loadAndConnectViaJDWP 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 context.Context, gapidAPK *gapidapk.APK, pid int, d adb.Device) error { +func (p *Process) loadAndConnectViaJDWP( + ctx context.Context, + gapidAPK *gapidapk.APK, + pid int, + d adb.Device) error { + const ( reconnectAttempts = 10 reconnectDelay = time.Second @@ -96,7 +102,11 @@ func loadLibrariesViaJDWP(ctx context.Context, gapidAPK *gapidapk.APK, pid int, if err := d.Forward(ctx, adb.TCPPort(jdwpPort), adb.Jdwp(pid)); err != nil { return log.Err(ctx, err, "Setting up JDWP port forwarding") } - defer d.RemoveForward(ctx, adb.TCPPort(jdwpPort)) // must come before: defer stop() + defer func() { + // Clone context to ignore cancellation. + ctx := keys.Clone(context.Background(), ctx) + d.RemoveForward(ctx, adb.TCPPort(jdwpPort)) + }() ctx, stop := task.WithCancel(ctx) defer stop() @@ -106,23 +116,21 @@ func loadLibrariesViaJDWP(ctx context.Context, gapidAPK *gapidapk.APK, pid int, // 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 - } + err = task.Retry(ctx, reconnectAttempts, reconnectDelay, func(ctx context.Context) error { + if sock, err = net.Dial("tcp", fmt.Sprintf("localhost:%v", jdwpPort)); err != nil { + return err + } + if conn, err = jdwp.Open(ctx, sock); err != nil { sock.Close() + return err } - log.I(ctx, "Failed to connect: %v", err) - time.Sleep(reconnectDelay) - } + return nil + }) if err != nil { return log.Err(ctx, err, "Connecting to JDWP") } defer sock.Close() - classLoaderThread := jdwp.ThreadID(0) - processABI := func(j *jdbg.JDbg) (*device.ABI, error) { abiName := j.Class("android.os.Build").Field("CPU_ABI").Get().(string) abi := device.ABIByName(abiName) @@ -132,26 +140,11 @@ func loadLibrariesViaJDWP(ctx context.Context, gapidAPK *gapidapk.APK, pid int, return abi, nil } - loadGAPII := func(j *jdbg.JDbg) error { - abi, err := processABI(j) - if err != nil { - return err - } - interceptorPath := gapidAPK.LibInterceptorPath(abi) - gapiiPath := gapidAPK.LibGAPIIPath(abi) - ctx = log.V{"gapii.so": gapiiPath, "process abi": abi.Name}.Bind(ctx) - - // Load the library. - log.D(ctx, "Loading GAPII library...") - // Work around for loading libraries in the N previews. See b/29441142. - j.Class("java.lang.Runtime").Call("getRuntime").Call("doLoad", interceptorPath, nil) - j.Class("java.lang.Runtime").Call("getRuntime").Call("doLoad", gapiiPath, nil) - log.D(ctx, "Library loaded") - return nil - } + classLoaderThread := jdwp.ThreadID(0) log.I(ctx, "Waiting for ApplicationLoaders.getClassLoader()") - if getClassLoader, err := waitForVulkanLoad(ctx, conn); err == nil { + getClassLoader, err := waitForVulkanLoad(ctx, conn) + if err == nil { // If err != nil that means we could not find or break in getClassLoader // so we have no vulkan support. classLoaderThread = getClassLoader.Thread @@ -164,31 +157,49 @@ func loadLibrariesViaJDWP(ctx context.Context, gapidAPK *gapidapk.APK, pid int, newLibraryPath := j.String(":" + libsPath) obj := j.GetStackObject("librarySearchPath").Call("concat", newLibraryPath) j.SetStackObject("librarySearchPath", obj) - // If successfully loaded vulkan support, then we should be good to go - // load libgapii and friends here. - return loadGAPII(j) + return nil }) if err != nil { return log.Err(ctx, err, "JDWP failure") } + } else { + log.W(ctx, "Couldn't break in ApplicationLoaders.getClassLoader. Vulkan will not be supported.") + } + + // Wait for Application.onCreate to be called. + log.I(ctx, "Waiting for Application.onCreate()") + onCreate, err := waitForOnCreate(ctx, conn, classLoaderThread) + if err != nil { + return log.Err(ctx, err, "Waiting for Application.OnCreate") } - // 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. - log.I(ctx, "Waiting for Application.onCreate()") - onCreate, err := waitForOnCreate(ctx, conn, classLoaderThread) + // Connect to GAPII. + // This has to be done on a separate go-routine as the call to load gapii + // will block until a connection is made. + connErr := make(chan error) + go func() { connErr <- p.connect(ctx) }() + + // Load GAPII library. + err = jdbg.Do(conn, onCreate.Thread, func(j *jdbg.JDbg) error { + abi, err := processABI(j) if err != nil { - return log.Err(ctx, err, "Waiting for Application.OnCreate") + return err } + interceptorPath := gapidAPK.LibInterceptorPath(abi) + gapiiPath := gapidAPK.LibGAPIIPath(abi) + ctx = log.V{"gapii.so": gapiiPath, "process abi": abi.Name}.Bind(ctx) - // Create a JDbg session to install and load the libraries. - log.I(ctx, "Installing interceptor libraries") - if err := jdbg.Do(conn, onCreate.Thread, loadGAPII); err != nil { - return log.Err(ctx, err, "JDWP failure") - } + // Load the library. + log.D(ctx, "Loading GAPII library...") + // Work around for loading libraries in the N previews. See b/29441142. + j.Class("java.lang.Runtime").Call("getRuntime").Call("doLoad", interceptorPath, nil) + j.Class("java.lang.Runtime").Call("getRuntime").Call("doLoad", gapiiPath, nil) + log.D(ctx, "Library loaded") + return nil + }) + if err != nil { + return log.Err(ctx, err, "loadGAPII") } - return nil + return <-connErr }