rea-agents 6.3.0 → 6.4.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.
Files changed (487) hide show
  1. package/README.md +7 -4
  2. package/bridge/ghidra/ReaGhidraApplySeeds.java +151 -0
  3. package/bridge/ghidra/ReaGhidraBridge.java +597 -209
  4. package/bridge/ghidra/ReaGhidraNoReturnFix.java +123 -0
  5. package/dist/adb/AdbConfiguration.js +88 -0
  6. package/dist/adb/AdbDeviceOutput.js +186 -0
  7. package/dist/adb/AdbProvider.js +1190 -0
  8. package/dist/adb/AdbShellOutput.js +309 -0
  9. package/dist/android/AndroidTargetSnapshot.js +104 -22
  10. package/dist/android/JadxConfiguration.js +44 -24
  11. package/dist/android/JadxProvider.js +51 -19
  12. package/dist/apktool/ApktoolConfiguration.js +84 -0
  13. package/dist/apktool/ApktoolDecodeOutput.js +59 -0
  14. package/dist/apktool/ApktoolProvider.js +506 -0
  15. package/dist/application/AnalysisProvider.js +8 -2
  16. package/dist/application/AppBundleExecutable.js +27 -22
  17. package/dist/application/BinaryTargetResolver.js +22 -2
  18. package/dist/application/CapabilityInventory.js +11 -1
  19. package/dist/application/ClientConfigurationDocument.js +70 -0
  20. package/dist/application/ClientConfigurationFiles.js +91 -0
  21. package/dist/application/ClientRegistrationEnvironment.js +35 -0
  22. package/dist/application/ClientRegistrationStatus.js +66 -31
  23. package/dist/application/DirectAnalysis.js +2 -2
  24. package/dist/application/DirectAnalysisCleanup.js +1 -1
  25. package/dist/application/DirectAnalysisStatus.js +1 -1
  26. package/dist/application/Doctor.js +5 -3
  27. package/dist/application/DoctorDiagnostics.js +1 -1
  28. package/dist/application/EvidenceBundleCommands.js +6 -12
  29. package/dist/application/GeminiClientSettings.js +139 -0
  30. package/dist/application/HermesClientHome.js +115 -0
  31. package/dist/application/HopperDownload.js +39 -0
  32. package/dist/application/InvestigationProviders.js +6 -0
  33. package/dist/application/JsonFiles.js +6 -5
  34. package/dist/{cliJsonFile.js → application/JsonInputFile.js} +108 -35
  35. package/dist/application/LinuxHopper.js +4 -12
  36. package/dist/application/MacHopper.js +172 -149
  37. package/dist/application/MipsElfAbiFlags.js +140 -0
  38. package/dist/application/ProviderOperationExecution.js +31 -0
  39. package/dist/application/ReconstructionObligationCandidates.js +9 -3
  40. package/dist/application/ReferenceSourceImport.js +76 -29
  41. package/dist/application/ReferenceSourceImportEntries.js +18 -9
  42. package/dist/application/ReferenceSourceImportPolicy.js +99 -23
  43. package/dist/application/ReferenceSourceImportTypes.js +30 -22
  44. package/dist/application/ReferenceSourceVcsAdapter.js +145 -30
  45. package/dist/application/RegularFileRead.js +93 -7
  46. package/dist/application/Setup.js +54 -6
  47. package/dist/application/SetupClientConfiguration.js +116 -16
  48. package/dist/application/SetupHost.js +102 -24
  49. package/dist/application/SetupPlan.js +5 -8
  50. package/dist/application/SetupSkill.js +58 -39
  51. package/dist/application/SupportedClients.js +290 -65
  52. package/dist/application/Uninstall.js +158 -10
  53. package/dist/application/UpdateMaintenance.js +17 -21
  54. package/dist/application/Utf8JsonInput.js +7 -0
  55. package/dist/application/WebScriptExportService.js +131 -65
  56. package/dist/application/WebSourceLocationService.js +3 -0
  57. package/dist/application/adb/AdbDeviceAnalysisPort.js +1 -0
  58. package/dist/application/adb/AdbDeviceAnalysisService.js +13 -0
  59. package/dist/application/analysisView/AnalysisViewService.js +40 -2
  60. package/dist/application/apktool/ApktoolResourceAnalysisPort.js +1 -0
  61. package/dist/application/apktool/ApktoolResourceAnalysisService.js +13 -0
  62. package/dist/application/binary/AnalysisClientCleanup.js +0 -24
  63. package/dist/application/binary/BinarySession.js +2 -1
  64. package/dist/application/binaryDiagnostics/BinaryLayoutService.js +2 -1
  65. package/dist/application/binaryDiagnostics/PeResourcesPort.js +1 -0
  66. package/dist/application/binaryDiagnostics/PeResourcesService.js +111 -0
  67. package/dist/application/binaryDiagnostics/RecordedCrashService.js +2 -1
  68. package/dist/application/evm/EvmInterfaceService.js +3 -0
  69. package/dist/application/firmware/FirmwareAnalysisService.js +3 -0
  70. package/dist/application/investigation/InspectEvidenceBundle.js +18 -0
  71. package/dist/application/javascript/ElectronBoundaryGraphContext.js +3 -2
  72. package/dist/application/javascript/ElectronBoundaryGraphIpc.js +2 -2
  73. package/dist/application/javascript/ElectronBoundaryGraphNative.js +19 -6
  74. package/dist/application/javascript/ElectronBoundaryGraphWindows.js +3 -3
  75. package/dist/application/javascript/JavaScriptApplicationService.js +22 -11
  76. package/dist/application/javascript/JavaScriptApplicationWorkflowEvidence.js +5 -6
  77. package/dist/application/javascript/JavaScriptArtifactAnalysis.js +171 -30
  78. package/dist/application/javascript/JavaScriptArtifactGraphAccumulator.js +14 -16
  79. package/dist/application/javascript/JavaScriptArtifactGraphBuilder.js +67 -19
  80. package/dist/application/javascript/JavaScriptArtifactGraphBundlers.js +4 -3
  81. package/dist/application/javascript/JavaScriptArtifactGraphContext.js +11 -5
  82. package/dist/application/javascript/JavaScriptArtifactGraphFindings.js +19 -16
  83. package/dist/application/javascript/JavaScriptArtifactGraphStructure.js +92 -10
  84. package/dist/application/javascript/JavaScriptArtifactPathResolution.js +258 -235
  85. package/dist/application/javascript/JavaScriptArtifactReconstruction.js +115 -26
  86. package/dist/application/javascript/JavaScriptArtifactReconstructionInput.js +4 -1
  87. package/dist/application/javascript/JavaScriptModuleRelationships.js +6 -4
  88. package/dist/application/javascript/JavaScriptRecoveryService.js +3 -0
  89. package/dist/application/javascript/JavaScriptSemanticGraphAsyncProjection.js +9 -3
  90. package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +199 -61
  91. package/dist/application/javascript/JavaScriptSemanticGraphConstruction.js +12 -19
  92. package/dist/application/javascript/JavaScriptSemanticGraphDataProjection.js +4 -1
  93. package/dist/application/javascript/JavaScriptSemanticGraphFlowProjection.js +5 -3
  94. package/dist/application/javascript/JavaScriptSemanticGraphObjectProjection.js +5 -3
  95. package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +19 -8
  96. package/dist/application/jeb/JebAnalysisPort.js +1 -0
  97. package/dist/application/jeb/JebAnalysisService.js +13 -0
  98. package/dist/application/managed/ManagedApplicationGraphService.js +1 -1
  99. package/dist/application/native/NativeUiActionTrace.js +16 -8
  100. package/dist/application/native/NativeValueTrace.js +23 -6
  101. package/dist/application/process/ProcessCli.js +25 -20
  102. package/dist/artifacts/ArtifactPaths.js +129 -8
  103. package/dist/artifacts/ArtifactProvider.js +20 -15
  104. package/dist/artifacts/ArtifactReader.js +23 -0
  105. package/dist/artifacts/ArtifactResourceScope.js +100 -0
  106. package/dist/artifacts/AsarArtifactReader.js +176 -102
  107. package/dist/artifacts/AsarHeader.js +24 -0
  108. package/dist/artifacts/DirectoryArtifactReader.js +168 -65
  109. package/dist/artifacts/MachOSliceArtifactReader.js +116 -39
  110. package/dist/artifacts/NativeDmgArtifactReader.js +29 -22
  111. package/dist/artifacts/SafeOutputTree.js +245 -70
  112. package/dist/artifacts/SafeOutputTreeCleanup.js +45 -0
  113. package/dist/artifacts/SafeOutputTreeCreationFailure.js +20 -0
  114. package/dist/artifacts/SafeOutputTreeIdentity.js +42 -0
  115. package/dist/artifacts/SafeOutputTreeReadback.js +33 -0
  116. package/dist/artifacts/ZipArtifactReader.js +77 -27
  117. package/dist/artifacts/apple/InterfaceBuilderAnalysis.js +58 -18
  118. package/dist/artifacts/apple/InterfaceBuilderDecodeBudget.js +62 -24
  119. package/dist/artifacts/apple/KeyedArchiveReader.js +32 -13
  120. package/dist/artifacts/extraction/ArtifactExtraction.js +110 -86
  121. package/dist/artifacts/inventory/ArtifactGraphConstruction.js +110 -43
  122. package/dist/artifacts/inventory/ArtifactInventory.js +5 -4
  123. package/dist/artifacts/inventory/classify.js +75 -32
  124. package/dist/artifacts/inventory/hashStableRootArtifact.js +48 -15
  125. package/dist/artifacts/inventory/reader.js +13 -5
  126. package/dist/artifacts/inventory/scanCanonical.js +62 -25
  127. package/dist/artifacts/inventory/scanReader.js +38 -11
  128. package/dist/artifacts/javascript/JavaScriptArtifactFiles.js +15 -9
  129. package/dist/artifacts/javascript/JavaScriptSourceAnalysis.js +3 -0
  130. package/dist/browser/BrowserScenarioPartialObservation.js +8 -2
  131. package/dist/browser/BrowserScenarioSecrets.js +52 -37
  132. package/dist/browser/CdpBrowserProvider.js +5 -0
  133. package/dist/browser/CdpCaptureDocuments.js +40 -6
  134. package/dist/browser/CdpCaptureDomSvg.js +210 -0
  135. package/dist/browser/CdpCaptureEventBodyShapes.js +4 -8
  136. package/dist/browser/CdpCaptureEventHandlers.js +26 -9
  137. package/dist/browser/CdpCaptureEvents.js +4 -9
  138. package/dist/browser/CdpConnection.js +22 -5
  139. package/dist/browser/CdpPageCapture.js +1 -1
  140. package/dist/browser/CdpTargetSession.js +30 -34
  141. package/dist/browser/CdpWebMcpDiscovery.js +32 -10
  142. package/dist/browser/ElectronFileScope.js +99 -9
  143. package/dist/browser/PlaywrightBrowserScenarioProvider.js +60 -4
  144. package/dist/browser/PlaywrightElectronActiveProvider.js +11 -6
  145. package/dist/browser/PlaywrightScenarioArtifacts.js +5 -5
  146. package/dist/browser/PlaywrightScenarioBrowser.js +69 -7
  147. package/dist/browser/PlaywrightScenarioSession.js +127 -54
  148. package/dist/browser/PlaywrightScenarioStorage.js +507 -0
  149. package/dist/browser/PngVisualDiff.js +198 -29
  150. package/dist/browser/WebSourceMapFetcher.js +3 -3
  151. package/dist/browser/assets/LocalWebScriptArtifacts.js +3 -1
  152. package/dist/browser/assets/PublishWebScripts.js +75 -20
  153. package/dist/browser/assets/WebArtifactReads.js +23 -1
  154. package/dist/browser/assets/WebSourceLocationArtifacts.js +1 -0
  155. package/dist/browser/execution/CdpExecutionCapture.js +3 -3
  156. package/dist/browser/execution/CdpPreciseCoverage.js +5 -8
  157. package/dist/browser/history/HarCapture.js +14 -0
  158. package/dist/browser/history/HarCaptureProcess.js +2 -10
  159. package/dist/browser/history/MitmproxyCaptureAdapter.js +3 -1
  160. package/dist/browser/modules/WebModuleArtifacts.js +1 -0
  161. package/dist/cli/adbCommands.js +337 -0
  162. package/dist/cli/analysisViewCommands.js +39 -1
  163. package/dist/cli/apktoolCommands.js +53 -0
  164. package/dist/cli/artifactCommands.js +8 -11
  165. package/dist/cli/binaryDiagnosticsCommands.js +19 -1
  166. package/dist/cli/browserAdvancedCommands.js +65 -36
  167. package/dist/cli/coreBinaryCommands.js +0 -5
  168. package/dist/cli/coreNativeCommands.js +1 -14
  169. package/dist/cli/electronCommands.js +6 -1
  170. package/dist/cli/evmCommands.js +13 -4
  171. package/dist/cli/firmwareCommands.js +7 -2
  172. package/dist/cli/javascriptApplicationAnalysis.js +17 -1
  173. package/dist/cli/javascriptRecoveryCommands.js +22 -15
  174. package/dist/cli/jebCommands.js +99 -0
  175. package/dist/cli/jsonOutput.js +11 -4
  176. package/dist/cli/referenceSourceImportStatus.js +7 -0
  177. package/dist/cli/setupCommands.js +15 -3
  178. package/dist/cli/utilityCommands.js +66 -18
  179. package/dist/cli/webScriptCommands.js +20 -1
  180. package/dist/cli/webSourceLocationCommands.js +21 -9
  181. package/dist/cli.js +6 -0
  182. package/dist/cliBrowserScenarioCommands.js +2 -1
  183. package/dist/cliCommandNames.js +32 -0
  184. package/dist/cliJsonInput.js +2 -2
  185. package/dist/cliObservationOptions.js +3 -0
  186. package/dist/composition/adb.js +3 -0
  187. package/dist/composition/apktool.js +3 -0
  188. package/dist/composition/binaryDiagnostics.js +4 -0
  189. package/dist/composition/directAnalysis.js +12 -4
  190. package/dist/composition/javascriptAnalysisWorkerProcess.js +48 -0
  191. package/dist/composition/jeb.js +6 -0
  192. package/dist/config/environment.js +10 -0
  193. package/dist/config/ghidraLanguageOverride.js +9 -0
  194. package/dist/config/mcpInputSchemaProfile.js +27 -0
  195. package/dist/config/parseConfig.js +28 -1
  196. package/dist/contracts/adb/adbToolContracts.js +353 -0
  197. package/dist/contracts/advertisedInputExamples.js +48 -0
  198. package/dist/contracts/analysisDetail.js +10 -0
  199. package/dist/contracts/analysisViewToolContracts.js +17 -2
  200. package/dist/contracts/apktool/apktoolToolContracts.js +33 -0
  201. package/dist/contracts/artifactToolContracts.js +8 -5
  202. package/dist/contracts/browserToolContracts.js +1 -1
  203. package/dist/contracts/compactInputSchema.js +256 -0
  204. package/dist/contracts/functionWorkflowToolContracts.js +1 -1
  205. package/dist/contracts/inputSchemaPresentation.js +10 -10
  206. package/dist/contracts/javascript/electronToolContracts.js +15 -3
  207. package/dist/contracts/javascript/javascriptRecoveryToolContracts.js +1 -1
  208. package/dist/contracts/jeb/jebToolContracts.js +64 -0
  209. package/dist/contracts/native/binaryDiagnosticsToolContracts.js +25 -3
  210. package/dist/contracts/officialToolContracts.js +5 -5
  211. package/dist/contracts/sessionToolContracts.js +2 -2
  212. package/dist/contracts/sessionToolSchemas.js +6 -2
  213. package/dist/contracts/toolContractExamples.js +6 -0
  214. package/dist/contracts/toolContractTypes.js +3 -0
  215. package/dist/contracts/toolContracts.js +6 -0
  216. package/dist/contracts/toolEffects.js +167 -0
  217. package/dist/contracts/toolOutputSchemaGroups.js +5 -3
  218. package/dist/contracts/toolSchemaMetadata.js +82 -7
  219. package/dist/domain/adb/adbDeviceAnalysis.js +583 -0
  220. package/dist/domain/analysisErrorCleanup.js +29 -0
  221. package/dist/domain/analysisErrorPresentation.js +18 -6
  222. package/dist/domain/analysisErrorProjection.js +4 -1
  223. package/dist/domain/analysisSnapshot.js +2 -1
  224. package/dist/domain/analysisView/analysisView.js +52 -2
  225. package/dist/domain/analysisView/binaryLayoutView.js +2 -0
  226. package/dist/domain/analysisView/javascriptApplicationView.js +2 -0
  227. package/dist/domain/analysisView/nativeFunctionView.js +155 -0
  228. package/dist/domain/android/androidApplication.js +25 -20
  229. package/dist/domain/apktool/apktoolResourceAnalysis.js +77 -0
  230. package/dist/domain/apple/appleApplication.js +40 -23
  231. package/dist/domain/apple/appleBundleAnatomy.js +95 -28
  232. package/dist/domain/apple/dyldPaths.js +9 -1
  233. package/dist/domain/apple/dylibResolution.js +2 -2
  234. package/dist/domain/apple/interfaceBuilderGraph.js +22 -19
  235. package/dist/domain/apple/interfaceBuilderKeyedArchive.js +10 -1
  236. package/dist/domain/artifactComparison.js +35 -7
  237. package/dist/domain/artifactGraph.js +5 -3
  238. package/dist/domain/artifactIdentity.js +30 -2
  239. package/dist/domain/artifactInspection.js +3 -43
  240. package/dist/domain/artifactIntegrityPolicy.js +10 -0
  241. package/dist/domain/artifactInventoryEvidence.js +11 -12
  242. package/dist/domain/artifactPathSyntax.js +1 -1
  243. package/dist/domain/binaryTarget.js +16 -0
  244. package/dist/domain/binaryTargetTypes.js +8 -1
  245. package/dist/domain/browserObservationSchemas.js +8 -4
  246. package/dist/domain/canonicalJsonDigestSteps.js +34 -12
  247. package/dist/domain/compactStringIdentity.js +9 -0
  248. package/dist/domain/evidence.js +2 -1
  249. package/dist/domain/evidenceRecordSummary.js +144 -0
  250. package/dist/domain/inputIssueProjection.js +16 -1
  251. package/dist/domain/javascript/electronStaticAnalysisBrowser.js +12 -13
  252. package/dist/domain/javascript/electronStaticAnalysisIpc.js +11 -20
  253. package/dist/domain/javascript/electronStaticAnalysisNative.js +70 -40
  254. package/dist/domain/javascript/electronStaticAnalysisValues.js +16 -0
  255. package/dist/domain/javascript/javascriptAnalysisResourceControls.js +45 -0
  256. package/dist/domain/javascript/javascriptApplicationAnalysis.js +4 -1
  257. package/dist/domain/javascript/javascriptApplicationChangeGraph.js +6 -6
  258. package/dist/domain/javascript/javascriptApplicationGraph.js +79 -18
  259. package/dist/domain/javascript/javascriptApplicationProjectionResources.js +35 -0
  260. package/dist/domain/javascript/javascriptApplicationVersionItems.js +28 -12
  261. package/dist/domain/javascript/javascriptApplicationVersionKeys.js +9 -1
  262. package/dist/domain/javascript/javascriptAstFingerprint.js +58 -31
  263. package/dist/domain/javascript/javascriptAstValues.js +32 -0
  264. package/dist/domain/javascript/javascriptElectronMemberWrites.js +50 -0
  265. package/dist/domain/javascript/javascriptModuleSemanticIr.js +15 -0
  266. package/dist/domain/javascript/javascriptNativeHandoff.js +6 -1
  267. package/dist/domain/javascript/javascriptRpcEndpoints.js +253 -0
  268. package/dist/domain/javascript/javascriptRuntimeLoadState.js +1 -3
  269. package/dist/domain/javascript/javascriptRuntimeReconciliationGraph.js +7 -2
  270. package/dist/domain/javascript/javascriptRuntimeStaticCandidates.js +8 -11
  271. package/dist/domain/javascript/javascriptSemanticAnalysis.js +190 -33
  272. package/dist/domain/javascript/javascriptSemanticCallableResults.js +42 -0
  273. package/dist/domain/javascript/javascriptSemanticCalls.js +8 -1
  274. package/dist/domain/javascript/javascriptSemanticDerivedAnalysis.js +15 -4
  275. package/dist/domain/javascript/javascriptSemanticFileProjection.js +1 -0
  276. package/dist/domain/javascript/javascriptSemanticFingerprints.js +42 -41
  277. package/dist/domain/javascript/javascriptSemanticGraph.js +69 -23
  278. package/dist/domain/javascript/javascriptSemanticGraphSchemas.js +2 -10
  279. package/dist/domain/javascript/javascriptSemanticIterationSources.js +40 -7
  280. package/dist/domain/javascript/javascriptSemanticMemberMutations.js +341 -87
  281. package/dist/domain/javascript/javascriptSemanticMutationValues.js +78 -35
  282. package/dist/domain/javascript/javascriptSemanticObjectReferences.js +381 -0
  283. package/dist/domain/javascript/javascriptSemanticObjects.js +7 -1
  284. package/dist/domain/javascript/javascriptSemanticProjection.js +52 -14
  285. package/dist/domain/javascript/javascriptSemanticPropertyPaths.js +77 -0
  286. package/dist/domain/javascript/javascriptSemanticQuery.js +27 -29
  287. package/dist/domain/javascript/javascriptSemanticQueryIdentity.js +1 -3
  288. package/dist/domain/javascript/javascriptSemanticReturnedReferences.js +219 -0
  289. package/dist/domain/javascript/javascriptSemanticReturns.js +13 -23
  290. package/dist/domain/javascript/javascriptSemanticState.js +22 -2
  291. package/dist/domain/javascript/javascriptSemanticTraversal.js +19 -1
  292. package/dist/domain/javascript/javascriptSemanticValues.js +47 -19
  293. package/dist/domain/javascript/javascriptSourceAnalysis.js +1 -0
  294. package/dist/domain/javascript/javascriptStaticAnalysis.js +29 -17
  295. package/dist/domain/javascript/javascriptStaticAnalysisBundler.js +8 -6
  296. package/dist/domain/javascript/javascriptStaticAnalysisCalls.js +49 -5
  297. package/dist/domain/javascript/javascriptStaticAnalysisFindings.js +8 -4
  298. package/dist/domain/javascript/javascriptStaticAnalysisState.js +1 -0
  299. package/dist/domain/jeb/jebAnalysis.js +114 -0
  300. package/dist/domain/jsonShape.js +63 -52
  301. package/dist/domain/jsonValue.js +3 -0
  302. package/dist/domain/managed/managedApplicationGraph.js +37 -0
  303. package/dist/domain/managed/managedApplicationGraphCoverage.js +8 -2
  304. package/dist/domain/managed/managedApplicationGraphNodes.js +32 -22
  305. package/dist/domain/managed/managedArtifact.js +1 -0
  306. package/dist/domain/managed/managedInspectionEvidence.js +13 -0
  307. package/dist/domain/native/nativeAotPe.js +1408 -0
  308. package/dist/domain/native/nativeInstruction.js +21 -0
  309. package/dist/domain/native/nativeMetadataRecovery.js +79 -6
  310. package/dist/domain/native/peResources.js +94 -0
  311. package/dist/domain/nativeConvention.js +33 -0
  312. package/dist/domain/process/processCapture.js +91 -1
  313. package/dist/domain/process/processCaptureExample.js +24 -0
  314. package/dist/domain/process/processCaptureValidation.js +79 -0
  315. package/dist/domain/process/processScenario.js +59 -7
  316. package/dist/domain/propertyListKeys.js +3 -1
  317. package/dist/domain/referenceSourceGraph.js +25 -16
  318. package/dist/domain/referenceSourceImportParsing.js +6 -3
  319. package/dist/domain/referenceSourcePathIdentity.js +22 -0
  320. package/dist/domain/referenceSourceRequireBindings.js +137 -0
  321. package/dist/domain/webBundleAnalyzerInspection.js +14 -2
  322. package/dist/domain/webCaptureDiff.js +102 -4
  323. package/dist/domain/webScriptExportError.js +10 -4
  324. package/dist/domain/webScriptExportPlan.js +7 -13
  325. package/dist/domain/zipPackageFormat.js +4 -1
  326. package/dist/domain/zodCollectionSteps.js +24 -0
  327. package/dist/dotnet/ManagedArtifactInspector.js +55 -43
  328. package/dist/dotnet/ManagedDecodeBudget.js +80 -0
  329. package/dist/dotnet/ManagedMemberInspector.js +13 -6
  330. package/dist/dotnet/ManagedMemberInspectorCore.js +2 -2
  331. package/dist/dotnet/ManagedMemberInstructionDecoder.js +4 -2
  332. package/dist/dotnet/ManagedMemberRows.js +9 -4
  333. package/dist/dotnet/ManagedMetadataHeaps.js +15 -5
  334. package/dist/dotnet/ManagedMetadataInventoryRows.js +20 -11
  335. package/dist/dotnet/ManagedMetadataLayout.js +10 -4
  336. package/dist/dotnet/ManagedMethodBodyReader.js +8 -3
  337. package/dist/dotnet/ManagedNativeBoundaryHelpers.js +3 -2
  338. package/dist/dotnet/ManagedNativeBoundaryInspector.js +49 -34
  339. package/dist/dotnet/ManagedPeReader.js +41 -25
  340. package/dist/dotnet/ManagedReaderFailure.js +2 -1
  341. package/dist/dotnet/ManagedStaticProvider.js +55 -24
  342. package/dist/evaluation/AgentEvaluationReport.js +9 -5
  343. package/dist/evaluation/CodexAgentEval.js +43 -13
  344. package/dist/evm/EvmoleFailures.js +0 -2
  345. package/dist/evm/EvmoleInterfaceProvider.js +67 -2
  346. package/dist/filesystem/OwnedDirectoryHandle.js +29 -0
  347. package/dist/filesystem/OwnedFileHandle.js +36 -0
  348. package/dist/filesystem/RegularFile.js +33 -1
  349. package/dist/filesystem/StableFileHash.js +46 -0
  350. package/dist/filesystem/readFileHandleChunks.js +26 -0
  351. package/dist/firmware/FirmwareCommand.js +25 -3
  352. package/dist/firmware/FirmwareFiles.js +23 -8
  353. package/dist/firmware/FirmwareProvider.js +156 -32
  354. package/dist/firmware/FirmwarePublication.js +24 -3
  355. package/dist/firmware/FirmwareRelease.js +1 -0
  356. package/dist/generatedPackageMetadata.js +2 -2
  357. package/dist/ghidra/GhidraAnalysisProfile.js +55 -4
  358. package/dist/ghidra/GhidraAnalysisSeeds.js +211 -0
  359. package/dist/ghidra/GhidraClient.js +76 -4
  360. package/dist/ghidra/GhidraDiagnostics.js +11 -8
  361. package/dist/ghidra/GhidraFunctionValues.js +26 -2
  362. package/dist/ghidra/GhidraInstallation.js +1 -1
  363. package/dist/ghidra/GhidraInventoryValues.js +23 -1
  364. package/dist/ghidra/GhidraLanguageCatalog.js +99 -0
  365. package/dist/ghidra/GhidraLauncher.js +80 -39
  366. package/dist/ghidra/GhidraLoadImageAttest.js +49 -2
  367. package/dist/ghidra/GhidraMipsProfile.js +79 -0
  368. package/dist/ghidra/GhidraProvider.js +57 -9
  369. package/dist/ghidra/GhidraProviderCapabilities.js +18 -8
  370. package/dist/ghidra/GhidraProviderClient.js +282 -21
  371. package/dist/ghidra/GhidraSessionValues.js +9 -2
  372. package/dist/hopper/HopperAnalysisProfile.js +2 -0
  373. package/dist/hopper/HopperClient.js +15 -6
  374. package/dist/hopper/HopperMachOImage.js +2 -0
  375. package/dist/hopper/HopperProvider.js +13 -2
  376. package/dist/ida/IdaOperationRunner.js +8 -2
  377. package/dist/ida/IdaProvider.js +7 -0
  378. package/dist/ida/IdaSessionClient.js +3 -1
  379. package/dist/ida/IdaWorkspace.js +4 -2
  380. package/dist/javascript/analysis/JavaScriptAnalysisTransfer.js +326 -0
  381. package/dist/javascript/analysis/JavaScriptAnalysisWorker.js +520 -0
  382. package/dist/javascript/analysis/JavaScriptAnalysisWorkerProtocol.js +63 -0
  383. package/dist/javascript/analysis/JavaScriptAnalysisWorkerSchemas.js +521 -0
  384. package/dist/javascript/analysis/JavaScriptAnalysisWorkerServer.js +116 -0
  385. package/dist/javascript/recovery/RecoveryFiles.js +18 -7
  386. package/dist/javascript/recovery/WakaruCommand.js +22 -9
  387. package/dist/javascript/recovery/WakaruExecution.js +11 -7
  388. package/dist/javascript/recovery/WakaruProvider.js +139 -16
  389. package/dist/javascript/recovery/WakaruPublication.js +14 -4
  390. package/dist/javascript/recovery/WakaruRelease.js +10 -3
  391. package/dist/javascript/recovery/WakaruReport.js +12 -7
  392. package/dist/javascript/recovery/WakaruVersion.js +24 -0
  393. package/dist/javascript/sourceMaps/SourceMapCommand.js +16 -3
  394. package/dist/javascript/sourceMaps/SourceMapDecoder.js +63 -7
  395. package/dist/javascript/sourceMaps/SourceMapFormat.js +15 -4
  396. package/dist/jeb/JebMcpConnection.js +179 -0
  397. package/dist/jeb/JebMcpEndpoint.js +40 -0
  398. package/dist/jeb/JebProvider.js +299 -0
  399. package/dist/mcpDoctor.js +15 -6
  400. package/dist/native/AppleDispatchCoverage.js +25 -12
  401. package/dist/native/AppleDispatchMetadata.js +14 -7
  402. package/dist/native/AppleMachoFixups.js +71 -15
  403. package/dist/native/AppleMachoSelection.js +23 -3
  404. package/dist/native/NativeMacOSProvider.js +91 -16
  405. package/dist/native/NativeMachoInspection.js +11 -7
  406. package/dist/native/NativeUiHelperRuntime.js +6 -1
  407. package/dist/native/NativeUiObservation.js +4 -3
  408. package/dist/native/pe/PeResourceLayout.js +120 -0
  409. package/dist/native/pe/PeResourceParser.js +286 -0
  410. package/dist/native/pe/PeResourcesProvider.js +63 -0
  411. package/dist/process/BoundedFileBytes.js +30 -7
  412. package/dist/process/DarwinProcessRunTokenReader.js +8 -3
  413. package/dist/process/OwnedCommand.js +4 -3
  414. package/dist/process/ProcessOwnership.js +104 -112
  415. package/dist/process/ProcessOwnershipObservation.js +96 -39
  416. package/dist/process/ProcessOwnershipProcessTree.js +4 -4
  417. package/dist/process/ProcessRunTokenObservations.js +48 -0
  418. package/dist/process/ProviderDiagnosticRedaction.js +17 -0
  419. package/dist/process/ProviderProcess.js +4 -3
  420. package/dist/process/capture/FilesystemSnapshot.js +28 -4
  421. package/dist/process/capture/ProcessCaptureLifecycle.js +350 -59
  422. package/dist/process/capture/ProcessCaptureProgress.js +62 -0
  423. package/dist/process/capture/ProcessHarness.js +194 -26
  424. package/dist/process/capture/ProcessNormalization.js +3 -1
  425. package/dist/process/capture/ProcessScenarioRuntimeValidation.js +5 -2
  426. package/dist/process/capture/TerminalRenderer.js +24 -7
  427. package/dist/reference/ReferenceSourceReader.js +22 -5
  428. package/dist/reference/ReferenceSourceReaderEntries.js +114 -51
  429. package/dist/reference/ReferenceSourceReaderErrors.js +12 -0
  430. package/dist/reference/ReferenceSourceReaderFile.js +50 -10
  431. package/dist/reference/ReferenceSourceReaderValidate.js +4 -4
  432. package/dist/server/EvidenceMcpServer.js +36 -1
  433. package/dist/server/admittedToolOperation.js +9 -0
  434. package/dist/server/contractToolHandler.js +21 -0
  435. package/dist/server/createServer.js +97 -25
  436. package/dist/server/registerAdbTools.js +36 -0
  437. package/dist/server/registerAnalysisViewTool.js +4 -9
  438. package/dist/server/registerAndroidTools.js +3 -12
  439. package/dist/server/registerApktoolTools.js +14 -0
  440. package/dist/server/registerApplicationTools/compareExportShapes.js +4 -6
  441. package/dist/server/registerApplicationTools/compareSourceToBundle.js +4 -5
  442. package/dist/server/registerApplicationTools/compareVersions.js +3 -2
  443. package/dist/server/registerApplicationTools/coverage.js +3 -2
  444. package/dist/server/registerApplicationTools/obligationLedger.js +3 -2
  445. package/dist/server/registerApplicationTools/projectMobileApplicationGraph.js +3 -2
  446. package/dist/server/registerApplicationTools/traceFeature.js +3 -2
  447. package/dist/server/registerApplicationTools/traceSemantics.js +4 -5
  448. package/dist/server/registerArtifactComparisonTool.js +5 -2
  449. package/dist/server/registerBinaryDiagnosticsTools.js +16 -4
  450. package/dist/server/registerBrowserScenarioTool.js +10 -7
  451. package/dist/server/registerBrowserTools.js +12 -9
  452. package/dist/server/registerBundleComparisonTool.js +16 -2
  453. package/dist/server/registerCloseLifecycleTool.js +32 -3
  454. package/dist/server/registerElectronTools.js +28 -10
  455. package/dist/server/registerEvmTools.js +4 -9
  456. package/dist/server/registerFirmwareTools.js +3 -11
  457. package/dist/server/registerFunctionComparisonTool.js +5 -2
  458. package/dist/server/registerJavaScriptRecoveryTool.js +4 -9
  459. package/dist/server/registerJavaScriptRuntimeObservationTools.js +9 -6
  460. package/dist/server/registerJebTools.js +16 -0
  461. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +4 -6
  462. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +3 -2
  463. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +3 -2
  464. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +3 -2
  465. package/dist/server/registerPeResourcesTool.js +9 -0
  466. package/dist/server/registerProcessComparisonTool.js +5 -2
  467. package/dist/server/registerRecordedCrashTools.js +4 -9
  468. package/dist/server/registerSessionRecordTools.js +2 -1
  469. package/dist/server/registerSessionTools.js +49 -18
  470. package/dist/server/registerWebModuleTool.js +4 -9
  471. package/dist/server/registerWebNetworkCaptureTool.js +4 -9
  472. package/dist/server/registerWebRuntimeTools.js +6 -5
  473. package/dist/server/registerWebScriptTool.js +6 -3
  474. package/dist/server/registerWebSourceLocationTool.js +4 -9
  475. package/dist/server/toolResult.js +21 -2
  476. package/dist/windows/WindowsPrivateRuntime.js +12 -5
  477. package/install.sh +7 -4
  478. package/licenses/nativeaot-parser-MIT.txt +28 -0
  479. package/native/windows/build/manifest.json +2 -2
  480. package/native/windows/build/rea-windows-x64.node +0 -0
  481. package/package.json +33 -5
  482. package/scripts/rea.mjs +16 -1
  483. package/skills/reverse-engineer-anything/SKILL.md +52 -51
  484. package/skills/reverse-engineer-anything/references/connection-and-recovery.md +127 -0
  485. package/skills/reverse-engineer-anything/references/evidence-workflows.md +8 -0
  486. package/skills/reverse-engineer-anything/references/javascript-applications.md +92 -13
  487. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +22 -1
