Skip to content

Commit 5f0bd9b

Browse files
authored
Merge pull request #78275 from andrurogerz/swift-inspect-android
[swift-inspect] implement Android support including remote heap iteration
2 parents cb47eb1 + f2649f2 commit 5f0bd9b

25 files changed

+999
-15
lines changed

tools/swift-inspect/CMakeLists.txt

+21-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.28)
22

33
project(swift-inspect
4-
LANGUAGES CXX Swift)
4+
LANGUAGES C CXX Swift)
55

66
# Set C++ standard
77
set(CMAKE_CXX_STANDARD 17)
@@ -27,7 +27,17 @@ if(WIN32)
2727
Sources/SwiftInspectClient/SwiftInspectClient.cpp)
2828
target_link_libraries(SwiftInspectClient PRIVATE
2929
SwiftInspectClientInterface)
30-
elseif(LINUX)
30+
endif()
31+
32+
if (ANDROID)
33+
add_library(AndroidCLib STATIC
34+
Sources/AndroidCLib/heap.c)
35+
target_include_directories(AndroidCLib PUBLIC
36+
Sources/AndroidCLib/include)
37+
set_property(TARGET AndroidCLib PROPERTY POSITION_INDEPENDENT_CODE ON)
38+
endif()
39+
40+
if(ANDROID OR LINUX)
3141
add_library(LinuxSystemHeaders INTERFACE)
3242
target_include_directories(LinuxSystemHeaders INTERFACE
3343
Sources/SwiftInspectLinux/SystemHeaders)
@@ -38,6 +48,8 @@ elseif(LINUX)
3848
Sources/SwiftInspectLinux/MemoryMap.swift
3949
Sources/SwiftInspectLinux/Process.swift
4050
Sources/SwiftInspectLinux/ProcFS.swift
51+
Sources/SwiftInspectLinux/PTrace.swift
52+
Sources/SwiftInspectLinux/RegisterSet.swift
4153
Sources/SwiftInspectLinux/SymbolCache.swift)
4254
target_compile_options(SwiftInspectLinux PRIVATE
4355
-Xcc -D_GNU_SOURCE)
@@ -52,6 +64,7 @@ add_executable(swift-inspect
5264
Sources/swift-inspect/Operations/DumpConformanceCache.swift
5365
Sources/swift-inspect/Operations/DumpGenericMetadata.swift
5466
Sources/swift-inspect/Operations/DumpRawMetadata.swift
67+
Sources/swift-inspect/AndroidRemoteProcess.swift
5568
Sources/swift-inspect/Backtrace.swift
5669
Sources/swift-inspect/DarwinRemoteProcess.swift
5770
Sources/swift-inspect/LinuxRemoteProcess.swift
@@ -71,7 +84,12 @@ target_link_libraries(swift-inspect PRIVATE
7184
if(WIN32)
7285
target_link_libraries(swift-inspect PRIVATE
7386
SwiftInspectClientInterface)
74-
elseif(LINUX)
87+
endif()
88+
if(ANDROID)
89+
target_link_libraries(swift-inspect PRIVATE
90+
AndroidCLib)
91+
endif()
92+
if(ANDROID OR LINUX)
7593
target_link_libraries(swift-inspect PRIVATE
7694
SwiftInspectLinux)
7795
endif()

tools/swift-inspect/Package.swift

+7-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ let package = Package(
1919
.product(name: "ArgumentParser", package: "swift-argument-parser"),
2020
.target(name: "SwiftInspectClient", condition: .when(platforms: [.windows])),
2121
.target(name: "SwiftInspectClientInterface", condition: .when(platforms: [.windows])),
22-
.target(name: "SwiftInspectLinux", condition: .when(platforms: [.linux])),
22+
.target(name: "SwiftInspectLinux", condition: .when(platforms: [.linux, .android])),
23+
.target(name: "AndroidCLib", condition: .when(platforms: [.android])),
2324
],
2425
swiftSettings: [.unsafeFlags(["-parse-as-library"])]),
2526
.target(name: "SwiftInspectClient"),
@@ -32,6 +33,11 @@ let package = Package(
3233
.systemLibrary(
3334
name: "LinuxSystemHeaders",
3435
path: "Sources/SwiftInspectLinux/SystemHeaders"),
36+
.target(
37+
name: "AndroidCLib",
38+
path: "Sources/AndroidCLib",
39+
publicHeadersPath: "include",
40+
cSettings: [.unsafeFlags(["-fPIC"])]),
3541
.systemLibrary(
3642
name: "SwiftInspectClientInterface"),
3743
.testTarget(

tools/swift-inspect/README.md

+41
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,27 @@ In order to build on Linux, some additional parameters must be passed to the bui
2424
swift build -Xswiftc -I$(git rev-parse --show-toplevel)/include/swift/SwiftRemoteMirror -Xlinker -lswiftRemoteMirror
2525
~~~
2626

27+
#### Android
28+
29+
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.
30+
31+
~~~cmd
32+
set ANDROID_ARCH=aarch64
33+
set ANDROID_API_LEVEL=29
34+
set ANDROID_CLANG_VERSION=17.0.2
35+
set ANDROID_NDK_ROOT=C:\Android\android-sdk\ndk\26.3.11579264
36+
set SDKROOT_ANDROID=%LocalAppData%\Programs\Swift\Platforms\0.0.0\Android.platform\Developer\SDKs\Android.sdk
37+
swift build --triple %ANDROID_ARCH%-unknown-linux-android%ANDROID_API_LEVEL% ^
38+
--sdk %ANDROID_NDK_ROOT%\toolchains\llvm\prebuilt\windows-x86_64\sysroot ^
39+
-Xswiftc -sdk -Xswiftc %SDKROOT_ANDROID% ^
40+
-Xswiftc -sysroot -Xswiftc %ANDROID_NDK_ROOT%\toolchains\llvm\prebuilt\windows-x86_64\sysroot ^
41+
-Xswiftc -I -Xswiftc %SDKROOT_ANDROID%\usr\include ^
42+
-Xswiftc -Xclang-linker -Xswiftc -resource-dir -Xswiftc -Xclang-linker -Xswiftc %ANDROID_NDK_ROOT%\toolchains\llvm\prebuilt\windows-x86_64\lib\clang\%ANDROID_CLANG_VERSION% ^
43+
-Xlinker -L%ANDROID_NDK_ROOT%\toolchains\llvm\prebuilt\windows-x86_64\lib\clang\%ANDROID_CLANG_VERSION%\lib\linux\%ANDROID_ARCH% ^
44+
-Xcc -I%SDKROOT_ANDROID%\usr\include\swift\SwiftRemoteMirror ^
45+
-Xlinker %SDKROOT_ANDROID%\usr\lib\swift\android\%ANDROID_ARCH%\libswiftRemoteMirror.so
46+
~~~
47+
2748
#### CMake
2849

2950
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
3859
cmake -B out -G Ninja -S . -D ArgumentParser_DIR=... -D CMAKE_Swift_FLAGS="-Xcc -I$(git rev-parse --show-toplevel)/include/swift/SwiftRemoteMirror"
3960
~~~
4061

62+
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.
63+
64+
~~~cmd
65+
set ANDROID_ARCH=aarch64
66+
set ANDROID_API_LEVEL=29
67+
set ANDROID_CLANG_VERSION=17.0.2
68+
set ANDROID_NDK_ROOT=C:\Android\android-sdk\ndk\26.3.11579264
69+
set ANDROID_ARCH_ABI=arm64-v8a
70+
set SDKROOT_ANDROID=%LocalAppData%\Programs\Swift\Platforms\0.0.0\Android.platform\Developer\SDKs\Android.sdk
71+
cmake -B build -S . -G Ninja ^
72+
-D CMAKE_BUILD_WITH_INSTALL_RPATH=YES ^
73+
-D CMAKE_SYSTEM_NAME=Android ^
74+
-D CMAKE_ANDROID_ARCH_ABI=%ANDROID_ARCH_ABI% ^
75+
-D CMAKE_SYSTEM_VERSION=%ANDROID_API_LEVEL% ^
76+
-D CMAKE_Swift_COMPILER_TARGET=%ANDROID_ARCH%-unknown-linux-android%ANDROID_API_LEVEL% ^
77+
-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" ^
78+
-D ArgumentParser_DIR=...
79+
cmake --build build
80+
~~~
81+
4182
Building with CMake requires a local copy of [swift-argument-parser](https://github.com/apple/swift-argument-parser) built with CMake.
4283
The `ArumentParser_DIR=` definition must refer to the `cmake/modules` sub-directory of the swift-argument-parser build output directory.
4384

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include <string.h>
14+
15+
#include "heap.h"
16+
17+
/* The heap metadata buffer is interpreted as an array of 8-byte pairs. The
18+
* first pair contains metadata describing the buffer itself: max valid index
19+
* (e.g. size of the buffer) and next index (e.g. write cursor/position). Each
20+
* subsequent pair describes the address and length of a heap entry in the
21+
* remote process. A 4KiB page provides sufficient space for the header and
22+
* 255 (address, length) pairs.
23+
*
24+
* ------------
25+
* | uint64_t | max valid index (e.g. sizeof(buffer) / sizeof(uint64_t))
26+
* ------------
27+
* | uint64_t | next free index (starts at 2)
28+
* ------------
29+
* | uint64_t | heap item 1 address
30+
* ------------
31+
* | uint64_t | heap item 1 size
32+
* ------------
33+
* | uint64_t | heap item 2 address
34+
* ------------
35+
* | uint64_t | heap item 2 size
36+
* ------------
37+
* | uint64_t | ...
38+
* ------------
39+
* | uint64_t | ...
40+
* ------------
41+
* | uint64_t | heap item N address
42+
* ------------
43+
* | uint64_t | heap item N size
44+
* ------------
45+
*/
46+
47+
#if !__has_builtin(__builtin_debugtrap)
48+
#error("compiler support for __builtin_debugtrap is required")
49+
#endif
50+
51+
#define MAX_VALID_IDX 0
52+
#define NEXT_FREE_IDX 1
53+
#define HEADER_SIZE 2
54+
#define ENTRY_SIZE 2
55+
56+
// Callback for malloc_iterate. Because this function is meant to be copied to
57+
// a different process for execution, it must not make any function calls to
58+
// ensure compiles to simple, position-independent code. It is implemented in C
59+
// for readability/maintainability. It is placed in its own code section to
60+
// simplify calculating its size.
61+
__attribute__((noinline, used, section("heap_iterator")))
62+
static void heap_iterate_callback(unsigned long base, unsigned long size, void *arg) {
63+
volatile uint64_t *data = (uint64_t*)arg;
64+
while (data[NEXT_FREE_IDX] >= data[MAX_VALID_IDX]) {
65+
// SIGTRAP indicates the buffer is full and needs to be drained before more
66+
// entries can be written.
67+
__builtin_debugtrap();
68+
69+
// After the SIGTRAP, the signal handler advances the instruction pointer
70+
// (PC) to the next instruction. Inserting a nop instruction here ensures
71+
// the CPU has a clear, executable instruction to process, which avoids
72+
// potential speculative execution or pipeline issues that could arise if
73+
// the next instruction were a control transfer like a branch or jump.
74+
__asm__ __volatile__("nop");
75+
}
76+
data[data[NEXT_FREE_IDX]++] = base;
77+
data[data[NEXT_FREE_IDX]++] = size;
78+
}
79+
80+
// The linker implicitly defines __start- and __stop- prefixed symbols that mark
81+
// the start and end of user defined sections.
82+
extern char __stop_heap_iterator[];
83+
84+
void* heap_iterate_callback_start() {
85+
return (void*)heap_iterate_callback;
86+
}
87+
88+
size_t heap_iterate_callback_len() {
89+
return (uintptr_t)__stop_heap_iterator - (uintptr_t)heap_iterate_callback;
90+
}
91+
92+
bool heap_iterate_metadata_init(void* data, size_t len) {
93+
uint64_t *metadata = data;
94+
const uint64_t max_entries = len / sizeof(uint64_t);
95+
if (max_entries < HEADER_SIZE + ENTRY_SIZE)
96+
return false;
97+
98+
memset(data, 0, len);
99+
metadata[MAX_VALID_IDX] = max_entries;
100+
metadata[NEXT_FREE_IDX] = HEADER_SIZE;
101+
return true;
102+
}
103+
104+
bool heap_iterate_metadata_process(
105+
void* data, size_t len, void* callback_context, heap_iterate_entry_callback_t callback) {
106+
uint64_t *metadata = data;
107+
const uint64_t max_entries = len / sizeof(uint64_t);
108+
const uint64_t end_index = metadata[NEXT_FREE_IDX];
109+
110+
if (metadata[MAX_VALID_IDX] != max_entries || end_index > max_entries)
111+
return false;
112+
113+
for (size_t i = HEADER_SIZE; i < end_index; i += ENTRY_SIZE) {
114+
const uint64_t base = metadata[i];
115+
const uint64_t size = metadata[i + 1];
116+
callback(callback_context, base, size);
117+
}
118+
119+
return true;
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#pragma once
14+
15+
#include <stdbool.h>
16+
#include <stdint.h>
17+
18+
#if defined(__cplusplus)
19+
extern "C" {
20+
#endif
21+
22+
// Location of the heap_iterate callback.
23+
void* heap_iterate_callback_start();
24+
25+
// Size of the heap_iterate callback.
26+
size_t heap_iterate_callback_len();
27+
28+
// Initialize the provided buffer to receive heap iteration metadata.
29+
bool heap_iterate_metadata_init(void* data, size_t len);
30+
31+
// Callback invoked by heap_iterate_data_process for each heap entry .
32+
typedef void (*heap_iterate_entry_callback_t)(void* context, uint64_t base, uint64_t len);
33+
34+
// Process all heap iteration entries in the provided buffer.
35+
bool heap_iterate_metadata_process(
36+
void* data, size_t len, void* callback_context, heap_iterate_entry_callback_t callback);
37+
38+
#if defined(__cplusplus)
39+
}
40+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module AndroidCLib {
2+
header "heap.h"
3+
export *
4+
}

0 commit comments

Comments
 (0)