Chapter 20: ADB Integration¶
The Android Debug Bridge is the single most-used tool in the Android developer's box: adb shell, adb install, adb logcat, adb push. On a physical phone it rides USB or Wi-Fi. The emulator has neither — the guest kernel sees a virtio network device, but that network is a user-mode NAT with no route from the host to the guest's adbd socket, and there is no USB at all. Yet adb devices reliably lists emulator-5554, and everything works. This chapter explains the machinery that makes that illusion seamless.
The trick is that the emulator does not expose the guest's TCP port to the host. Instead it carries the ADB transport over an in-VM pipe — qemu_pipe — and the host process acts as a proxy: it opens a loopback TCP server on the host, the guest's adbd opens a pipe to the emulator, and the emulator splices the two together. Layered on top of that proxy are three more pieces: a discovery handshake so the adb server learns about emulators on non-standard ports, an in-process ADB client (AdbConnection) the emulator uses to drive its own guest for features like logcat and file push, and a host-side AdbInterface that locates and shells out to the real adb binary. We trace all four, grounding every claim in the source under external/qemu/android/.
20.1 The Problem: No Wire to the Guest¶
A real device connects to adb one of two ways. Over USB, adbd in the device talks to the host adb server through a USB bulk endpoint. Over TCP/IP (adb connect ip:port), adbd listens on a real TCP socket reachable on the LAN. The emulator can offer neither directly:
- There is no emulated USB controller wired to
adbd. - The guest's network is the QEMU user-mode (SLIRP) NAT. The guest gets
10.0.2.15; the host appears as10.0.2.2. Nothing on the host can open a connection into the guest's5555without an explicit port redirection.
The historical solution — still selectable for ancient images — was exactly that explicit redirection: forward host loopback adb_port to guest TCP 5555. The modern solution is a private channel that bypasses the network entirely. Which one an AVD uses is decided by avdInfo_getAdbdCommunicationMode.
20.1.1 Legacy redirection versus pipe mode¶
The decision lives in external/qemu/android/emu/avd/src/android/avd/info.c. Every QEMU2-class system image uses pipe mode; only pre-API-16 images fall back to the legacy network redirect:
// Source: external/qemu/android/emu/avd/src/android/avd/info.c
AdbdCommunicationMode avdInfo_getAdbdCommunicationMode(const AvdInfo* i,
bool isQemu2) {
if (isQemu2) {
// All qemu2-compatible system images support modern communication mode.
return ADBD_COMMUNICATION_MODE_PIPE;
}
if (i->apiLevel < 16 || (i->apiLevel > 99 && !i->isMarshmallowOrHigher)) {
// QEMU pipe for ADB communication was added in android-4.1.1_r1 API 16
D("API < 16 or unknown, forcing ro.adb.qemud==0");
return ADBD_COMMUNICATION_MODE_LEGACY;
}
return ADBD_COMMUNICATION_MODE_PIPE;
}
The two modes are dispatched in setup_console_and_adb_ports (external/qemu/android/android-emu/android/qemu-setup.cpp): legacy mode calls agents->net->slirpRedir(false, adb_port, 5555) to install the NAT redirect, while pipe mode calls android_adb_server_init(adb_port) and later registers the pipe service. The rest of this chapter is about pipe mode, which is what every device you will ever launch actually uses.
How the two modes reach the guest's adbd:
flowchart TB
subgraph LEGACY["Legacy mode (API < 16)"]
direction LR
L1["Host adb server"] -->|"loopback 5555"| L2["SLIRP NAT redirect"]
L2 -->|"guest tcp 5555"| L3["guest adbd"]
end
subgraph PIPE["Pipe mode (modern)"]
direction LR
P1["Host adb server"] -->|"loopback adb_port"| P2["AdbHostListener"]
P2 --> P3["AdbGuestPipe proxy"]
P3 -->|"qemu_pipe"| P4["guest adbd"]
end
20.2 The Console / ADB Port Pair¶
Every emulator instance owns a pair of adjacent localhost TCP ports: an even console port and the odd port just above it for ADB. The first instance gets 5554 (console) and 5555 (ADB); the next gets 5556/5557; and so on. This is the pattern behind the emulator-5554 serial number you see in adb devices.
The allocation runs in android_ports_setup in external/qemu/android/android-emu/android/qemu-setup.cpp. The base port defaults to ANDROID_CONSOLE_BASEPORT, defined as 5554 in hardware/google/aemu/host-common/include/host-common/constants.h, and the loop tries up to MAX_ANDROID_EMULATORS (16) successive even ports, stepping by two:
// Source: external/qemu/android/android-emu/android/qemu-setup.cpp
for (; tries > 0; tries--, base_port += 2) {
/* setup first redirection for ADB, the Android Debug Bridge */
adb_port = base_port + 1;
if (!setup_console_and_adb_ports(base_port, adb_port, legacy_adb, agents)) {
continue;
}
D("control console listening on port %d, ADB on port %d", base_port, adb_port);
success = 1;
break;
}
Once a pair binds, three globals are stored and the in-process ADB client is told which port to dial:
// Source: external/qemu/android/android-emu/android/qemu-setup.cpp
android_base_port = base_port;
android_adb_port = adb_port;
android_serial_number_port = adb_port - 1;
android::emulation::AdbConnection::setAdbPort(android_adb_port);
Note android_serial_number_port = adb_port - 1 — the console port. That is the number baked into the emulator-NNNN serial, which is why the serial uses the even console port and not the odd ADB port. Sixteen slots cover 5554–5584; the MAX_ANDROID_EMULATORS cap is also why the upper port bound is computed as lower_bound + (MAX_ANDROID_EMULATORS - 1) * 2 + 1 in the command-line parser (external/qemu/android/emu/cmdline/src/android/cmdline-option.cpp).
20.2.1 Who reaches which port¶
The two odd-and-even ports are genuinely different interfaces, and Chapter 8 covers the telnet console in depth. For ADB the key fact is that the odd adb_port is not a console-style server you can telnet into. It is a bare loopback listener that the adb server connects to and that the emulator hands straight to the proxy. Nothing speaks the telnet console protocol there.
Port responsibilities for the first instance:
| Port | Role | Bound by |
|---|---|---|
| 5554 | Telnet control console; basis for the emulator-5554 serial |
android_console_start |
| 5555 | ADB proxy listener; raw ADB transport bytes | AdbHostListener |
| 8554+ | gRPC control plane (computed separately, see Chapter 8) | qemu_setup_grpc |
20.3 The qemu_pipe Transport¶
qemu_pipe is the emulator's generic guest-to-host channel: the guest opens /dev/qemu_pipe, writes a pipe:<service> string, and from then on the file descriptor is a bidirectional byte stream wired to a host-side AndroidPipe::Service of that name. ADB rides the service named qemud:adb.
On the host the service is AdbGuestPipe::Service, constructed with the literal name in external/qemu/android/android-emu/android/emulation/AdbGuestPipe.h:
// Source: external/qemu/android/android-emu/android/emulation/AdbGuestPipe.h
Service(AdbHostAgent* hostAgent)
: AndroidPipe::Service("qemud:adb"), mHostAgent(hostAgent) {}
When the guest's adbd opens /dev/qemu_pipe and writes pipe:qemud:adb:<port>\0, the pipe layer matches the qemud:adb service name and calls AdbGuestPipe::Service::create, minting one AdbGuestPipe instance per transport. The trailing port (decimal, 5555 by default) is guest-local and the emulator ignores it — the header comment notes it is "assumed to be here for obsolete reasons." Each pipe instance is a state machine that owns one ADB transport for its lifetime.
The end-to-end wiring, copied almost verbatim from the documentation block in AdbGuestPipe.h:
guest side | emulator | host
adbd <-> /dev/qemu_pipe <-> AdbGuestPipe <-> AdbHostListener <-> ADB Server
20.3.1 Where the pipe service is registered¶
The services are created and registered at emulation setup in external/qemu/android/android-emu/android/adb-server.cpp. The choice between the classic pipe and the newer vsock transport is a feature flag:
// Source: external/qemu/android/android-emu/android/adb-server.cpp
void registerServices() {
if (feature_is_enabled(kFeature_VirtioVsockPipe)) {
auto service = new AdbVsockPipe::Service(&hostListener);
hostListener.setGuestAgent(service);
adbGuestAgent = service;
} else {
auto service = new AdbGuestPipe::Service(&hostListener);
hostListener.setGuestAgent(service);
AndroidPipe::Service::add(std::unique_ptr<AdbGuestPipe::Service>(service));
adbGuestAgent = service;
}
...
}
AdbVsockPipe::Service (external/qemu/android/android-emu/android/emulation/AdbVsockPipe.h) is the virtio-vsock variant: same proxy role, but the guest side speaks vsock instead of the legacy pipe device. Both implement the same AdbGuestAgent interface so the host listener is identical for either transport. Regardless of whether the vsock or the legacy pipe transport is chosen, registerServices() always registers a qemud:adb-debug service via AdbDebugPipe::Service (external/qemu/android/android-emu/android/emulation/AdbDebugPipe.h) after the vsock/pipe if/else block. The service is registered unconditionally; only the stderr stream it dumps proxied ADB traffic to is attached, when the emulator is launched with -debug adb (VERBOSE_CHECK(adb)).
20.4 Two Agents: Host Listener and Guest Pipe¶
The proxy is split into two cooperating objects defined in external/qemu/android/android-emu/android/emulation/AdbTypes.h. The header's own comment is the clearest description of the contract:
- The
AdbHostAgent(in practiceAdbHostListener) manages the host ADB server port. It is told tostartListening()for one connection,stopListening(), andnotifyServer()when a guest is ready. - The
AdbGuestAgent(in practiceAdbGuestPipe::Service) models all guest pipe connections. It receivesonHostConnection(socket, portType)when the server connects, and tells the host agent when to listen or notify.
// Source: external/qemu/android/android-emu/android/emulation/AdbTypes.h
struct AdbHostAgent {
virtual ~AdbHostAgent() = default;
virtual void startListening() = 0;
virtual void stopListening() = 0;
virtual void notifyServer() = 0;
};
AdbHostListener::reset (external/qemu/android/android-emu/android/emulation/AdbHostListener.cpp) creates the loopback TCP server on adb_port. It prefers IPv4 but falls back to IPv6-only on systems without IPv4, and it creates a second server on a random port for JDWP (used by Android Studio's Icebox debugger):
// Source: external/qemu/android/android-emu/android/emulation/AdbHostListener.cpp
mRegularAdbServer = AsyncSocketServer::createTcpLoopbackServer(
adbPort,
[this](int socket) {
return onHostServerConnection(socket, AdbPortType::RegularAdb);
},
mode, android::base::ThreadLooper::get());
When the host adb server connects to that loopback port, onHostServerConnection simply forwards the accepted socket to the guest agent as an AdbPortType::RegularAdb (or Jdwp) connection. The AdbGuestPipe::Service then finds a pipe that is waiting and hands it the socket.
Object responsibilities and the calls between them:
sequenceDiagram
participant Server as Host adb server
participant Listener as AdbHostListener
participant Service as AdbGuestPipe Service
participant Pipe as AdbGuestPipe
Pipe->>Listener: startListening
Pipe->>Listener: notifyServer
Server->>Listener: TCP connect on adb_port
Listener->>Service: onHostConnection socket
Service->>Service: searchForActivePipe
Service->>Pipe: onHostConnection socket
Service->>Listener: stopListening if no pipe still waiting
20.5 The Pipe Handshake State Machine¶
AdbGuestPipe is an explicit state machine. The states are enumerated in AdbGuestPipe.h and the transitions implement a tiny four-step protocol between adbd and the emulator. The protocol, again from the header:
- The guest opens
/dev/qemu_pipeand writespipe:qemud:adb:<port>\0, creating the pipe. - The guest writes
acceptto tell the emulator to start accepting one connection from the hostadbserver. - Once the host server connects, the emulator replies
ok(orko/ closes on error). - The guest writes
start, after which all bytes are proxied verbatim between guest and host.
A freshly created pipe is constructed in the WaitingForGuestAcceptCommand state, with the literal accept set as the expected command:
// Source: external/qemu/android/android-emu/android/emulation/AdbGuestPipe.cpp
setExpectedGuestCommand("accept", State::WaitingForGuestAcceptCommand);
The command matcher in onGuestSendCommand compares the guest's bytes against the expected command byte-for-byte. A mismatch is treated as an I/O error that forces the guest to tear down the transport — "closing the connection now is easier than sending 'ko'." On a full match of accept, the pipe calls waitForHostConnection; on a full match of start, it flips to ProxyingData:
// Source: external/qemu/android/android-emu/android/emulation/AdbGuestPipe.cpp
if (mState == State::WaitingForGuestAcceptCommand) {
waitForHostConnection();
} else if (mState == State::WaitingForGuestStartCommand) {
mState = State::ProxyingData;
DINIT("%s: [%p] Adb connected, start proxing data", __func__, this);
...
}
waitForHostConnection is where the two agents meet: it moves the pipe to WaitingForHostAdbConnection, then asks the host listener to both listen and notify the server, so an adb server on a non-standard port still learns this instance exists:
// Source: external/qemu/android/android-emu/android/emulation/AdbGuestPipe.cpp
void AdbGuestPipe::waitForHostConnection() {
mState = State::WaitingForHostAdbConnection;
mHostAgent->startListening();
mHostAgent->notifyServer();
}
When the host server then connects, AdbGuestPipe::onHostConnection wraps the socket in a non-blocking, no-delay FD watch and queues the ok reply that step 3 promises:
// Source: external/qemu/android/android-emu/android/emulation/AdbGuestPipe.cpp
setReply("ok", State::SendingAcceptReplyOk);
signalWake(PIPE_WAKE_READ);
20.5.1 Why the guest keeps opening pipes¶
A subtle detail the header calls out: adbd typically creates a new AdbGuestPipe immediately, but only ever has one pipe in accept at a time. So the service usually holds several pipes — one actively proxying, plus pending ones in WaitingForGuestAcceptCommand. searchForActivePipe picks the one in WaitingForHostAdbConnection when a host socket arrives, and once no pipe is left waiting, the service calls stopListening so the host port stops accepting until the next accept. This is what lets a single emulator multiplex many concurrent ADB transports (one per adb shell, adb logcat, file transfer, and so on) over the one pipe service.
The full per-pipe lifecycle:
stateDiagram-v2
[*] --> WaitingForGuestAcceptCommand : pipe created
WaitingForGuestAcceptCommand --> WaitingForHostAdbConnection : guest sent accept
WaitingForHostAdbConnection --> SendingAcceptReplyOk : host adb connected
SendingAcceptReplyOk --> WaitingForGuestStartCommand : ok reply sent
WaitingForGuestStartCommand --> ProxyingData : guest sent start
ProxyingData --> ClosedByGuest : guest closes pipe
ProxyingData --> ClosedByHost : host socket closed
ClosedByGuest --> [*]
ClosedByHost --> [*]
20.6 ADB Server Discovery¶
The host adb server normally finds emulators by probing: at startup it scans the 16 standard odd ports 5555, 5557, ... 5585 on loopback and tries to speak the ADB transport protocol to each. That works when the emulator landed on a standard port. But if adb is restarted after the emulator started on a non-standard port — or the emulator simply wants to be found promptly without waiting for the next scan — the emulator proactively notifies the server. This is the job of AdbHostServer in external/qemu/android/emu/adb/interface/src/android/emulation/AdbHostServer.cpp.
The server itself listens on a well-known TCP port. getClientPort returns the default 5037 unless overridden by the ANDROID_ADB_SERVER_PORT environment variable:
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/AdbHostServer.cpp
int AdbHostServer::getClientPort() {
int clientPort = kDefaultAdbClientPort; // 5037
const std::string_view kVarName = "ANDROID_ADB_SERVER_PORT";
std::string env = System::get()->envGet(kVarName);
if (!env.empty()) {
long port = strtol(env.c_str(), NULL, 0);
if (port <= 0 || port >= 65536) { ... return -1; }
clientPort = static_cast<int>(port);
}
return clientPort;
}
20.6.1 The host:emulator notification¶
notify connects to the server on port 5037 and sends a single ADB-protocol message that tells the server exactly which loopback port to attach to:
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/AdbHostServer.cpp
bool AdbHostServer::notify(int adbEmulatorPort, int adbClientPort) {
ScopedSocket socket = connectToAdbServer(adbClientPort);
if (!socket.valid()) {
// No adb server running: this is common, do not warn.
return false;
}
auto message = StringFormat("host:emulator:%d", adbEmulatorPort);
return sendMessage(socket.get(), message);
}
The message is framed in ADB's request wire format: a four-byte hex length prefix followed by the payload. sendMessage builds %04x%s:
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/AdbHostServer.cpp
auto wireformat = StringFormat("%04x%s", (uint32_t)message.size(), message.c_str());
return android::base::socketSendAll(fd, wireformat.c_str(), wireformat.size());
So host:emulator:5555 becomes 0012host:emulator:5555 on the wire. The server reads the port and adds a transport for emulator-5554. Two places trigger this notification: android_adb_server_notify in adb-server.cpp fires it asynchronously right after emulation setup (so a slow IPv6 loopback cannot stall startup), and AdbGuestPipe::waitForHostConnection fires it every time a guest pipe starts waiting.
// Source: external/qemu/android/android-emu/android/adb-server.cpp
void android_adb_server_notify(int port) {
auto globals = sGlobals.ptr();
globals->hostListener.reset(port);
// Could take seconds if ipv6 loopback is down; run async so startup is fast.
android::base::async([globals] {
globals->hostListener.notifyServer();
});
}
20.6.2 Negotiating the protocol version¶
AdbHostServer::getProtocolVersion connects to 5037 and sends host:version, then parses the server's OKAY-prefixed hex reply. The emulator uses this to pick a compatible adb binary (Section 20.8). The reply protocol is the standard ADB host-service handshake: a four-byte OKAY or FAIL, then a length-prefixed payload that, for host:version, is a four-byte hex version number.
How discovery flows end to end:
flowchart LR
EMU["Emulator process"] -->|"connect 5037"| SRV["Host adb server"]
EMU -->|"host:emulator:5555"| SRV
SRV -->|"connect loopback 5555"| LST["AdbHostListener"]
LST --> PIPE["AdbGuestPipe proxy"]
PIPE -->|"qemu_pipe transport"| ADBD["guest adbd"]
SRV -->|"lists emulator-5554"| CLI["adb client / adb devices"]
20.7 ADB Authentication¶
ADB is authenticated: adbd will only let a host talk to it once the host proves possession of a trusted RSA key (this is the "Allow USB debugging?" dialog on real devices). The emulator participates in this protocol in two distinct roles, and the auth code lives in external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/adbkey.cpp with declarations in adbkey.h.
The wire-level auth is a three-message dance using the ADB transport commands. The relevant subtypes are defined in AdbConnection.cpp:
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/AdbConnection.cpp
#define ADB_AUTH_TOKEN 1
#define ADB_AUTH_SIGNATURE 2
#define ADB_AUTH_RSAPUBLICKEY 3
The sequence: adbd sends A_AUTH with an ADB_AUTH_TOKEN (a 20-byte random challenge); the client signs it with its private key and replies A_AUTH/ADB_AUTH_SIGNATURE; if no known key matches, the client may offer its public key with ADB_AUTH_RSAPUBLICKEY, prompting the on-device authorization dialog. TOKEN_SIZE is 20 and the RSA modulus is 2048 bits, both fixed in adbkey.h:
// Source: external/qemu/android/emu/adb/interface/include/android/emulation/control/adb/adbkey.h
constexpr const int ANDROID_PUBKEY_MODULUS_SIZE = 2048 / 8;
constexpr const int TOKEN_SIZE = 20;
constexpr const char* kPrivateKeyFileName = "adbkey";
constexpr const char* kPublicKeyFileName = "adbkey.pub";
The host's RSA key pair is the user's standard ADB key (adbkey / adbkey.pub), located by getPrivateAdbKeyPath / getPublicAdbKeyPath, and generated by adb_auth_keygen if absent. android_pubkey_encode produces the Android-specific binary public-key format, and sign_auth_token signs the challenge token.
20.7.1 The pull-key gRPC and the direct-bridge caveat¶
There is also a gRPC RPC that lets a trusted client fetch the emulator's private key so it can authenticate to the same device: AdbServiceImpl::pullAdbKey in external/qemu/android/android-grpc/services/adb/server/src/android/emulation/control/adb/AdbService.cpp reads getPrivateAdbKeyPath() and returns it in an AdbKey protobuf. This is how an embedded UI can drive adb against the emulator without a separate authorization prompt.
The in-process client (Section 20.9) deliberately refuses to install a new public key into the guest. AdbConnection::sendPublicKeyToDevice sets the connection to failed and bails out, citing bug b/150160590 — offering a key from a second connection conflicts with the real adb server's auth, so the direct bridge disables itself rather than fight over the device:
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/AdbConnection.cpp
void sendPublicKeyToDevice() {
setState(AdbState::failed);
mSocket->close();
LOG(ERROR) << "We are not offering to install a public key due to "
"b/150160590, direct bridge disabled.\n" ...;
return;
...
}
20.8 Locating and Driving the Real adb Binary¶
Many host-side features prefer to shell out to the genuine adb executable rather than reimplement the protocol. AdbInterface (external/qemu/android/emu/adb/interface/include/android/emulation/control/adb/AdbInterface.h) is the abstraction: it finds an adb binary, builds the right serial argument, and runs commands asynchronously with a result callback.
The serial string is derived from the console port, matching what adb devices shows:
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/AdbInterface.cpp
virtual void setSerialNumberPort(int port) final {
mSerialString = std::string("emulator-") + std::to_string(port);
}
20.8.1 Which adb binary¶
AdbLocatorImpl::availableAdb searches three locations in order, keeping only candidates that actually report a version string:
platform-tools/adbunder the SDK root resolved from the environment (getSdkRootDirectoryByEnv).platform-tools/adbunder the SDK root resolved relative to the emulator executable (getSdkRootDirectoryByPath).- Whatever
adbis onPATH(System::which).
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/AdbInterface.cpp
std::vector<std::string> AdbLocatorImpl::availableAdb() {
std::vector<std::string> available = {};
auto adb = platformPath(android::ConfigDirs::getSdkRootDirectoryByEnv());
if (adb && extractProtocolVersion(*adb)) available.push_back(std::move(*adb));
adb = platformPath(android::ConfigDirs::getSdkRootDirectoryByPath());
if (adb && extractProtocolVersion(*adb)) available.push_back(std::move(*adb));
adb = System::get()->which(PathUtils::toExecutableName("adb"));
if (adb && extractProtocolVersion(*adb)) available.push_back(std::move(*adb));
return available;
}
selectAdbPath then prefers the candidate whose protocol version matches the running adb server (queried via AdbDaemon::getProtocolVersion, which under the hood is AdbHostServer::getProtocolVersion from Section 20.6.2). Matching versions avoids the situation where launching a mismatched adb silently kills and restarts the user's server. If no match exists it falls back to the newest available, and flags whether it meets kMinAdbProtocol.
20.8.2 Running a command¶
runAdbCommand constructs an AdbThroughExe (a subprocess wrapper) with the resolved binary, the emulator-NNNN serial, and the requested args, then starts it asynchronously. If the serial is still empty it back-fills it from android_serial_number_port:
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/AdbInterface.cpp
command = std::make_shared<AdbThroughExe>(
mLooper, adbPath(), mSerialString, args, want_output,
timeout_ms, std::move(result_callback));
command->start();
enqueueCommand layers retry-with-backoff on top, distinguishing the very first command (longer retry, since adb may still be launching) from later ones via INITIAL_ADB_RETRY_LIMIT versus SUBSEQUENT_ADB_RETRY_LIMIT. The whole thing is gated on android_qemu_mode() — the interface no-ops outside QEMU mode.
20.9 The In-Process ADB Client¶
Some features cannot afford to depend on the user's adb binary at all — for example, pushing a file or running logcat very early in boot, or in headless/CI environments where no SDK is installed. For these the emulator embeds its own minimal ADB client, AdbConnection, which connects directly to the loopback adb_port and speaks the raw transport protocol to the guest's adbd. This is the "direct bridge."
The transport command set is encoded in AdbConnection.cpp as a 4-byte little-endian enum — the same A_CNXN/A_AUTH/A_OPEN/A_OKAY/A_CLSE/A_WRTE commands the real adb uses on USB and TCP:
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/AdbConnection.cpp
enum class AdbWireMessage {
A_SYNC = 0x434e5953,
A_CNXN = 0x4e584e43,
A_AUTH = 0x48545541,
A_OPEN = 0x4e45504f,
A_OKAY = 0x59414b4f,
A_CLSE = 0x45534c43,
A_WRTE = 0x45545257,
};
setAdbPort (called from qemu-setup.cpp, Section 20.2) constructs the connection against the loopback port using the thread looper's async socket:
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/AdbConnection.cpp
void AdbConnection::setAdbPort(int adbPort) {
auto looper = android::base::ThreadLooper::get();
setAdbSocket(new AsyncSocket(looper, adbPort));
}
20.9.1 Handshake and connection state¶
The client state is the AdbState enum in AdbConnection.h: disconnected, socket, connecting, authorizing, offer_key, connected, failed. On connect it sends A_CNXN with a banner and version; adbd replies with A_AUTH (triggering the signature flow of Section 20.7), then its own A_CNXN. handleConnect parses the device banner — device::ro.product.name=...;features=... — extracting the feature set so callers can query hasFeature("shell_v2"), and finally moves to connected:
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/AdbConnection.cpp
mProtocolVersion = msg.msg.arg0;
mMaxPayloadSize = msg.msg.arg1;
// parse "features=..." out of the banner...
setState(AdbState::connected);
Once connected, open(service) issues an A_OPEN for a service string and returns an AdbStream — a blocking std::iostream whose rdbuf proxies ADB A_WRTE/A_OKAY framing. The documented usage in AdbConnection.h is striking in its simplicity:
// Source: external/qemu/android/emu/adb/interface/include/android/emulation/control/adb/AdbConnection.h
auto stream = AdbConnection::connection()->open("shell:getprop");
while (getline(stream, line)) {
cout << line << '\n';
}
20.9.2 Shell, logcat, and file push on top of the stream¶
AdbShellStream (external/qemu/android/emu/adb/interface/include/android/emulation/control/adb/AdbShellStream.h) wraps AdbConnection::open to present a uniform interface over both shell protocol versions. The ADB shell v2 protocol multiplexes stdin, stdout, stderr, and the exit code over one stream using a one-byte id header, declared in AdbConnection.h:
// Source: external/qemu/android/emu/adb/interface/include/android/emulation/control/adb/AdbConnection.h
struct ShellHeader {
enum Id : uint8_t {
kIdStdin = 0, kIdStdout = 1, kIdStderr = 2,
kIdExit = 3, // data[0] is the exit code
kIdCloseStdin = 4, kIdWindowSizeChange = 5, kIdInvalid = 255,
};
Id id;
uint32_t length;
} __attribute__((packed));
AdbShellStream::isV1 reports which protocol is in use; for v1 the exit code is never set and stderr is folded into stdout, while v2 separates the three streams. This is what backs the gRPC logcat and shell RPCs and the file-push helpers without ever shelling out to adb.
AdbInterfaceImpl::runAdbCommand falls back to this in-process path for shell and logcat only under a precise condition: the emulator was launched with -no-direct-adb, the external bridge has already failed(), and the command is one of those two. In that case it builds an AdbDirect rather than an AdbThroughExe:
// Source: external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/AdbInterface.cpp
if (getConsoleAgents()->settings->android_cmdLineOptions()->no_direct_adb &&
AdbConnection::failed() && (args[0] == "shell" || args[0] == "logcat")) {
command = std::make_shared<AdbDirect>(args, std::move(result_callback), want_output);
}
Because a public key can only ever be offered by one connection at a time (b/150160590), the static AdbConnection::failed() guard ensures the in-process client and the external adb server never both try to authorize, which would otherwise leave the device flapping between authorized and unauthorized.
The two host-side clients and how they reach the guest:
flowchart TB
subgraph HOST["Emulator host process"]
IFACE["AdbInterface (shells out)"]
DIRECT["AdbConnection (in-process)"]
SHELL["AdbShellStream"]
LISTEN["AdbHostListener : loopback adb_port"]
SHELL --> DIRECT
end
EXE["external adb binary"]
SRV["host adb server : 5037"]
IFACE -->|"fork/exec -s emulator-5554"| EXE
EXE --> SRV
SRV -->|"connect adb_port"| LISTEN
DIRECT -->|"connect adb_port"| LISTEN
LISTEN --> PIPE["AdbGuestPipe proxy"]
PIPE -->|"qemu_pipe"| ADBD["guest adbd"]
20.10 Observability: Sniffing ADB Traffic¶
Because the emulator sits in the middle of every ADB transport, it can log the bytes flowing both ways. AdbMessageSniffer (external/qemu/android/android-emu/android/emulation/AdbMessageSniffer.h) is the decoder; each AdbGuestPipe owns two of them — one per direction — created in the pipe constructor and keyed off the AVD's test_monitorAdb hardware setting:
// Source: external/qemu/android/android-emu/android/emulation/AdbGuestPipe.cpp
mReceivedMesg(AdbMessageSniffer::create("HOST==>GUEST",
getConsoleAgents()->settings->hw()->test_monitorAdb)),
mSendingMesg(AdbMessageSniffer::create("HOST<==GUEST",
getConsoleAgents()->settings->hw()->test_monitorAdb))
Separately, launching with -debug adb (the VERBOSE_CHECK(adb) flag in adb-server.cpp) attaches a StdioStream(stderr) to the qemud:adb-debug pipe, so the raw proxied traffic is dumped as it flows. Both mechanisms are diagnostic only; neither alters the transport.
The JDWP and snapshot cases get an extra layer: AdbHub (external/qemu/android/android-emu/android/emulation/AdbHub.h) parses incoming traffic on a pipe and, for JDWP transports, routes it through a JdwpProxy that can answer some messages itself. AdbHub is also what lets a snapshot reconnect a fresh host connection to an existing guest stream without disconnecting the guest — needsHubTranslation() returns true for JDWP pipes and for pipes reused from a snapshot.
20.11 Try It¶
These commands assume an emulator is running and the SDK platform-tools are on your PATH.
- See the serial-number / port mapping the chapter describes:
- Confirm the ADB loopback port is the odd port one above the console port:
- Talk to the ADB server the way the emulator's
AdbHostServer::notifydoes, asking for its protocol version (host:version). The000cis the 12-byte length prefix:
- Watch the pipe handshake and proxied traffic by launching the emulator with ADB debug logging:
- Force the emulator onto a non-default
adbserver port to exercise the discovery path, then confirm the emulator still registers:
ANDROID_ADB_SERVER_PORT=5038 adb start-server
emulator -avd <name> # AdbHostServer::getClientPort reads the env var
ANDROID_ADB_SERVER_PORT=5038 adb devices
- Exercise the in-process direct bridge for
shell/logcat:
Summary¶
- The emulator has no USB and no host-routable guest network, so modern AVDs carry the ADB transport over the in-VM
qemu_pipeservice namedqemud:adb, with the host process acting as a TCP-to-pipe proxy. Pre-API-16 images fall back to a SLIRP port redirect; the choice is made byavdInfo_getAdbdCommunicationMode. - Each instance owns an adjacent console/ADB port pair starting at
5554/5555and stepping by two (android_ports_setup); theemulator-NNNNserial uses the even console port (android_serial_number_port = adb_port - 1). - The proxy splits into
AdbHostListener(a loopback TCP server onadb_port, plus a random JDWP port) andAdbGuestPipe::Service, communicating through theAdbHostAgent/AdbGuestAgentinterfaces inAdbTypes.h. - Each transport is an
AdbGuestPipestate machine driven by a four-word protocol: the guest writesaccept, the emulator waits for the host server then repliesok, the guest writesstart, and from then on bytes are proxied verbatim. AdbHostServer::notifysendshost:emulator:<port>to theadbserver on port 5037 so emulators on non-standard ports are discovered without waiting for the server's port scan;getClientPorthonorsANDROID_ADB_SERVER_PORT.- ADB authentication uses RSA token signing (
ADB_AUTH_TOKEN/SIGNATURE/RSAPUBLICKEYwith a 20-byte token and 2048-bit key) handled inadbkey.cpp; a gRPCpullAdbKeyRPC shares the private key with trusted clients, and the in-process bridge refuses to install a public key to avoid auth conflicts. AdbInterfacelocates a version-matchedadbbinary (SDK-by-env, SDK-by-path, thenPATH) and shells out with-s emulator-NNNN, whileAdbConnectionis a full in-process ADB client speaking the raw transport directly toadb_port, backingAdbShellStreamfor shell/logcat and file push.AdbMessageSniffer, the-debug adb(qemud:adb-debug) pipe, andAdbHub/JdwpProxygive observability and JDWP/snapshot handling on top of the same proxy.
Key Source Files¶
| File | Purpose |
|---|---|
external/qemu/android/android-emu/android/qemu-setup.cpp |
Allocates the console/ADB port pair and wires legacy versus pipe mode |
external/qemu/android/android-emu/android/adb-server.cpp |
Registers the pipe/vsock services and triggers server notification |
external/qemu/android/android-emu/android/emulation/AdbTypes.h |
AdbHostAgent / AdbGuestAgent proxy contract |
external/qemu/android/android-emu/android/emulation/AdbHostListener.cpp |
Loopback TCP server on adb_port plus JDWP port |
external/qemu/android/android-emu/android/emulation/AdbGuestPipe.cpp |
The qemud:adb pipe handshake state machine |
external/qemu/android/emu/adb/interface/src/android/emulation/AdbHostServer.cpp |
host:emulator: discovery and protocol-version query |
external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/AdbInterface.cpp |
Locates and shells out to the real adb binary |
external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/AdbConnection.cpp |
In-process ADB transport client (direct bridge) |
external/qemu/android/emu/adb/interface/src/android/emulation/control/adb/adbkey.cpp |
RSA key handling and auth-token signing |
external/qemu/android/android-grpc/services/adb/server/src/android/emulation/control/adb/AdbService.cpp |
gRPC pullAdbKey service |