Skip to content

Commit e90f5c5

Browse files
committed
[swift-inspect] refactor heap iteration
1 parent f981f35 commit e90f5c5

File tree

6 files changed

+161
-118
lines changed

6 files changed

+161
-118
lines changed

tools/swift-inspect/Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ let package = Package(
3636
.target(
3737
name: "AndroidCLib",
3838
path: "Sources/AndroidCLib",
39-
publicHeadersPath: "include"),
39+
publicHeadersPath: "include",
40+
cSettings: [.unsafeFlags(["-fPIC"])]),
4041
.systemLibrary(
4142
name: "SwiftInspectClientInterface"),
4243
.testTarget(

tools/swift-inspect/Sources/AndroidCLib/heap.c

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,16 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#include "heap.h"
13+
#include <string.h>
1414

15-
#if defined(__aarch64__) || defined(__ARM64__) || defined(_M_ARM64)
16-
#define DEBUG_BREAK() asm("brk #0x0; nop")
17-
#elif defined(_M_X64) || defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
18-
#define DEBUG_BREAK() asm("int3; nop")
19-
#else
20-
#error("only aarch64 and x86_64 are supported")
21-
#endif
15+
#include "heap.h"
2216

23-
/* We allocate a buffer in the remote process that it populates with metadata
24-
* describing each heap entry it enumerates. We then read the contents of the
25-
* buffer, and individual heap entry contents, with process_vm_readv.
26-
*
27-
* The buffer is interpreted as an array of 8-byte pairs. The first pair
28-
* contains metadata describing the buffer itself: max valid index (e.g. size of
29-
* the buffer) and next index (e.g. write cursor/position). Each subsequent pair
30-
* describes the address and length of a heap entry in the remote process.
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.
3123
*
3224
* ------------
3325
* | uint64_t | max valid index (e.g. sizeof(buffer) / sizeof(uint64_t))
@@ -52,26 +44,71 @@
5244
* ------------
5345
*/
5446

55-
// NOTE: this function cannot call any other functions and must only use
56-
// relative branches. We could inline assembly instead, but C is more reabable
57-
// and maintainable.
58-
static void remote_callback_start(unsigned long base, unsigned long size, void *arg) {
47+
#define MAX_VALID_IDX 0
48+
#define NEXT_FREE_IDX 1
49+
#define HEADER_SIZE 2
50+
#define ENTRY_SIZE 2
51+
52+
#if defined(__aarch64__) || defined(__ARM64__) || defined(_M_ARM64)
53+
#define DEBUG_BREAK() asm("brk #0x0")
54+
#elif defined(_M_X64) || defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
55+
#define DEBUG_BREAK() asm("int3; nop")
56+
#else
57+
#error("only aarch64 and x86_64 are supported")
58+
#endif
59+
60+
// Callback for heap_iterate. Because this function is meant to be copied to
61+
// a different process for execution, it must not make any function calls. It
62+
// could be written as asm, but simple C is more readable/maintainable and
63+
// should consistently compile to movable, position-independent code.
64+
static void heap_iterate_callback(unsigned long base, unsigned long size, void *arg) {
5965
volatile uint64_t *data = (uint64_t*)arg;
60-
while (data[HEAP_ITERATE_DATA_NEXT_FREE_IDX] >= data[HEAP_ITERATE_DATA_MAX_VALID_IDX]) {
66+
while (data[NEXT_FREE_IDX] >= data[MAX_VALID_IDX]) {
67+
// SIGTRAP indicates the buffer is full and needs to be drained before more
68+
// entries can be written.
6169
DEBUG_BREAK();
6270
}
63-
data[data[HEAP_ITERATE_DATA_NEXT_FREE_IDX]++] = base;
64-
data[data[HEAP_ITERATE_DATA_NEXT_FREE_IDX]++] = size;
71+
data[data[NEXT_FREE_IDX]++] = base;
72+
data[data[NEXT_FREE_IDX]++] = size;
6573
}
6674

67-
// NOTE: this function is here to mark the end of remote_callback_start and is never
68-
// called.
69-
static void remote_callback_end() {}
75+
// Placeholer function to mark the end of the remote callback code.
76+
static void heap_iterate_callback_end() {}
77+
78+
void* heap_iterate_callback_start() {
79+
return (void*)heap_iterate_callback;
80+
}
7081

71-
void* heap_callback_start() {
72-
return (void*)remote_callback_start;
82+
size_t heap_iterate_callback_len() {
83+
return (size_t)(heap_iterate_callback_end - heap_iterate_callback);
7384
}
7485

75-
size_t heap_callback_len() {
76-
return (size_t)(remote_callback_end - remote_callback_start);
86+
bool heap_iterate_metadata_init(void* data, size_t len) {
87+
uint64_t *metadata = data;
88+
const uint64_t max_entries = len / sizeof(uint64_t);
89+
if (max_entries < HEADER_SIZE + ENTRY_SIZE)
90+
return false;
91+
92+
memset(data, 0, len);
93+
metadata[MAX_VALID_IDX] = max_entries;
94+
metadata[NEXT_FREE_IDX] = HEADER_SIZE;
95+
return true;
7796
}
97+
98+
bool heap_iterate_metadata_process(
99+
void* data, size_t len, void* callback_context, heap_iterate_entry_callback_t callback) {
100+
uint64_t *metadata = data;
101+
const uint64_t max_entries = len / sizeof(uint64_t);
102+
103+
if (metadata[MAX_VALID_IDX] != max_entries ||
104+
metadata[NEXT_FREE_IDX] > max_entries)
105+
return false;
106+
107+
for (size_t i = HEADER_SIZE; i < metadata[NEXT_FREE_IDX]; i += ENTRY_SIZE) {
108+
const uint64_t base = metadata[i];
109+
const uint64_t size = metadata[i + 1];
110+
callback(callback_context, base, size);
111+
}
112+
113+
return true;
114+
}

tools/swift-inspect/Sources/AndroidCLib/include/heap.h

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,26 @@
1414

1515
#include <stdbool.h>
1616
#include <stdint.h>
17-
#include <unistd.h>
1817

1918
#if defined(__cplusplus)
2019
extern "C" {
2120
#endif
2221

23-
#define HEAP_ITERATE_DATA_MAX_VALID_IDX 0
24-
#define HEAP_ITERATE_DATA_NEXT_FREE_IDX 1
25-
#define HEAP_ITERATE_DATA_HEADER_SIZE 2
26-
#define HEAP_ITERATE_DATA_ENTRY_SIZE 2
22+
// Location of the heap_iterate callback.
23+
void* heap_iterate_callback_start();
2724

28-
typedef void (*heap_iterate_callback_t)(void* context, uint64_t base, uint64_t len);
29-
bool heap_iterate(pid_t pid, void* callback_context, heap_iterate_callback_t callback);
25+
// Size of the heap_iterate callback.
26+
size_t heap_iterate_callback_len();
3027

31-
void* heap_callback_start();
32-
size_t heap_callback_len();
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);
3337

3438
#if defined(__cplusplus)
3539
}

tools/swift-inspect/Sources/SwiftInspectLinux/MemoryMap.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ public class MemoryMap {
2525
public let device: String
2626
public let inode: UInt64
2727
public let pathname: String?
28+
29+
public func isHeapRegion() -> Bool {
30+
guard let name = self.pathname else { return false }
31+
if name == "[anon:libc_malloc]" { return true }
32+
if name.hasPrefix("[anon:scudo:") { return true }
33+
if name.hasPrefix("[anon:GWP-ASan") { return true }
34+
return false;
35+
}
2836
}
2937

3038
public let entries: [Entry]

tools/swift-inspect/Sources/SwiftInspectLinux/Process.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public class Process {
8686
public func readArray<T>(address: UInt64, upToCount: UInt) throws -> [T] {
8787
guard upToCount > 0 else { return [] }
8888

89+
// TODO: impement using readMem
8990
let maxSize = upToCount * UInt(MemoryLayout<T>.stride)
9091
let array: [T] = Array(unsafeUninitializedCapacity: Int(upToCount)) { buffer, initCount in
9192
var local = iovec(iov_base: buffer.baseAddress!, iov_len: maxSize)
@@ -102,6 +103,16 @@ public class Process {
102103
return array
103104
}
104105

106+
public func readMem(remoteAddr: UInt64, localAddr: UnsafeRawPointer, len: UInt) throws {
107+
var local = iovec(iov_base: UnsafeMutableRawPointer(mutating: localAddr), iov_len: len)
108+
var remote = iovec(iov_base: UnsafeMutableRawPointer(bitPattern: UInt(remoteAddr)), iov_len: len)
109+
110+
let bytesRead = process_vm_readv(self.pid, &local, 1, &remote, 1, 0)
111+
guard bytesRead == len else {
112+
throw ProcessError.processVmReadFailure(pid: self.pid, address: remoteAddr, size: UInt64(len))
113+
}
114+
}
115+
105116
public func writeMem(remoteAddr: UInt64, localAddr: UnsafeRawPointer, len: UInt) throws {
106117
var local = iovec(iov_base: UnsafeMutableRawPointer(mutating: localAddr), iov_len: len)
107118
var remote = iovec(iov_base: UnsafeMutableRawPointer(bitPattern: UInt(remoteAddr)), iov_len: len)

0 commit comments

Comments
 (0)