rea-agents 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -650
- package/bridge/mitmproxy/capture.py +210 -0
- package/bridge/native/rea_lldb_tracer.py +303 -0
- package/bridge/process/ProcessRunTokenReader.swift +263 -0
- package/bridge/process/ProcessRunTokenReaderMain.swift +35 -0
- package/bridge/pwndbg/core_context.py +101 -0
- package/bridge/pwndbg/launch.py +39 -0
- package/bridge/pwndbg/launch_impl.py +34 -0
- package/bridge/pwntools/decoder_runtime.py +59 -0
- package/bridge/pwntools/layout.py +39 -0
- package/bridge/pwntools/layout_impl.py +309 -0
- package/bridge/pwntools/recorded_crash.py +39 -0
- package/bridge/pwntools/recorded_crash_impl.py +143 -0
- package/dist/android/JadxProvider.js +1 -0
- package/dist/application/CapabilityInventory.js +21 -0
- package/dist/application/DirectAnalysis.js +97 -23
- package/dist/application/EvidenceBundleFiles.js +1 -1
- package/dist/application/ReconstructionObligationCandidates.js +5 -1
- package/dist/application/WebNetworkCapturePort.js +1 -0
- package/dist/application/WebNetworkCaptureService.js +132 -0
- package/dist/application/WindowsCapabilities.js +1 -1
- package/dist/application/binary/AnalysisSnapshotCache.js +49 -6
- package/dist/application/binary/BinarySession.js +38 -10
- package/dist/application/binary/BinarySessionOpen.js +16 -3
- package/dist/application/binary/BinarySessionRecords.js +20 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutPort.js +1 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutService.js +57 -0
- package/dist/application/binaryDiagnostics/RecordedCrashPort.js +1 -0
- package/dist/application/binaryDiagnostics/RecordedCrashService.js +60 -0
- package/dist/application/evm/EvmInterfacePort.js +1 -0
- package/dist/application/evm/EvmInterfaceService.js +62 -0
- package/dist/application/javascript/JavaScriptApplicationEvidence.js +1 -2
- package/dist/application/javascript/JavaScriptApplicationService.js +15 -2
- package/dist/application/javascript/JavaScriptArtifactAnalysis.js +42 -23
- package/dist/application/javascript/JavaScriptArtifactGraphDocuments.js +26 -2
- package/dist/application/javascript/JavaScriptArtifactPathResolution.js +74 -24
- package/dist/application/javascript/JavaScriptArtifactReconstruction.js +23 -10
- package/dist/application/javascript/JavaScriptSemanticGraphAsyncProjection.js +7 -7
- package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +145 -70
- package/dist/application/javascript/JavaScriptSemanticGraphChildProcessProjection.js +14 -15
- package/dist/application/javascript/JavaScriptSemanticGraphConstruction.js +41 -1
- package/dist/application/javascript/JavaScriptSemanticGraphDataProjection.js +7 -7
- package/dist/application/javascript/JavaScriptSemanticGraphFlowProjection.js +5 -9
- package/dist/application/javascript/JavaScriptSemanticGraphObjectProjection.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphProjection.js +118 -12
- package/dist/application/javascript/JavaScriptSemanticGraphResourceProjection.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +5 -5
- package/dist/application/{ProcessCli.js → process/ProcessCli.js} +10 -10
- package/dist/application/{ProcessEvidence.js → process/ProcessEvidence.js} +4 -4
- package/dist/artifacts/ArtifactProvider.js +30 -3
- package/dist/artifacts/ArtifactProviderMetadata.js +2 -1
- package/dist/artifacts/ZipArtifactReader.js +3 -0
- package/dist/artifacts/apple/DylibResolutionReader.js +259 -0
- package/dist/artifacts/apple/MachoLoadCommandReader.js +294 -0
- package/dist/{application → artifacts/extraction}/ArtifactExtraction.js +10 -10
- package/dist/{application → artifacts/inventory}/ArtifactGraphConstruction.js +3 -3
- package/dist/{application → artifacts/inventory}/ArtifactInventory.js +4 -4
- package/dist/{application/ArtifactInventory → artifacts/inventory}/classify.js +2 -2
- package/dist/{application/ArtifactInventory → artifacts/inventory}/reader.js +5 -5
- package/dist/{application/ArtifactInventory → artifacts/inventory}/scanCanonical.js +3 -3
- package/dist/{application/ArtifactInventory → artifacts/inventory}/scanReader.js +5 -5
- package/dist/artifacts/readStableArtifact.js +20 -4
- package/dist/browser/CdpCaptureEventHandlers.js +90 -2
- package/dist/browser/CdpCaptureEvents.js +9 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +12 -3
- package/dist/browser/history/CaptureFailures.js +101 -0
- package/dist/browser/history/CaptureFormatError.js +10 -0
- package/dist/browser/history/CaptureRedaction.js +65 -0
- package/dist/browser/history/CaptureRelease.js +12 -0
- package/dist/browser/history/HarCapture.js +165 -0
- package/dist/browser/history/HarCaptureAdapter.js +20 -0
- package/dist/browser/history/HarCaptureProcess.js +57 -0
- package/dist/browser/history/HarJson.js +91 -0
- package/dist/browser/history/HarSchema.js +72 -0
- package/dist/browser/history/HistoricalCaptureDecoder.js +186 -0
- package/dist/browser/history/HistoricalCaptureFormatAdapter.js +1 -0
- package/dist/browser/history/MitmproxyCaptureAdapter.js +42 -0
- package/dist/cli/artifactCommands.js +26 -1
- package/dist/cli/binaryDiagnosticsCommands.js +39 -0
- package/dist/cli/coreAnalysisCommands.js +2 -2
- package/dist/cli/coreBinaryCommands.js +8 -8
- package/dist/cli/electronCommands.js +16 -9
- package/dist/cli/evmCommands.js +24 -0
- package/dist/cli/firmwareCommands.js +6 -3
- package/dist/cli/javascriptApplicationAnalysis.js +16 -3
- package/dist/cli/jsonOutput.js +133 -0
- package/dist/cli/nativeCallCommands.js +35 -0
- package/dist/cli/processCommands.js +1 -1
- package/dist/cli/streamedJsonOutput.js +56 -0
- package/dist/cli/utilityCommands.js +2 -0
- package/dist/cli/webNetworkCaptureCommands.js +40 -0
- package/dist/cli.js +9 -3
- package/dist/cliBrowserScenarioCommands.js +2 -2
- package/dist/cliCommandNames.js +6 -0
- package/dist/cliJsonInput.js +30 -1
- package/dist/cliOutput.js +21 -6
- package/dist/composition/binaryDiagnostics.js +8 -0
- package/dist/composition/evm.js +4 -0
- package/dist/composition/webNetworkCaptures.js +6 -0
- package/dist/contracts/artifactToolContracts.js +6 -0
- package/dist/contracts/browserCaptureToolInputSchema.js +43 -0
- package/dist/contracts/browserToolContracts.js +4 -3
- package/dist/contracts/errorSchemas.js +8 -1
- package/dist/contracts/evm/evmToolContracts.js +20 -0
- package/dist/contracts/investigationExamples.js +2 -2
- package/dist/contracts/managed/managedToolContracts.js +7 -3
- package/dist/contracts/native/binaryDiagnosticsToolContracts.js +42 -0
- package/dist/contracts/native/nativeToolContracts.js +17 -1
- package/dist/contracts/officialToolContracts.js +12 -9
- package/dist/contracts/process/processCaptureExample.js +2 -0
- package/dist/contracts/sessionLifecycleInputs.js +22 -5
- package/dist/contracts/sessionStatusContract.js +10 -2
- package/dist/contracts/sessionToolContracts.js +8 -1
- package/dist/contracts/sessionToolSchemas.js +10 -3
- package/dist/contracts/toolContractExamples.js +1 -1
- package/dist/contracts/toolContracts.js +6 -0
- package/dist/contracts/toolEffects.js +30 -0
- package/dist/contracts/toolOutputSchemaGroups.js +5 -1
- package/dist/contracts/toolOutputSchemaPrimitives.js +1 -1
- package/dist/contracts/webNetworkCaptureToolContracts.js +24 -0
- package/dist/domain/analysisErrorBase.js +15 -0
- package/dist/domain/analysisErrorCore.js +59 -5
- package/dist/domain/analysisErrorPresentation.js +29 -1
- package/dist/domain/analysisErrorProjection.js +52 -2
- package/dist/domain/analysisSnapshot.js +166 -2
- package/dist/domain/apple/dyldPaths.js +81 -0
- package/dist/domain/apple/dylibResolution.js +367 -0
- package/dist/domain/apple/dylibResolutionFindings.js +75 -0
- package/dist/domain/artifactPathSyntax.js +1 -1
- package/dist/domain/browserObservation.js +1 -1
- package/dist/domain/browserObservationSchemas.js +13 -0
- package/dist/domain/browserScenarioValues.js +9 -2
- package/dist/domain/callPathSchemas.js +1 -1
- package/dist/domain/canonicalDigest.js +13 -1
- package/dist/domain/changedBehavior.js +3 -3
- package/dist/domain/comparisonStatus.js +2 -30
- package/dist/domain/conformanceTrustGate.js +0 -16
- package/dist/domain/evidence.js +17 -5
- package/dist/domain/evm/evmInterface.js +63 -0
- package/dist/domain/explicitSensitiveFailure.js +92 -0
- package/dist/domain/explicitSensitiveValues.js +10 -0
- package/dist/domain/firmware/firmwareAnalysis.js +8 -1
- package/dist/domain/javascript/electronActiveObservation.js +9 -4
- package/dist/domain/javascript/htmlArtifactReferences.js +40 -0
- package/dist/domain/javascript/javascriptExportShapeComparison.js +21 -5
- package/dist/domain/javascript/javascriptExportShapeVariants.js +0 -10
- package/dist/domain/javascript/javascriptSemanticGraph.js +21 -5
- package/dist/domain/jsonValue.js +4 -0
- package/dist/domain/localPath.js +2 -0
- package/dist/domain/native/binaryLayout.js +357 -0
- package/dist/domain/native/nativeCallObservation.js +164 -0
- package/dist/domain/native/objcSwiftMetadata.js +0 -39
- package/dist/domain/native/recordedCrash.js +266 -0
- package/dist/domain/native/recordedCrashNoteCoverage.js +73 -0
- package/dist/domain/{processCapture.js → process/processCapture.js} +139 -2
- package/dist/domain/{processCaptureExample.js → process/processCaptureExample.js} +5 -0
- package/dist/domain/process/processCaptureParsing.js +54 -0
- package/dist/domain/{processCaptureValidation.js → process/processCaptureValidation.js} +18 -2
- package/dist/domain/{processComparison.js → process/processComparison.js} +1 -1
- package/dist/domain/{processObservation.js → process/processObservation.js} +1 -1
- package/dist/domain/{processTraceSpecification.js → process/processTraceSpecification.js} +1 -1
- package/dist/domain/providerSelectionError.js +4 -2
- package/dist/domain/reconstructionVerification.js +3 -3
- package/dist/domain/reconstructionVerificationSchemas.js +1 -1
- package/dist/domain/residualUnknown.js +3 -2
- package/dist/domain/staticRuntimeCorrelation.js +3 -3
- package/dist/domain/webCaptureDiff.js +1 -0
- package/dist/domain/webNetworkCapture.js +118 -0
- package/dist/domain/webNetworkCaptureCoordinates.js +58 -0
- package/dist/dotnet/ManagedExceptionRegionValidation.js +41 -0
- package/dist/dotnet/ManagedMemberInspector.js +4 -2
- package/dist/dotnet/ManagedMemberInspectorCore.js +38 -32
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +23 -7
- package/dist/dotnet/ManagedMemberRows.js +14 -3
- package/dist/dotnet/ManagedMetadataHeaps.js +20 -2
- package/dist/dotnet/ManagedMetadataInventory.js +38 -28
- package/dist/dotnet/ManagedMetadataInventoryRows.js +24 -10
- package/dist/dotnet/ManagedMethodBodyReader.js +7 -1
- package/dist/dotnet/ManagedNativeBoundaryHelpers.js +15 -3
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +3 -1
- package/dist/dotnet/ManagedPeReader.js +11 -0
- package/dist/evm/EvmBytecodeCarrier.js +21 -0
- package/dist/evm/EvmWorkerLimits.js +40 -0
- package/dist/evm/EvmoleFailures.js +126 -0
- package/dist/evm/EvmoleInterfaceAdapter.js +53 -0
- package/dist/evm/EvmoleInterfaceProvider.js +273 -0
- package/dist/evm/EvmoleInterfaceWorker.js +87 -0
- package/dist/evm/EvmoleRelease.js +16 -0
- package/dist/firmware/FirmwareCommand.js +1 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraLauncher.js +1 -0
- package/dist/ghidra/GhidraProvider.js +2 -2
- package/dist/ghidra/GhidraProviderCapabilities.js +1 -1
- package/dist/hopper/BridgeLauncher.js +2 -0
- package/dist/hopper/LinuxPrivateDisplayProbe.js +4 -2
- package/dist/javascript/recovery/WakaruCommand.js +3 -0
- package/dist/javascript/sourceMaps/SourceMapCommand.js +1 -0
- package/dist/native/CommandRunner.js +2 -1
- package/dist/native/LldbCallTracer.js +278 -0
- package/dist/native/NativeCallObservation.js +135 -0
- package/dist/native/NativeMacOSProvider.js +19 -3
- package/dist/native/NativeMacOSProviderMetadata.js +8 -3
- package/dist/native/pwndbg/PwndbgCoreContext.js +160 -0
- package/dist/native/pwntools/PwntoolsDecoder.js +203 -0
- package/dist/native/pwntools/PwntoolsFailures.js +180 -0
- package/dist/native/pwntools/PwntoolsLayoutProvider.js +36 -0
- package/dist/native/pwntools/PwntoolsRecordedCrashProvider.js +75 -0
- package/dist/native/pwntools/PwntoolsRelease.js +19 -0
- package/dist/native/pwntools/PwntoolsResourceLimits.js +76 -0
- package/dist/native/pwntools/RecordedCrashSourceBindings.js +46 -0
- package/dist/native/pwntools/RecordedCrashStructureBindings.js +101 -0
- package/dist/process/DarwinProcessRunTokenReader.js +294 -0
- package/dist/process/OwnedCommand.js +113 -0
- package/dist/process/ProcessOwnership.js +263 -33
- package/dist/process/ProcessOwnershipObservation.js +161 -24
- package/dist/process/ProviderProcess.js +30 -3
- package/dist/{application → process/capture}/ProcessCaptureCapability.js +15 -0
- package/dist/{application → process/capture}/ProcessCaptureError.js +17 -1
- package/dist/process/capture/ProcessCaptureLifecycle.js +615 -0
- package/dist/{application → process/capture}/ProcessHarness.js +195 -29
- package/dist/{application → process/capture}/ProcessSampling.js +2 -2
- package/dist/server/createServer.js +22 -0
- package/dist/server/promptCompletion.js +10 -3
- package/dist/server/registerBinaryDiagnosticsTools.js +17 -0
- package/dist/server/registerEvidenceTools.js +1 -1
- package/dist/server/registerEvmTools.js +17 -0
- package/dist/server/registerProcessComparisonTool.js +1 -1
- package/dist/server/registerRecordedCrashTools.js +17 -0
- package/dist/server/registerSessionTools.js +2 -2
- package/dist/server/registerWebNetworkCaptureTool.js +28 -0
- package/dist/server/sessionAvailabilityPolicy.js +3 -0
- package/dist/server/sessionToolPolicies.js +1 -1
- package/dist/server/toolResult.js +1 -1
- package/native/windows/build/manifest.json +2 -2
- package/native/windows/build/rea-windows-x64.node +0 -0
- package/package.json +37 -9
- package/scripts/rea.mjs +11 -3
- package/skills/reverse-engineer-anything/SKILL.md +23 -3
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +29 -0
- package/third_party/README.md +60 -0
- package/third_party/evmole/LICENSE +21 -0
- package/third_party/evmole/README.md +22 -0
- package/third_party/pwndbg/LICENSE.md +21 -0
- package/third_party/pwndbg/README.md +23 -0
- package/third_party/pwntools/README.md +40 -0
- package/dist/application/AuthorizedArtifactInventory.js +0 -7
- package/dist/application/ProcessCaptureLifecycle.js +0 -299
- package/dist/application/StoreFileLock.js +0 -124
- package/dist/contracts/processCaptureExample.js +0 -2
- package/dist/domain/bytecodeProvider.js +0 -178
- package/dist/domain/customProtocolCapture.js +0 -137
- package/dist/domain/eventProcessTree.js +0 -225
- package/dist/domain/mobileApplicationInvestigation.js +0 -159
- package/dist/domain/packageAnalysis.js +0 -123
- package/dist/domain/peInspection.js +0 -197
- package/dist/domain/processCaptureParsing.js +0 -21
- package/dist/domain/protocolCapture.js +0 -217
- package/dist/domain/runtimeIdentification.js +0 -212
- package/dist/generatedMcpToolCatalog.js +0 -37
- /package/dist/application/{ArtifactExtractionDestination.js → artifacts/ArtifactExtractionDestination.js} +0 -0
- /package/dist/{application/ArtifactInventory → artifacts/inventory}/policy.js +0 -0
- /package/dist/{application/ArtifactInventory → artifacts/inventory}/types.js +0 -0
- /package/dist/domain/{processEvidenceProvider.js → process/processEvidenceProvider.js} +0 -0
- /package/dist/domain/{processScenario.js → process/processScenario.js} +0 -0
- /package/dist/domain/{processTraceComparison.js → process/processTraceComparison.js} +0 -0
- /package/dist/domain/{processTraceDimensionProjection.js → process/processTraceDimensionProjection.js} +0 -0
- /package/dist/domain/{processTraceEvaluation.js → process/processTraceEvaluation.js} +0 -0
- /package/dist/{application → process/capture}/FilesystemSnapshot.js +0 -0
- /package/dist/{application → process/capture}/ProcessCaptureEnvironment.js +0 -0
- /package/dist/{application → process/capture}/ProcessCaptureJournal.js +0 -0
- /package/dist/{application → process/capture}/ProcessFilesystemEffects.js +0 -0
- /package/dist/{application → process/capture}/ProcessNormalization.js +0 -0
- /package/dist/{application → process/capture}/ProcessScenarioRuntimeValidation.js +0 -0
- /package/dist/{application → process/capture}/ProcessTimer.js +0 -0
- /package/dist/{application → process/capture}/TerminalRenderer.js +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""REA offline adapter using mitmproxy's unchanged native tnetstring decoder.
|
|
2
|
+
|
|
3
|
+
No FlowReader migrations, HAR conversion, proxy listener, or target requests.
|
|
4
|
+
The containing mitmdump process and its configuration directory are REA-owned.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import base64
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
import math
|
|
11
|
+
import re
|
|
12
|
+
import resource
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from mitmproxy import ctx, version
|
|
16
|
+
from mitmproxy.io import tnetstring
|
|
17
|
+
|
|
18
|
+
PROFILE = "12.2.3"
|
|
19
|
+
OUTPUT_BYTES = 96 * 1024 * 1024
|
|
20
|
+
CREDENTIALS = {b"authorization", b"proxy-authorization", b"cookie", b"set-cookie"}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CaptureFailure(Exception):
|
|
24
|
+
def __init__(self, reason, message, pointer=""):
|
|
25
|
+
self.reason, self.pointer = reason, pointer
|
|
26
|
+
super().__init__(message)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def pointer(parent, key):
|
|
30
|
+
return parent + "/" + str(key).replace("~", "~0").replace("/", "~1")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BoundedReader:
|
|
34
|
+
"""Refuse malicious declared lengths before the upstream parser allocates."""
|
|
35
|
+
def __init__(self, handle, size):
|
|
36
|
+
self.handle, self.size = handle, size
|
|
37
|
+
|
|
38
|
+
def read(self, count):
|
|
39
|
+
if count < 0 or count > self.size - self.handle.tell():
|
|
40
|
+
raise CaptureFailure("format", "Native record declares bytes beyond the retained capture.")
|
|
41
|
+
return self.handle.read(count)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def project_record(state, secrets):
|
|
45
|
+
binaries, numbers, redactions = [], [], []
|
|
46
|
+
byte_secrets = []
|
|
47
|
+
for secret in secrets:
|
|
48
|
+
try:
|
|
49
|
+
byte_secrets.append(secret.encode("utf-8", errors="strict"))
|
|
50
|
+
except UnicodeEncodeError:
|
|
51
|
+
# Non-scalar JSON strings have no UTF-8 byte representation.
|
|
52
|
+
# Keep them in secrets for the ordinary string projection.
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
def redact(text, path, original):
|
|
56
|
+
if any(secret in text or secret in original for secret in secrets):
|
|
57
|
+
redactions.append({"pointer": path, "reason": "explicit-sensitive-value"})
|
|
58
|
+
return None
|
|
59
|
+
return text
|
|
60
|
+
|
|
61
|
+
def transport_value(value, path, transport_url):
|
|
62
|
+
if not (transport_url or re.fullmatch(r"/(?:backup/)*request/(?:path|authority)", path)):
|
|
63
|
+
return value, False
|
|
64
|
+
binary = isinstance(value, bytes)
|
|
65
|
+
pattern = rb"^([\x00-\x20]*(?:[a-z][a-z0-9+.-]*:)?//)[^/?#]*@" if binary else r"^([\x00-\x20]*(?:[a-z][a-z0-9+.-]*:)?//)[^/?#]*@"
|
|
66
|
+
replacement = rb"\1" if binary else r"\1"
|
|
67
|
+
separator = b"@" if binary else "@"
|
|
68
|
+
safe_url = re.sub(pattern, replacement, value, flags=re.I)
|
|
69
|
+
if path.endswith("/authority") and separator in safe_url:
|
|
70
|
+
safe_url = safe_url.rsplit(separator, 1)[1]
|
|
71
|
+
changed = safe_url != value
|
|
72
|
+
if changed:
|
|
73
|
+
redactions.append({"pointer": path, "reason": "transport-credential"})
|
|
74
|
+
return safe_url, changed
|
|
75
|
+
|
|
76
|
+
def validate_omitted(value, path, depth):
|
|
77
|
+
before = len(binaries), len(numbers), len(redactions)
|
|
78
|
+
visit(value, path, depth)
|
|
79
|
+
del binaries[before[0]:]
|
|
80
|
+
del numbers[before[1]:]
|
|
81
|
+
del redactions[before[2]:]
|
|
82
|
+
|
|
83
|
+
def visit(value, path, depth=0, credential=False, transport_url=False):
|
|
84
|
+
if depth > 64:
|
|
85
|
+
raise CaptureFailure("input-limit", "Native capture exceeds the 64-level evidence nesting budget.", path)
|
|
86
|
+
if credential:
|
|
87
|
+
validate_omitted(value, path, depth)
|
|
88
|
+
redactions.append({"pointer": path, "reason": "transport-credential"})
|
|
89
|
+
if isinstance(value, bytes):
|
|
90
|
+
binaries.append({"pointer": path, "representation": "producer-bytes", "state": "redacted", "content_base64": None, "bytes": None, "sha256": None})
|
|
91
|
+
return None
|
|
92
|
+
if isinstance(value, bytes):
|
|
93
|
+
safe_bytes, credential_url = transport_value(value, path, transport_url)
|
|
94
|
+
content_base64 = base64.b64encode(value).decode("ascii")
|
|
95
|
+
digest = hashlib.sha256(value).hexdigest()
|
|
96
|
+
hidden = any(secret in value or secret in safe_bytes for secret in byte_secrets) or any(secret in content_base64 or secret in digest for secret in secrets)
|
|
97
|
+
safe_text = None
|
|
98
|
+
try:
|
|
99
|
+
safe_text = safe_bytes.decode("utf-8", errors="strict")
|
|
100
|
+
except UnicodeDecodeError:
|
|
101
|
+
pass
|
|
102
|
+
if hidden:
|
|
103
|
+
redactions.append({"pointer": path, "reason": "explicit-sensitive-value"})
|
|
104
|
+
exclude = hidden or credential_url
|
|
105
|
+
binaries.append({"pointer": path, "representation": "producer-bytes", "state": "redacted" if exclude else "retained", "content_base64": None if exclude else content_base64, "bytes": None if exclude else len(value), "sha256": None if exclude else digest})
|
|
106
|
+
if hidden:
|
|
107
|
+
return None
|
|
108
|
+
return safe_text
|
|
109
|
+
if isinstance(value, str):
|
|
110
|
+
safe_text, _ = transport_value(value, path, transport_url)
|
|
111
|
+
return redact(safe_text, path, value)
|
|
112
|
+
if isinstance(value, bool) or value is None:
|
|
113
|
+
return value
|
|
114
|
+
if isinstance(value, (int, float)):
|
|
115
|
+
literal = repr(value)
|
|
116
|
+
if any(secret in literal for secret in secrets):
|
|
117
|
+
redactions.append({"pointer": path, "reason": "explicit-sensitive-value"})
|
|
118
|
+
return None
|
|
119
|
+
numbers.append({"pointer": path, "producer_type": "integer" if isinstance(value, int) else "float", "literal": literal})
|
|
120
|
+
return value if (abs(value) <= 9007199254740991 if isinstance(value, int) else math.isfinite(value)) else None
|
|
121
|
+
if isinstance(value, (list, tuple)):
|
|
122
|
+
fields = re.fullmatch(r"/(?:backup/)*(?:request|response)/(?:headers|trailers)", path)
|
|
123
|
+
if fields:
|
|
124
|
+
result = []
|
|
125
|
+
for index, field in enumerate(value):
|
|
126
|
+
if not isinstance(field, (list, tuple)) or len(field) != 2 or not isinstance(field[0], bytes):
|
|
127
|
+
raise CaptureFailure("format", "Native header/trailer is not an ordered byte pair.", pointer(path, index))
|
|
128
|
+
result.append([visit(field[0], pointer(pointer(path, index), 0), depth + 2), visit(field[1], pointer(pointer(path, index), 1), depth + 2, field[0].lower() in CREDENTIALS, field[0].lower() in {b"location", b"referer", b"origin"})])
|
|
129
|
+
return result
|
|
130
|
+
return [visit(item, pointer(path, index), depth + 1) for index, item in enumerate(value)]
|
|
131
|
+
if isinstance(value, dict):
|
|
132
|
+
result = {}
|
|
133
|
+
for key, item in value.items():
|
|
134
|
+
if not isinstance(key, str):
|
|
135
|
+
raise CaptureFailure("format", "Native record has a non-string dictionary key.", path)
|
|
136
|
+
if key == "__proto__":
|
|
137
|
+
raise CaptureFailure("unsupported", "Native object contains a __proto__ member that the current JSON schema boundary cannot preserve.", path)
|
|
138
|
+
if any(secret in key for secret in secrets):
|
|
139
|
+
# Validate the omitted subtree too, then discard all of its
|
|
140
|
+
# coordinates rather than inventing a replacement identity.
|
|
141
|
+
validate_omitted(item, pointer(path, key), depth + 1)
|
|
142
|
+
redactions.append({"pointer": path, "reason": "explicit-sensitive-value", "scope": "property-name"})
|
|
143
|
+
continue
|
|
144
|
+
result[key] = visit(item, pointer(path, key), depth + 1)
|
|
145
|
+
return result
|
|
146
|
+
raise CaptureFailure("decoder", "Native parser returned an unexpected value type: " + type(value).__name__ + ".", path)
|
|
147
|
+
|
|
148
|
+
return {"reported": visit(state, ""), "binary_fields": binaries, "numeric_literals": numbers, "redactions": redactions}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def decode(request):
|
|
152
|
+
if version.VERSION != PROFILE:
|
|
153
|
+
raise CaptureFailure("unsupported", "Expected mitmproxy " + PROFILE + "; observed " + version.VERSION + ".")
|
|
154
|
+
records = []
|
|
155
|
+
capture = Path(request["snapshot_path"])
|
|
156
|
+
size = capture.stat().st_size
|
|
157
|
+
with capture.open("rb") as handle:
|
|
158
|
+
reader = BoundedReader(handle, size)
|
|
159
|
+
while handle.tell() < size:
|
|
160
|
+
start = handle.tell()
|
|
161
|
+
try:
|
|
162
|
+
state = tnetstring.load(reader)
|
|
163
|
+
except CaptureFailure:
|
|
164
|
+
raise
|
|
165
|
+
except RecursionError:
|
|
166
|
+
raise CaptureFailure("input-limit", "Native parser exceeded its recursion budget at byte offset " + str(start) + ".") from None
|
|
167
|
+
except (ValueError, IndexError, UnicodeError, EOFError):
|
|
168
|
+
raise CaptureFailure("format", "Malformed native record at byte offset " + str(start) + ".") from None
|
|
169
|
+
if not isinstance(state, dict):
|
|
170
|
+
raise CaptureFailure("format", "Native flow record must be a dictionary at byte offset " + str(start) + ".")
|
|
171
|
+
records.append({"ordinal": len(records), "location": {"kind": "byte-range", "offset": start, "bytes": handle.tell() - start}, **project_record(state, request["sensitive_values"]), "limitations": ["Fields are original native states without FlowReader migration. Binary fields contain exact producer bytes; reported UTF-8 strings for those fields are derived display views. Unknown flow/version extensions are retained without interpretation."]})
|
|
172
|
+
return {"decoder": {"id": "mitmproxy-native-tnetstring", "name": "REA offline native mitmproxy adapter", "version": PROFILE}, "container": {"reported": None, "numeric_literals": [], "redactions": [], "records_pointer": None}, "total_records": len(records), "records": records}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def failure_reply(error):
|
|
176
|
+
"""Keep producer constraints, resource exhaustion and adapter defects distinct."""
|
|
177
|
+
if isinstance(error, CaptureFailure):
|
|
178
|
+
return {"ok": False, "reason": error.reason, "message": str(error), "pointer": error.pointer}
|
|
179
|
+
if isinstance(error, MemoryError):
|
|
180
|
+
return {"ok": False, "reason": "resource-limit", "message": "Native decoder exhausted its 768 MiB address-space budget.", "pointer": ""}
|
|
181
|
+
return {"ok": False, "reason": "decoder", "message": "Offline native decoder failed unexpectedly: " + type(error).__name__ + ".", "pointer": ""}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class OfflineCapture:
|
|
185
|
+
def load(self, loader):
|
|
186
|
+
loader.add_option("rea_request_path", str, "", "REA private offline capture request")
|
|
187
|
+
|
|
188
|
+
def running(self):
|
|
189
|
+
resource.setrlimit(resource.RLIMIT_AS, (768 * 1024 * 1024, 768 * 1024 * 1024))
|
|
190
|
+
resource.setrlimit(resource.RLIMIT_CPU, (30, 30))
|
|
191
|
+
resource.setrlimit(resource.RLIMIT_FSIZE, (OUTPUT_BYTES, OUTPUT_BYTES))
|
|
192
|
+
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
|
|
193
|
+
request = json.loads(Path(ctx.options.rea_request_path).read_text(encoding="utf-8"))
|
|
194
|
+
try:
|
|
195
|
+
response = {"ok": True, "value": decode(request)}
|
|
196
|
+
except Exception as error:
|
|
197
|
+
response = failure_reply(error)
|
|
198
|
+
try:
|
|
199
|
+
try:
|
|
200
|
+
output = json.dumps(response, ensure_ascii=True, allow_nan=False, separators=(",", ":")).encode("ascii")
|
|
201
|
+
except Exception as error:
|
|
202
|
+
output = json.dumps(failure_reply(error), separators=(",", ":")).encode("ascii")
|
|
203
|
+
if len(output) > OUTPUT_BYTES:
|
|
204
|
+
output = b'{"ok":false,"reason":"limit","message":"Native reply exceeds the complete-evidence output budget.","pointer":""}'
|
|
205
|
+
Path(request["reply_path"]).write_bytes(output)
|
|
206
|
+
finally:
|
|
207
|
+
ctx.master.shutdown()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
addons = [OfflineCapture()]
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""Bounded native call observation executed inside LLDB's embedded Python.
|
|
2
|
+
|
|
3
|
+
REA imports this module into `lldb --batch` and runs `rea_trace <config.json>`.
|
|
4
|
+
It launches one owned process, stops at the requested functions and
|
|
5
|
+
Objective-C methods, records each entry from registers and memory reads only,
|
|
6
|
+
continues, and kills the process once it exits, the event limit is reached or
|
|
7
|
+
the duration elapses. No expression is evaluated and no target code runs on
|
|
8
|
+
the tracer's behalf. The result is written as JSON to the configured path.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
import lldb
|
|
15
|
+
|
|
16
|
+
ARGUMENT_REGISTERS = {
|
|
17
|
+
"arm64": ["x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7"],
|
|
18
|
+
"arm64e": ["x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7"],
|
|
19
|
+
"x86_64": ["rdi", "rsi", "rdx", "rcx", "r8", "r9"],
|
|
20
|
+
"x86_64h": ["rdi", "rsi", "rdx", "rcx", "r8", "r9"],
|
|
21
|
+
}
|
|
22
|
+
MAX_SELECTOR_BYTES = 1024
|
|
23
|
+
MAX_REPORTED_LOCATIONS = 64
|
|
24
|
+
MAX_OTHER_STOPS = 256
|
|
25
|
+
LISTENER_NAME = "rea-native-call-observation"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _hex(value):
|
|
29
|
+
return "0x%x" % value
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _location(address, target):
|
|
33
|
+
"""Load and file address, module and symbol of one code address."""
|
|
34
|
+
module = address.GetModule()
|
|
35
|
+
symbol = address.GetSymbol()
|
|
36
|
+
file_address = address.GetFileAddress()
|
|
37
|
+
return {
|
|
38
|
+
"load_address": _hex(address.GetLoadAddress(target)),
|
|
39
|
+
"file_address": None
|
|
40
|
+
if file_address == lldb.LLDB_INVALID_ADDRESS
|
|
41
|
+
else _hex(file_address),
|
|
42
|
+
"module": module.GetFileSpec().GetFilename() if module.IsValid() else None,
|
|
43
|
+
"module_path": str(module.GetFileSpec()) if module.IsValid() else None,
|
|
44
|
+
"symbol": symbol.GetName() if symbol.IsValid() else None,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _no_files():
|
|
49
|
+
return lldb.SBFileSpecList()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _create_breakpoints(target, spec):
|
|
53
|
+
"""LLDB breakpoints for one request; a class-qualified method may need two."""
|
|
54
|
+
if spec["kind"] == "function":
|
|
55
|
+
modules = lldb.SBFileSpecList()
|
|
56
|
+
if spec.get("module"):
|
|
57
|
+
modules.Append(lldb.SBFileSpec(spec["module"], False))
|
|
58
|
+
return [
|
|
59
|
+
target.BreakpointCreateByName(
|
|
60
|
+
spec["name"], lldb.eFunctionNameTypeAuto, modules, _no_files()
|
|
61
|
+
)
|
|
62
|
+
]
|
|
63
|
+
class_name = spec.get("class_name")
|
|
64
|
+
method_type = spec.get("method_type", "any")
|
|
65
|
+
if class_name is None:
|
|
66
|
+
return [
|
|
67
|
+
target.BreakpointCreateByName(
|
|
68
|
+
spec["selector"], lldb.eFunctionNameTypeSelector, _no_files(), _no_files()
|
|
69
|
+
)
|
|
70
|
+
]
|
|
71
|
+
prefixes = {"instance": ["-"], "class": ["+"], "any": ["-", "+"]}[method_type]
|
|
72
|
+
return [
|
|
73
|
+
target.BreakpointCreateByName(
|
|
74
|
+
"%s[%s %s]" % (prefix, class_name, spec["selector"]),
|
|
75
|
+
lldb.eFunctionNameTypeFull,
|
|
76
|
+
_no_files(),
|
|
77
|
+
_no_files(),
|
|
78
|
+
)
|
|
79
|
+
for prefix in prefixes
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _dynamic_class(target, pointer):
|
|
84
|
+
"""Class of an Objective-C receiver from LLDB's runtime reader, without running code."""
|
|
85
|
+
if pointer == 0:
|
|
86
|
+
return None
|
|
87
|
+
data = lldb.SBData.CreateDataFromUInt64Array(
|
|
88
|
+
target.GetByteOrder(), target.GetAddressByteSize(), [pointer]
|
|
89
|
+
)
|
|
90
|
+
value = target.CreateValueFromData(
|
|
91
|
+
"receiver", data, target.GetBasicType(lldb.eBasicTypeObjCID)
|
|
92
|
+
)
|
|
93
|
+
dynamic = value.GetDynamicValue(lldb.eDynamicDontRunTarget)
|
|
94
|
+
name = dynamic.GetTypeName() if dynamic.IsValid() else None
|
|
95
|
+
if not name or name in ("id", "objc_object *"):
|
|
96
|
+
return None
|
|
97
|
+
return name[:-2] if name.endswith(" *") else name
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _wanted(spec, symbol):
|
|
101
|
+
"""Selector-only requests may admit only instance or only class methods."""
|
|
102
|
+
if spec["kind"] != "objc-method" or spec.get("class_name") is not None:
|
|
103
|
+
return True
|
|
104
|
+
method_type = spec.get("method_type", "any")
|
|
105
|
+
if method_type == "any":
|
|
106
|
+
return True
|
|
107
|
+
return symbol.startswith("-[" if method_type == "instance" else "+[")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _record(context, thread, index, sequence):
|
|
111
|
+
target, process, config = context["target"], context["process"], context["config"]
|
|
112
|
+
frame = thread.GetFrameAtIndex(0)
|
|
113
|
+
location = _location(frame.GetPCAddress(), target)
|
|
114
|
+
symbol = location["symbol"] or ""
|
|
115
|
+
if not _wanted(config["breakpoints"][index], symbol):
|
|
116
|
+
return None
|
|
117
|
+
names = ARGUMENT_REGISTERS.get(config["architecture"], [])
|
|
118
|
+
registers = [
|
|
119
|
+
{"name": name, "value": _hex(frame.FindRegister(name).GetValueAsUnsigned())}
|
|
120
|
+
for name in names[: config["argument_registers"]]
|
|
121
|
+
]
|
|
122
|
+
receiver_class = None
|
|
123
|
+
selector = None
|
|
124
|
+
if symbol.startswith("-[") or symbol.startswith("+["):
|
|
125
|
+
error = lldb.SBError()
|
|
126
|
+
text = process.ReadCStringFromMemory(
|
|
127
|
+
frame.FindRegister(names[1]).GetValueAsUnsigned(),
|
|
128
|
+
MAX_SELECTOR_BYTES,
|
|
129
|
+
error,
|
|
130
|
+
)
|
|
131
|
+
selector = text if error.Success() and text else None
|
|
132
|
+
if symbol.startswith("-["):
|
|
133
|
+
receiver_class = _dynamic_class(
|
|
134
|
+
target, frame.FindRegister(names[0]).GetValueAsUnsigned()
|
|
135
|
+
)
|
|
136
|
+
frames = min(thread.GetNumFrames(), 1 + config["backtrace_frames"])
|
|
137
|
+
record = {
|
|
138
|
+
"sequence": sequence,
|
|
139
|
+
"elapsed_ms": round((time.monotonic() - context["started"]) * 1000, 3),
|
|
140
|
+
"thread_id": thread.GetThreadID(),
|
|
141
|
+
"breakpoint_index": index,
|
|
142
|
+
"receiver_class": receiver_class,
|
|
143
|
+
"selector": selector,
|
|
144
|
+
"registers": registers,
|
|
145
|
+
"backtrace": [
|
|
146
|
+
_location(thread.GetFrameAtIndex(depth).GetPCAddress(), target)
|
|
147
|
+
for depth in range(1, frames)
|
|
148
|
+
],
|
|
149
|
+
}
|
|
150
|
+
record.update(location)
|
|
151
|
+
return record
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _handle_stop(context, events, other_stops):
|
|
155
|
+
"""Record breakpoint hits of one stop; return an outcome when tracing must end."""
|
|
156
|
+
process = context["process"]
|
|
157
|
+
for thread in process:
|
|
158
|
+
reason = thread.GetStopReason()
|
|
159
|
+
if reason == lldb.eStopReasonBreakpoint:
|
|
160
|
+
index = context["breakpoint_index"].get(thread.GetStopReasonDataAtIndex(0))
|
|
161
|
+
if index is None:
|
|
162
|
+
continue
|
|
163
|
+
record = _record(context, thread, index, len(events))
|
|
164
|
+
if record is not None:
|
|
165
|
+
events.append(record)
|
|
166
|
+
if len(events) >= context["config"]["max_events"]:
|
|
167
|
+
return "event-limit"
|
|
168
|
+
elif reason in (lldb.eStopReasonSignal, lldb.eStopReasonException):
|
|
169
|
+
other_stops.append(thread.GetStopDescription(256))
|
|
170
|
+
if len(other_stops) >= MAX_OTHER_STOPS:
|
|
171
|
+
return "stop-limit"
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _breakpoint_report(target, created):
|
|
176
|
+
report = []
|
|
177
|
+
for index, breakpoints in enumerate(created):
|
|
178
|
+
locations = []
|
|
179
|
+
count = 0
|
|
180
|
+
for breakpoint in breakpoints:
|
|
181
|
+
count += breakpoint.GetNumLocations()
|
|
182
|
+
for position in range(breakpoint.GetNumLocations()):
|
|
183
|
+
if len(locations) < MAX_REPORTED_LOCATIONS:
|
|
184
|
+
locations.append(
|
|
185
|
+
_location(breakpoint.GetLocationAtIndex(position).GetAddress(), target)
|
|
186
|
+
)
|
|
187
|
+
report.append({"index": index, "location_count": count, "locations": locations})
|
|
188
|
+
return report
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _launch_info(target, config, listener):
|
|
192
|
+
launch = lldb.SBLaunchInfo(config["arguments"])
|
|
193
|
+
launch.SetListener(listener)
|
|
194
|
+
environment = target.GetEnvironment()
|
|
195
|
+
for key, value in config["environment"].items():
|
|
196
|
+
environment.Set(key, value, True)
|
|
197
|
+
launch.SetEnvironment(environment, False)
|
|
198
|
+
if config.get("working_directory"):
|
|
199
|
+
launch.SetWorkingDirectory(config["working_directory"])
|
|
200
|
+
launch.AddOpenFileAction(0, "/dev/null", True, False)
|
|
201
|
+
launch.AddOpenFileAction(1, config["stdout_path"], False, True)
|
|
202
|
+
launch.AddOpenFileAction(2, config["stderr_path"], False, True)
|
|
203
|
+
return launch
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _wait_for_exit(listener, process, seconds):
|
|
207
|
+
event = lldb.SBEvent()
|
|
208
|
+
deadline = time.monotonic() + seconds
|
|
209
|
+
while process.GetState() != lldb.eStateExited and time.monotonic() < deadline:
|
|
210
|
+
listener.WaitForEvent(1, event)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def trace(debugger, config):
|
|
214
|
+
debugger.SetAsync(True)
|
|
215
|
+
# Stop at the symbol itself, where argument registers still hold the ABI
|
|
216
|
+
# arguments, even when line tables would let LLDB skip the prologue.
|
|
217
|
+
lldb.SBDebugger.SetInternalVariable(
|
|
218
|
+
"target.skip-prologue", "false", debugger.GetInstanceName()
|
|
219
|
+
)
|
|
220
|
+
error = lldb.SBError()
|
|
221
|
+
target = debugger.CreateTarget(
|
|
222
|
+
config["executable"], config["architecture"], None, False, error
|
|
223
|
+
)
|
|
224
|
+
if not target.IsValid():
|
|
225
|
+
return {"status": "target-error", "error": error.GetCString()}
|
|
226
|
+
created = [_create_breakpoints(target, spec) for spec in config["breakpoints"]]
|
|
227
|
+
breakpoint_index = {}
|
|
228
|
+
for index, breakpoints in enumerate(created):
|
|
229
|
+
for breakpoint in breakpoints:
|
|
230
|
+
breakpoint_index[breakpoint.GetID()] = index
|
|
231
|
+
listener = lldb.SBListener(LISTENER_NAME)
|
|
232
|
+
started = time.monotonic()
|
|
233
|
+
process = target.Launch(_launch_info(target, config, listener), error)
|
|
234
|
+
if not error.Success() or not process.IsValid():
|
|
235
|
+
return {"status": "launch-error", "error": error.GetCString()}
|
|
236
|
+
with open(config["pid_path"], "w") as handle:
|
|
237
|
+
handle.write(str(process.GetProcessID()))
|
|
238
|
+
context = {
|
|
239
|
+
"target": target,
|
|
240
|
+
"process": process,
|
|
241
|
+
"config": config,
|
|
242
|
+
"started": started,
|
|
243
|
+
"breakpoint_index": breakpoint_index,
|
|
244
|
+
}
|
|
245
|
+
events = []
|
|
246
|
+
other_stops = []
|
|
247
|
+
outcome = None
|
|
248
|
+
deadline = started + config["duration_ms"] / 1000
|
|
249
|
+
event = lldb.SBEvent()
|
|
250
|
+
while outcome is None:
|
|
251
|
+
if time.monotonic() >= deadline:
|
|
252
|
+
outcome = "duration-elapsed"
|
|
253
|
+
break
|
|
254
|
+
if not listener.WaitForEvent(1, event):
|
|
255
|
+
continue
|
|
256
|
+
if not lldb.SBProcess.EventIsProcessEvent(event):
|
|
257
|
+
continue
|
|
258
|
+
state = lldb.SBProcess.GetStateFromEvent(event)
|
|
259
|
+
if state == lldb.eStateExited:
|
|
260
|
+
outcome = "exited"
|
|
261
|
+
elif state == lldb.eStateStopped and not lldb.SBProcess.GetRestartedFromEvent(event):
|
|
262
|
+
outcome = _handle_stop(context, events, other_stops)
|
|
263
|
+
if outcome is None:
|
|
264
|
+
process.Continue()
|
|
265
|
+
pid = process.GetProcessID()
|
|
266
|
+
exit_status = None
|
|
267
|
+
exit_description = None
|
|
268
|
+
if outcome == "exited":
|
|
269
|
+
exit_status = process.GetExitStatus()
|
|
270
|
+
exit_description = process.GetExitDescription()
|
|
271
|
+
else:
|
|
272
|
+
process.Kill()
|
|
273
|
+
_wait_for_exit(listener, process, 5)
|
|
274
|
+
return {
|
|
275
|
+
"status": "traced",
|
|
276
|
+
"version": lldb.SBDebugger.GetVersionString().splitlines()[0],
|
|
277
|
+
"pid": pid,
|
|
278
|
+
"outcome": outcome,
|
|
279
|
+
"exit_status": exit_status,
|
|
280
|
+
"exit_description": exit_description,
|
|
281
|
+
"killed": outcome != "exited",
|
|
282
|
+
"terminated": process.GetState() == lldb.eStateExited,
|
|
283
|
+
"elapsed_ms": round((time.monotonic() - started) * 1000, 3),
|
|
284
|
+
"breakpoints": _breakpoint_report(target, created),
|
|
285
|
+
"events": events,
|
|
286
|
+
"other_stops": other_stops,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def run(debugger, command, result, internal_dict):
|
|
291
|
+
"""LLDB command entry point: `rea_trace <config.json>`."""
|
|
292
|
+
with open(command.strip()) as handle:
|
|
293
|
+
config = json.load(handle)
|
|
294
|
+
try:
|
|
295
|
+
output = trace(debugger, config)
|
|
296
|
+
except Exception as error: # Reported as a tracer failure, never re-raised into LLDB.
|
|
297
|
+
output = {"status": "tracer-error", "error": "%s: %s" % (type(error).__name__, error)}
|
|
298
|
+
with open(config["result_path"], "w") as handle:
|
|
299
|
+
json.dump(output, handle)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def __lldb_init_module(debugger, internal_dict):
|
|
303
|
+
debugger.HandleCommand("command script add -f rea_lldb_tracer.run rea_trace")
|