@@ -27,6 +27,7 @@ import java.util.Comparator;
27
27
  import java.util.HashSet;
28
28
  import java.util.IdentityHashMap;
29
29
  import java.util.Iterator;
30
+ import java.util.LinkedHashMap;
30
31
  import java.util.List;
31
32
  import java.util.Locale;
32
33
  import java.util.Map;
@@ -80,12 +81,14 @@ import ghidra.program.database.mem.FileBytes;
80
81
  import ghidra.program.model.address.SegmentedAddress;
81
82
  import ghidra.program.model.reloc.Relocation;
82
83
  import java.io.ByteArrayOutputStream;
84
+ import ghidra.program.model.symbol.Namespace;
83
85
  import ghidra.program.model.symbol.RefType;
84
86
  import ghidra.program.model.symbol.Reference;
85
87
  import ghidra.program.model.symbol.ReferenceIterator;
86
88
  import ghidra.program.model.symbol.SourceType;
87
89
  import ghidra.program.model.symbol.Symbol;
88
90
  import ghidra.program.model.symbol.SymbolIterator;
91
+ import ghidra.program.model.symbol.SymbolTable;
89
92
  import ghidra.program.model.pcode.FunctionPrototype;
90
93
  import ghidra.program.model.pcode.HighFunction;
91
94
  import ghidra.program.model.pcode.HighParam;
