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 <[email protected]> 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; _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
