617 lines · cpp
1//===-- PlatformAndroid.cpp -----------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "lldb/Core/Module.h"10#include "lldb/Core/PluginManager.h"11#include "lldb/Core/Section.h"12#include "lldb/Host/HostInfo.h"13#include "lldb/Utility/LLDBLog.h"14#include "lldb/Utility/Log.h"15#include "lldb/Utility/UriParser.h"16#include "lldb/ValueObject/ValueObject.h"17 18#include "llvm/ADT/DenseMap.h"19 20#include "AdbClient.h"21#include "PlatformAndroid.h"22#include "PlatformAndroidRemoteGDBServer.h"23#include "lldb/Target/Target.h"24#include <optional>25 26using namespace lldb;27using namespace lldb_private;28using namespace lldb_private::platform_android;29using namespace std::chrono;30 31LLDB_PLUGIN_DEFINE(PlatformAndroid)32 33namespace {34 35#define LLDB_PROPERTIES_android36#include "PlatformAndroidProperties.inc"37 38enum {39#define LLDB_PROPERTIES_android40#include "PlatformAndroidPropertiesEnum.inc"41};42 43class PluginProperties : public Properties {44public:45 PluginProperties() {46 m_collection_sp = std::make_shared<OptionValueProperties>(47 PlatformAndroid::GetPluginNameStatic(false));48 m_collection_sp->Initialize(g_android_properties);49 }50};51 52static PluginProperties &GetGlobalProperties() {53 static PluginProperties g_settings;54 return g_settings;55}56 57uint32_t g_initialize_count = 0;58const unsigned int g_android_default_cache_size =59 2048; // Fits inside 4k adb packet.60 61} // end of anonymous namespace62 63void PlatformAndroid::Initialize() {64 PlatformLinux::Initialize();65 66 if (g_initialize_count++ == 0) {67#if defined(__ANDROID__)68 PlatformSP default_platform_sp(new PlatformAndroid(true));69 default_platform_sp->SetSystemArchitecture(HostInfo::GetArchitecture());70 Platform::SetHostPlatform(default_platform_sp);71#endif72 PluginManager::RegisterPlugin(73 PlatformAndroid::GetPluginNameStatic(false),74 PlatformAndroid::GetPluginDescriptionStatic(false),75 PlatformAndroid::CreateInstance, PlatformAndroid::DebuggerInitialize);76 }77}78 79void PlatformAndroid::Terminate() {80 if (g_initialize_count > 0) {81 if (--g_initialize_count == 0) {82 PluginManager::UnregisterPlugin(PlatformAndroid::CreateInstance);83 }84 }85 86 PlatformLinux::Terminate();87}88 89PlatformSP PlatformAndroid::CreateInstance(bool force, const ArchSpec *arch) {90 Log *log = GetLog(LLDBLog::Platform);91 if (log) {92 const char *arch_name;93 if (arch && arch->GetArchitectureName())94 arch_name = arch->GetArchitectureName();95 else96 arch_name = "<null>";97 98 const char *triple_cstr =99 arch ? arch->GetTriple().getTriple().c_str() : "<null>";100 101 LLDB_LOGF(log, "PlatformAndroid::%s(force=%s, arch={%s,%s})", __FUNCTION__,102 force ? "true" : "false", arch_name, triple_cstr);103 }104 105 bool create = force;106 if (!create && arch && arch->IsValid()) {107 const llvm::Triple &triple = arch->GetTriple();108 switch (triple.getVendor()) {109 case llvm::Triple::PC:110 create = true;111 break;112 113#if defined(__ANDROID__)114 // Only accept "unknown" for the vendor if the host is android and if115 // "unknown" wasn't specified (it was just returned because it was NOT116 // specified).117 case llvm::Triple::VendorType::UnknownVendor:118 create = !arch->TripleVendorWasSpecified();119 break;120#endif121 default:122 break;123 }124 125 if (create) {126 switch (triple.getEnvironment()) {127 case llvm::Triple::Android:128 break;129 130#if defined(__ANDROID__)131 // Only accept "unknown" for the OS if the host is android and it132 // "unknown" wasn't specified (it was just returned because it was NOT133 // specified)134 case llvm::Triple::EnvironmentType::UnknownEnvironment:135 create = !arch->TripleEnvironmentWasSpecified();136 break;137#endif138 default:139 create = false;140 break;141 }142 }143 }144 145 if (create) {146 LLDB_LOGF(log, "PlatformAndroid::%s() creating remote-android platform",147 __FUNCTION__);148 return PlatformSP(new PlatformAndroid(false));149 }150 151 LLDB_LOGF(152 log, "PlatformAndroid::%s() aborting creation of remote-android platform",153 __FUNCTION__);154 155 return PlatformSP();156}157 158void PlatformAndroid::DebuggerInitialize(Debugger &debugger) {159 if (!PluginManager::GetSettingForPlatformPlugin(debugger,160 GetPluginNameStatic(false))) {161 PluginManager::CreateSettingForPlatformPlugin(162 debugger, GetGlobalProperties().GetValueProperties(),163 "Properties for the Android platform plugin.",164 /*is_global_property=*/true);165 }166}167 168PlatformAndroid::PlatformAndroid(bool is_host)169 : PlatformLinux(is_host), m_sdk_version(0) {}170 171llvm::StringRef PlatformAndroid::GetPluginDescriptionStatic(bool is_host) {172 if (is_host)173 return "Local Android user platform plug-in.";174 return "Remote Android user platform plug-in.";175}176 177Status PlatformAndroid::ConnectRemote(Args &args) {178 m_device_id.clear();179 180 if (IsHost())181 return Status::FromErrorString(182 "can't connect to the host platform, always connected");183 184 if (!m_remote_platform_sp)185 m_remote_platform_sp = PlatformSP(new PlatformAndroidRemoteGDBServer());186 187 const char *url = args.GetArgumentAtIndex(0);188 if (!url)189 return Status::FromErrorString("URL is null.");190 std::optional<URI> parsed_url = URI::Parse(url);191 if (!parsed_url)192 return Status::FromErrorStringWithFormat("Invalid URL: %s", url);193 if (parsed_url->hostname != "localhost")194 m_device_id = parsed_url->hostname.str();195 196 auto error = PlatformLinux::ConnectRemote(args);197 if (error.Success()) {198 auto resolved_device_id_or_error = AdbClient::ResolveDeviceID(m_device_id);199 if (!resolved_device_id_or_error)200 return Status::FromError(resolved_device_id_or_error.takeError());201 m_device_id = *resolved_device_id_or_error;202 }203 return error;204}205 206Status PlatformAndroid::GetFile(const FileSpec &source,207 const FileSpec &destination) {208 if (IsHost() || !m_remote_platform_sp)209 return PlatformLinux::GetFile(source, destination);210 211 FileSpec source_spec(source.GetPath(false), FileSpec::Style::posix);212 if (source_spec.IsRelative())213 source_spec = GetRemoteWorkingDirectory().CopyByAppendingPathComponent(214 source_spec.GetPathAsConstString(false).GetStringRef());215 216 Status error;217 auto sync_service = GetSyncService(error);218 219 // If sync service is available, try to use it220 if (error.Success() && sync_service) {221 uint32_t mode = 0, size = 0, mtime = 0;222 error = sync_service->Stat(source_spec, mode, size, mtime);223 if (error.Success()) {224 if (mode != 0)225 return sync_service->PullFile(source_spec, destination);226 227 // mode == 0 can signify that adbd cannot access the file due security228 // constraints - fall through to try "cat ..." as a fallback.229 Log *log = GetLog(LLDBLog::Platform);230 LLDB_LOGF(log, "Got mode == 0 on '%s': try to get file via 'shell cat'",231 source_spec.GetPath(false).c_str());232 }233 }234 235 // Fallback to shell cat command if sync service failed or returned mode == 0236 std::string source_file = source_spec.GetPath(false);237 238 Log *log = GetLog(LLDBLog::Platform);239 LLDB_LOGF(log, "Using shell cat fallback for '%s'", source_file.c_str());240 241 if (strchr(source_file.c_str(), '\'') != nullptr)242 return Status::FromErrorString(243 "Doesn't support single-quotes in filenames");244 245 AdbClientUP adb(GetAdbClient(error));246 if (error.Fail())247 return error;248 249 char cmd[PATH_MAX];250 snprintf(cmd, sizeof(cmd), "%scat '%s'", GetRunAs().c_str(),251 source_file.c_str());252 253 return adb->ShellToFile(cmd, minutes(1), destination);254}255 256Status PlatformAndroid::PutFile(const FileSpec &source,257 const FileSpec &destination, uint32_t uid,258 uint32_t gid) {259 if (IsHost() || !m_remote_platform_sp)260 return PlatformLinux::PutFile(source, destination, uid, gid);261 262 FileSpec destination_spec(destination.GetPath(false), FileSpec::Style::posix);263 if (destination_spec.IsRelative())264 destination_spec = GetRemoteWorkingDirectory().CopyByAppendingPathComponent(265 destination_spec.GetPath(false));266 267 // TODO: Set correct uid and gid on remote file.268 Status error;269 auto sync_service = GetSyncService(error);270 if (error.Fail())271 return error;272 return sync_service->PushFile(source, destination_spec);273}274 275const char *PlatformAndroid::GetCacheHostname() { return m_device_id.c_str(); }276 277Status PlatformAndroid::DownloadModuleSlice(const FileSpec &src_file_spec,278 const uint64_t src_offset,279 const uint64_t src_size,280 const FileSpec &dst_file_spec) {281 std::string source_file = src_file_spec.GetPath(false);282 if (source_file.empty())283 return Status::FromErrorString("Source file path cannot be empty");284 285 std::string destination_file = dst_file_spec.GetPath(false);286 if (destination_file.empty())287 return Status::FromErrorString("Destination file path cannot be empty");288 289 // In Android API level 23 and above, dynamic loader is able to load .so290 // file directly from APK. In that case, src_offset will be an non-zero.291 if (src_offset == 0) // Use GetFile for a normal file.292 return GetFile(src_file_spec, dst_file_spec);293 294 if (source_file.find('\'') != std::string::npos)295 return Status::FromErrorString(296 "Doesn't support single-quotes in filenames");297 298 // For zip .so file, src_file_spec will be "zip_path!/so_path".299 // Extract "zip_path" from the source_file.300 static constexpr llvm::StringLiteral k_zip_separator("!/");301 size_t pos = source_file.find(k_zip_separator);302 if (pos != std::string::npos)303 source_file.resize(pos);304 305 Status error;306 AdbClientUP adb(GetAdbClient(error));307 if (error.Fail())308 return error;309 310 // Use 'shell dd' to download the file slice with the offset and size.311 char cmd[PATH_MAX];312 snprintf(cmd, sizeof(cmd),313 "%sdd if='%s' iflag=skip_bytes,count_bytes "314 "skip=%" PRIu64 " count=%" PRIu64 " status=none",315 GetRunAs().c_str(), source_file.c_str(), src_offset, src_size);316 317 return adb->ShellToFile(cmd, minutes(1), dst_file_spec);318}319 320Status PlatformAndroid::DisconnectRemote() {321 Status error = PlatformLinux::DisconnectRemote();322 if (error.Success()) {323 m_device_id.clear();324 m_sdk_version = 0;325 }326 return error;327}328 329uint32_t PlatformAndroid::GetDefaultMemoryCacheLineSize() {330 return g_android_default_cache_size;331}332 333uint32_t PlatformAndroid::GetSdkVersion() {334 if (!IsConnected())335 return 0;336 337 if (m_sdk_version != 0)338 return m_sdk_version;339 340 std::string version_string;341 Status error;342 AdbClientUP adb(GetAdbClient(error));343 if (error.Fail())344 return 0;345 error =346 adb->Shell("getprop ro.build.version.sdk", seconds(5), &version_string);347 version_string = llvm::StringRef(version_string).trim().str();348 349 if (error.Fail() || version_string.empty()) {350 Log *log = GetLog(LLDBLog::Platform);351 LLDB_LOGF(log, "Get SDK version failed. (error: %s, output: %s)",352 error.AsCString(), version_string.c_str());353 return 0;354 }355 356 // FIXME: improve error handling357 llvm::to_integer(version_string, m_sdk_version);358 return m_sdk_version;359}360 361Status PlatformAndroid::DownloadSymbolFile(const lldb::ModuleSP &module_sp,362 const FileSpec &dst_file_spec) {363 // For oat file we can try to fetch additional debug info from the device364 llvm::StringRef extension = module_sp->GetFileSpec().GetFileNameExtension();365 if (extension != ".oat" && extension != ".odex")366 return Status::FromErrorString(367 "Symbol file downloading only supported for oat and odex files");368 369 // If we have no information about the platform file we can't execute oatdump370 if (!module_sp->GetPlatformFileSpec())371 return Status::FromErrorString("No platform file specified");372 373 // Symbolizer isn't available before SDK version 23374 if (GetSdkVersion() < 23)375 return Status::FromErrorString(376 "Symbol file generation only supported on SDK 23+");377 378 // If we already have symtab then we don't have to try and generate one379 if (module_sp->GetSectionList()->FindSectionByName(ConstString(".symtab")) !=380 nullptr)381 return Status::FromErrorString("Symtab already available in the module");382 383 Status error;384 AdbClientUP adb(GetAdbClient(error));385 if (error.Fail())386 return error;387 std::string tmpdir;388 error = adb->Shell("mktemp --directory --tmpdir /data/local/tmp", seconds(5),389 &tmpdir);390 if (error.Fail() || tmpdir.empty())391 return Status::FromErrorStringWithFormat(392 "Failed to generate temporary directory on the device (%s)",393 error.AsCString());394 tmpdir = llvm::StringRef(tmpdir).trim().str();395 396 // Create file remover for the temporary directory created on the device397 std::unique_ptr<std::string, std::function<void(std::string *)>>398 tmpdir_remover(&tmpdir, [&adb](std::string *s) {399 StreamString command;400 command.Printf("rm -rf %s", s->c_str());401 Status error = adb->Shell(command.GetData(), seconds(5), nullptr);402 403 Log *log = GetLog(LLDBLog::Platform);404 if (log && error.Fail())405 LLDB_LOGF(log, "Failed to remove temp directory: %s",406 error.AsCString());407 });408 409 FileSpec symfile_platform_filespec(tmpdir);410 symfile_platform_filespec.AppendPathComponent("symbolized.oat");411 412 // Execute oatdump on the remote device to generate a file with symtab413 StreamString command;414 command.Printf("oatdump --symbolize=%s --output=%s",415 module_sp->GetPlatformFileSpec().GetPath(false).c_str(),416 symfile_platform_filespec.GetPath(false).c_str());417 error = adb->Shell(command.GetData(), minutes(1), nullptr);418 if (error.Fail())419 return Status::FromErrorStringWithFormat("Oatdump failed: %s",420 error.AsCString());421 422 // Download the symbolfile from the remote device423 return GetFile(symfile_platform_filespec, dst_file_spec);424}425 426bool PlatformAndroid::GetRemoteOSVersion() {427 m_os_version = llvm::VersionTuple(GetSdkVersion());428 return !m_os_version.empty();429}430 431llvm::StringRef432PlatformAndroid::GetLibdlFunctionDeclarations(lldb_private::Process *process) {433 SymbolContextList matching_symbols;434 std::vector<const char *> dl_open_names = {"__dl_dlopen", "dlopen"};435 const char *dl_open_name = nullptr;436 Target &target = process->GetTarget();437 for (auto *name : dl_open_names) {438 target.GetImages().FindFunctionSymbols(439 ConstString(name), eFunctionNameTypeFull, matching_symbols);440 if (matching_symbols.GetSize()) {441 dl_open_name = name;442 break;443 }444 }445 // Older platform versions have the dl function symbols mangled446 if (dl_open_name == dl_open_names[0])447 return R"(448 extern "C" void* dlopen(const char*, int) asm("__dl_dlopen");449 extern "C" void* dlsym(void*, const char*) asm("__dl_dlsym");450 extern "C" int dlclose(void*) asm("__dl_dlclose");451 extern "C" char* dlerror(void) asm("__dl_dlerror");452 )";453 454 return PlatformPOSIX::GetLibdlFunctionDeclarations(process);455}456 457PlatformAndroid::AdbClientUP PlatformAndroid::GetAdbClient(Status &error) {458 AdbClientUP adb = std::make_unique<AdbClient>(m_device_id);459 error = adb->Connect();460 return adb;461}462 463llvm::StringRef PlatformAndroid::GetPropertyPackageName() {464 return GetGlobalProperties().GetPropertyAtIndexAs<llvm::StringRef>(465 ePropertyPlatformPackageName, "");466}467 468std::string PlatformAndroid::GetRunAs() {469 llvm::StringRef run_as = GetPropertyPackageName();470 if (!run_as.empty()) {471 // When LLDB fails to pull file from a package directory due to security472 // constraint, user needs to set the package name to473 // 'platform.plugin.remote-android.package-name' property in order to run474 // shell commands as the package user using 'run-as' (e.g. to get file with475 // 'cat' and 'dd').476 // https://cs.android.com/android/platform/superproject/+/master:477 // system/core/run-as/run-as.cpp;l=39-61;478 // drc=4a77a84a55522a3b122f9c63ef0d0b8a6a131627479 return std::string("run-as '") + run_as.str() + "' ";480 }481 return run_as.str();482}483 484static bool NeedsCmdlineSupplement(const ProcessInstanceInfo &proc_info) {485 llvm::StringRef name =486 proc_info.GetExecutableFile().GetFilename().GetStringRef();487 return name.contains("app_process") || name.contains("zygote");488}489 490// Fetch /proc/PID/cmdline for processes to get actual package names.491// Android apps often show as "zygote" or "app_process" without this.492static void SupplementWithCmdlineInfo(ProcessInstanceInfoList &proc_infos,493 AdbClient *adb) {494 if (proc_infos.empty())495 return;496 497 llvm::DenseMap<lldb::pid_t, ProcessInstanceInfo *> pid_map;498 std::string pid_list;499 for (auto &proc_info : proc_infos) {500 if (NeedsCmdlineSupplement(proc_info)) {501 lldb::pid_t pid = proc_info.GetProcessID();502 pid_map[pid] = &proc_info;503 if (!pid_list.empty())504 pid_list += " ";505 pid_list += std::to_string(pid);506 }507 }508 509 if (pid_list.empty())510 return;511 512 Log *log = GetLog(LLDBLog::Platform);513 514 // Use xargs -P to parallelize cmdline fetching (up to 8 concurrent reads)515 StreamString cmd;516 cmd.Printf(517 "echo '%s' | xargs -n 1 -P 8 sh -c "518 "'echo \"$1:$(cat /proc/$1/cmdline 2>/dev/null | tr \"\\0\" \" \")\"' sh",519 pid_list.c_str());520 521 std::string cmdline_output;522 Status error = adb->Shell(cmd.GetData(), seconds(5), &cmdline_output);523 524 if (error.Fail() || cmdline_output.empty())525 return;526 527 llvm::SmallVector<llvm::StringRef, 256> lines;528 llvm::StringRef(cmdline_output).split(lines, '\n', -1, false);529 530 for (llvm::StringRef line : lines) {531 line = line.trim();532 auto [pid_str, cmdline] = line.split(':');533 if (pid_str.empty() || cmdline.empty())534 continue;535 536 cmdline = cmdline.trim();537 538 lldb::pid_t pid;539 if (!llvm::to_integer(pid_str, pid) || cmdline.empty())540 continue;541 542 auto it = pid_map.find(pid);543 if (it == pid_map.end())544 continue;545 546 ProcessInstanceInfo *proc_info = it->second;547 llvm::SmallVector<llvm::StringRef, 16> args;548 cmdline.split(args, ' ', -1, false);549 550 if (!args.empty()) {551 proc_info->GetExecutableFile().SetFile(args[0], FileSpec::Style::posix);552 553 if (args.size() > 1) {554 Args process_args;555 for (size_t i = 1; i < args.size(); ++i) {556 if (!args[i].empty())557 process_args.AppendArgument(args[i]);558 }559 proc_info->SetArguments(process_args, false);560 }561 562 LLDB_LOGF(log,563 "PlatformAndroid::%s supplemented PID %llu with cmdline: %s",564 __FUNCTION__, static_cast<unsigned long long>(pid),565 cmdline.str().c_str());566 }567 }568}569 570uint32_t571PlatformAndroid::FindProcesses(const ProcessInstanceInfoMatch &match_info,572 ProcessInstanceInfoList &proc_infos) {573 proc_infos.clear();574 575 if (IsHost())576 return PlatformLinux::FindProcesses(match_info, proc_infos);577 578 if (!m_remote_platform_sp)579 return 0;580 581 // Android-specific process name handling:582 // Apps spawned from zygote initially appear as "app_process" or "zygote"583 // in the process list, but their actual package names (e.g.,584 // "com.example.app") are only available in /proc/PID/cmdline. To support585 // name-based matching, we must first fetch cmdline info for all processes,586 // then apply the original name filter.587 ProcessInstanceInfoMatch broad_match_info = match_info;588 broad_match_info.SetNameMatchType(NameMatch::Ignore);589 590 ProcessInstanceInfoList all_procs;591 uint32_t count =592 m_remote_platform_sp->FindProcesses(broad_match_info, all_procs);593 594 if (count > 0) {595 Status error;596 AdbClientUP adb(GetAdbClient(error));597 if (error.Success())598 SupplementWithCmdlineInfo(all_procs, adb.get());599 600 // Apply the original name matching against supplemented process info.601 for (auto &proc_info : all_procs) {602 if (match_info.Matches(proc_info))603 proc_infos.push_back(proc_info);604 }605 }606 607 return proc_infos.size();608}609 610std::unique_ptr<AdbSyncService> PlatformAndroid::GetSyncService(Status &error) {611 auto sync_service = std::make_unique<AdbSyncService>(m_device_id);612 error = sync_service->SetupSyncConnection();613 if (error.Fail())614 return nullptr;615 return sync_service;616}617