@@ -120,6 +123,8 @@ public final class ReaGhidraBridge extends HeadlessScript {
120
123
  );
121
124
  private static final String[] CAPABILITIES = {
122
125
  "annotate_native_function",
126
+ "set_address_name",
127
+ "set_addresses_names",
123
128
  "inspect_native_load_image",
124
129
  "read_bytes",
125
130
  "address_to_file_offset",
@@ -373,7 +378,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
373
378
  case "inspect_native_instruction" -> inspectNativeInstruction(request.params);
374
379
  case "resolve_native_call_targets" -> resolveNativeCallTargets(request.params);
375
380
  case "xrefs" -> xrefs(request.params);
376
- case "analyze_function" -> analyzeFunction(request.params);
381
+ case "analyze_function" -> {
382
+ FunctionAnalysisObservation observation = analyzeFunctionObservation(request.params);
383
+ yield functionObservation(observation.dossier(), observation.limitations());
384
+ }
385
+ case "set_address_name", "set_addresses_names" -> {
386
+ if (!descriptor.transport.equals("unix-socket"))
387
+ throw new RequestFailure("method_unavailable", "Windows P0 does not admit database mutation");
388
+ yield request.method.equals("set_address_name")
389
+ ? setAddressName(request.params) : setAddressesNames(request.params);
390
+ }
377
391
  case "annotate_native_function" -> {
378
392
  if (!descriptor.transport.equals("unix-socket"))
379
393
  throw new RequestFailure("method_unavailable", "Windows P0 does not admit database mutation");
@@ -417,12 +431,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
417
431
  result.addProperty("analysis_timed_out", timedOut);
418
432
  result.add("capabilities", GSON.toJsonTree(
419
433
  java.util.Arrays.stream(CAPABILITIES)
420
- .filter(value -> !readOnly || !value.equals("annotate_native_function"))
434
+ .filter(value -> !readOnly || !(value.equals("annotate_native_function")
435
+ || value.equals("set_address_name") || value.equals("set_addresses_names")))
421
436
  .toArray(String[]::new)
422
437
  ));
423
438
  result.add("target", target);
424
439
  JsonArray extensions = analysisExtensions.reports();
425
440
  if (extensions.size() > 0) result.add("analysis_extensions", extensions);
441
+ // Written by ReaGhidraApplySeeds before auto-analysis; REA checks it
442
+ // against the committed profile.
443
+ String seeds = currentProgram.getOptions(ghidra.program.model.listing.Program.PROGRAM_INFO)
444
+ .getString("REA analysis seeds", null);
445
+ if (seeds != null) result.add("analysis_seeds", JsonParser.parseString(seeds));
426
446
  return result;
427
447
  }
428
448
 
@@ -641,10 +661,14 @@ public final class ReaGhidraBridge extends HeadlessScript {
641
661
  private JsonElement procedurePseudocode(JsonObject params) throws Exception {
642
662
  requireKeys(params, Set.of("document", "procedure"));
643
663
  requireDocument(params);
644
- String value = decompiledText(
645
- decompile(resolveProcedure(requireString(params, "procedure")))
664
+ Function function = resolveProcedure(requireString(params, "procedure"));
665
+ String value = decompiledText(decompile(function));
666
+ return functionObservation(
667
+ value == null ? JsonNull.INSTANCE : GSON.toJsonTree(value),
668
+ noReturnCallLimitations(
669
+ currentProgram.getListing().getInstructions(function.getBody(), true)
670
+ )
646
671
  );
647
- return value == null ? JsonNull.INSTANCE : GSON.toJsonTree(value);
648
672
  }
649
673
 
650
674
  private JsonElement procedureAssembly(JsonObject params) throws Exception {
@@ -652,7 +676,10 @@ public final class ReaGhidraBridge extends HeadlessScript {
652
676
  requireDocument(params);
653
677
  Function function = resolveProcedure(requireString(params, "procedure"));
654
678
  InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
655
- return GSON.toJsonTree(renderAssembly(scan.instructions));
679
+ return functionObservation(
680
+ GSON.toJsonTree(renderAssembly(scan.instructions)),
681
+ noReturnCallLimitations(scan.instructions)
682
+ );
656
683
  }
657
684
 
658
685
  private JsonObject readFunctionInstructions(JsonObject params) throws Exception {
@@ -670,11 +697,13 @@ public final class ReaGhidraBridge extends HeadlessScript {
670
697
  limitations.add(
671
698
  "The fast path does not invoke the decompiler or scan whole-program names and strings."
672
699
  );
700
+ List<String> boundaryLimitations = noReturnCallLimitations(scan.instructions);
701
+ for (String limitation : boundaryLimitations) limitations.add(limitation);
673
702
  JsonObject result = new JsonObject();
674
703
  result.add("procedure", procedureIdentity(function));
675
704
  result.add("instructions", items);
676
705
  result.add("limitations", limitations);
677
- return result;
706
+ return functionObservation(result, boundaryLimitations);
678
707
  }
679
708
 
680
709
  private JsonArray procedureCalls(JsonObject params, boolean callers) throws Exception {
@@ -707,7 +736,12 @@ public final class ReaGhidraBridge extends HeadlessScript {
707
736
  result.addProperty("signature", function.getPrototypeString(false, true));
708
737
  result.add("locals", functionLocals(function));
709
738
  result.add("classification", functionClassification(function));
710
- return result;
739
+ return functionObservation(
740
+ result,
741
+ noReturnCallLimitations(
742
+ currentProgram.getListing().getInstructions(function.getBody(), true)
743
+ )
744
+ );
711
745
  }
712
746
 
713
747
  private JsonObject procedureReferences(JsonObject params) throws Exception {
@@ -841,14 +875,13 @@ public final class ReaGhidraBridge extends HeadlessScript {
841
875
 
842
876
  private JsonObject instructionReference(Reference reference) {
843
877
  JsonObject item = new JsonObject();
844
- RefType type = reference.getReferenceType();
878
+ JsonObject kind = referenceKind(reference);
879
+ item.addProperty("source_address", canonicalAddress(reference.getFromAddress()));
845
880
  item.addProperty("target_address", canonicalAddress(reference.getToAddress()));
846
- item.addProperty("type", type.getName());
847
- item.addProperty("call", type.isCall());
848
- item.addProperty("jump", type.isJump());
849
- item.addProperty("indirect", type.isIndirect());
850
- item.addProperty("computed", type.isComputed());
851
- item.addProperty("operand_index", reference.getOperandIndex());
881
+ for (String field : List.of("type", "call", "jump", "indirect", "computed",
882
+ "operand_index", "data", "read", "write", "primary", "provenance"))
883
+ item.add(field, kind.get(field));
884
+ item.addProperty("source", normalizedSource(reference.getSource()));
852
885
  return item;
853
886
  }
854
887
 
@@ -1028,11 +1061,11 @@ public final class ReaGhidraBridge extends HeadlessScript {
1028
1061
  for (String field : List.of("name", "comment", "inline_comment")) {
1029
1062
  if (params.has(field)) validateAnnotationText(requireText(params, field), field);
1030
1063
  }
1031
- String leafName = params.has("name")
1032
- ? annotationLeafName(function, requireString(params, "name")) : null;
1033
1064
  int transaction = currentProgram.startTransaction("REA function annotations");
1034
1065
  boolean commit = false;
1035
1066
  try {
1067
+ String leafName = params.has("name")
1068
+ ? symbolLeafName(function.getSymbol(), requireString(params, "name")) : null;
1036
1069
  // Validate edits through Ghidra's own setters, including name rules.
1037
1070
  // A later invalid name must roll back earlier comment writes.
1038
1071
  for (String field : List.of("comment", "inline_comment")) {
@@ -1081,18 +1114,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
1081
1114
  }
1082
1115
  }
1083
1116
 
1084
- private String annotationLeafName(Function function, String requested) {
1117
+ private String symbolLeafName(Symbol symbol, String requested) throws ghidra.util.exception.InvalidInputException {
1085
1118
  // Fully qualified readback must be usable as an idempotent rename input.
1086
- if (requested.equals(procedureName(function))) return function.getName();
1087
- String namespace = function.getParentNamespace().isGlobal()
1088
- ? "" : function.getParentNamespace().getName(true);
1119
+ if (requested.equals(symbol.getName(true))) return symbol.getName();
1120
+ String namespace = symbol.getParentNamespace().isGlobal()
1121
+ ? "" : symbol.getParentNamespace().getName(true);
1089
1122
  String prefix = namespace + "::";
1090
1123
  if (!namespace.isEmpty() && requested.startsWith(prefix)) {
1091
1124
  String leaf = requested.substring(prefix.length());
1092
1125
  if (!leaf.isEmpty()) return leaf;
1093
- throw new RequestFailure("invalid_function_name",
1094
- "Function name has an empty leaf within namespace " + namespace + " at " +
1095
- canonicalAddress(function.getEntryPoint()) + ": " + requested);
1126
+ throw new ghidra.util.exception.InvalidInputException(
1127
+ "Symbol name has an empty leaf within namespace " + namespace + " at " +
1128
+ canonicalAddress(symbol.getAddress()) + ": " + requested);
1096
1129
  }
1097
1130
  return requested;
1098
1131
  }
@@ -1120,6 +1153,10 @@ public final class ReaGhidraBridge extends HeadlessScript {
1120
1153
  }
1121
1154
 
1122
1155
  private JsonObject analyzeFunction(JsonObject params) throws Exception {
1156
+ return analyzeFunctionObservation(params).dossier();
1157
+ }
1158
+
1159
+ private FunctionAnalysisObservation analyzeFunctionObservation(JsonObject params) throws Exception {
1123
1160
  requireKeys(params, Set.of("procedure"));
1124
1161
  Function function = resolveProcedure(requireString(params, "procedure"));
1125
1162
  InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
@@ -1177,12 +1214,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
1177
1214
  limitations.add(
1178
1215
  "Synthetic Ghidra entry-point references without actionable memory sources are omitted."
1179
1216
  );
1217
+ List<String> boundaryLimitations = noReturnCallLimitations(scan.instructions);
1218
+ for (String limitation : boundaryLimitations) {
1219
+ limitations.add(limitation);
1220
+ }
1180
1221
  if (decompilation != null && decompilation.getErrorMessage() != null &&
1181
1222
  !decompilation.getErrorMessage().isBlank()) {
1182
1223
  limitations.add("Ghidra decompiler diagnostic: " + decompilation.getErrorMessage());
1183
1224
  }
1184
1225
  result.add("limitations", limitations);
1185
- return result;
1226
+ return new FunctionAnalysisObservation(result, boundaryLimitations);
1186
1227
  }
1187
1228
 
1188
1229
  private JsonObject nativeValueFlow(DecompileResults decompilation) throws Exception {
@@ -1417,6 +1458,89 @@ public final class ReaGhidraBridge extends HeadlessScript {
1417
1458
  return symbol == null ? JsonNull.INSTANCE : GSON.toJsonTree(symbol.getName(true));
1418
1459
  }
1419
1460
 
1461
+ // Analyst naming: a function entry renames the function, other addresses get a primary label.
1462
+ private boolean applyAddressName(Address address, String name) throws Exception {
1463
+ if (!currentProgram.getMemory().contains(address))
1464
+ throw new RequestFailure("invalid_request", "Address is not mapped: " + canonicalAddress(address));
1465
+ validateAnnotationText(name, "name");
1466
+ int transaction = currentProgram.startTransaction("REA address name");
1467
+ boolean commit = false;
1468
+ try {
1469
+ Function function = currentProgram.getFunctionManager().getFunctionAt(address);
1470
+ SymbolTable table = currentProgram.getSymbolTable();
1471
+ Symbol primary = function == null ? table.getPrimarySymbol(address) : function.getSymbol();
1472
+ Namespace namespace = primary == null
1473
+ ? currentProgram.getGlobalNamespace()
1474
+ : primary.getParentNamespace();
1475
+ String expected = primary == null ? name : symbolLeafName(primary, name);
1476
+ for (Symbol symbol : table.getLabelOrFunctionSymbols(expected, namespace)) {
1477
+ if (!address.equals(symbol.getAddress())) return false;
1478
+ }
1479
+ if (function != null) {
1480
+ function.setName(expected, SourceType.USER_DEFINED);
1481
+ } else {
1482
+ if (primary != null && !primary.isDynamic()) {
1483
+ primary.setName(expected, SourceType.USER_DEFINED);
1484
+ } else {
1485
+ Symbol created = table.createLabel(address, expected, SourceType.USER_DEFINED);
1486
+ created.setPrimary();
1487
+ }
1488
+ }
1489
+ invalidateAnalysisCaches();
1490
+ Symbol readback = currentProgram.getSymbolTable().getPrimarySymbol(address);
1491
+ commit = readback != null && expected.equals(readback.getName());
1492
+ return commit;
1493
+ }
1494
+ catch (ghidra.util.exception.InvalidInputException | ghidra.util.exception.DuplicateNameException exception) {
1495
+ return false;
1496
+ }
1497
+ finally {
1498
+ currentProgram.endTransaction(transaction, commit);
1499
+ }
1500
+ }
1501
+
1502
+ private JsonElement setAddressName(JsonObject params) throws Exception {
1503
+ requireKeys(params, Set.of("document", "address", "name"));
1504
+ requireDocument(params);
1505
+ return GSON.toJsonTree(applyAddressName(requireAddress(params, "address"), requireString(params, "name")));
1506
+ }
1507
+
1508
+ private JsonElement setAddressesNames(JsonObject params) throws Exception {
1509
+ requireKeys(params, Set.of("document", "names"));
1510
+ requireDocument(params);
1511
+ if (!params.has("names") || !params.get("names").isJsonObject())
1512
+ throw new RequestFailure("invalid_request", "names must be an object of address -> name");
1513
+ Map<Address, List<AddressNameRequest>> requestsByAddress = new LinkedHashMap<>();
1514
+ JsonObject result = new JsonObject();
1515
+ for (Map.Entry<String, JsonElement> entry : params.getAsJsonObject("names").entrySet()) {
1516
+ try {
1517
+ Address address = parseReaAddress(entry.getKey());
1518
+ requestsByAddress.computeIfAbsent(address, ignored -> new ArrayList<>())
1519
+ .add(new AddressNameRequest(entry.getKey(), address, entry.getValue().getAsString()));
1520
+ }
1521
+ catch (RequestFailure failure) {
1522
+ result.addProperty(entry.getKey(), false);
1523
+ }
1524
+ }
1525
+ for (List<AddressNameRequest> requests : requestsByAddress.values()) {
1526
+ String name = requests.get(0).name;
1527
+ boolean conflictingAliasNames = requests.stream()
1528
+ .anyMatch(request -> !request.name.equals(name));
1529
+ boolean success = false;
1530
+ if (!conflictingAliasNames) {
1531
+ try {
1532
+ success = applyAddressName(requests.get(0).address, name);
1533
+ }
1534
+ catch (RequestFailure failure) {
1535
+ // Keep per-address failures in the existing batch result shape.
1536
+ }
1537
+ }
1538
+ for (AddressNameRequest request : requests)
1539
+ result.addProperty(request.inputAddress, success);
1540
+ }
1541
+ return result;
1542
+ }
1543
+
1420
1544
  private JsonArray listDocuments(JsonObject params) {
1421
1545
  requireKeys(params, Set.of());
1422
1546
  JsonArray result = new JsonArray();
@@ -1880,9 +2004,20 @@ public final class ReaGhidraBridge extends HeadlessScript {
1880
2004
  );
1881
2005
  JumpTable.LoadTable[] loadTables = jumpTable.getLoadTables();
1882
2006
  RecoveredAArch64RelativeTable recoveredAArch64 =
1883
- loadTables.length == 0
1884
- ? recoverAArch64RelativeTable(jumpTable)
1885
- : null;
2007
+ recoverAArch64RelativeTable(jumpTable);
2008
+ if (recoveredAArch64 != null && loadTables.length > 0) {
2009
+ // A decoded table only adds evidence when Ghidra's own load table
2010
+ // describes the same bytes; any disagreement keeps Ghidra's view.
2011
+ boolean agrees = false;
2012
+ for (JumpTable.LoadTable loadTable : loadTables) {
2013
+ if (loadTable.getAddress().equals(recoveredAArch64.tableAddress) &&
2014
+ loadTable.getSize() == recoveredAArch64.entrySize &&
2015
+ loadTable.getNum() == recoveredAArch64.mappings.size()) {
2016
+ agrees = true;
2017
+ }
2018
+ }
2019
+ if (!agrees) recoveredAArch64 = null;
2020
+ }
1886
2021
  List<Address> referencedDataAddresses = loadTables.length == 0
1887
2022
  ? dispatchPathDataReferences(jumpTable)
1888
2023
  : List.of();
@@ -1916,7 +2051,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
1916
2051
  source.add("evidence", evidence);
1917
2052
  dataSources.add(source);
1918
2053
  }
1919
- if (recoveredAArch64 != null) {
2054
+ if (recoveredAArch64 != null && loadTables.length == 0) {
1920
2055
  JsonObject source = new JsonObject();
1921
2056
  source.addProperty(
1922
2057
  "address",
@@ -1931,9 +2066,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
1931
2066
  inferenceEvidence(
1932
2067
  "jump-table",
1933
2068
  "ghidra-instruction-and-memory",
1934
- "Decoded " + recoveredAArch64.mappings.size() +
2069
+ "Decoded " + recoveredAArch64.mappings.size() + " " +
2070
+ (recoveredAArch64.signedEntries ? "signed" : "unsigned") +
1935
2071
  " entries of size " + recoveredAArch64.entrySize + " bytes from " + canonicalAddress(recoveredAArch64.tableAddress) +
1936
- "; the AArch64 LDRB-or-LDRH/ADR/ADD/BR chain uses a selector guarded by an unsigned upper-bound branch."
2072
+ "; the AArch64 ADRP/ADD table, indexed load, ADR base, ADD" +
2073
+ (recoveredAArch64.offsetShift == 0 ? "" : " LSL #" + recoveredAArch64.offsetShift) +
2074
+ "/BR chain uses a selector bounded by CMP #" + recoveredAArch64.compareImmediate +
2075
+ " and " + recoveredAArch64.guardMnemonic + " on one straight-line path" +
2076
+ (recoveredAArch64.nullTarget == null
2077
+ ? "."
2078
+ : "; zero entries are routed by CBZ to " + canonicalAddress(recoveredAArch64.nullTarget) + ".")
1937
2079
  )
1938
2080
  );
1939
2081
  source.add("evidence", evidence);
@@ -1972,6 +2114,33 @@ public final class ReaGhidraBridge extends HeadlessScript {
1972
2114
  result.add("data_sources", dataSources);
1973
2115
  Address[] targets = jumpTable.getCases();
1974
2116
  TypedJumpTableCases typedCases = typedJumpTableCases(jumpTable, caseTokens);
2117
+ // Table indices identify the register bounded by CMP. Typed labels may
2118
+ // instead identify a selector before normalization (for example, SUB
2119
+ // index, selector, #10). Do not merge those distinct value identities.
2120
+ if (recoveredAArch64 != null && recoveredAArch64.priorSelectorDefinition != null) {
2121
+ typedCases.limitations.add(
2122
+ "REA decoded the relative table, but the compared selector has an earlier register definition at " +
2123
+ canonicalAddress(recoveredAArch64.priorSelectorDefinition) + "; its table indices are not proven to use the typed switch selector's case values. Ghidra's typed labels are retained and additional case labels remain unknown."
2124
+ );
2125
+ recoveredAArch64 = null;
2126
+ }
2127
+ if (recoveredAArch64 != null) {
2128
+ for (Map.Entry<Long, Address> typed : typedCases.cases.entrySet()) {
2129
+ long value = typed.getKey();
2130
+ if (value < 0 || value >= recoveredAArch64.mappings.size()) {
2131
+ typedCases.limitations.add("Typed case labels and decoded table indices do not use a verified common selector; Ghidra's typed labels are retained.");
2132
+ recoveredAArch64 = null;
2133
+ break;
2134
+ }
2135
+ RecoveredAArch64Mapping decoded = recoveredAArch64.mappings.get((int) value);
2136
+ boolean divertedZero = decoded.nullEntry && typed.getValue().equals(recoveredAArch64.unreachableZeroTarget);
2137
+ if (!typed.getValue().equals(decoded.targetAddress) && !divertedZero) {
2138
+ typedCases.limitations.add("Typed case evidence and the decoded relative table disagree on a destination; Ghidra's typed labels are retained.");
2139
+ recoveredAArch64 = null;
2140
+ break;
2141
+ }
2142
+ }
2143
+ }
1975
2144
  JsonArray mappings = new JsonArray();
1976
2145
  JsonArray defaultTargets = new JsonArray();
1977
2146
  for (Address target : typedCases.defaults) {
@@ -1979,27 +2148,30 @@ public final class ReaGhidraBridge extends HeadlessScript {
1979
2148
  }
1980
2149
  Set<Address> coveredTargets = new HashSet<>(typedCases.cases.values());
1981
2150
  coveredTargets.addAll(typedCases.defaults);
2151
+ if (recoveredAArch64 != null && recoveredAArch64.unreachableZeroTarget != null) {
2152
+ Address unreachable = recoveredAArch64.unreachableZeroTarget;
2153
+ coveredTargets.add(unreachable);
2154
+ for (RecoveredAArch64Mapping recovered : recoveredAArch64.mappings) {
2155
+ if (recovered.nullEntry && unreachable.equals(typedCases.cases.get((long) recovered.tableIndex))) {
2156
+ typedCases.cases.remove((long) recovered.tableIndex);
2157
+ }
2158
+ }
2159
+ typedCases.limitations.add(
2160
+ "Ghidra recovered destination " + canonicalAddress(unreachable) +
2161
+ " as the zero-offset decode of the relative table, but the CBZ before the dispatch diverts zero entries to " +
2162
+ canonicalAddress(recoveredAArch64.nullTarget) + ", so the indirect branch never reaches it for an in-range case."
2163
+ );
2164
+ }
1982
2165
  if (recoveredAArch64 != null) {
1983
2166
  for (int index = 0; index < recoveredAArch64.mappings.size(); index += 1) {
1984
2167
  monitor.checkCancelled();
1985
2168
  RecoveredAArch64Mapping recovered = recoveredAArch64.mappings.get(index);
1986
2169
  if (
1987
2170
  typedCases.unknownTargets.contains(recovered.targetAddress) ||
1988
- typedCases.ambiguousValues.contains((long) recovered.caseValue)
2171
+ typedCases.ambiguousValues.contains((long) recovered.tableIndex)
1989
2172
  ) continue;
1990
- Address typedTarget = typedCases.cases.get((long) recovered.caseValue);
1991
- if (typedTarget != null && !typedTarget.equals(recovered.targetAddress)) {
1992
- typedCases.cases.remove((long) recovered.caseValue);
1993
- typedCases.ambiguousValues.add((long) recovered.caseValue);
1994
- typedCases.unknownTargets.add(typedTarget);
1995
- typedCases.unknownTargets.add(recovered.targetAddress);
1996
- typedCases.limitations.add(
1997
- "Typed case evidence and the verified relative table disagree on a destination; both destinations remain unknown for that case."
1998
- );
1999
- continue;
2000
- }
2001
2173
  JsonObject mapping = new JsonObject();
2002
- mapping.addProperty("case_value", recovered.caseValue);
2174
+ mapping.addProperty("case_value", recovered.tableIndex);
2003
2175
  String targetAddress = canonicalAddress(recovered.targetAddress);
2004
2176
  mapping.addProperty("target_address", targetAddress);
2005
2177
  mapping.addProperty("confidence", "high");
@@ -2008,14 +2180,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
2008
2180
  inferenceEvidence(
2009
2181
  "jump-table",
2010
2182
  "ghidra-instruction-and-memory",
2011
- "Case value " + recovered.caseValue + (recoveredAArch64.entrySize == 1 ? " indexes byte " : " indexes halfword at byte offset ") +
2012
- (recovered.caseValue * recoveredAArch64.entrySize) + " at " + canonicalAddress(recoveredAArch64.tableAddress) +
2013
- "; the loaded unsigned entry selects a four-byte instruction offset from " +
2014
- canonicalAddress(recoveredAArch64.branchBase) + ", reaching " + targetAddress + "."
2183
+ "Case value " + recovered.tableIndex + (recoveredAArch64.entrySize == 1 ? " indexes byte " : " indexes halfword at byte offset ") +
2184
+ (recovered.tableIndex * recoveredAArch64.entrySize) + " at " + canonicalAddress(recoveredAArch64.tableAddress) +
2185
+ (recovered.nullEntry
2186
+ ? "; the entry is zero, so the CBZ before the dispatch branches to " + targetAddress + "."
2187
+ : "; the loaded " + (recoveredAArch64.signedEntries ? "signed" : "unsigned") +
2188
+ " entry " + recovered.encodedEntry + " selects a " +
2189
+ (recoveredAArch64.offsetShift == 2 ? "four-byte instruction" : "byte") +
2190
+ " offset from " + canonicalAddress(recoveredAArch64.branchBase) + ", reaching " + targetAddress + ".")
2015
2191
  )
2016
2192
  );
2017
2193
  mapping.add("evidence", evidence);
2018
- if (!typedCases.cases.containsKey((long) recovered.caseValue)) {
2194
+ if (!typedCases.cases.containsKey((long) recovered.tableIndex)) {
2019
2195
  mappings.add(mapping);
2020
2196
  }
2021
2197
  coveredTargets.add(recovered.targetAddress);
@@ -2089,7 +2265,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
2089
2265
  );
2090
2266
  }
2091
2267
  if (recoveredAArch64 == null && loadTables.length == 0 && currentProgram.getLanguageID().getIdAsString().startsWith("AARCH64:")) {
2092
- limitations.add("REA did not verify a supported byte/halfword relative dispatch chain with exact unsigned bounds and register definitions; unsupported encodings and ambiguous bounds remain unknown.");
2268
+ limitations.add("REA did not verify a supported signed or unsigned byte/halfword relative dispatch chain with exact unsigned bounds and register definitions; unsupported encodings and ambiguous bounds remain unknown.");
2093
2269
  }
2094
2270
  if (targets.length == 0) {
2095
2271
  limitations.add(
@@ -2219,12 +2395,21 @@ public final class ReaGhidraBridge extends HeadlessScript {
2219
2395
  }
2220
2396
 
2221
2397
  private static final class RecoveredAArch64Mapping {
2222
- private final int caseValue;
2398
+ private final int tableIndex;
2223
2399
  private final Address targetAddress;
2224
-
2225
- private RecoveredAArch64Mapping(int caseValue, Address targetAddress) {
2226
- this.caseValue = caseValue;
2400
+ private final long encodedEntry;
2401
+ private final boolean nullEntry;
2402
+
2403
+ private RecoveredAArch64Mapping(
2404
+ int tableIndex,
2405
+ Address targetAddress,
2406
+ long encodedEntry,
2407
+ boolean nullEntry
2408
+ ) {
2409
+ this.tableIndex = tableIndex;
2227
2410
  this.targetAddress = targetAddress;
2411
+ this.encodedEntry = encodedEntry;
2412
+ this.nullEntry = nullEntry;
2228
2413
  }
2229
2414
  }
2230
2415
 
@@ -2232,24 +2417,70 @@ public final class ReaGhidraBridge extends HeadlessScript {
2232
2417
  private final Address tableAddress;
2233
2418
  private final Address branchBase;
2234
2419
  private final int entrySize;
2420
+ private final Address priorSelectorDefinition;
2421
+ private final boolean signedEntries;
2422
+ private final int offsetShift;
2423
+ private final String guardMnemonic;
2424
+ private final long compareImmediate;
2425
+ private final Address nullTarget;
2426
+ private final Address unreachableZeroTarget;
2235
2427
  private final List<RecoveredAArch64Mapping> mappings;
2236
2428
 
2237
2429
  private RecoveredAArch64RelativeTable(
2238
2430
  Address tableAddress,
2239
2431
  Address branchBase,
2240
2432
  int entrySize,
2433
+ Address priorSelectorDefinition,
2434
+ boolean signedEntries,
2435
+ int offsetShift,
2436
+ String guardMnemonic,
2437
+ long compareImmediate,
2438
+ Address nullTarget,
2439
+ Address unreachableZeroTarget,
2241
2440
  List<RecoveredAArch64Mapping> mappings
2242
2441
  ) {
2243
2442
  this.tableAddress = tableAddress;
2244
2443
  this.branchBase = branchBase;
2245
2444
  this.entrySize = entrySize;
2445
+ this.priorSelectorDefinition = priorSelectorDefinition;
2446
+ this.signedEntries = signedEntries;
2447
+ this.offsetShift = offsetShift;
2448
+ this.guardMnemonic = guardMnemonic;
2449
+ this.compareImmediate = compareImmediate;
2450
+ this.nullTarget = nullTarget;
2451
+ this.unreachableZeroTarget = unreachableZeroTarget;
2246
2452
  this.mappings = mappings;
2247
2453
  }
2248
2454
  }
2249
2455
 
2456
+ private static final Pattern AARCH64_DISPATCH_ADD = Pattern.compile(
2457
+ "(?i)^([wx]\\d+),\\s*([wx]\\d+),\\s*([wx]\\d+)(?:,\\s*LSL\\s*#(?:0x)?([0-9a-f]+))?$"
2458
+ );
2459
+ private static final Pattern AARCH64_TABLE_LOAD = Pattern.compile(
2460
+ "(?i)^([wx]\\d+),\\s*\\[([wx]\\d+),\\s*([wx]\\d+)(?:,\\s*(LSL|UXTW)\\s*(?:#(?:0x)?([0-9a-f]+))?)?\\s*\\]$"
2461
+ );
2462
+ private static final Pattern AARCH64_REGISTER_AND_ADDRESS = Pattern.compile(
2463
+ "(?i)^([wx]\\d+),\\s*(0x[0-9a-f]+)$"
2464
+ );
2465
+ private static final Pattern AARCH64_ADD_IMMEDIATE = Pattern.compile(
2466
+ "(?i)^([wx]\\d+),\\s*([wx]\\d+),\\s*#?(0x[0-9a-f]+|[0-9]+)$"
2467
+ );
2468
+ private static final Pattern AARCH64_COMPARE_IMMEDIATE = Pattern.compile(
2469
+ "(?i)^([wx]\\d+),\\s*#?(0x[0-9a-f]+|[0-9]+)$"
2470
+ );
2471
+ private static final Pattern AARCH64_MOVE_REGISTER = Pattern.compile(
2472
+ "(?i)^([wx]\\d+),\\s*([wx]\\d+)$"
2473
+ );
2474
+ private static final Set<String> AARCH64_FLAG_PRESERVING =
2475
+ Set.of("MOV", "ADRP", "ADR", "ADD");
2476
+
2250
2477
  /**
2251
- * Recover only the directly verified AArch64 byte/halfword-indexed relative branch
2252
- * table form. Other instruction sequences keep their case labels unknown.
2478
+ * Recover only directly verified AArch64 relative branch tables:
2479
+ * ADRP/ADD table base, LDRB/LDRH/LDRSB/LDRSH entry load with an exact
2480
+ * index scale, ADR branch base, optional CBZ on the loaded entry, and
2481
+ * ADD (byte or LSL #2 scale)/BR, with a selector bounded by CMP #imm and an
2482
+ * unsigned B.HI or B.CS/B.HS guard on one straight-line path. Any other
2483
+ * instruction sequence keeps its case labels unknown.
2253
2484
  */
2254
2485
  private RecoveredAArch64RelativeTable recoverAArch64RelativeTable(
2255
2486
  JumpTable jumpTable
@@ -2271,167 +2502,265 @@ public final class ReaGhidraBridge extends HeadlessScript {
2271
2502
  if (instruction.getAddress().equals(jumpTable.getSwitchAddress())) break;
2272
2503
  }
2273
2504
  int branchIndex = instructions.size() - 1;
2274
- if (branchIndex < 1 ||
2275
- !instructions.get(branchIndex).getMnemonicString().equalsIgnoreCase("BR")) {
2505
+ if (branchIndex < 1 || !mnemonic(instructions.get(branchIndex)).equals("BR")) {
2276
2506
  return null;
2277
2507
  }
2278
-
2279
- Address[] recoveredTargets = jumpTable.getCases();
2280
2508
  Set<String> recoveredTargetSet = new HashSet<>();
2281
- for (Address target : recoveredTargets) recoveredTargetSet.add(canonicalAddress(target));
2282
- List<Address> dataAddresses = dispatchPathDataReferences(jumpTable);
2283
-
2284
- for (int loadIndex = 0; loadIndex < branchIndex; loadIndex += 1) {
2285
- Instruction load = instructions.get(loadIndex);
2286
- int entrySize = load.getMnemonicString().equalsIgnoreCase("LDRB") ? 1 : load.getMnemonicString().equalsIgnoreCase("LDRH") ? 2 : 0;
2287
- if (entrySize == 0 || branchIndex != loadIndex + 2 || loadIndex < 3 ||
2288
- !instructions.get(loadIndex - 1).getMnemonicString().equalsIgnoreCase("ADR") ||
2289
- !instructions.get(loadIndex - 2).getMnemonicString().equalsIgnoreCase("ADD") ||
2290
- !instructions.get(loadIndex - 3).getMnemonicString().equalsIgnoreCase("ADRP")) continue;
2291
- String loadOperands = operands(load);
2292
- java.util.regex.Matcher loadMatcher = java.util.regex.Pattern
2293
- .compile(entrySize == 1 ? "(?i)^[wx]\\d+,\\s*\\[([wx]\\d+),\\s*([wx]\\d+)\\]$" : "(?i)^[wx]\\d+,\\s*\\[([wx]\\d+),\\s*([wx]\\d+),\\s*LSL\\s*#(?:0x)?1\\]$")
2294
- .matcher(loadOperands);
2295
- if (!loadMatcher.matches()) continue;
2296
- String loadedRegister = normalizedRegister(loadOperands.substring(0, loadOperands.indexOf(',')));
2297
- String tableBaseRegister = normalizedRegister(loadMatcher.group(1));
2298
- String indexRegister = normalizedRegister(loadMatcher.group(2));
2299
-
2300
- Address resolvedTableAddress = null;
2301
- for (int index = loadIndex - 3; index < loadIndex - 1; index += 1) {
2302
- Instruction instruction = instructions.get(index);
2303
- String mnemonic = instruction.getMnemonicString().toUpperCase(Locale.ROOT);
2304
- String text = operands(instruction);
2305
- if (mnemonic.equals("ADRP") &&
2306
- text.matches("(?i)^[wx]\\d+,\\s*0x[0-9a-f]+$")) {
2307
- String[] parts = text.split(",");
2308
- if (normalizedRegister(parts[0]).equals(tableBaseRegister)) {
2309
- resolvedTableAddress = addressFromOperand(parts[1]);
2310
- }
2311
- }
2312
- else if (mnemonic.equals("ADD") && resolvedTableAddress != null &&
2313
- text.matches("(?i)^[wx]\\d+,\\s*[wx]\\d+,\\s*#?(?:0x[0-9a-f]+|[0-9]+)$")) {
2314
- String[] parts = text.split(",");
2315
- if (normalizedRegister(parts[0]).equals(tableBaseRegister) &&
2316
- normalizedRegister(parts[1]).equals(tableBaseRegister)) {
2317
- resolvedTableAddress = resolvedTableAddress.add(parseInteger(parts[2].trim().replaceFirst("^#", "")));
2318
- }
2319
- }
2509
+ for (Address target : jumpTable.getCases()) recoveredTargetSet.add(canonicalAddress(target));
2510
+ if (recoveredTargetSet.isEmpty()) return null;
2511
+ return matchAArch64RelativeDispatch(
2512
+ instructions,
2513
+ branchIndex,
2514
+ recoveredTargetSet,
2515
+ dispatchPathDataReferences(jumpTable)
2516
+ );
2517
+ }
2518
+
2519
+ private RecoveredAArch64RelativeTable matchAArch64RelativeDispatch(
2520
+ List<Instruction> instructions,
2521
+ int branchIndex,
2522
+ Set<String> recoveredTargetSet,
2523
+ List<Address> dataAddresses
2524
+ ) throws Exception {
2525
+ String branchRegister = normalizedRegister(operands(instructions.get(branchIndex)));
2526
+
2527
+ // ADD Xd, Xn, Xm{, LSL #s}; BR Xd. LSL applies to Xm only.
2528
+ Instruction dispatchAdd = instructions.get(branchIndex - 1);
2529
+ if (!mnemonic(dispatchAdd).equals("ADD")) return null;
2530
+ java.util.regex.Matcher addMatcher = AARCH64_DISPATCH_ADD.matcher(operands(dispatchAdd));
2531
+ if (!addMatcher.matches() || !addMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x")) return null;
2532
+ if (!normalizedRegister(addMatcher.group(1)).equals(branchRegister)) return null;
2533
+ int offsetShift = addMatcher.group(4) == null ? 0 : Integer.parseInt(addMatcher.group(4), 16);
2534
+ if (offsetShift != 0 && offsetShift != 2) return null;
2535
+ String firstSource = normalizedRegister(addMatcher.group(2));
2536
+ String secondSource = normalizedRegister(addMatcher.group(3));
2537
+ if (firstSource.equals(secondSource)) return null;
2538
+
2539
+ // The entry register is the source whose latest definition is the
2540
+ // table load; the other source must be defined by ADR.
2541
+ int loadIndex = -1;
2542
+ String entryRegister = null;
2543
+ String baseRegister = null;
2544
+ for (String candidate : offsetShift == 0 ? List.of(secondSource, firstSource) : List.of(secondSource)) {
2545
+ int writer = latestWriter(instructions, branchIndex - 1, candidate);
2546
+ if (writer >= 0 && Set.of("LDRB", "LDRH", "LDRSB", "LDRSH").contains(mnemonic(instructions.get(writer)))) {
2547
+ loadIndex = writer;
2548
+ entryRegister = candidate;
2549
+ baseRegister = candidate.equals(firstSource) ? secondSource : firstSource;
2550
+ break;
2320
2551
  }
2321
- if (resolvedTableAddress == null || !currentProgram.getMemory().contains(resolvedTableAddress) ||
2322
- (!dataAddresses.isEmpty() && !dataAddresses.contains(resolvedTableAddress))) continue;
2323
- Address tableAddress = resolvedTableAddress;
2324
-
2325
- Address branchBase = null;
2326
- boolean addressChain = false;
2327
- for (int index = loadIndex - 1; index < branchIndex; index += 1) {
2328
- Instruction instruction = instructions.get(index);
2329
- String mnemonic = instruction.getMnemonicString().toUpperCase(Locale.ROOT);
2330
- String text = operands(instruction);
2331
- if (index < loadIndex && mnemonic.equals("ADR") && text.matches("(?i)^[wx]\\d+,\\s*0x[0-9a-f]+$")) {
2332
- int comma = text.indexOf(',');
2333
- String destination = normalizedRegister(text.substring(0, comma));
2334
- if (!destination.equals(normalizedRegister(operands(instructions.get(branchIndex))))) continue;
2335
- branchBase = addressFromOperand(text.substring(comma + 1));
2336
- }
2337
- if (index > loadIndex && mnemonic.equals("ADD") && branchBase != null &&
2338
- text.matches("(?i)^[wx]\\d+,\\s*[wx]\\d+,\\s*[wx]\\d+,\\s*LSL\\s*#(?:0x)?2$")) {
2339
- String[] parts = text.split(",");
2340
- String destination = normalizedRegister(parts[0]);
2341
- String base = normalizedRegister(parts[1]);
2342
- String offset = normalizedRegister(parts[2]);
2343
- if (destination.equals(base) &&
2344
- base.equals(normalizedRegister(operands(instructions.get(branchIndex)))) &&
2345
- offset.equals(loadedRegister)) {
2346
- addressChain = true;
2347
- }
2348
- }
2552
+ }
2553
+ if (loadIndex < 0) return null;
2554
+
2555
+ Instruction load = instructions.get(loadIndex);
2556
+ String loadMnemonic = mnemonic(load);
2557
+ int entrySize = loadMnemonic.endsWith("B") ? 1 : 2;
2558
+ boolean signedEntries = loadMnemonic.startsWith("LDRS");
2559
+ java.util.regex.Matcher loadMatcher = AARCH64_TABLE_LOAD.matcher(operands(load));
2560
+ if (!loadMatcher.matches()) return null;
2561
+ // A signed load into a W register sign-extends only to 32 bits; the
2562
+ // 64-bit dispatch ADD would then read a zero-extended value.
2563
+ if (signedEntries && !loadMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x")) return null;
2564
+ String tableBaseRegister = normalizedRegister(loadMatcher.group(2));
2565
+ String indexRegister = normalizedRegister(loadMatcher.group(3));
2566
+ boolean wordIndex = loadMatcher.group(3).toLowerCase(Locale.ROOT).startsWith("w");
2567
+ String extend = loadMatcher.group(4) == null ? null : loadMatcher.group(4).toUpperCase(Locale.ROOT);
2568
+ int indexShift = loadMatcher.group(5) == null ? 0 : Integer.parseInt(loadMatcher.group(5), 16);
2569
+ if (wordIndex ? !"UXTW".equals(extend) : "UXTW".equals(extend)) return null;
2570
+ if (indexShift != (entrySize == 1 ? 0 : 1)) return null;
2571
+
2572
+ // Only ADR and one CBZ on the entry may sit between load and ADD.
2573
+ Address nullTarget = null;
2574
+ for (int index = loadIndex + 1; index < branchIndex - 1; index += 1) {
2575
+ Instruction instruction = instructions.get(index);
2576
+ String name = mnemonic(instruction);
2577
+ if (name.equals("ADR")) continue;
2578
+ if (name.equals("CBZ") && nullTarget == null) {
2579
+ String[] parts = operands(instruction).split(",");
2580
+ Address[] flows = instruction.getFlows();
2581
+ if (parts.length != 2 || flows.length != 1 ||
2582
+ !normalizedRegister(parts[0]).equals(entryRegister)) return null;
2583
+ nullTarget = flows[0];
2584
+ continue;
2349
2585
  }
2350
- if (!addressChain || branchBase == null) continue;
2351
-
2352
- int entryCount = -1;
2353
- Address defaultTarget = null;
2354
- for (int compareIndex = 0; compareIndex < loadIndex; compareIndex += 1) {
2355
- Instruction compare = instructions.get(compareIndex);
2356
- if (!compare.getMnemonicString().equalsIgnoreCase("CMP")) continue;
2357
- java.util.regex.Matcher compareMatcher = java.util.regex.Pattern
2358
- .compile("(?i)^([wx]\\d+),\\s*#?(0x[0-9a-f]+|[0-9]+)$")
2359
- .matcher(operands(compare));
2360
- if (!compareMatcher.matches()) continue;
2361
- String comparedRegister = normalizedRegister(compareMatcher.group(1));
2362
- long upperBound = parseInteger(compareMatcher.group(2));
2363
- if (upperBound < 0 || upperBound >= 4096) continue;
2364
- for (int guardIndex = compareIndex + 1; guardIndex < loadIndex; guardIndex += 1) {
2365
- Instruction guard = instructions.get(guardIndex);
2366
- String mnemonic = guard.getMnemonicString().toUpperCase(Locale.ROOT);
2367
- if (!mnemonic.equals("B.HI")) continue;
2368
- Address[] flows = guard.getFlows();
2369
- if (flows.length != 1 || !recoveredTargetSet.contains(canonicalAddress(flows[0]))) continue;
2370
- boolean supportedSequence = true;
2371
- for (int sequenceIndex = compareIndex + 1; sequenceIndex < loadIndex; sequenceIndex += 1) {
2372
- String sequenceMnemonic = instructions.get(sequenceIndex)
2373
- .getMnemonicString().toUpperCase(Locale.ROOT);
2374
- if (!Set.of("B.HI", "MOV", "ADRP", "ADD", "ADR").contains(sequenceMnemonic)) {
2375
- supportedSequence = false;
2376
- break;
2377
- }
2378
- }
2379
- Set<String> selectorAliases = new HashSet<>();
2380
- selectorAliases.add(comparedRegister);
2381
- Set<String> boundedIndexes = new HashSet<>();
2382
- if (compareMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x")) boundedIndexes.add(comparedRegister);
2383
- for (int sequenceIndex = compareIndex + 1; sequenceIndex < loadIndex; sequenceIndex++) {
2384
- Instruction instruction = instructions.get(sequenceIndex);
2385
- String[] parts = operands(instruction).split(",");
2386
- if (parts.length == 0) continue;
2387
- if (instruction.getMnemonicString().equalsIgnoreCase("MOV")) {
2388
- String destination = normalizedRegister(parts[0]);
2389
- String source = parts.length == 2 ? normalizedRegister(parts[1]) : "";
2390
- boolean aliasesSelector = selectorAliases.contains(source);
2391
- boolean fullWidthBounded = boundedIndexes.contains(source) ||
2392
- (parts[0].trim().toLowerCase(Locale.ROOT).startsWith("w") && aliasesSelector);
2393
- if (aliasesSelector) selectorAliases.add(destination);
2394
- else selectorAliases.remove(destination);
2395
- if (fullWidthBounded) boundedIndexes.add(destination);
2396
- else boundedIndexes.remove(destination);
2397
- } else if (Set.of("ADRP", "ADD", "ADR").contains(instruction.getMnemonicString().toUpperCase(Locale.ROOT))) {
2398
- selectorAliases.remove(normalizedRegister(parts[0]));
2399
- boundedIndexes.remove(normalizedRegister(parts[0]));
2400
- }
2401
- }
2402
- boolean selectorFeedsTable = boundedIndexes.contains(indexRegister);
2403
- if (compareIndex < loadIndex - 3 && selectorFeedsTable && supportedSequence) {
2404
- entryCount = (int) upperBound + 1;
2405
- defaultTarget = flows[0];
2406
- break;
2407
- }
2408
- }
2409
- if (entryCount > 0) break;
2410
- }
2411
- if (entryCount <= 0 || entryCount > 4096 || defaultTarget == null) continue;
2412
-
2413
- List<RecoveredAArch64Mapping> mappings = new ArrayList<>();
2414
- Set<String> mappedTargets = new HashSet<>();
2415
- boolean valid = true;
2416
- for (int caseValue = 0; caseValue < entryCount; caseValue += 1) {
2417
- Address entry = tableAddress.add((long) caseValue * entrySize);
2418
- int encodedOffset = entrySize == 1 ? currentProgram.getMemory().getByte(entry) & 0xff : currentProgram.getMemory().getShort(entry) & 0xffff;
2419
- Address target = branchBase.add((long) encodedOffset * 4L);
2420
- String canonicalTarget = canonicalAddress(target);
2421
- if (!recoveredTargetSet.contains(canonicalTarget)) {
2422
- valid = false;
2423
- break;
2586
+ return null;
2587
+ }
2588
+
2589
+ int baseIndex = latestWriter(instructions, branchIndex - 1, baseRegister);
2590
+ if (baseIndex < 0 || !mnemonic(instructions.get(baseIndex)).equals("ADR")) return null;
2591
+ java.util.regex.Matcher baseMatcher = AARCH64_REGISTER_AND_ADDRESS.matcher(operands(instructions.get(baseIndex)));
2592
+ if (!baseMatcher.matches()) return null;
2593
+ Address branchBase = addressFromOperand(baseMatcher.group(2));
2594
+
2595
+ int tableAddIndex = latestWriter(instructions, loadIndex, tableBaseRegister);
2596
+ if (tableAddIndex < 0 || !mnemonic(instructions.get(tableAddIndex)).equals("ADD")) return null;
2597
+ java.util.regex.Matcher tableAddMatcher = AARCH64_ADD_IMMEDIATE.matcher(operands(instructions.get(tableAddIndex)));
2598
+ if (!tableAddMatcher.matches() ||
2599
+ !normalizedRegister(tableAddMatcher.group(1)).equals(tableBaseRegister) ||
2600
+ !normalizedRegister(tableAddMatcher.group(2)).equals(tableBaseRegister)) return null;
2601
+ int tablePageIndex = latestWriter(instructions, tableAddIndex, tableBaseRegister);
2602
+ if (tablePageIndex < 0 || !mnemonic(instructions.get(tablePageIndex)).equals("ADRP")) return null;
2603
+ java.util.regex.Matcher pageMatcher = AARCH64_REGISTER_AND_ADDRESS.matcher(operands(instructions.get(tablePageIndex)));
2604
+ if (!pageMatcher.matches() || !normalizedRegister(pageMatcher.group(1)).equals(tableBaseRegister)) return null;
2605
+ Address tableAddress = addressFromOperand(pageMatcher.group(2)).add(parseInteger(tableAddMatcher.group(3)));
2606
+ if (!currentProgram.getMemory().contains(tableAddress) ||
2607
+ (!dataAddresses.isEmpty() && !dataAddresses.contains(tableAddress))) return null;
2608
+
2609
+ // Unsigned guard: CMP Rs, #imm, then B.HI (imm + 1 entries) or
2610
+ // B.CS/B.HS (imm entries), with only flag-preserving moves and
2611
+ // address arithmetic between the compare and the load.
2612
+ int guardIndex = -1;
2613
+ for (int index = loadIndex - 1; index >= 0; index -= 1) {
2614
+ String name = mnemonic(instructions.get(index));
2615
+ if (name.startsWith("B.")) {
2616
+ guardIndex = index;
2617
+ break;
2618
+ }
2619
+ if (!AARCH64_FLAG_PRESERVING.contains(name)) return null;
2620
+ }
2621
+ if (guardIndex < 1) return null;
2622
+ Instruction guard = instructions.get(guardIndex);
2623
+ String guardMnemonic = mnemonic(guard);
2624
+ if (!Set.of("B.HI", "B.CS", "B.HS").contains(guardMnemonic)) return null;
2625
+ Address[] guardFlows = guard.getFlows();
2626
+ if (guardFlows.length != 1) return null;
2627
+ Address defaultTarget = guardFlows[0];
2628
+ int compareIndex = -1;
2629
+ for (int index = guardIndex - 1; index >= 0; index -= 1) {
2630
+ String name = mnemonic(instructions.get(index));
2631
+ if (name.equals("CMP")) {
2632
+ compareIndex = index;
2633
+ break;
2634
+ }
2635
+ if (!AARCH64_FLAG_PRESERVING.contains(name)) return null;
2636
+ }
2637
+ if (compareIndex < 0) return null;
2638
+ java.util.regex.Matcher compareMatcher = AARCH64_COMPARE_IMMEDIATE.matcher(operands(instructions.get(compareIndex)));
2639
+ if (!compareMatcher.matches()) return null;
2640
+ String comparedRegister = normalizedRegister(compareMatcher.group(1));
2641
+ boolean comparedFullWidth = compareMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x");
2642
+ long compareImmediate = parseInteger(compareMatcher.group(2));
2643
+ long entryCountLong = guardMnemonic.equals("B.HI") ? compareImmediate + 1 : compareImmediate;
2644
+ if (entryCountLong <= 0 || entryCountLong > 4096) return null;
2645
+ int entryCount = (int) entryCountLong;
2646
+
2647
+ // The loaded index must carry the compared value: either the compared
2648
+ // register itself, unchanged since CMP, or a MOV copy made after CMP.
2649
+ // A W compare bounds only the low 32 bits, so an X index needs a
2650
+ // zero-extending W copy; a W index is zero-extended by UXTW.
2651
+ int indexWriter = latestWriter(instructions, loadIndex, indexRegister);
2652
+ boolean bounded = false;
2653
+ if (indexWriter < compareIndex) {
2654
+ bounded = indexRegister.equals(comparedRegister) && (wordIndex || comparedFullWidth);
2655
+ }
2656
+ else if (mnemonic(instructions.get(indexWriter)).equals("MOV")) {
2657
+ java.util.regex.Matcher moveMatcher = AARCH64_MOVE_REGISTER.matcher(operands(instructions.get(indexWriter)));
2658
+ if (moveMatcher.matches() &&
2659
+ normalizedRegister(moveMatcher.group(2)).equals(comparedRegister) &&
2660
+ latestWriter(instructions, indexWriter, comparedRegister) < compareIndex) {
2661
+ boolean wordCopy = moveMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("w");
2662
+ bounded = wordCopy || comparedFullWidth;
2663
+ }
2664
+ }
2665
+ if (!bounded) return null;
2666
+
2667
+ // Every fact above holds only on one straight-line fall-through path
2668
+ // with no other entry into the window.
2669
+ int windowStart = Math.min(Math.min(tablePageIndex, baseIndex), compareIndex);
2670
+ for (int index = windowStart + 1; index <= branchIndex; index += 1) {
2671
+ monitor.checkCancelled();
2672
+ Instruction previous = instructions.get(index - 1);
2673
+ Instruction current = instructions.get(index);
2674
+ Address fallThrough = previous.getFallThrough();
2675
+ if (fallThrough == null || !fallThrough.equals(current.getAddress())) return null;
2676
+ ReferenceIterator incoming = currentProgram.getReferenceManager().getReferencesTo(current.getAddress());
2677
+ while (incoming.hasNext()) {
2678
+ if (incoming.next().getReferenceType().isFlow()) return null;
2679
+ }
2680
+ }
2681
+
2682
+ List<RecoveredAArch64Mapping> mappings = new ArrayList<>();
2683
+ Set<String> mappedTargets = new HashSet<>();
2684
+ for (int tableIndex = 0; tableIndex < entryCount; tableIndex += 1) {
2685
+ monitor.checkCancelled();
2686
+ Address entry = tableAddress.add((long) tableIndex * entrySize);
2687
+ if (!currentProgram.getMemory().contains(entry)) return null;
2688
+ long encoded;
2689
+ if (entrySize == 1) {
2690
+ byte value = currentProgram.getMemory().getByte(entry);
2691
+ encoded = signedEntries ? value : value & 0xffL;
2692
+ }
2693
+ else {
2694
+ short value = currentProgram.getMemory().getShort(entry);
2695
+ encoded = signedEntries ? value : value & 0xffffL;
2696
+ }
2697
+ if (encoded == 0 && nullTarget != null) {
2698
+ mappings.add(new RecoveredAArch64Mapping(tableIndex, nullTarget, encoded, true));
2699
+ continue;
2700
+ }
2701
+ Address target;
2702
+ try {
2703
+ target = branchBase.addNoWrap(encoded << offsetShift);
2704
+ }
2705
+ catch (ghidra.program.model.address.AddressOverflowException overflow) {
2706
+ return null;
2707
+ }
2708
+ String canonicalTarget = canonicalAddress(target);
2709
+ if (!recoveredTargetSet.contains(canonicalTarget)) return null;
2710
+ mappedTargets.add(canonicalTarget);
2711
+ mappings.add(new RecoveredAArch64Mapping(tableIndex, target, encoded, false));
2712
+ }
2713
+ if (mappedTargets.contains(canonicalAddress(defaultTarget))) return null;
2714
+ // Ghidra's recovered destinations must be fully accounted for by the
2715
+ // decoded entries, the guard default, and the CBZ null path.
2716
+ Set<String> accountedTargets = new HashSet<>(mappedTargets);
2717
+ accountedTargets.add(canonicalAddress(defaultTarget));
2718
+ if (nullTarget != null) accountedTargets.add(canonicalAddress(nullTarget));
2719
+ // Ghidra does not model the CBZ, so it may decode a zero entry as a
2720
+ // branch to the base itself. Only a zero entry encodes offset 0, and
2721
+ // the CBZ diverts every zero entry, so the BR never reaches the base.
2722
+ Address unreachableZeroTarget = null;
2723
+ boolean divertedZeroEntry = mappings.stream().anyMatch(mapping -> mapping.nullEntry);
2724
+ if (divertedZeroEntry &&
2725
+ recoveredTargetSet.contains(canonicalAddress(branchBase)) &&
2726
+ !mappedTargets.contains(canonicalAddress(branchBase)) &&
2727
+ !branchBase.equals(defaultTarget) &&
2728
+ !branchBase.equals(nullTarget)) {
2729
+ unreachableZeroTarget = branchBase;
2730
+ accountedTargets.add(canonicalAddress(branchBase));
2731
+ }
2732
+ if (!accountedTargets.containsAll(recoveredTargetSet)) return null;
2733
+ int selectorWriter = latestWriter(instructions, compareIndex, comparedRegister);
2734
+ return new RecoveredAArch64RelativeTable(
2735
+ tableAddress,
2736
+ branchBase,
2737
+ entrySize,
2738
+ selectorWriter < 0 ? null : instructions.get(selectorWriter).getAddress(),
2739
+ signedEntries,
2740
+ offsetShift,
2741
+ guardMnemonic,
2742
+ compareImmediate,
2743
+ nullTarget,
2744
+ unreachableZeroTarget,
2745
+ List.copyOf(mappings)
2746
+ );
2747
+ }
2748
+
2749
+ private static String mnemonic(Instruction instruction) {
2750
+ return instruction.getMnemonicString().toUpperCase(Locale.ROOT);
2751
+ }
2752
+
2753
+ /** Index of the last instruction before {@code before} that writes {@code register}, or -1. */
2754
+ private static int latestWriter(List<Instruction> instructions, int before, String register) {
2755
+ for (int index = before - 1; index >= 0; index -= 1) {
2756
+ for (Object result : instructions.get(index).getResultObjects()) {
2757
+ if (result instanceof Register written &&
2758
+ normalizedRegister(written.getBaseRegister().getName()).equals(register)) {
2759
+ return index;
2424
2760
  }
2425
- mappedTargets.add(canonicalTarget);
2426
- mappings.add(new RecoveredAArch64Mapping(caseValue, target));
2427
2761
  }
2428
- if (!valid || mappedTargets.contains(canonicalAddress(defaultTarget))) continue;
2429
- Set<String> accountedTargets = new HashSet<>(mappedTargets);
2430
- accountedTargets.add(canonicalAddress(defaultTarget));
2431
- if (!accountedTargets.equals(recoveredTargetSet)) continue;
2432
- return new RecoveredAArch64RelativeTable(tableAddress, branchBase, entrySize, List.copyOf(mappings));
2433
2762
  }
2434
- return null;
2763
+ return -1;
2435
2764
  }
2436
2765
 
2437
2766
  private static String operands(Instruction instruction) {
@@ -2578,6 +2907,63 @@ public final class ReaGhidraBridge extends HeadlessScript {
2578
2907
  return new InstructionScan(List.copyOf(instructions), iterator.hasNext());
2579
2908
  }
2580
2909
 
2910
+ private JsonObject functionObservation(
2911
+ JsonElement value,
2912
+ List<String> limitations) {
2913
+ JsonObject result = new JsonObject();
2914
+ result.add("value", value);
2915
+ result.add("limitations", GSON.toJsonTree(limitations));
2916
+ return result;
2917
+ }
2918
+
2919
+ private List<String> noReturnCallLimitations(Iterable<Instruction> instructions)
2920
+ throws Exception {
2921
+ List<String> limitations = new ArrayList<>();
2922
+ for (Instruction instruction : instructions) {
2923
+ monitor.checkCancelled();
2924
+ if (!instruction.getFlowType().isCall() || !instruction.getFlowType().isTerminal()) {
2925
+ continue;
2926
+ }
2927
+ Address[] destinations = instruction.getFlows();
2928
+ StringBuilder limitation = new StringBuilder();
2929
+ limitation.append("Ghidra Listing reports a terminal call at ")
2930
+ .append(canonicalAddress(instruction.getAddress()));
2931
+ if (destinations.length == 0) {
2932
+ limitation.append(" with no resolved callee; its no-return basis is unknown.");
2933
+ }
2934
+ else {
2935
+ limitation.append(" to ");
2936
+ for (int index = 0; index < destinations.length; index++) {
2937
+ if (index > 0) limitation.append(", ");
2938
+ Address destination = destinations[index];
2939
+ limitation.append(canonicalAddress(destination));
2940
+ Function target = currentProgram.getFunctionManager().getFunctionAt(destination);
2941
+ if (target == null) {
2942
+ limitation.append(" (callee identity unknown)");
2943
+ }
2944
+ else {
2945
+ limitation.append(" ").append(procedureName(target));
2946
+ limitation.append(" (external=").append(target.isExternal());
2947
+ limitation.append(", thunk=").append(target.isThunk());
2948
+ limitation.append(", hasNoReturn=").append(target.hasNoReturn()).append(")");
2949
+ }
2950
+ }
2951
+ limitation.append("; fallthrough is excluded by Ghidra's flow model.");
2952
+ Function target = destinations.length == 1
2953
+ ? currentProgram.getFunctionManager().getFunctionAt(destinations[0])
2954
+ : null;
2955
+ if (target != null && target.hasNoReturn()) {
2956
+ limitation.append(" This records Ghidra's FunctionManager flag, not an independent verification that the callee cannot return.");
2957
+ }
2958
+ else {
2959
+ limitation.append(" The reason for the terminal flow is not established by this observation.");
2960
+ }
2961
+ }
2962
+ limitations.add(limitation.toString());
2963
+ }
2964
+ return limitations;
2965
+ }
2966
+
2581
2967
  private static String renderAssembly(List<Instruction> instructions) {
2582
2968
  return String.join("\n", renderAssemblyLines(instructions));
2583
2969
  }
@@ -3406,7 +3792,9 @@ public final class ReaGhidraBridge extends HeadlessScript {
3406
3792
  .thenComparing(Reference::isPrimary);
3407
3793
 
3408
3794
  private record InventoryItem(Address address, String value, JsonObject facts) {}
3795
+ private record AddressNameRequest(String inputAddress, Address address, String name) {}
3409
3796
  private record FunctionEntry(Function function, InventoryItem item) {}
3797
+ private record FunctionAnalysisObservation(JsonObject dossier, List<String> limitations) {}
3410
3798
  private record InstructionScan(List<Instruction> instructions, boolean truncated) {}
3411
3799
  private record SessionDescriptor(
3412
3800
  String transport,