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/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): + } + } +} 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 }