[Lldb-commits] [lldb] [lldb][Android] Fix platform process list regression (PR #164333)
Chad Smith via lldb-commits
lldb-commits at lists.llvm.org
Mon Oct 20 16:00:59 PDT 2025
https://github.com/cs01 created https://github.com/llvm/llvm-project/pull/164333
## Summary
After https://github.com/llvm/llvm-project/pull/160931, `platform process list` without arguments returns no processes on remote Android, breaking the ability to enumerate processes. This occurred because FindProcesses fell back to GDB Remote Protocol only when `MatchAllProcesses()` was true, missing other important scenarios.
## Bug Description
After the implementation of FindProcesses in PlatformAndroid (#160931), `platform process list` without arguments fails to return any processes:
```
(lldb) platform process list
error: no processes were found on the "remote-android" platform
```
Additionally, results were inconsistent between different query modes:
- `platform process list -n com.example.hellojni` returned processes without triple info
- `platform process list -p 5276` returned correct triple info
- `platform process list` (all processes) returned nothing
## Root Cause
The original implementation used a simple `MatchAllProcesses()` check to decide when to fall back to GDB Remote Protocol. This logic was too narrow and missed important cases like:
- Wildcard/regex name matching (`-s`, `-e`, `-c`, `-r` flags)
- Filtering by architecture, user, PID, etc.
- Enumerating all processes
### Why Android is Different from Regular Linux
On regular Linux, process enumeration reads `/proc/[pid]/exe`, which is a symlink to the actual binary (e.g., `/usr/bin/firefox`, `/bin/bash`). This gives you the canonical process name directly. The `/proc/[pid]/cmdline` contains arguments which can be misleading (e.g., `python script.py` - do you want "python" or "script.py"?), so Linux platforms use the exe symlink as the reliable source.
**Android breaks this assumption** due to the Zygote process model:
- Android apps are forked from a pre-warmed Java VM called Zygote
- `/proc/[pid]/exe` points to `app_process64` for **all Android apps** (not the actual app)
- The actual package name (e.g., `com.example.hellojni`) only appears in `/proc/[pid]/cmdline`
- GDB Remote Protocol reads exe and reports the binary name (`app_process64`), not the package name
- To find processes by package name AND support wildcard/regex matching, we need to search cmdline
## Solution
Use `ps -A -o PID,ARGS` to get process list with cmdline information for all remote Android queries.
**Remote Android:**
- Use `ps -A` to list all processes with their command lines
- Match process name against cmdline (contains Android package names)
- Support all name matching modes (exact, starts-with, ends-with, contains, regex)
- Read ELF header from `/proc/[pid]/exe` for architecture (e_machine at offset 18)
- Read `/proc/[pid]/status` for parent PID, UIDs, GIDs
- Read `/proc/[pid]/cmdline` for command line arguments (or package name)
These all work now
- `platform process list -n com.example.app`
- `platform process list -s com.example`
- `platform process list -c example`
- `platform process list -r "com\..*\.app"`
- `process attach -n com.example.app`
## Test Results
### Before this fix:
```
(lldb) platform process list
error: no processes were found on the "remote-android" platform
(lldb) platform process list -n com.example.hellojni
1 matching process was found on "remote-android"
PID PARENT USER TRIPLE NAME
====== ====== ========== ============================== ============================
5276 359 u0_a192 com.example.hellojni
^^^^^^^^ Missing triple!
```
### After this fix:
```
(lldb) platform process list
PID PARENT USER TRIPLE NAME
====== ====== ========== ============================== ============================
1 0 root aarch64-unknown-linux-android init
2 0 root [kthreadd]
359 1 system aarch64-unknown-linux-android app_process64
5276 359 u0_a192 aarch64-unknown-linux-android com.example.hellojni
5357 5355 u0_a192 aarch64-unknown-linux-android sh
5377 5370 u0_a192 aarch64-unknown-linux-android lldb-server
^^^^^^^^ User-space processes now have triples!
(lldb) platform process list -n com.example.hellojni
1 matching process was found on "remote-android"
PID PARENT USER TRIPLE NAME
====== ====== ========== ============================== ============================
5276 359 u0_a192 aarch64-unknown-linux-android com.example.hellojni
(lldb) process attach -n com.example.hellojni
Process 5276 stopped
* thread #1, name = 'example.hellojni', stop reason = signal SIGSTOP
```
## Test Plan
With an Android device/emulator connected:
1. Start lldb-server on device:
```bash
adb push lldb-server /data/local/tmp/
adb shell chmod +x /data/local/tmp/lldb-server
adb shell /data/local/tmp/lldb-server platform --listen 127.0.0.1:9500 --server
```
2. Connect from LLDB:
```
(lldb) platform select remote-android
(lldb) platform connect connect://127.0.0.1:9500
(lldb) platform process list
```
3. Verify:
- `platform process list` returns all processes with triple information
- `platform process list -n com.example.app` finds Android apps by package name
- `process attach -n com.example.app` successfully attaches to Android apps
## Impact
Restores `platform process list` on Android with architecture information and package name lookup. All name matching modes now work correctly.
Fixes https://github.com/llvm/llvm-project/issues/164192
>From 9c9a440d2a1dd02299a2e67cb245f2efa9e5a87b Mon Sep 17 00:00:00 2001
From: Chad Smith <cssmith at fb.com>
Date: Mon, 20 Oct 2025 14:18:16 -0700
Subject: [PATCH] [lldb][Android] Fix platform process list regression after
FindProcesses implementation
---
.../Platform/Android/PlatformAndroid.cpp | 287 +++++++++++++-----
.../Platform/Android/PlatformAndroid.h | 4 +-
2 files changed, 222 insertions(+), 69 deletions(-)
diff --git a/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp b/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp
index 57d88f615e2b3..4b93cfa9b3797 100644
--- a/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp
+++ b/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp
@@ -10,10 +10,13 @@
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/Section.h"
#include "lldb/Host/HostInfo.h"
+#include "lldb/Utility/DataExtractor.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/UriParser.h"
#include "lldb/ValueObject/ValueObject.h"
+#include "llvm/BinaryFormat/ELF.h"
+#include "llvm/Support/Base64.h"
#include "AdbClient.h"
#include "PlatformAndroid.h"
@@ -571,40 +574,98 @@ void PlatformAndroid::PopulateProcessCommandLine(
process_info.SetArguments(process_args, false);
}
-// Helper function to populate architecture from /proc/[pid]/exe
+// Helper function to populate architecture from /proc/[pid]/exe by reading ELF
+// header
void PlatformAndroid::PopulateProcessArchitecture(
lldb::pid_t pid, ProcessInstanceInfo &process_info) {
- // Read /proc/[pid]/exe to get executable path for architecture detection
+ // Read the first 20 bytes of /proc/[pid]/exe to parse the ELF header
+ // We need the ELF identification (16 bytes) plus e_machine field (2 bytes at
+ // offset 18)
Status error;
- AdbClientUP exe_adb = GetAdbClient(error);
+ AdbClientUP elf_adb = GetAdbClient(error);
if (error.Fail())
return;
- std::string exe_output;
- StreamString exe_cmd;
- exe_cmd.Printf("readlink /proc/%llu/exe 2>/dev/null",
+ std::string elf_header_base64;
+ StreamString elf_cmd;
+ // Use dd to read just the ELF header (first 20 bytes is enough for e_machine)
+ // Output as base64 to avoid parsing issues with binary data
+ elf_cmd.Printf("dd if=/proc/%llu/exe bs=20 count=1 2>/dev/null | base64",
static_cast<unsigned long long>(pid));
- Status exe_error = exe_adb->Shell(exe_cmd.GetData(), seconds(5), &exe_output);
+ Status elf_error =
+ elf_adb->Shell(elf_cmd.GetData(), seconds(5), &elf_header_base64);
- if (exe_error.Fail() || exe_output.empty())
+ if (elf_error.Fail() || elf_header_base64.empty())
return;
- exe_output = llvm::StringRef(exe_output).trim().str();
+ // Decode base64 using LLVM's base64 decoder
+ std::vector<char> header_bytes;
+ llvm::Error decode_error = llvm::decodeBase64(
+ llvm::StringRef(elf_header_base64).trim(), header_bytes);
+
+ Log *log = GetLog(LLDBLog::Platform);
+ if (decode_error) {
+ LLDB_LOGF(log, "PlatformAndroid::%s base64 decode failed for PID %llu: %s",
+ __FUNCTION__, static_cast<unsigned long long>(pid),
+ llvm::toString(std::move(decode_error)).c_str());
+ return;
+ }
+
+ LLDB_LOGF(log, "PlatformAndroid::%s decoded %zu bytes for PID %llu",
+ __FUNCTION__, header_bytes.size(),
+ static_cast<unsigned long long>(pid));
+
+ // Need at least 20 bytes for ELF header check
+ if (header_bytes.size() < 20) {
+ LLDB_LOGF(log, "PlatformAndroid::%s insufficient bytes (%zu < 20)",
+ __FUNCTION__, header_bytes.size());
+ return;
+ }
+
+ // Use DataExtractor to parse ELF header
+ DataExtractor extractor(header_bytes.data(), header_bytes.size(),
+ eByteOrderLittle, 4);
+ lldb::offset_t offset = 0;
+
+ // Verify ELF magic number (0x7f 'E' 'L' 'F')
+ if (extractor.GetU8(&offset) != 0x7f || extractor.GetU8(&offset) != 'E' ||
+ extractor.GetU8(&offset) != 'L' || extractor.GetU8(&offset) != 'F') {
+ LLDB_LOGF(log,
+ "PlatformAndroid::%s invalid ELF magic at offset 0 for PID %llu",
+ __FUNCTION__, static_cast<unsigned long long>(pid));
+ return;
+ }
+
+ // Get ELF class (32-bit vs 64-bit) from e_ident[EI_CLASS]
+ bool is_64bit = (extractor.GetU8(&offset) == llvm::ELF::ELFCLASS64);
+
+ // e_machine is at offset 18 in both 32-bit and 64-bit ELF headers
+ offset = offsetof(llvm::ELF::Elf32_Ehdr, e_machine);
+ uint16_t e_machine = extractor.GetU16(&offset);
+
+ LLDB_LOGF(log, "PlatformAndroid::%s ELF class=%s e_machine=0x%x for PID %llu",
+ __FUNCTION__, is_64bit ? "64-bit" : "32-bit", e_machine,
+ static_cast<unsigned long long>(pid));
- // Determine architecture from exe path
ArchSpec arch;
- if (exe_output.find("64") != std::string::npos ||
- exe_output.find("arm64") != std::string::npos ||
- exe_output.find("aarch64") != std::string::npos) {
+ switch (e_machine) {
+ case llvm::ELF::EM_ARM:
+ arch.SetTriple("armv7-unknown-linux-android");
+ break;
+ case llvm::ELF::EM_AARCH64:
arch.SetTriple("aarch64-unknown-linux-android");
- } else if (exe_output.find("x86_64") != std::string::npos) {
- arch.SetTriple("x86_64-unknown-linux-android");
- } else if (exe_output.find("x86") != std::string::npos ||
- exe_output.find("i686") != std::string::npos) {
+ break;
+ case llvm::ELF::EM_386:
arch.SetTriple("i686-unknown-linux-android");
- } else {
- // Default to armv7 for 32-bit ARM (most common on Android)
- arch.SetTriple("armv7-unknown-linux-android");
+ break;
+ case llvm::ELF::EM_X86_64:
+ arch.SetTriple("x86_64-unknown-linux-android");
+ break;
+ default:
+ LLDB_LOGF(log, "PlatformAndroid::%s unknown e_machine=0x%x", __FUNCTION__,
+ e_machine);
+ arch.SetTriple("unknown-unknown-linux-android");
+ break;
}
if (arch.IsValid())
@@ -623,26 +684,22 @@ PlatformAndroid::FindProcesses(const ProcessInstanceInfoMatch &match_info,
if (IsHost())
return PlatformLinux::FindProcesses(match_info, proc_infos);
- // Remote Android platform: implement process name lookup using 'pidof' over
- // adb.
+ // Remote Android: Always use 'ps' to get process list with cmdline
+ // information (which contains Android package names). This ensures consistent
+ // naming for all queries (by name, PID, arch, user, etc.) - Android apps
+ // will show their package names instead of "app_process64".
- // LLDB stores the search name in GetExecutableFile() (even though it's
- // actually a process name like "com.android.chrome" rather than an
- // executable path). If no search name is provided, we can't use
- // 'pidof', so return early with no results.
const ProcessInstanceInfo &match_process_info = match_info.GetProcessInfo();
- if (!match_process_info.GetExecutableFile() ||
- match_info.GetNameMatchType() == NameMatch::Ignore) {
- return 0;
- }
+ std::string process_name;
+ NameMatch name_match_type = NameMatch::Ignore;
- // Extract the process name to search for (typically an Android package name
- // like "com.example.app" or binary name like "app_process64")
- std::string process_name = match_process_info.GetExecutableFile().GetPath();
- if (process_name.empty())
- return 0;
+ // If there's a name to match, extract it
+ if (match_process_info.GetExecutableFile()) {
+ process_name = match_process_info.GetExecutableFile().GetPath();
+ name_match_type = match_info.GetNameMatchType();
+ }
- // Use adb to find the process by name
+ // Use adb to get process list
Status error;
AdbClientUP adb(GetAdbClient(error));
if (error.Fail()) {
@@ -652,44 +709,39 @@ PlatformAndroid::FindProcesses(const ProcessInstanceInfoMatch &match_info,
return 0;
}
- // Use 'pidof' command to get PIDs for the process name.
- // Quote the process name to handle special characters (spaces, etc.)
- std::string pidof_output;
- StreamString command;
- command.Printf("pidof '%s'", process_name.c_str());
- error = adb->Shell(command.GetData(), seconds(5), &pidof_output);
+ std::string ps_output;
+ error = adb->Shell("ps -A -o PID,ARGS", seconds(5), &ps_output);
if (error.Fail()) {
Log *log = GetLog(LLDBLog::Platform);
- LLDB_LOG(log, "PlatformAndroid::{} 'pidof {}' failed: {}", __FUNCTION__,
- process_name.c_str(), error.AsCString());
- return 0;
- }
-
- // Parse PIDs from pidof output.
- // Note: pidof can return multiple PIDs (space-separated) if multiple
- // instances of the same executable are running.
- pidof_output = llvm::StringRef(pidof_output).trim().str();
- if (pidof_output.empty()) {
- Log *log = GetLog(LLDBLog::Platform);
- LLDB_LOGF(log, "PlatformAndroid::%s no process found with name '%s'",
- __FUNCTION__, process_name.c_str());
+ LLDB_LOG(log, "PlatformAndroid::{} 'ps -A' failed: {}", __FUNCTION__,
+ error.AsCString());
return 0;
}
- // Split the output by whitespace to handle multiple PIDs
- llvm::SmallVector<llvm::StringRef, 8> pid_strings;
- llvm::StringRef(pidof_output).split(pid_strings, ' ', -1, false);
-
Log *log = GetLog(LLDBLog::Platform);
- // Process each PID and gather information
+ llvm::SmallVector<llvm::StringRef, 256> lines;
+ llvm::StringRef(ps_output).split(lines, '\n', -1, false);
+
uint32_t num_matches = 0;
- for (llvm::StringRef pid_str : pid_strings) {
- pid_str = pid_str.trim();
- if (pid_str.empty())
+
+ for (llvm::StringRef line : lines) {
+ line = line.trim();
+ if (line.empty())
continue;
+ if (line.starts_with("PID"))
+ continue;
+
+ // Parse PID (first whitespace-separated field)
+ auto space_pos = line.find(' ');
+ if (space_pos == llvm::StringRef::npos)
+ continue;
+
+ llvm::StringRef pid_str = line.substr(0, space_pos);
+ llvm::StringRef cmdline = line.substr(space_pos + 1).trim();
+
lldb::pid_t pid;
if (!llvm::to_integer(pid_str, pid)) {
LLDB_LOGF(log, "PlatformAndroid::%s failed to parse PID from: '%s'",
@@ -697,23 +749,57 @@ PlatformAndroid::FindProcesses(const ProcessInstanceInfoMatch &match_info,
continue;
}
+ bool name_matches = true;
+ if (name_match_type != NameMatch::Ignore && !process_name.empty()) {
+ name_matches = false;
+ switch (name_match_type) {
+ case NameMatch::Equals:
+ name_matches = (cmdline == process_name);
+ break;
+ case NameMatch::StartsWith:
+ name_matches = cmdline.starts_with(process_name);
+ break;
+ case NameMatch::EndsWith:
+ name_matches = cmdline.ends_with(process_name);
+ break;
+ case NameMatch::Contains:
+ name_matches = cmdline.contains(process_name);
+ break;
+ case NameMatch::RegularExpression: {
+ llvm::Regex regex(process_name);
+ name_matches = regex.match(cmdline);
+ break;
+ }
+ default:
+ name_matches = true;
+ break;
+ }
+ }
+
+ if (!name_matches)
+ continue;
+
ProcessInstanceInfo process_info;
process_info.SetProcessID(pid);
- process_info.GetExecutableFile().SetFile(process_name,
- FileSpec::Style::posix);
- // Populate additional process information
+ // Set the executable name from cmdline (first token, typically)
+ llvm::StringRef exe_name = cmdline;
+ auto first_space = cmdline.find(' ');
+ if (first_space != llvm::StringRef::npos)
+ exe_name = cmdline.substr(0, first_space);
+
+ process_info.GetExecutableFile().SetFile(exe_name, FileSpec::Style::posix);
+
PopulateProcessStatusInfo(pid, process_info);
PopulateProcessCommandLine(pid, process_info);
PopulateProcessArchitecture(pid, process_info);
- // Check if this process matches the criteria
if (match_info.Matches(process_info)) {
proc_infos.push_back(process_info);
num_matches++;
LLDB_LOGF(log, "PlatformAndroid::%s found process '%s' with PID %llu",
- __FUNCTION__, process_name.c_str(),
+ __FUNCTION__, exe_name.str().c_str(),
static_cast<unsigned long long>(pid));
}
}
@@ -721,6 +807,71 @@ PlatformAndroid::FindProcesses(const ProcessInstanceInfoMatch &match_info,
return num_matches;
}
+bool PlatformAndroid::GetProcessInfo(lldb::pid_t pid,
+ ProcessInstanceInfo &proc_info) {
+ // On native Android or when remote, use ps to get process info with
+ // cmdline (which contains Android package names).
+ if (IsHost() || pid == LLDB_INVALID_PROCESS_ID)
+ return PlatformLinux::GetProcessInfo(pid, proc_info);
+
+ // Use ps to get process info for this specific PID
+ Status error;
+ AdbClientUP adb(GetAdbClient(error));
+ if (error.Fail())
+ return false;
+
+ StreamString cmd;
+ cmd.Printf("ps -A -o PID,ARGS -p %llu", static_cast<unsigned long long>(pid));
+
+ std::string ps_output;
+ error = adb->Shell(cmd.GetData(), seconds(5), &ps_output);
+ if (error.Fail())
+ return false;
+
+ // Parse ps output - should be 2 lines: header + process
+ llvm::SmallVector<llvm::StringRef, 2> lines;
+ llvm::StringRef(ps_output).split(lines, '\n', -1, false);
+
+ for (llvm::StringRef line : lines) {
+ line = line.trim();
+ if (line.empty() || line.starts_with("PID"))
+ continue;
+
+ // Parse PID and cmdline
+ auto space_pos = line.find(' ');
+ if (space_pos == llvm::StringRef::npos)
+ continue;
+
+ llvm::StringRef pid_str = line.substr(0, space_pos);
+ llvm::StringRef cmdline = line.substr(space_pos + 1).trim();
+
+ lldb::pid_t parsed_pid;
+ if (!llvm::to_integer(pid_str, parsed_pid) || parsed_pid != pid)
+ continue;
+
+ // Found our process, populate info
+ proc_info.Clear();
+ proc_info.SetProcessID(pid);
+
+ // Set executable name from cmdline (first token)
+ llvm::StringRef exe_name = cmdline;
+ auto first_space = cmdline.find(' ');
+ if (first_space != llvm::StringRef::npos)
+ exe_name = cmdline.substr(0, first_space);
+
+ proc_info.GetExecutableFile().SetFile(exe_name, FileSpec::Style::posix);
+
+ // Populate additional info
+ PopulateProcessStatusInfo(pid, proc_info);
+ PopulateProcessCommandLine(pid, proc_info);
+ PopulateProcessArchitecture(pid, proc_info);
+
+ return true;
+ }
+
+ return false;
+}
+
std::unique_ptr<AdbSyncService> PlatformAndroid::GetSyncService(Status &error) {
auto sync_service = std::make_unique<AdbSyncService>(m_device_id);
error = sync_service->SetupSyncConnection();
diff --git a/lldb/source/Plugins/Platform/Android/PlatformAndroid.h b/lldb/source/Plugins/Platform/Android/PlatformAndroid.h
index e771c6ae97d4d..fe28913b417b7 100644
--- a/lldb/source/Plugins/Platform/Android/PlatformAndroid.h
+++ b/lldb/source/Plugins/Platform/Android/PlatformAndroid.h
@@ -60,7 +60,9 @@ class PlatformAndroid : public platform_linux::PlatformLinux {
uint32_t GetDefaultMemoryCacheLineSize() override;
uint32_t FindProcesses(const ProcessInstanceInfoMatch &match_info,
- ProcessInstanceInfoList &proc_infos) override;
+ ProcessInstanceInfoList &process_infos) override;
+
+ bool GetProcessInfo(lldb::pid_t pid, ProcessInstanceInfo &proc_info) override;
protected:
const char *GetCacheHostname() override;
More information about the lldb-commits
mailing list