Skip to content

Commit 0ca638c

Browse files
authored
Update DebugClient into OpenGraphShims (#160)
1 parent b84178f commit 0ca638c

File tree

10 files changed

+238
-157
lines changed

10 files changed

+238
-157
lines changed

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ if attributeGraphCondition {
262262
}
263263
} else {
264264
openGraphShimsTarget.dependencies.append("OpenGraph")
265+
package.platforms = [.iOS(.v13), .macOS(.v10_15), .macCatalyst(.v13), .tvOS(.v13), .watchOS(.v5)]
265266
}
266267

267268
let compatibilityTestCondition = envEnable("OPENGRAPH_COMPATIBILITY_TEST")

Sources/OpenGraphCxx/DebugServer/DebugServer.mm

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
#include <errno.h>
2929
#include <assert.h>
3030

31+
OG_EXTERN_C_BEGIN
32+
// DYLD_INTERPOSE does not work. Directly use the hook one here to match the semantics.
33+
bool og_variant_has_internal_diagnostics(const char *subsystem);
34+
OG_EXTERN_C_END
35+
3136
// MARK: DebugServer public API Implementation
3237

3338
OG::DebugServer::DebugServer(OGDebugServerMode mode) {
@@ -260,7 +265,7 @@
260265
if (
261266
(mode & OGDebugServerModeValid)
262267
&& !OG::DebugServer::has_shared_server()
263-
/*&& os_variant_has_internal_diagnostics("com.apple.AttributeGraph")*/
268+
&& og_variant_has_internal_diagnostics("org.OpenSwiftUIProject.OpenGraph")
264269
) {
265270
_shared_server = new DebugServer(mode);
266271
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// interpose.c
3+
// OpenGraphCxx
4+
5+
#include <OpenGraph/OGBase.h>
6+
#include "stdio.h"
7+
#include "stdbool.h"
8+
#include "string.h"
9+
10+
#if OG_TARGET_OS_DARWIN
11+
extern bool os_variant_has_internal_diagnostics(const char *subsystem);
12+
#endif
13+
14+
bool og_variant_has_internal_diagnostics(const char *subsystem) {
15+
if (strcmp(subsystem, "org.OpenSwiftUIProject.OpenGraph") == 0) {
16+
return true;
17+
} else if (strcmp(subsystem, "com.apple.AttributeGraph") == 0) {
18+
return true;
19+
} else {
20+
#if OG_TARGET_OS_DARWIN
21+
return os_variant_has_internal_diagnostics(subsystem);
22+
#else
23+
return false;
24+
#endif
25+
}
26+
}
27+
28+
#if OG_TARGET_OS_DARWIN
29+
#define DYLD_INTERPOSE(_replacement,_replacee) \
30+
__attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee \
31+
__attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee };
32+
33+
DYLD_INTERPOSE(og_variant_has_internal_diagnostics, os_variant_has_internal_diagnostics)
34+
#endif
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
//
2+
// DebugServerTests.swift
3+
// OpenGraphShims
4+
5+
#if canImport(Darwin)
6+
public import Foundation
7+
public import Network
8+
9+
public struct ConnectionUpdates: AsyncSequence {
10+
public typealias Element = NWConnection.State
11+
12+
private let stream: AsyncStream<NWConnection.State>
13+
14+
fileprivate init(stream: AsyncStream<NWConnection.State>) {
15+
self.stream = stream
16+
}
17+
18+
public func makeAsyncIterator() -> AsyncStream<NWConnection.State>.AsyncIterator {
19+
stream.makeAsyncIterator()
20+
}
21+
}
22+
23+
@_spi(Debug)
24+
public final class DebugClient {
25+
public enum Command: String, CaseIterable, Hashable {
26+
case graphDescription = "graph/description"
27+
case profilerStart = "profiler/start"
28+
case profilerStop = "profiler/stop"
29+
case profilerReset = "profiler/reset"
30+
case profilerMark = "profiler/mark"
31+
}
32+
33+
private var connection: NWConnection?
34+
private let queue = DispatchQueue(label: "org.openswiftuiproject.opengraph.debugclient")
35+
36+
public init() {}
37+
38+
public func connect(to url: URL) -> ConnectionUpdates {
39+
guard let host = url.host, let port = url.port else {
40+
return ConnectionUpdates(stream: AsyncStream { continuation in
41+
continuation.yield(.failed(NWError.posix(.EINVAL)))
42+
continuation.finish()
43+
})
44+
}
45+
let nwHost = NWEndpoint.Host(host)
46+
let nwPort = NWEndpoint.Port(integerLiteral: UInt16(port))
47+
connection = NWConnection(host: nwHost, port: nwPort, using: .tcp)
48+
let stream = AsyncStream<NWConnection.State> { continuation in
49+
connection?.stateUpdateHandler = { state in
50+
continuation.yield(state)
51+
if case .cancelled = state {
52+
continuation.finish()
53+
}
54+
}
55+
connection?.start(queue: queue)
56+
}
57+
return ConnectionUpdates(stream: stream)
58+
}
59+
60+
public func sendMessage(token: UInt32, data: Data) async throws {
61+
guard let connection else {
62+
throw ClientError.notConnected
63+
}
64+
let header = DebugServerMessageHeader(
65+
token: token,
66+
unknown: 0,
67+
length: numericCast(data.count),
68+
unknown2: 0
69+
)
70+
let headerData = withUnsafePointer(to: header) {
71+
Data(bytes: UnsafeRawPointer($0), count: MemoryLayout<DebugServerMessageHeader>.size)
72+
}
73+
try await send(data: headerData, on: connection)
74+
guard header.length > 0 else {
75+
return
76+
}
77+
try await send(data: data, on: connection)
78+
}
79+
80+
public func receiveMessage() async throws -> (header: DebugServerMessageHeader, data: Data) {
81+
guard let connection = connection else {
82+
throw ClientError.notConnected
83+
}
84+
let headerData = try await receive(
85+
length: MemoryLayout<DebugServerMessageHeader>.size,
86+
from: connection
87+
)
88+
let header = headerData.withUnsafeBytes { bytes in
89+
let buffer = bytes.bindMemory(to: UInt32.self)
90+
return DebugServerMessageHeader(
91+
token: buffer[0],
92+
unknown: buffer[1],
93+
length: buffer[2],
94+
unknown2: buffer[3]
95+
)
96+
}
97+
guard header.length > 0 else {
98+
return (header: header, data: Data())
99+
}
100+
let payloadData = try await receive(
101+
length: numericCast(header.length),
102+
from: connection
103+
)
104+
return (header: header, data: payloadData)
105+
}
106+
107+
public func disconnect() {
108+
connection?.cancel()
109+
connection = nil
110+
}
111+
112+
private func send(data: Data, on connection: NWConnection) async throws {
113+
return try await withCheckedThrowingContinuation { continuation in
114+
connection.send(content: data, completion: .contentProcessed { error in
115+
if let error {
116+
continuation.resume(throwing: error)
117+
} else {
118+
continuation.resume()
119+
}
120+
})
121+
}
122+
}
123+
124+
private func receive(length: Int, from connection: NWConnection) async throws -> Data {
125+
return try await withCheckedThrowingContinuation { continuation in
126+
connection.receive(minimumIncompleteLength: length, maximumLength: length) { data, _, isComplete, error in
127+
if let error {
128+
continuation.resume(throwing: error)
129+
} else if let data {
130+
continuation.resume(returning: data)
131+
} else {
132+
continuation.resume(throwing: ClientError.noDataReceived)
133+
}
134+
}
135+
}
136+
}
137+
}
138+
139+
enum ClientError: Error {
140+
case invalidURL
141+
case notConnected
142+
case connectionCancelled
143+
case noDataReceived
144+
}
145+
146+
#endif
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// DebugServerMessageHeader.swift
3+
// OpenGraphShims
4+
5+
@_spi(Debug)
6+
public struct DebugServerMessageHeader {
7+
public let token: UInt32
8+
public let unknown: UInt32
9+
public let length: UInt32
10+
public let unknown2: UInt32
11+
12+
public init(token: UInt32, unknown: UInt32, length: UInt32, unknown2: UInt32) {
13+
self.token = token
14+
self.unknown = unknown
15+
self.length = length
16+
self.unknown2 = unknown2
17+
}
18+
}

Tests/OpenGraphCompatibilityTests/Debug/DebugServerTests.swift

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ struct DebugServerTests {
1515
#expect(DebugServer.copyURL() == nil)
1616
}
1717

18-
// TODO: hook via private API of dyld
1918
// To make AG start debugServer, we need to pass internal_diagnostics check.
2019
// In debug mode, we can breakpoint on `_ZN2AG11DebugServer5startEj` and
2120
// executable `reg write w0 1` after `internal_diagnostics` call.
22-
// Or we can disable SIP on the target darwinOS and run `sudo sysctl kern.osvariant_status=xx` to workaround
23-
@Test(
24-
.disabled(if: compatibilityTestEnabled, "Skip on AG due to internal_diagnostics check"),
25-
)
21+
// Or we can disable SIP on the target darwinOS and run `sudo sysctl kern.osvariant_status=xx` to workaround.
22+
// Or you can add `breakpoint set -n os_variant_has_internal_diagnostics -C "thread return 1"`
23+
// to your lldbinit or run it before AGDebugServerStart call.
24+
@Test(.disabled(if: compatibilityTestEnabled, "Skip on AG on CI due to internal_diagnostics check"))
2625
func testMode1() throws {
2726
let _ = try #require(DebugServer.start(mode: [.valid]))
2827
let url = try #require(DebugServer.copyURL()) as URL
@@ -33,9 +32,7 @@ struct DebugServerTests {
3332
DebugServer.stop()
3433
}
3534

36-
@Test(
37-
.disabled(if: compatibilityTestEnabled, "Skip on AG due to internal_diagnostics check"),
38-
)
35+
@Test(.disabled(if: compatibilityTestEnabled, "Skip on AG on CI due to internal_diagnostics check"))
3936
func testMode3() throws {
4037
let _ = try #require(DebugServer.start(mode: [.valid, .networkInterface]))
4138
let url = try #require(DebugServer.copyURL()) as URL

Tests/OpenGraphCxxTests/DebugServer/DebugClient.swift

Lines changed: 0 additions & 127 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../Sources/OpenGraphShims/DebugClient.swift

0 commit comments

Comments
 (0)