Skip to content

[swift-inspect] implement Android support including remote heap iteration #78275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f981f35
[swift-inspect] full Android implementation including heap iteration
andrurogerz Nov 11, 2024
e90f5c5
[swift-inspect] refactor heap iteration
andrurogerz Dec 9, 2024
43c9623
[swift-inspect] misc cleanup
andrurogerz Dec 9, 2024
d7e8c74
[swift-inspect] add ptrace retries
andrurogerz Dec 10, 2024
21ea119
[swift-inspect] fix Android README.md
andrurogerz Dec 10, 2024
714ce52
[swift-inspect] indent RegisterSet.swift
andrurogerz Dec 10, 2024
25da1a9
[swift-inspect] use malloc_disable/malloc_enable when iterating the heap
andrurogerz Dec 10, 2024
ac3fec1
[swift-inspect] move Android-specific memory region naming to Android
andrurogerz Dec 10, 2024
0a436ba
[swift-inspect] minor cleanup
andrurogerz Dec 10, 2024
03dae5d
[swift-inspect] minor cleanup and documentation
andrurogerz Dec 10, 2024
c0257d8
add missing copyright header to RegisterSet.swift
andrurogerz Dec 17, 2024
9fdd46f
refactor remote function calls to their own methods
andrurogerz Dec 18, 2024
7b10cc8
PR feedback: README.md updates
andrurogerz Jan 6, 2025
48d6237
PR Feedback: use __builtin_debugtrap and safe function size calculation
andrurogerz Jan 6, 2025
5488f2f
PR Feedback: improve PTrace implementation
andrurogerz Jan 6, 2025
9604994
PR Feedback: make PTrace a non-copyable struct
andrurogerz Jan 6, 2025
5cc83cc
Merge branch 'main' into swift-inspect-android
andrurogerz Jan 6, 2025
61cb0c3
PR Feedback
andrurogerz Jan 7, 2025
0ca4971
Merge branch 'swift-inspect-android' of github.com:andrurogerz/swift …
andrurogerz Jan 7, 2025
21d139d
missed a __volatile__
andrurogerz Jan 7, 2025
5dd761a
force 8-byte alignment on heap_iterate_callback_end
andrurogerz Jan 8, 2025
b997bdb
Define a new text section and use a linker script to safely calculat…
andrurogerz Jan 12, 2025
a4a7384
add CMake build support for Android
andrurogerz Jan 6, 2025
e6dc459
remove custom linker script in favor of implicit section maker symbols
andrurogerz Jan 13, 2025
4a9087c
update Android build command in README.md for newst Swift Android SDK
andrurogerz Jan 14, 2025
6381de4
PR feedback: fix incorrect comment
andrurogerz Jan 15, 2025
f2649f2
PR Feedback: make PTrace.init fileprivate
andrurogerz Jan 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions tools/swift-inspect/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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()
Expand Down
8 changes: 7 additions & 1 deletion tools/swift-inspect/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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(
Expand Down
41 changes: 41 additions & 0 deletions tools/swift-inspect/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.

Expand Down
120 changes: 120 additions & 0 deletions tools/swift-inspect/Sources/AndroidCLib/heap.c
Original file line number Diff line number Diff line change
@@ -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 changes: 40 additions & 0 deletions tools/swift-inspect/Sources/AndroidCLib/include/heap.h
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module AndroidCLib {
header "heap.h"
export *
}
Loading