| 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-exception |
| 6 | // |
| 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/Scalar.h" |
| 16 | #include "lldb/Utility/UriParser.h" |
| 17 | #include "lldb/ValueObject/ValueObject.h" |
| 18 | |
| 19 | #include "AdbClient.h" |
| 20 | #include "PlatformAndroid.h" |
| 21 | #include "PlatformAndroidRemoteGDBServer.h" |
| 22 | #include "lldb/Target/Target.h" |
| 23 | #include <optional> |
| 24 | |
| 25 | using namespace lldb; |
| 26 | using namespace lldb_private; |
| 27 | using namespace lldb_private::platform_android; |
| 28 | using namespace std::chrono; |
| 29 | |
| 30 | LLDB_PLUGIN_DEFINE(PlatformAndroid) |
| 31 | |
| 32 | namespace { |
| 33 | |
| 34 | #define LLDB_PROPERTIES_android |
| 35 | #include "PlatformAndroidProperties.inc" |
| 36 | |
| 37 | enum { |
| 38 | #define LLDB_PROPERTIES_android |
| 39 | #include "PlatformAndroidPropertiesEnum.inc" |
| 40 | }; |
| 41 | |
| 42 | class PluginProperties : public Properties { |
| 43 | public: |
| 44 | PluginProperties() { |
| 45 | m_collection_sp = std::make_shared<OptionValueProperties>( |
| 46 | PlatformAndroid::GetPluginNameStatic(is_host: false)); |
| 47 | m_collection_sp->Initialize(g_android_properties); |
| 48 | } |
| 49 | }; |
| 50 | |
| 51 | static PluginProperties &GetGlobalProperties() { |
| 52 | static PluginProperties g_settings; |
| 53 | return g_settings; |
| 54 | } |
| 55 | |
| 56 | uint32_t g_initialize_count = 0; |
| 57 | const unsigned int g_android_default_cache_size = |
| 58 | 2048; // Fits inside 4k adb packet. |
| 59 | |
| 60 | } // end of anonymous namespace |
| 61 | |
| 62 | void PlatformAndroid::Initialize() { |
| 63 | PlatformLinux::Initialize(); |
| 64 | |
| 65 | if (g_initialize_count++ == 0) { |
| 66 | #if defined(__ANDROID__) |
| 67 | PlatformSP default_platform_sp(new PlatformAndroid(true)); |
| 68 | default_platform_sp->SetSystemArchitecture(HostInfo::GetArchitecture()); |
| 69 | Platform::SetHostPlatform(default_platform_sp); |
| 70 | #endif |
| 71 | PluginManager::RegisterPlugin( |
| 72 | name: PlatformAndroid::GetPluginNameStatic(is_host: false), |
| 73 | description: PlatformAndroid::GetPluginDescriptionStatic(is_host: false), |
| 74 | create_callback: PlatformAndroid::CreateInstance, debugger_init_callback: PlatformAndroid::DebuggerInitialize); |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | void PlatformAndroid::Terminate() { |
| 79 | if (g_initialize_count > 0) { |
| 80 | if (--g_initialize_count == 0) { |
| 81 | PluginManager::UnregisterPlugin(create_callback: PlatformAndroid::CreateInstance); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | PlatformLinux::Terminate(); |
| 86 | } |
| 87 | |
| 88 | PlatformSP PlatformAndroid::CreateInstance(bool force, const ArchSpec *arch) { |
| 89 | Log *log = GetLog(mask: LLDBLog::Platform); |
| 90 | if (log) { |
| 91 | const char *arch_name; |
| 92 | if (arch && arch->GetArchitectureName()) |
| 93 | arch_name = arch->GetArchitectureName(); |
| 94 | else |
| 95 | arch_name = "<null>" ; |
| 96 | |
| 97 | const char *triple_cstr = |
| 98 | arch ? arch->GetTriple().getTriple().c_str() : "<null>" ; |
| 99 | |
| 100 | LLDB_LOGF(log, "PlatformAndroid::%s(force=%s, arch={%s,%s})" , __FUNCTION__, |
| 101 | force ? "true" : "false" , arch_name, triple_cstr); |
| 102 | } |
| 103 | |
| 104 | bool create = force; |
| 105 | if (!create && arch && arch->IsValid()) { |
| 106 | const llvm::Triple &triple = arch->GetTriple(); |
| 107 | switch (triple.getVendor()) { |
| 108 | case llvm::Triple::PC: |
| 109 | create = true; |
| 110 | break; |
| 111 | |
| 112 | #if defined(__ANDROID__) |
| 113 | // Only accept "unknown" for the vendor if the host is android and if |
| 114 | // "unknown" wasn't specified (it was just returned because it was NOT |
| 115 | // specified). |
| 116 | case llvm::Triple::VendorType::UnknownVendor: |
| 117 | create = !arch->TripleVendorWasSpecified(); |
| 118 | break; |
| 119 | #endif |
| 120 | default: |
| 121 | break; |
| 122 | } |
| 123 | |
| 124 | if (create) { |
| 125 | switch (triple.getEnvironment()) { |
| 126 | case llvm::Triple::Android: |
| 127 | break; |
| 128 | |
| 129 | #if defined(__ANDROID__) |
| 130 | // Only accept "unknown" for the OS if the host is android and it |
| 131 | // "unknown" wasn't specified (it was just returned because it was NOT |
| 132 | // specified) |
| 133 | case llvm::Triple::EnvironmentType::UnknownEnvironment: |
| 134 | create = !arch->TripleEnvironmentWasSpecified(); |
| 135 | break; |
| 136 | #endif |
| 137 | default: |
| 138 | create = false; |
| 139 | break; |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | if (create) { |
| 145 | LLDB_LOGF(log, "PlatformAndroid::%s() creating remote-android platform" , |
| 146 | __FUNCTION__); |
| 147 | return PlatformSP(new PlatformAndroid(false)); |
| 148 | } |
| 149 | |
| 150 | LLDB_LOGF( |
| 151 | log, "PlatformAndroid::%s() aborting creation of remote-android platform" , |
| 152 | __FUNCTION__); |
| 153 | |
| 154 | return PlatformSP(); |
| 155 | } |
| 156 | |
| 157 | void PlatformAndroid::DebuggerInitialize(Debugger &debugger) { |
| 158 | if (!PluginManager::GetSettingForPlatformPlugin(debugger, |
| 159 | setting_name: GetPluginNameStatic(is_host: false))) { |
| 160 | PluginManager::CreateSettingForPlatformPlugin( |
| 161 | debugger, properties_sp: GetGlobalProperties().GetValueProperties(), |
| 162 | description: "Properties for the Android platform plugin." , |
| 163 | /*is_global_property=*/true); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | PlatformAndroid::PlatformAndroid(bool is_host) |
| 168 | : PlatformLinux(is_host), m_sdk_version(0) {} |
| 169 | |
| 170 | llvm::StringRef PlatformAndroid::GetPluginDescriptionStatic(bool is_host) { |
| 171 | if (is_host) |
| 172 | return "Local Android user platform plug-in." ; |
| 173 | return "Remote Android user platform plug-in." ; |
| 174 | } |
| 175 | |
| 176 | Status PlatformAndroid::ConnectRemote(Args &args) { |
| 177 | m_device_id.clear(); |
| 178 | |
| 179 | if (IsHost()) |
| 180 | return Status::FromErrorString( |
| 181 | str: "can't connect to the host platform, always connected" ); |
| 182 | |
| 183 | if (!m_remote_platform_sp) |
| 184 | m_remote_platform_sp = PlatformSP(new PlatformAndroidRemoteGDBServer()); |
| 185 | |
| 186 | const char *url = args.GetArgumentAtIndex(idx: 0); |
| 187 | if (!url) |
| 188 | return Status::FromErrorString(str: "URL is null." ); |
| 189 | std::optional<URI> parsed_url = URI::Parse(uri: url); |
| 190 | if (!parsed_url) |
| 191 | return Status::FromErrorStringWithFormat(format: "Invalid URL: %s" , url); |
| 192 | if (parsed_url->hostname != "localhost" ) |
| 193 | m_device_id = parsed_url->hostname.str(); |
| 194 | |
| 195 | auto error = PlatformLinux::ConnectRemote(args); |
| 196 | if (error.Success()) { |
| 197 | AdbClient adb; |
| 198 | error = AdbClient::CreateByDeviceID(device_id: m_device_id, adb); |
| 199 | if (error.Fail()) |
| 200 | return error; |
| 201 | |
| 202 | m_device_id = adb.GetDeviceID(); |
| 203 | } |
| 204 | return error; |
| 205 | } |
| 206 | |
| 207 | Status PlatformAndroid::GetFile(const FileSpec &source, |
| 208 | const FileSpec &destination) { |
| 209 | if (IsHost() || !m_remote_platform_sp) |
| 210 | return PlatformLinux::GetFile(source, destination); |
| 211 | |
| 212 | FileSpec source_spec(source.GetPath(denormalize: false), FileSpec::Style::posix); |
| 213 | if (source_spec.IsRelative()) |
| 214 | source_spec = GetRemoteWorkingDirectory().CopyByAppendingPathComponent( |
| 215 | component: source_spec.GetPathAsConstString(denormalize: false).GetStringRef()); |
| 216 | |
| 217 | Status error; |
| 218 | auto sync_service = GetSyncService(error); |
| 219 | if (error.Fail()) |
| 220 | return error; |
| 221 | |
| 222 | uint32_t mode = 0, size = 0, mtime = 0; |
| 223 | error = sync_service->Stat(remote_file: source_spec, mode, size, mtime); |
| 224 | if (error.Fail()) |
| 225 | return error; |
| 226 | |
| 227 | if (mode != 0) |
| 228 | return sync_service->PullFile(remote_file: source_spec, local_file: destination); |
| 229 | |
| 230 | std::string source_file = source_spec.GetPath(denormalize: false); |
| 231 | |
| 232 | Log *log = GetLog(mask: LLDBLog::Platform); |
| 233 | LLDB_LOGF(log, "Got mode == 0 on '%s': try to get file via 'shell cat'" , |
| 234 | source_file.c_str()); |
| 235 | |
| 236 | if (strchr(s: source_file.c_str(), c: '\'') != nullptr) |
| 237 | return Status::FromErrorString( |
| 238 | str: "Doesn't support single-quotes in filenames" ); |
| 239 | |
| 240 | // mode == 0 can signify that adbd cannot access the file due security |
| 241 | // constraints - try "cat ..." as a fallback. |
| 242 | AdbClientUP adb(GetAdbClient(error)); |
| 243 | if (error.Fail()) |
| 244 | return error; |
| 245 | |
| 246 | char cmd[PATH_MAX]; |
| 247 | snprintf(s: cmd, maxlen: sizeof(cmd), format: "%scat '%s'" , GetRunAs().c_str(), |
| 248 | source_file.c_str()); |
| 249 | |
| 250 | return adb->ShellToFile(command: cmd, timeout: minutes(1), output_file_spec: destination); |
| 251 | } |
| 252 | |
| 253 | Status PlatformAndroid::PutFile(const FileSpec &source, |
| 254 | const FileSpec &destination, uint32_t uid, |
| 255 | uint32_t gid) { |
| 256 | if (IsHost() || !m_remote_platform_sp) |
| 257 | return PlatformLinux::PutFile(source, destination, uid, gid); |
| 258 | |
| 259 | FileSpec destination_spec(destination.GetPath(denormalize: false), FileSpec::Style::posix); |
| 260 | if (destination_spec.IsRelative()) |
| 261 | destination_spec = GetRemoteWorkingDirectory().CopyByAppendingPathComponent( |
| 262 | component: destination_spec.GetPath(denormalize: false)); |
| 263 | |
| 264 | // TODO: Set correct uid and gid on remote file. |
| 265 | Status error; |
| 266 | auto sync_service = GetSyncService(error); |
| 267 | if (error.Fail()) |
| 268 | return error; |
| 269 | return sync_service->PushFile(local_file: source, remote_file: destination_spec); |
| 270 | } |
| 271 | |
| 272 | const char *PlatformAndroid::GetCacheHostname() { return m_device_id.c_str(); } |
| 273 | |
| 274 | Status PlatformAndroid::DownloadModuleSlice(const FileSpec &src_file_spec, |
| 275 | const uint64_t src_offset, |
| 276 | const uint64_t src_size, |
| 277 | const FileSpec &dst_file_spec) { |
| 278 | // In Android API level 23 and above, dynamic loader is able to load .so |
| 279 | // file directly from APK. In that case, src_offset will be an non-zero. |
| 280 | if (src_offset == 0) // Use GetFile for a normal file. |
| 281 | return GetFile(source: src_file_spec, destination: dst_file_spec); |
| 282 | |
| 283 | std::string source_file = src_file_spec.GetPath(denormalize: false); |
| 284 | if (source_file.find(c: '\'') != std::string::npos) |
| 285 | return Status::FromErrorString( |
| 286 | str: "Doesn't support single-quotes in filenames" ); |
| 287 | |
| 288 | // For zip .so file, src_file_spec will be "zip_path!/so_path". |
| 289 | // Extract "zip_path" from the source_file. |
| 290 | static constexpr llvm::StringLiteral k_zip_separator("!/" ); |
| 291 | size_t pos = source_file.find(svt: k_zip_separator); |
| 292 | if (pos != std::string::npos) |
| 293 | source_file.resize(n: pos); |
| 294 | |
| 295 | Status error; |
| 296 | AdbClientUP adb(GetAdbClient(error)); |
| 297 | if (error.Fail()) |
| 298 | return error; |
| 299 | |
| 300 | // Use 'shell dd' to download the file slice with the offset and size. |
| 301 | char cmd[PATH_MAX]; |
| 302 | snprintf(s: cmd, maxlen: sizeof(cmd), |
| 303 | format: "%sdd if='%s' iflag=skip_bytes,count_bytes " |
| 304 | "skip=%" PRIu64 " count=%" PRIu64 " status=none" , |
| 305 | GetRunAs().c_str(), source_file.c_str(), src_offset, src_size); |
| 306 | |
| 307 | return adb->ShellToFile(command: cmd, timeout: minutes(1), output_file_spec: dst_file_spec); |
| 308 | } |
| 309 | |
| 310 | Status PlatformAndroid::DisconnectRemote() { |
| 311 | Status error = PlatformLinux::DisconnectRemote(); |
| 312 | if (error.Success()) { |
| 313 | m_device_id.clear(); |
| 314 | m_sdk_version = 0; |
| 315 | } |
| 316 | return error; |
| 317 | } |
| 318 | |
| 319 | uint32_t PlatformAndroid::GetDefaultMemoryCacheLineSize() { |
| 320 | return g_android_default_cache_size; |
| 321 | } |
| 322 | |
| 323 | uint32_t PlatformAndroid::GetSdkVersion() { |
| 324 | if (!IsConnected()) |
| 325 | return 0; |
| 326 | |
| 327 | if (m_sdk_version != 0) |
| 328 | return m_sdk_version; |
| 329 | |
| 330 | std::string version_string; |
| 331 | Status error; |
| 332 | AdbClientUP adb(GetAdbClient(error)); |
| 333 | if (error.Fail()) |
| 334 | return 0; |
| 335 | error = |
| 336 | adb->Shell(command: "getprop ro.build.version.sdk" , timeout: seconds(5), output: &version_string); |
| 337 | version_string = llvm::StringRef(version_string).trim().str(); |
| 338 | |
| 339 | if (error.Fail() || version_string.empty()) { |
| 340 | Log *log = GetLog(mask: LLDBLog::Platform); |
| 341 | LLDB_LOGF(log, "Get SDK version failed. (error: %s, output: %s)" , |
| 342 | error.AsCString(), version_string.c_str()); |
| 343 | return 0; |
| 344 | } |
| 345 | |
| 346 | // FIXME: improve error handling |
| 347 | llvm::to_integer(S: version_string, Num&: m_sdk_version); |
| 348 | return m_sdk_version; |
| 349 | } |
| 350 | |
| 351 | Status PlatformAndroid::DownloadSymbolFile(const lldb::ModuleSP &module_sp, |
| 352 | const FileSpec &dst_file_spec) { |
| 353 | // For oat file we can try to fetch additional debug info from the device |
| 354 | llvm::StringRef extension = module_sp->GetFileSpec().GetFileNameExtension(); |
| 355 | if (extension != ".oat" && extension != ".odex" ) |
| 356 | return Status::FromErrorString( |
| 357 | str: "Symbol file downloading only supported for oat and odex files" ); |
| 358 | |
| 359 | // If we have no information about the platform file we can't execute oatdump |
| 360 | if (!module_sp->GetPlatformFileSpec()) |
| 361 | return Status::FromErrorString(str: "No platform file specified" ); |
| 362 | |
| 363 | // Symbolizer isn't available before SDK version 23 |
| 364 | if (GetSdkVersion() < 23) |
| 365 | return Status::FromErrorString( |
| 366 | str: "Symbol file generation only supported on SDK 23+" ); |
| 367 | |
| 368 | // If we already have symtab then we don't have to try and generate one |
| 369 | if (module_sp->GetSectionList()->FindSectionByName(section_dstr: ConstString(".symtab" )) != |
| 370 | nullptr) |
| 371 | return Status::FromErrorString(str: "Symtab already available in the module" ); |
| 372 | |
| 373 | Status error; |
| 374 | AdbClientUP adb(GetAdbClient(error)); |
| 375 | if (error.Fail()) |
| 376 | return error; |
| 377 | std::string tmpdir; |
| 378 | error = adb->Shell(command: "mktemp --directory --tmpdir /data/local/tmp" , timeout: seconds(5), |
| 379 | output: &tmpdir); |
| 380 | if (error.Fail() || tmpdir.empty()) |
| 381 | return Status::FromErrorStringWithFormat( |
| 382 | format: "Failed to generate temporary directory on the device (%s)" , |
| 383 | error.AsCString()); |
| 384 | tmpdir = llvm::StringRef(tmpdir).trim().str(); |
| 385 | |
| 386 | // Create file remover for the temporary directory created on the device |
| 387 | std::unique_ptr<std::string, std::function<void(std::string *)>> |
| 388 | tmpdir_remover(&tmpdir, [&adb](std::string *s) { |
| 389 | StreamString command; |
| 390 | command.Printf(format: "rm -rf %s" , s->c_str()); |
| 391 | Status error = adb->Shell(command: command.GetData(), timeout: seconds(5), output: nullptr); |
| 392 | |
| 393 | Log *log = GetLog(mask: LLDBLog::Platform); |
| 394 | if (log && error.Fail()) |
| 395 | LLDB_LOGF(log, "Failed to remove temp directory: %s" , |
| 396 | error.AsCString()); |
| 397 | }); |
| 398 | |
| 399 | FileSpec symfile_platform_filespec(tmpdir); |
| 400 | symfile_platform_filespec.AppendPathComponent(component: "symbolized.oat" ); |
| 401 | |
| 402 | // Execute oatdump on the remote device to generate a file with symtab |
| 403 | StreamString command; |
| 404 | command.Printf(format: "oatdump --symbolize=%s --output=%s" , |
| 405 | module_sp->GetPlatformFileSpec().GetPath(denormalize: false).c_str(), |
| 406 | symfile_platform_filespec.GetPath(denormalize: false).c_str()); |
| 407 | error = adb->Shell(command: command.GetData(), timeout: minutes(1), output: nullptr); |
| 408 | if (error.Fail()) |
| 409 | return Status::FromErrorStringWithFormat(format: "Oatdump failed: %s" , |
| 410 | error.AsCString()); |
| 411 | |
| 412 | // Download the symbolfile from the remote device |
| 413 | return GetFile(source: symfile_platform_filespec, destination: dst_file_spec); |
| 414 | } |
| 415 | |
| 416 | bool PlatformAndroid::GetRemoteOSVersion() { |
| 417 | m_os_version = llvm::VersionTuple(GetSdkVersion()); |
| 418 | return !m_os_version.empty(); |
| 419 | } |
| 420 | |
| 421 | llvm::StringRef |
| 422 | PlatformAndroid::GetLibdlFunctionDeclarations(lldb_private::Process *process) { |
| 423 | SymbolContextList matching_symbols; |
| 424 | std::vector<const char *> dl_open_names = {"__dl_dlopen" , "dlopen" }; |
| 425 | const char *dl_open_name = nullptr; |
| 426 | Target &target = process->GetTarget(); |
| 427 | for (auto name : dl_open_names) { |
| 428 | target.GetImages().FindFunctionSymbols( |
| 429 | name: ConstString(name), name_type_mask: eFunctionNameTypeFull, sc_list&: matching_symbols); |
| 430 | if (matching_symbols.GetSize()) { |
| 431 | dl_open_name = name; |
| 432 | break; |
| 433 | } |
| 434 | } |
| 435 | // Older platform versions have the dl function symbols mangled |
| 436 | if (dl_open_name == dl_open_names[0]) |
| 437 | return R"( |
| 438 | extern "C" void* dlopen(const char*, int) asm("__dl_dlopen"); |
| 439 | extern "C" void* dlsym(void*, const char*) asm("__dl_dlsym"); |
| 440 | extern "C" int dlclose(void*) asm("__dl_dlclose"); |
| 441 | extern "C" char* dlerror(void) asm("__dl_dlerror"); |
| 442 | )" ; |
| 443 | |
| 444 | return PlatformPOSIX::GetLibdlFunctionDeclarations(process); |
| 445 | } |
| 446 | |
| 447 | PlatformAndroid::AdbClientUP PlatformAndroid::GetAdbClient(Status &error) { |
| 448 | AdbClientUP adb(std::make_unique<AdbClient>(args&: m_device_id)); |
| 449 | if (adb) |
| 450 | error.Clear(); |
| 451 | else |
| 452 | error = Status::FromErrorString(str: "Failed to create AdbClient" ); |
| 453 | return adb; |
| 454 | } |
| 455 | |
| 456 | llvm::StringRef PlatformAndroid::GetPropertyPackageName() { |
| 457 | return GetGlobalProperties().GetPropertyAtIndexAs<llvm::StringRef>( |
| 458 | ePropertyPlatformPackageName, "" ); |
| 459 | } |
| 460 | |
| 461 | std::string PlatformAndroid::GetRunAs() { |
| 462 | llvm::StringRef run_as = GetPropertyPackageName(); |
| 463 | if (!run_as.empty()) { |
| 464 | // When LLDB fails to pull file from a package directory due to security |
| 465 | // constraint, user needs to set the package name to |
| 466 | // 'platform.plugin.remote-android.package-name' property in order to run |
| 467 | // shell commands as the package user using 'run-as' (e.g. to get file with |
| 468 | // 'cat' and 'dd'). |
| 469 | // https://cs.android.com/android/platform/superproject/+/master: |
| 470 | // system/core/run-as/run-as.cpp;l=39-61; |
| 471 | // drc=4a77a84a55522a3b122f9c63ef0d0b8a6a131627 |
| 472 | return std::string("run-as '" ) + run_as.str() + "' " ; |
| 473 | } |
| 474 | return run_as.str(); |
| 475 | } |
| 476 | |
| 477 | AdbClient::SyncService *PlatformAndroid::GetSyncService(Status &error) { |
| 478 | if (m_adb_sync_svc && m_adb_sync_svc->IsConnected()) |
| 479 | return m_adb_sync_svc.get(); |
| 480 | |
| 481 | AdbClientUP adb(GetAdbClient(error)); |
| 482 | if (error.Fail()) |
| 483 | return nullptr; |
| 484 | m_adb_sync_svc = adb->GetSyncService(error); |
| 485 | return (error.Success()) ? m_adb_sync_svc.get() : nullptr; |
| 486 | } |
| 487 | |