Skip to content

Dynamic Call Graph

Jordan Samhi edited this page Oct 23, 2026 · 1 revision

-mc records callsites reached during execution and, when possible, the method implementation that was entered. The resulting graph describes the supplied run; it does not enumerate all calls that could occur.

Enable -mc when instrumenting and when reporting. Adding it only during parsing cannot recover runtime dispatch information that was never recorded. It can be used alone for a graph-only report, or with coverage options.

Polymorphism

Consider this virtual call:

Animal animal = new Dog();
animal.speak();

The bytecode refers to Animal.speak(). If execution enters an instrumented override Dog.speak(), AndroLog can record the actual relationship:

caller → Dog.speak()

The same mechanism applies to interface dispatch. If one callsite reaches several implementations during the captured execution, it can contribute several confirmed targets.

AndroLog correlates the callsite and callee entry inside the app process. It checks the same thread, exact method subsignature, receiver identity for instance calls, and the direct caller stack. It does not infer a target simply from the next method name appearing in logcat. Normal polymorphic calls between instrumented methods should be confirmed when matching observations are retained.

Read the current edge styles

Style What AndroLog recorded
Solid: confirmed runtime target A matching instrumented entry confirmed the callee
Dashed: declared target The callsite was reached, but no callee entry was confirmed; the bytecode target is retained

The dashed style currently means missing entry confirmation, not necessarily an unknown target. The current report uses the same style for several different situations:

Situation Why an edge may be dashed
Constructor Constructor entry confirmation is deliberately skipped by the current tracer
Framework or native code The method body is outside the instrumented APK
Excluded APK code Scope filters prevented instrumentation of the callee
Dynamic invoke The current tracer records its declaration without confirming entry
Failure before method entry For example, an instance call on a null receiver
Incomplete observations A confirmation record was not retained, or matching failed
Legacy CALL= record The older format contains declared targets only

Constructors

Constructors are non-overridable direct calls, not virtual dispatch. For new Dog(), the constructor selected in the bytecode is already known. Android documents constructors under invoke-direct. Android bytecode specification

AndroLog's normal matching hook passes the receiver object. The current implementation avoids exposing an uninitialized receiver by skipping constructor entry confirmation. Consequently, even an app constructor with a known target is displayed as dashed. This is an implementation choice, not evidence that constructor dispatch is ambiguous. Constructor-specific confirmation and a separate known-target edge category are not implemented in the current version.

Framework calls

A direct or static framework call can have a known target despite the absence of a framework entry probe. Such calls still appear dashed under the current two-style scheme.

A virtual reference to a framework class is different. For example, View.draw() can dispatch to an app's MyView.draw() override. The declared framework class alone does not establish the implementation that ran. If the override is instrumented and matches the pending call, it can be confirmed normally.

Callbacks and asynchronous execution

A framework callback may be observed by coverage probes without a confirmed incoming call edge, because its caller is not instrumented. Reflection, uninstrumented intermediaries, and asynchronous work are not inferred as direct calls from a previously recorded app callsite. A temporal relationship is not enough to establish a direct caller-to-callee edge.

Navigate the class graph

The default class comes from manifest launcher metadata: MAIN with LAUNCHER, or LEANBACK_LAUNCHER for TV. Aliases resolve to their target activity classes. If several launcher classes have recorded calls, the first in alphabetical order opens.

Selecting a class shows relationships directly incident to its methods, including endpoints outside the class. Clicking a node emphasizes its incoming and outgoing edges and fades the others. Reset graph clears that emphasis; Fit adjusts the view. If no launcher has matching data, select a class manually. The report does not open the whole application's graph by default.

See HTML Report for screenshots and controls.

Counts and completeness

Each callsite declaration and each callsite/actual-target pair is emitted once per process. Edge counts represent distinct accepted records, not invocation frequency. Several callsites between the same methods can contribute to one edge. A callsite with confirmed targets contributes those targets instead of a declared fallback edge.

Missing records can omit edges or leave a declared edge. The tracer also captures stack traces and adds runtime overhead. Narrowing scope or selecting fewer probes can reduce work, but changes what the report can measure. Keep those choices consistent when comparing runs.

The JSON and Logs guide explains edge fields, callsite identifiers, and MC2 records. The troubleshooting guide covers unexpectedly missing confirmations.

Clone this wiki locally