"Σκόπει εἰς σεαυτόν. Ἐντὸς σοῦ πηγὴ ἀγαθοῦ ἐστιν, ἡ ἀεὶ ἐκβρύειν ἑτοίμη, ἐὰν ἀεὶ σκάπτῃς."
"Look within. Within is the fountain of good, and it will ever bubble up, if you will ever dig."
- Marcus Aurelius (121-180 AD), Roman Emperor and Stoic philosopher
"Ignis aurum probat, miseria fortes."
"Fire tests gold and adversity tests the brave."
-Seneca the Younger (c. 4 BC - AD 65), Roman statesman and Stoic philosopher
Stoic lets you look within your Android processes, giving you the courage to take on difficult bugs.
Stoic is a tool for
You can write plugins that
Stoic is fast. The first time you run a Stoic plugin in a process it will take 2-3 seconds to attach. Thereafter, Stoic plugins typically run in less than a second.
When you run Stoic on your laptop it syncs itself (via rsync over adb) to your
Android device. Most of the functionality is actually run directly on your device,
so you can run it directly from adb shell
if you prefer. Anything inside of
~/.config/stoic/sync
will also be sync'd, so you can have all your favorite
command-line utilities (bash
, vim
, etc) available whenever you connect to a new
device or emulator, pre-configured according to your custom bashrc
/vimrc
/etc.
git clone https://github.com/square/stoic && cd stoic
./build.sh
stoic setup
(this initializes ~/.config/stoic
)stoic --pkg com.square.stoic.example scratch
(if you don't specify a package, Stoic will run on com.square.stoic.example
by default - a simple app bundled with Stoic)~/.config/stoic/plugin
with Android Studio to modify this plugin and explore what Stoic can do.Stoic works on any API 26+ Android device / emulator, with any debuggable app (that I've tested so far).
Stoic bundles a few plugins:
Each plugin is a normal Java main
function. You access debugger functionality via the com.square.stoic.jvmti
package. e.g.
// get callbacks whenever any method of interest is called
val method = jvmti.virtualMachine.methodBySig("android/view/InputEventReceiver.dispatchInputEvent(ILandroid/view/InputEvent;)V")
jvmti.breakpoint(method.startLocation) { frame ->
println("dispatchInputEvent called")
}
// iterate over each bitmap in the heap
for (bitmap in jvmti.instances(Bitmap::class.java)) {
println("$bitmap: size=${bitmap.allocationByteCount}")
}
Stoic is built on public APIs so it will continue to work far into the future.
The primary technologies powering Stoic are
JVMTI,
Unix Domain Sockets, socat
, rsync
, and run-as
.
The first time you run Stoic on a process it will attach a jvmti agent which
will start a server inside the process. We connect to this server
through a unix domain socket (via run-as pkg socat ...
for permissions reasons).
Each socket connection corresponds to a unique plugin. We multiplex
stdin/stdout/stderr over this connection. See
https://github.com/square/stoic/blob/main/docs/ARCHITECTURE.md for more details.