mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Merge pull request #78275 from andrurogerz/swift-inspect-android
[swift-inspect] implement Android support including remote heap iteration
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
|
||||
project(swift-inspect
|
||||
LANGUAGES CXX Swift)
|
||||
LANGUAGES C CXX Swift)
|
||||
|
||||
# Set C++ standard
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
@@ -27,7 +27,17 @@ if(WIN32)
|
||||
Sources/SwiftInspectClient/SwiftInspectClient.cpp)
|
||||
target_link_libraries(SwiftInspectClient PRIVATE
|
||||
SwiftInspectClientInterface)
|
||||
elseif(LINUX)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
add_library(AndroidCLib STATIC
|
||||
Sources/AndroidCLib/heap.c)
|
||||
target_include_directories(AndroidCLib PUBLIC
|
||||
Sources/AndroidCLib/include)
|
||||
set_property(TARGET AndroidCLib PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
if(ANDROID OR LINUX)
|
||||
add_library(LinuxSystemHeaders INTERFACE)
|
||||
target_include_directories(LinuxSystemHeaders INTERFACE
|
||||
Sources/SwiftInspectLinux/SystemHeaders)
|
||||
@@ -38,6 +48,8 @@ elseif(LINUX)
|
||||
Sources/SwiftInspectLinux/MemoryMap.swift
|
||||
Sources/SwiftInspectLinux/Process.swift
|
||||
Sources/SwiftInspectLinux/ProcFS.swift
|
||||
Sources/SwiftInspectLinux/PTrace.swift
|
||||
Sources/SwiftInspectLinux/RegisterSet.swift
|
||||
Sources/SwiftInspectLinux/SymbolCache.swift)
|
||||
target_compile_options(SwiftInspectLinux PRIVATE
|
||||
-Xcc -D_GNU_SOURCE)
|
||||
@@ -52,6 +64,7 @@ add_executable(swift-inspect
|
||||
Sources/swift-inspect/Operations/DumpConformanceCache.swift
|
||||
Sources/swift-inspect/Operations/DumpGenericMetadata.swift
|
||||
Sources/swift-inspect/Operations/DumpRawMetadata.swift
|
||||
Sources/swift-inspect/AndroidRemoteProcess.swift
|
||||
Sources/swift-inspect/Backtrace.swift
|
||||
Sources/swift-inspect/DarwinRemoteProcess.swift
|
||||
Sources/swift-inspect/LinuxRemoteProcess.swift
|
||||
@@ -71,7 +84,12 @@ target_link_libraries(swift-inspect PRIVATE
|
||||
if(WIN32)
|
||||
target_link_libraries(swift-inspect PRIVATE
|
||||
SwiftInspectClientInterface)
|
||||
elseif(LINUX)
|
||||
endif()
|
||||
if(ANDROID)
|
||||
target_link_libraries(swift-inspect PRIVATE
|
||||
AndroidCLib)
|
||||
endif()
|
||||
if(ANDROID OR LINUX)
|
||||
target_link_libraries(swift-inspect PRIVATE
|
||||
SwiftInspectLinux)
|
||||
endif()
|
||||
|
||||
@@ -19,7 +19,8 @@ let package = Package(
|
||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||
.target(name: "SwiftInspectClient", condition: .when(platforms: [.windows])),
|
||||
.target(name: "SwiftInspectClientInterface", condition: .when(platforms: [.windows])),
|
||||
.target(name: "SwiftInspectLinux", condition: .when(platforms: [.linux])),
|
||||
.target(name: "SwiftInspectLinux", condition: .when(platforms: [.linux, .android])),
|
||||
.target(name: "AndroidCLib", condition: .when(platforms: [.android])),
|
||||
],
|
||||
swiftSettings: [.unsafeFlags(["-parse-as-library"])]),
|
||||
.target(name: "SwiftInspectClient"),
|
||||
@@ -32,6 +33,11 @@ let package = Package(
|
||||
.systemLibrary(
|
||||
name: "LinuxSystemHeaders",
|
||||
path: "Sources/SwiftInspectLinux/SystemHeaders"),
|
||||
.target(
|
||||
name: "AndroidCLib",
|
||||
path: "Sources/AndroidCLib",
|
||||
publicHeadersPath: "include",
|
||||
cSettings: [.unsafeFlags(["-fPIC"])]),
|
||||
.systemLibrary(
|
||||
name: "SwiftInspectClientInterface"),
|
||||
.testTarget(
|
||||
|
||||
@@ -24,6 +24,27 @@ In order to build on Linux, some additional parameters must be passed to the bui
|
||||
swift build -Xswiftc -I$(git rev-parse --show-toplevel)/include/swift/SwiftRemoteMirror -Xlinker -lswiftRemoteMirror
|
||||
~~~
|
||||
|
||||
#### Android
|
||||
|
||||
To cross-compile swift-inspect for Android on Windows, some additional parameters must be passed to the build tool to locate the toolchain and necessary libraries.
|
||||
|
||||
~~~cmd
|
||||
set ANDROID_ARCH=aarch64
|
||||
set ANDROID_API_LEVEL=29
|
||||
set ANDROID_CLANG_VERSION=17.0.2
|
||||
set ANDROID_NDK_ROOT=C:\Android\android-sdk\ndk\26.3.11579264
|
||||
set SDKROOT_ANDROID=%LocalAppData%\Programs\Swift\Platforms\0.0.0\Android.platform\Developer\SDKs\Android.sdk
|
||||
swift build --triple %ANDROID_ARCH%-unknown-linux-android%ANDROID_API_LEVEL% ^
|
||||
--sdk %ANDROID_NDK_ROOT%\toolchains\llvm\prebuilt\windows-x86_64\sysroot ^
|
||||
-Xswiftc -sdk -Xswiftc %SDKROOT_ANDROID% ^
|
||||
-Xswiftc -sysroot -Xswiftc %ANDROID_NDK_ROOT%\toolchains\llvm\prebuilt\windows-x86_64\sysroot ^
|
||||
-Xswiftc -I -Xswiftc %SDKROOT_ANDROID%\usr\include ^
|
||||
-Xswiftc -Xclang-linker -Xswiftc -resource-dir -Xswiftc -Xclang-linker -Xswiftc %ANDROID_NDK_ROOT%\toolchains\llvm\prebuilt\windows-x86_64\lib\clang\%ANDROID_CLANG_VERSION% ^
|
||||
-Xlinker -L%ANDROID_NDK_ROOT%\toolchains\llvm\prebuilt\windows-x86_64\lib\clang\%ANDROID_CLANG_VERSION%\lib\linux\%ANDROID_ARCH% ^
|
||||
-Xcc -I%SDKROOT_ANDROID%\usr\include\swift\SwiftRemoteMirror ^
|
||||
-Xlinker %SDKROOT_ANDROID%\usr\lib\swift\android\%ANDROID_ARCH%\libswiftRemoteMirror.so
|
||||
~~~
|
||||
|
||||
#### CMake
|
||||
|
||||
In order to build on Windows with CMake, some additional parameters must be passed to the build tool to locate the necessary Swift modules.
|
||||
@@ -38,6 +59,26 @@ In order to build on Linux with CMake, some additional parameters must be passed
|
||||
cmake -B out -G Ninja -S . -D ArgumentParser_DIR=... -D CMAKE_Swift_FLAGS="-Xcc -I$(git rev-parse --show-toplevel)/include/swift/SwiftRemoteMirror"
|
||||
~~~
|
||||
|
||||
In order to build for Android with CMake on Windows, some additiona parameters must be passed to the build tool to locate the necessary Swift modules.
|
||||
|
||||
~~~cmd
|
||||
set ANDROID_ARCH=aarch64
|
||||
set ANDROID_API_LEVEL=29
|
||||
set ANDROID_CLANG_VERSION=17.0.2
|
||||
set ANDROID_NDK_ROOT=C:\Android\android-sdk\ndk\26.3.11579264
|
||||
set ANDROID_ARCH_ABI=arm64-v8a
|
||||
set SDKROOT_ANDROID=%LocalAppData%\Programs\Swift\Platforms\0.0.0\Android.platform\Developer\SDKs\Android.sdk
|
||||
cmake -B build -S . -G Ninja ^
|
||||
-D CMAKE_BUILD_WITH_INSTALL_RPATH=YES ^
|
||||
-D CMAKE_SYSTEM_NAME=Android ^
|
||||
-D CMAKE_ANDROID_ARCH_ABI=%ANDROID_ARCH_ABI% ^
|
||||
-D CMAKE_SYSTEM_VERSION=%ANDROID_API_LEVEL% ^
|
||||
-D CMAKE_Swift_COMPILER_TARGET=%ANDROID_ARCH%-unknown-linux-android%ANDROID_API_LEVEL% ^
|
||||
-D CMAKE_Swift_FLAGS="-sdk %SDKROOT_ANDROID% -L%ANDROID_NDK_ROOT%\toolchains\llvm\prebuilt\windows-x86_64\lib\clang\%ANDROID_CLANG_VERSION%\lib\linux\%ANDROID_ARCH% -Xclang-linker -resource-dir -Xclang-linker %ANDROID_NDK_ROOT%\toolchains\llvm\prebuilt\windows-x86_64\lib\clang\%ANDROID_CLANG_VERSION% -Xcc -I%SDKROOT_ANDROID%\usr\include -I%SDKROOT_ANDROID%\usr\include\swift\SwiftRemoteMirror" ^
|
||||
-D ArgumentParser_DIR=...
|
||||
cmake --build build
|
||||
~~~
|
||||
|
||||
Building with CMake requires a local copy of [swift-argument-parser](https://github.com/apple/swift-argument-parser) built with CMake.
|
||||
The `ArumentParser_DIR=` definition must refer to the `cmake/modules` sub-directory of the swift-argument-parser build output directory.
|
||||
|
||||
|
||||
120
tools/swift-inspect/Sources/AndroidCLib/heap.c
Normal file
120
tools/swift-inspect/Sources/AndroidCLib/heap.c
Normal file
@@ -0,0 +1,120 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "heap.h"
|
||||
|
||||
/* The heap metadata buffer is interpreted as an array of 8-byte pairs. The
|
||||
* first pair contains metadata describing the buffer itself: max valid index
|
||||
* (e.g. size of the buffer) and next index (e.g. write cursor/position). Each
|
||||
* subsequent pair describes the address and length of a heap entry in the
|
||||
* remote process. A 4KiB page provides sufficient space for the header and
|
||||
* 255 (address, length) pairs.
|
||||
*
|
||||
* ------------
|
||||
* | uint64_t | max valid index (e.g. sizeof(buffer) / sizeof(uint64_t))
|
||||
* ------------
|
||||
* | uint64_t | next free index (starts at 2)
|
||||
* ------------
|
||||
* | uint64_t | heap item 1 address
|
||||
* ------------
|
||||
* | uint64_t | heap item 1 size
|
||||
* ------------
|
||||
* | uint64_t | heap item 2 address
|
||||
* ------------
|
||||
* | uint64_t | heap item 2 size
|
||||
* ------------
|
||||
* | uint64_t | ...
|
||||
* ------------
|
||||
* | uint64_t | ...
|
||||
* ------------
|
||||
* | uint64_t | heap item N address
|
||||
* ------------
|
||||
* | uint64_t | heap item N size
|
||||
* ------------
|
||||
*/
|
||||
|
||||
#if !__has_builtin(__builtin_debugtrap)
|
||||
#error("compiler support for __builtin_debugtrap is required")
|
||||
#endif
|
||||
|
||||
#define MAX_VALID_IDX 0
|
||||
#define NEXT_FREE_IDX 1
|
||||
#define HEADER_SIZE 2
|
||||
#define ENTRY_SIZE 2
|
||||
|
||||
// Callback for malloc_iterate. Because this function is meant to be copied to
|
||||
// a different process for execution, it must not make any function calls to
|
||||
// ensure compiles to simple, position-independent code. It is implemented in C
|
||||
// for readability/maintainability. It is placed in its own code section to
|
||||
// simplify calculating its size.
|
||||
__attribute__((noinline, used, section("heap_iterator")))
|
||||
static void heap_iterate_callback(unsigned long base, unsigned long size, void *arg) {
|
||||
volatile uint64_t *data = (uint64_t*)arg;
|
||||
while (data[NEXT_FREE_IDX] >= data[MAX_VALID_IDX]) {
|
||||
// SIGTRAP indicates the buffer is full and needs to be drained before more
|
||||
// entries can be written.
|
||||
__builtin_debugtrap();
|
||||
|
||||
// After the SIGTRAP, the signal handler advances the instruction pointer
|
||||
// (PC) to the next instruction. Inserting a nop instruction here ensures
|
||||
// the CPU has a clear, executable instruction to process, which avoids
|
||||
// potential speculative execution or pipeline issues that could arise if
|
||||
// the next instruction were a control transfer like a branch or jump.
|
||||
__asm__ __volatile__("nop");
|
||||
}
|
||||
data[data[NEXT_FREE_IDX]++] = base;
|
||||
data[data[NEXT_FREE_IDX]++] = size;
|
||||
}
|
||||
|
||||
// The linker implicitly defines __start- and __stop- prefixed symbols that mark
|
||||
// the start and end of user defined sections.
|
||||
extern char __stop_heap_iterator[];
|
||||
|
||||
void* heap_iterate_callback_start() {
|
||||
return (void*)heap_iterate_callback;
|
||||
}
|
||||
|
||||
size_t heap_iterate_callback_len() {
|
||||
return (uintptr_t)__stop_heap_iterator - (uintptr_t)heap_iterate_callback;
|
||||
}
|
||||
|
||||
bool heap_iterate_metadata_init(void* data, size_t len) {
|
||||
uint64_t *metadata = data;
|
||||
const uint64_t max_entries = len / sizeof(uint64_t);
|
||||
if (max_entries < HEADER_SIZE + ENTRY_SIZE)
|
||||
return false;
|
||||
|
||||
memset(data, 0, len);
|
||||
metadata[MAX_VALID_IDX] = max_entries;
|
||||
metadata[NEXT_FREE_IDX] = HEADER_SIZE;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool heap_iterate_metadata_process(
|
||||
void* data, size_t len, void* callback_context, heap_iterate_entry_callback_t callback) {
|
||||
uint64_t *metadata = data;
|
||||
const uint64_t max_entries = len / sizeof(uint64_t);
|
||||
const uint64_t end_index = metadata[NEXT_FREE_IDX];
|
||||
|
||||
if (metadata[MAX_VALID_IDX] != max_entries || end_index > max_entries)
|
||||
return false;
|
||||
|
||||
for (size_t i = HEADER_SIZE; i < end_index; i += ENTRY_SIZE) {
|
||||
const uint64_t base = metadata[i];
|
||||
const uint64_t size = metadata[i + 1];
|
||||
callback(callback_context, base, size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
40
tools/swift-inspect/Sources/AndroidCLib/include/heap.h
Normal file
40
tools/swift-inspect/Sources/AndroidCLib/include/heap.h
Normal file
@@ -0,0 +1,40 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Location of the heap_iterate callback.
|
||||
void* heap_iterate_callback_start();
|
||||
|
||||
// Size of the heap_iterate callback.
|
||||
size_t heap_iterate_callback_len();
|
||||
|
||||
// Initialize the provided buffer to receive heap iteration metadata.
|
||||
bool heap_iterate_metadata_init(void* data, size_t len);
|
||||
|
||||
// Callback invoked by heap_iterate_data_process for each heap entry .
|
||||
typedef void (*heap_iterate_entry_callback_t)(void* context, uint64_t base, uint64_t len);
|
||||
|
||||
// Process all heap iteration entries in the provided buffer.
|
||||
bool heap_iterate_metadata_process(
|
||||
void* data, size_t len, void* callback_context, heap_iterate_entry_callback_t callback);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,4 @@
|
||||
module AndroidCLib {
|
||||
header "heap.h"
|
||||
export *
|
||||
}
|
||||
157
tools/swift-inspect/Sources/SwiftInspectLinux/PTrace.swift
Normal file
157
tools/swift-inspect/Sources/SwiftInspectLinux/PTrace.swift
Normal file
@@ -0,0 +1,157 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import Foundation
|
||||
import LinuxSystemHeaders
|
||||
|
||||
// Provides scoped access to a PTrace object.
|
||||
public func withPTracedProcess(pid: pid_t, _ closure: (consuming PTrace) throws -> Void) throws {
|
||||
let ptrace = try PTrace(pid)
|
||||
try closure(ptrace)
|
||||
}
|
||||
|
||||
public struct PTrace: ~Copyable {
|
||||
enum PTraceError: Error {
|
||||
case operationFailure(_ command: CInt, pid: pid_t, errno: CInt = get_errno())
|
||||
case waitFailure(pid: pid_t, errno: CInt = get_errno())
|
||||
case unexpectedWaitStatus(pid: pid_t, status: CInt, sigInfo: siginfo_t? = nil)
|
||||
}
|
||||
|
||||
let pid: pid_t
|
||||
|
||||
// Initializing a PTrace instance attaches to the target process, waits for
|
||||
// it to stop, and leaves it in a stopped state. The caller may resume the
|
||||
// process by calling cont().
|
||||
// NOTE: clients must use withPTracedProcess instead of direct initialization.
|
||||
fileprivate init(_ pid: pid_t) throws {
|
||||
guard ptrace_attach(pid) != -1 else {
|
||||
throw PTraceError.operationFailure(PTRACE_ATTACH, pid: pid)
|
||||
}
|
||||
|
||||
while true {
|
||||
var status: CInt = 0
|
||||
let result = waitpid(pid, &status, 0)
|
||||
if result == -1 {
|
||||
if get_errno() == EINTR { continue }
|
||||
throw PTraceError.waitFailure(pid: pid)
|
||||
}
|
||||
|
||||
precondition(pid == result, "waitpid returned unexpected value \(result)")
|
||||
|
||||
if wIfStopped(status) { break }
|
||||
}
|
||||
|
||||
self.pid = pid
|
||||
}
|
||||
|
||||
deinit { _ = ptrace_detach(self.pid) }
|
||||
|
||||
public func cont() throws {
|
||||
if ptrace_cont(self.pid) == -1 {
|
||||
throw PTraceError.operationFailure(PTRACE_CONT, pid: self.pid)
|
||||
}
|
||||
}
|
||||
|
||||
public func interrupt() throws {
|
||||
if ptrace_interrupt(self.pid) == -1 {
|
||||
throw PTraceError.operationFailure(PTRACE_INTERRUPT, pid: self.pid)
|
||||
}
|
||||
}
|
||||
|
||||
public func getSigInfo() throws -> siginfo_t {
|
||||
var sigInfo = siginfo_t()
|
||||
if ptrace_getsiginfo(self.pid, &sigInfo) == -1 {
|
||||
throw PTraceError.operationFailure(PTRACE_GETSIGINFO, pid: self.pid)
|
||||
}
|
||||
|
||||
return sigInfo
|
||||
}
|
||||
|
||||
public func pokeData(addr: UInt64, value: UInt64) throws {
|
||||
if ptrace_pokedata(self.pid, UInt(addr), UInt(value)) == -1 {
|
||||
throw PTraceError.operationFailure(PTRACE_POKEDATA, pid: self.pid)
|
||||
}
|
||||
}
|
||||
|
||||
public func getRegSet() throws -> RegisterSet {
|
||||
var regSet = RegisterSet()
|
||||
try withUnsafeMutableBytes(of: ®Set) {
|
||||
var vec = iovec(iov_base: $0.baseAddress!, iov_len: MemoryLayout<RegisterSet>.size)
|
||||
if ptrace_getregset(self.pid, NT_PRSTATUS, &vec) == -1 {
|
||||
throw PTraceError.operationFailure(PTRACE_GETREGSET, pid: self.pid)
|
||||
}
|
||||
}
|
||||
|
||||
return regSet
|
||||
}
|
||||
|
||||
public func setRegSet(regSet: RegisterSet) throws {
|
||||
var regSetCopy = regSet
|
||||
try withUnsafeMutableBytes(of: ®SetCopy) {
|
||||
var vec = iovec(iov_base: $0.baseAddress!, iov_len: MemoryLayout<RegisterSet>.size)
|
||||
if ptrace_setregset(self.pid, NT_PRSTATUS, &vec) == -1 {
|
||||
throw PTraceError.operationFailure(PTRACE_SETREGSET, pid: self.pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call the function at the specified address in the attached process with the
|
||||
// provided argument array. The optional callback is invoked when the process
|
||||
// is stopped with a SIGTRAP signal. In this case, the caller is responsible
|
||||
// for taking action on the signal.
|
||||
public func jump(
|
||||
to address: UInt64, with args: [UInt64] = [],
|
||||
_ callback: ((borrowing PTrace) throws -> Void)? = nil
|
||||
) throws -> UInt64 {
|
||||
let origRegs = try self.getRegSet()
|
||||
defer { try? self.setRegSet(regSet: origRegs) }
|
||||
|
||||
// Set the return address to 0. This forces the function to return to 0 on
|
||||
// completion, resulting in a SIGSEGV with address 0 which will interrupt
|
||||
// the process and notify us (the tracer) via waitpid(). At that point, we
|
||||
// will restore the original state and continue the process.
|
||||
let returnAddr: UInt64 = 0
|
||||
|
||||
var newRegs = try origRegs.setupCall(self, to: address, with: args, returnTo: returnAddr)
|
||||
try self.setRegSet(regSet: newRegs)
|
||||
try self.cont()
|
||||
|
||||
var status: CInt = 0
|
||||
while true {
|
||||
let result = waitpid(self.pid, &status, 0)
|
||||
guard result != -1 else {
|
||||
if get_errno() == EINTR { continue }
|
||||
throw PTraceError.waitFailure(pid: self.pid)
|
||||
}
|
||||
|
||||
precondition(self.pid == result, "waitpid returned unexpected value \(result)")
|
||||
|
||||
guard wIfStopped(status) && !wIfExited(status) && !wIfSignaled(status) else {
|
||||
throw PTraceError.unexpectedWaitStatus(pid: self.pid, status: status)
|
||||
}
|
||||
|
||||
guard wStopSig(status) == SIGTRAP, let callback = callback else { break }
|
||||
|
||||
// give the caller the opportunity to handle SIGTRAP
|
||||
try callback(self)
|
||||
}
|
||||
|
||||
let sigInfo = try self.getSigInfo()
|
||||
newRegs = try self.getRegSet()
|
||||
|
||||
guard wStopSig(status) == SIGSEGV, siginfo_si_addr(sigInfo) == nil else {
|
||||
throw PTraceError.unexpectedWaitStatus(pid: self.pid, status: status, sigInfo: sigInfo)
|
||||
}
|
||||
|
||||
return UInt64(newRegs.returnValue())
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,25 @@
|
||||
import Foundation
|
||||
import LinuxSystemHeaders
|
||||
|
||||
// The Android version of iovec defineds iov_len as __kernel_size_t, while the
|
||||
// standard Linux definition is size_t. This extension makes the difference
|
||||
// easier to deal with.
|
||||
extension iovec {
|
||||
public init<T: BinaryInteger>(iov_base: UnsafeMutableRawPointer?, iov_len: T) {
|
||||
self.init()
|
||||
self.iov_base = iov_base
|
||||
#if os(Android)
|
||||
self.iov_len = __kernel_size_t(iov_len)
|
||||
#else
|
||||
self.iov_len = size_t(iov_len)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public class Process {
|
||||
public enum ProcessError: Error {
|
||||
case processVmReadFailure(pid: pid_t, address: UInt64, size: UInt64)
|
||||
case processVmWriteFailure(pid: pid_t, address: UInt64, size: UInt64)
|
||||
case malformedString(address: UInt64)
|
||||
}
|
||||
|
||||
@@ -69,19 +85,46 @@ public class Process {
|
||||
// read an array of type T elements from the target process
|
||||
public func readArray<T>(address: UInt64, upToCount: UInt) throws -> [T] {
|
||||
guard upToCount > 0 else { return [] }
|
||||
|
||||
let maxSize = upToCount * UInt(MemoryLayout<T>.stride)
|
||||
let array: [T] = Array(unsafeUninitializedCapacity: Int(upToCount)) { buffer, initCount in
|
||||
var local = iovec(iov_base: buffer.baseAddress!, iov_len: Int(maxSize))
|
||||
var local = iovec(iov_base: buffer.baseAddress!, iov_len: maxSize)
|
||||
var remote = iovec(
|
||||
iov_base: UnsafeMutableRawPointer(bitPattern: UInt(address)), iov_len: Int(maxSize))
|
||||
iov_base: UnsafeMutableRawPointer(bitPattern: UInt(address)), iov_len: maxSize)
|
||||
let bytesRead = process_vm_readv(self.pid, &local, 1, &remote, 1, 0)
|
||||
initCount = bytesRead / MemoryLayout<T>.stride
|
||||
}
|
||||
|
||||
guard array.count > 0 else {
|
||||
throw ProcessError.processVmReadFailure(pid: self.pid, address: address, size: UInt64(maxSize))
|
||||
throw ProcessError.processVmReadFailure(
|
||||
pid: self.pid, address: address, size: UInt64(maxSize))
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
// simple wrapper around process_vm_readv
|
||||
public func readMem(remoteAddr: UInt64, localAddr: UnsafeRawPointer, len: UInt) throws {
|
||||
var local = iovec(iov_base: UnsafeMutableRawPointer(mutating: localAddr), iov_len: len)
|
||||
var remote = iovec(
|
||||
iov_base: UnsafeMutableRawPointer(bitPattern: UInt(remoteAddr)), iov_len: len)
|
||||
|
||||
let bytesRead = process_vm_readv(self.pid, &local, 1, &remote, 1, 0)
|
||||
guard bytesRead == len else {
|
||||
throw ProcessError.processVmReadFailure(pid: self.pid, address: remoteAddr, size: UInt64(len))
|
||||
}
|
||||
}
|
||||
|
||||
// simple wrapper around process_vm_writev
|
||||
public func writeMem(remoteAddr: UInt64, localAddr: UnsafeRawPointer, len: UInt) throws {
|
||||
var local = iovec(iov_base: UnsafeMutableRawPointer(mutating: localAddr), iov_len: len)
|
||||
var remote = iovec(
|
||||
iov_base: UnsafeMutableRawPointer(bitPattern: UInt(remoteAddr)), iov_len: len)
|
||||
|
||||
let bytesWritten = process_vm_writev(self.pid, &local, 1, &remote, 1, 0)
|
||||
guard bytesWritten == len else {
|
||||
throw ProcessError.processVmWriteFailure(
|
||||
pid: self.pid, address: remoteAddr, size: UInt64(len))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import Foundation
|
||||
import LinuxSystemHeaders
|
||||
|
||||
#if arch(arm64)
|
||||
public typealias RegisterSet = user_pt_regs
|
||||
|
||||
extension RegisterSet {
|
||||
public static var trapInstructionSize: UInt { return 4 } // brk #0x0
|
||||
|
||||
public func setupCall(
|
||||
_ ptrace: borrowing PTrace, to funcAddr: UInt64, with args: [UInt64],
|
||||
returnTo returnAddr: UInt64
|
||||
) throws -> RegisterSet {
|
||||
// The first 8 arguments are passed in regsters. Any additional arguments
|
||||
// must be pushed on the stack, which is not implemented.
|
||||
precondition(args.count <= 8)
|
||||
|
||||
var registers = self
|
||||
registers.regs.0 = args.count > 0 ? args[0] : 0
|
||||
registers.regs.1 = args.count > 1 ? args[1] : 0
|
||||
registers.regs.2 = args.count > 2 ? args[2] : 0
|
||||
registers.regs.3 = args.count > 3 ? args[3] : 0
|
||||
registers.regs.4 = args.count > 4 ? args[4] : 0
|
||||
registers.regs.5 = args.count > 5 ? args[5] : 0
|
||||
registers.regs.6 = args.count > 6 ? args[6] : 0
|
||||
registers.regs.7 = args.count > 7 ? args[7] : 0
|
||||
registers.pc = funcAddr
|
||||
registers.regs.30 = returnAddr // link register (x30)
|
||||
return registers
|
||||
}
|
||||
|
||||
public func returnValue() -> UInt64 {
|
||||
return self.regs.0
|
||||
}
|
||||
|
||||
public mutating func step(_ bytes: UInt) {
|
||||
self.pc += UInt64(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#elseif arch(x86_64)
|
||||
public typealias RegisterSet = pt_regs
|
||||
|
||||
extension RegisterSet {
|
||||
public static var trapInstructionSize: UInt { return 1 } // int3
|
||||
|
||||
public func setupCall(
|
||||
_ ptrace: borrowing PTrace, to funcAddr: UInt64, with args: [UInt64],
|
||||
returnTo returnAddr: UInt64
|
||||
) throws -> RegisterSet {
|
||||
// The first 6 arguments are passed in registers. Any additional arguments
|
||||
// must be pushed on the stack, which is not implemented.
|
||||
precondition(args.count <= 6)
|
||||
|
||||
var registers = self
|
||||
registers.rdi = UInt(args.count > 0 ? args[0] : 0)
|
||||
registers.rsi = UInt(args.count > 1 ? args[1] : 0)
|
||||
registers.rdx = UInt(args.count > 2 ? args[2] : 0)
|
||||
registers.rcx = UInt(args.count > 3 ? args[3] : 0)
|
||||
registers.r8 = UInt(args.count > 4 ? args[4] : 0)
|
||||
registers.r9 = UInt(args.count > 5 ? args[5] : 0)
|
||||
registers.rip = UInt(funcAddr)
|
||||
registers.rax = 0 // rax is the number of args in a va_args function
|
||||
|
||||
// push the return address onto the stack
|
||||
registers.rsp -= UInt(MemoryLayout<UInt64>.size)
|
||||
try ptrace.pokeData(addr: UInt64(registers.rsp), value: returnAddr)
|
||||
|
||||
return registers
|
||||
}
|
||||
|
||||
public func returnValue() -> UInt64 {
|
||||
return UInt64(self.rax)
|
||||
}
|
||||
|
||||
public mutating func step(_ bytes: UInt) {
|
||||
self.rip += UInt(bytes)
|
||||
}
|
||||
}
|
||||
#else
|
||||
#error("Only arm64 and x86_64 architectures are supported")
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
static inline
|
||||
int get_errno() {
|
||||
return errno;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/mman.h>
|
||||
@@ -0,0 +1,70 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include <linux/ptrace.h>
|
||||
|
||||
static inline
|
||||
int ptrace_retry(int op, pid_t pid, void *addr, void *data) {
|
||||
int retries = 3;
|
||||
int result;
|
||||
do {
|
||||
result = ptrace(op, pid, addr, data);
|
||||
} while (result < 0 && (errno == EAGAIN || errno == EBUSY) && retries-- > 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline
|
||||
int ptrace_attach(pid_t pid) {
|
||||
return ptrace_retry(PTRACE_ATTACH, pid, 0, 0);
|
||||
}
|
||||
|
||||
static inline
|
||||
int ptrace_detach(pid_t pid) {
|
||||
return ptrace_retry(PTRACE_DETACH, pid, 0, 0);
|
||||
}
|
||||
|
||||
static inline
|
||||
int ptrace_cont(pid_t pid) {
|
||||
return ptrace_retry(PTRACE_CONT, pid, 0, 0);
|
||||
}
|
||||
|
||||
static inline
|
||||
int ptrace_interrupt(pid_t pid) {
|
||||
return ptrace_retry(PTRACE_INTERRUPT, pid, 0, 0);
|
||||
}
|
||||
|
||||
static inline
|
||||
int ptrace_getsiginfo(pid_t pid, siginfo_t *siginfo) {
|
||||
return ptrace_retry(PTRACE_GETSIGINFO, pid, 0, siginfo);
|
||||
}
|
||||
|
||||
static inline
|
||||
int ptrace_pokedata(pid_t pid, unsigned long addr, unsigned long value) {
|
||||
return ptrace_retry(PTRACE_POKEDATA, pid, (void*)(uintptr_t)addr, (void*)(uintptr_t)value);
|
||||
}
|
||||
|
||||
static inline
|
||||
int ptrace_getregset(pid_t pid, int type, struct iovec *regs) {
|
||||
return ptrace_retry(PTRACE_GETREGSET, pid, (void*)(uintptr_t)type, regs);
|
||||
}
|
||||
|
||||
static inline
|
||||
int ptrace_setregset(pid_t pid, int type, struct iovec *regs) {
|
||||
return ptrace_retry(PTRACE_SETREGSET, pid, (void*)(uintptr_t)type, regs);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
static inline
|
||||
void* siginfo_si_addr(siginfo_t siginfo) {
|
||||
return siginfo.si_addr;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
@@ -0,0 +1,36 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static inline
|
||||
bool wIfStopped(int status) {
|
||||
return WIFSTOPPED(status) != 0;
|
||||
}
|
||||
|
||||
static inline
|
||||
bool wIfExited(int status) {
|
||||
return WIFEXITED(status) != 0;
|
||||
}
|
||||
|
||||
static inline
|
||||
bool wIfSignaled(int status) {
|
||||
return WIFSIGNALED(status) != 0;
|
||||
}
|
||||
|
||||
static inline
|
||||
int wStopSig(int status) {
|
||||
return WSTOPSIG(status);
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
module LinuxSystemHeaders {
|
||||
header "auxvec.h"
|
||||
header "elf.h"
|
||||
header "link.h"
|
||||
header "uio.h"
|
||||
header "_auxvec.h"
|
||||
header "_elf.h"
|
||||
header "_errno.h"
|
||||
header "_link.h"
|
||||
header "_mman.h"
|
||||
header "_ptrace.h"
|
||||
header "_signal.h"
|
||||
header "_uio.h"
|
||||
header "_unistd.h"
|
||||
header "_wait.h"
|
||||
export *
|
||||
}
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if os(Android)
|
||||
|
||||
import AndroidCLib
|
||||
import Foundation
|
||||
import LinuxSystemHeaders
|
||||
import SwiftInspectLinux
|
||||
import SwiftRemoteMirror
|
||||
|
||||
extension MemoryMap.Entry {
|
||||
public func isHeapRegion() -> Bool {
|
||||
guard let name = self.pathname else { return false }
|
||||
// The heap region naming convention is found in AOSP's libmemunreachable at
|
||||
// android/platform/system/memory/libmemunreachable/MemUnreachable.cpp.
|
||||
if name == "[anon:libc_malloc]" { return true }
|
||||
if name.hasPrefix("[anon:scudo:") { return true }
|
||||
if name.hasPrefix("[anon:GWP-ASan") { return true }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
internal final class AndroidRemoteProcess: LinuxRemoteProcess {
|
||||
enum RemoteProcessError: Error {
|
||||
case missingSymbol(_ name: String)
|
||||
case heapIterationFailed
|
||||
}
|
||||
|
||||
struct RemoteSymbol {
|
||||
let addr: UInt64?
|
||||
let name: String
|
||||
init(_ name: String, _ symbolCache: SymbolCache) {
|
||||
self.name = name
|
||||
if let symbolRange = symbolCache.address(of: name) {
|
||||
self.addr = symbolRange.start
|
||||
} else {
|
||||
self.addr = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We call mmap/munmap in the remote process to alloc/free memory for our own
|
||||
// use without impacting existing allocations in the remote process.
|
||||
lazy var mmapSymbol: RemoteSymbol = RemoteSymbol("mmap", self.symbolCache)
|
||||
lazy var munmapSymbol: RemoteSymbol = RemoteSymbol("munmap", self.symbolCache)
|
||||
|
||||
// We call malloc_iterate in the remote process to enumerate all items in the
|
||||
// remote process' heap. We use malloc_disable/malloc_enable to ensure no
|
||||
// malloc/free requests can race with malloc_iterate.
|
||||
lazy var mallocDisableSymbol: RemoteSymbol = RemoteSymbol("malloc_disable", self.symbolCache)
|
||||
lazy var mallocEnableSymbol: RemoteSymbol = RemoteSymbol("malloc_enable", self.symbolCache)
|
||||
lazy var mallocIterateSymbol: RemoteSymbol = RemoteSymbol("malloc_iterate", self.symbolCache)
|
||||
|
||||
// Linux and Android have no supported method to enumerate allocations in the
|
||||
// heap of a remote process. Android does, however, support the malloc_iterate
|
||||
// API, which enumerates allocations in the current process. We leverage this
|
||||
// API by invoking it in the remote process with ptrace and using simple IPC
|
||||
// (SIGTRAP and process_vm_readv and process_vm_writev) to fetch the results.
|
||||
override internal func iterateHeap(_ body: (swift_addr_t, UInt64) -> Void) {
|
||||
var regionCount = 0
|
||||
var allocCount = 0
|
||||
do {
|
||||
try withPTracedProcess(pid: self.processIdentifier) { ptrace in
|
||||
for entry in self.memoryMap.entries {
|
||||
// Limiting malloc_iterate calls to only memory regions that are known
|
||||
// to contain heap allocations is not strictly necessary but it does
|
||||
// significantly improve the speed of heap iteration.
|
||||
guard entry.isHeapRegion() else { continue }
|
||||
|
||||
// collect all of the allocations in this heap region
|
||||
let allocations: [(base: swift_addr_t, len: UInt64)]
|
||||
allocations = try self.iterateHeapRegion(ptrace, region: entry)
|
||||
regionCount += 1
|
||||
allocCount += allocations.count
|
||||
|
||||
// process all of the collected allocations
|
||||
for alloc in allocations { body(alloc.base, alloc.len) }
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("failed iterating remote heap: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
if regionCount == 0 {
|
||||
// This condition most likely indicates the MemoryMap.Entry.isHeapRegion
|
||||
// filtering is needs to be modified to support a new heap region naming
|
||||
// convention in a newer Android version.
|
||||
print("WARNING: no heap regions found")
|
||||
print("swift-inspect may need to be updated for a newer Android version")
|
||||
} else if allocCount == 0 {
|
||||
print("WARNING: no heap items enumerated")
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate a single heap region in the remote process and return an array
|
||||
// of (base, len) pairs describing each heap allocation in the region.
|
||||
internal func iterateHeapRegion(_ ptrace: borrowing PTrace, region: MemoryMap.Entry) throws -> [(
|
||||
base: swift_addr_t, len: UInt64
|
||||
)] {
|
||||
// Allocate a page-sized buffer in the remote process that malloc_iterate
|
||||
// will populaate with metadata describing each heap entry it enumerates.
|
||||
let dataLen = sysconf(Int32(_SC_PAGESIZE))
|
||||
let remoteDataAddr = try self.mmapRemote(
|
||||
ptrace, len: dataLen, prot: PROT_READ | PROT_WRITE, flags: MAP_ANON | MAP_PRIVATE)
|
||||
defer {
|
||||
_ = try? self.munmapRemote(ptrace, addr: remoteDataAddr, len: dataLen)
|
||||
}
|
||||
|
||||
// Allocate and inialize a local buffer that will be used to copy metadata
|
||||
// to/from the target process.
|
||||
let buffer = UnsafeMutableRawPointer.allocate(
|
||||
byteCount: dataLen, alignment: MemoryLayout<UInt64>.alignment)
|
||||
defer { buffer.deallocate() }
|
||||
guard heap_iterate_metadata_init(buffer, dataLen) else {
|
||||
throw RemoteProcessError.heapIterationFailed
|
||||
}
|
||||
try self.process.writeMem(remoteAddr: remoteDataAddr, localAddr: buffer, len: UInt(dataLen))
|
||||
|
||||
// Allocate an rwx region to hold the malloc_iterate callback that will be
|
||||
// executed in the remote process.
|
||||
let codeLen = heap_iterate_callback_len()
|
||||
let remoteCodeAddr = try mmapRemote(
|
||||
ptrace, len: codeLen, prot: PROT_READ | PROT_WRITE | PROT_EXEC, flags: MAP_ANON | MAP_PRIVATE)
|
||||
defer {
|
||||
_ = try? self.munmapRemote(ptrace, addr: remoteCodeAddr, len: codeLen)
|
||||
}
|
||||
|
||||
// Copy the malloc_iterate callback implementation to the remote process.
|
||||
let codeStart = heap_iterate_callback_start()!
|
||||
try self.process.writeMem(
|
||||
remoteAddr: remoteCodeAddr, localAddr: codeStart, len: UInt(codeLen))
|
||||
|
||||
guard let mallocIterateAddr = self.mallocIterateSymbol.addr else {
|
||||
throw RemoteProcessError.missingSymbol(self.mallocIterateSymbol.name)
|
||||
}
|
||||
|
||||
// Disable malloc/free while enumerating the region to get a consistent
|
||||
// snapshot of existing allocations.
|
||||
try self.mallocDisableRemote(ptrace)
|
||||
defer {
|
||||
_ = try? self.mallocEnableRemote(ptrace)
|
||||
}
|
||||
|
||||
// Collects (base, len) pairs describing each heap allocation in the remote
|
||||
// process.
|
||||
var allocations: [(base: swift_addr_t, len: UInt64)] = []
|
||||
|
||||
let regionLen = region.endAddr - region.startAddr
|
||||
let args = [region.startAddr, regionLen, remoteCodeAddr, remoteDataAddr]
|
||||
_ = try ptrace.jump(to: mallocIterateAddr, with: args) { ptrace in
|
||||
// This callback is invoked when a SIGTRAP is encountered in the remote
|
||||
// process. In this context, this signal indicates there is no more room
|
||||
// in the allocated metadata region (see AndroidCLib/heap.c).
|
||||
// Immediately read the heap metadata from the remote process, skip past
|
||||
// the trap/break instruction, and resume the remote process.
|
||||
try self.process.readMem(remoteAddr: remoteDataAddr, localAddr: buffer, len: UInt(dataLen))
|
||||
allocations.append(contentsOf: try self.processHeapMetadata(buffer: buffer, len: dataLen))
|
||||
|
||||
guard heap_iterate_metadata_init(buffer, dataLen) else {
|
||||
throw RemoteProcessError.heapIterationFailed
|
||||
}
|
||||
try self.process.writeMem(remoteAddr: remoteDataAddr, localAddr: buffer, len: UInt(dataLen))
|
||||
|
||||
var regs = try ptrace.getRegSet()
|
||||
regs.step(RegisterSet.trapInstructionSize)
|
||||
|
||||
try ptrace.setRegSet(regSet: regs)
|
||||
try ptrace.cont()
|
||||
}
|
||||
|
||||
try self.process.readMem(remoteAddr: remoteDataAddr, localAddr: buffer, len: UInt(dataLen))
|
||||
allocations.append(contentsOf: try self.processHeapMetadata(buffer: buffer, len: dataLen))
|
||||
|
||||
return allocations
|
||||
}
|
||||
|
||||
// Process heap metadata generated by our malloc_iterate callback in the
|
||||
// remote process and return an array of (base, len) pairs describing each
|
||||
// heap allocation.
|
||||
internal func processHeapMetadata(buffer: UnsafeMutableRawPointer, len: Int) throws -> [(
|
||||
base: UInt64, len: UInt64
|
||||
)] {
|
||||
let callback: @convention(c) (UnsafeMutableRawPointer?, UInt64, UInt64) -> Void = {
|
||||
let allocationsPointer = $0!.assumingMemoryBound(to: [(UInt64, UInt64)].self)
|
||||
allocationsPointer.pointee.append(($1, $2))
|
||||
}
|
||||
|
||||
var allocations: [(UInt64, UInt64)] = []
|
||||
try withUnsafeMutablePointer(to: &allocations) {
|
||||
let context = UnsafeMutableRawPointer($0)
|
||||
if !heap_iterate_metadata_process(buffer, Int(len), context, callback) {
|
||||
throw RemoteProcessError.heapIterationFailed
|
||||
}
|
||||
}
|
||||
|
||||
return allocations
|
||||
}
|
||||
|
||||
// call mmap in the remote process with the provided arguments
|
||||
internal func mmapRemote(_ ptrace: borrowing PTrace, len: Int, prot: Int32, flags: Int32) throws
|
||||
-> UInt64
|
||||
{
|
||||
guard let sym = self.mmapSymbol.addr else {
|
||||
throw RemoteProcessError.missingSymbol(self.mmapSymbol.name)
|
||||
}
|
||||
let args = [0, UInt64(len), UInt64(prot), UInt64(flags)]
|
||||
return try ptrace.jump(to: sym, with: args)
|
||||
}
|
||||
|
||||
// call munmap in the remote process with the provdied arguments
|
||||
internal func munmapRemote(_ ptrace: borrowing PTrace, addr: UInt64, len: Int) throws -> UInt64 {
|
||||
guard let sym = self.munmapSymbol.addr else {
|
||||
throw RemoteProcessError.missingSymbol(self.munmapSymbol.name)
|
||||
}
|
||||
let args: [UInt64] = [addr, UInt64(len)]
|
||||
return try ptrace.jump(to: sym, with: args)
|
||||
}
|
||||
|
||||
// call malloc_disable in the remote process
|
||||
internal func mallocDisableRemote(_ ptrace: borrowing PTrace) throws {
|
||||
guard let sym = self.mallocDisableSymbol.addr else {
|
||||
throw RemoteProcessError.missingSymbol(self.mallocDisableSymbol.name)
|
||||
}
|
||||
_ = try ptrace.jump(to: sym)
|
||||
}
|
||||
|
||||
// call malloc_enable in the remote process
|
||||
internal func mallocEnableRemote(_ ptrace: borrowing PTrace) throws {
|
||||
guard let sym = self.mallocEnableSymbol.addr else {
|
||||
throw RemoteProcessError.missingSymbol(self.mallocEnableSymbol.name)
|
||||
}
|
||||
_ = try ptrace.jump(to: sym)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -10,7 +10,7 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if os(Linux)
|
||||
#if os(Linux) || os(Android)
|
||||
import Foundation
|
||||
import SwiftInspectLinux
|
||||
import SwiftRemoteMirror
|
||||
|
||||
@@ -168,6 +168,31 @@ internal func getRemoteProcess(processId: ProcessIdentifier,
|
||||
return LinuxRemoteProcess(processId: processId)
|
||||
}
|
||||
|
||||
#elseif os(Android)
|
||||
import Foundation
|
||||
|
||||
internal typealias ProcessIdentifier = AndroidRemoteProcess.ProcessIdentifier
|
||||
|
||||
internal func process(matching: String) -> ProcessIdentifier? {
|
||||
guard let processId = AndroidRemoteProcess.ProcessIdentifier(matching) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let procfsPath = "/proc/\(processId)"
|
||||
var isDirectory: Bool = false
|
||||
guard FileManager.default.fileExists(atPath: procfsPath, isDirectory: &isDirectory)
|
||||
&& isDirectory else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return processId
|
||||
}
|
||||
|
||||
internal func getRemoteProcess(processId: ProcessIdentifier,
|
||||
options: UniversalOptions) -> (any RemoteProcess)? {
|
||||
return AndroidRemoteProcess(processId: processId)
|
||||
}
|
||||
|
||||
#else
|
||||
#error("Unsupported platform")
|
||||
#endif
|
||||
|
||||
@@ -22,7 +22,14 @@ extension DefaultStringInterpolation {
|
||||
|
||||
enum Std {
|
||||
struct File: TextOutputStream {
|
||||
var underlying: UnsafeMutablePointer<FILE>
|
||||
|
||||
#if os(Android)
|
||||
typealias File = OpaquePointer
|
||||
#else
|
||||
typealias File = UnsafeMutablePointer<FILE>
|
||||
#endif
|
||||
|
||||
var underlying: File
|
||||
|
||||
mutating func write(_ string: String) {
|
||||
fputs(string, underlying)
|
||||
@@ -34,4 +41,4 @@ enum Std {
|
||||
|
||||
internal func disableStdErrBuffer() {
|
||||
setvbuf(stderr, nil, Int32(_IONBF), 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ internal struct SwiftInspect: ParsableCommand {
|
||||
DumpArrays.self,
|
||||
DumpConcurrency.self,
|
||||
]
|
||||
#elseif os(Windows)
|
||||
#elseif os(Windows) || os(Android)
|
||||
static let subcommands: [ParsableCommand.Type] = [
|
||||
DumpConformanceCache.self,
|
||||
DumpRawMetadata.self,
|
||||
|
||||
Reference in New Issue
Block a user