@eyeauras/insight-cli 0.3.2 → 0.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.
- package/.cli-package-build.json +1 -1
- package/SKILL.md +4 -1
- package/dist/src/arguments.js +9 -4
- package/dist/src/cli.js +45 -37
- package/dist/src/code-references.d.ts +48 -0
- package/dist/src/code-references.js +154 -0
- package/dist/src/context-diagnostics.d.ts +25 -0
- package/dist/src/context-diagnostics.js +82 -0
- package/dist/src/contexts.d.ts +13 -1
- package/dist/src/contexts.js +38 -9
- package/dist/src/function-boundaries.d.ts +9 -0
- package/dist/src/function-boundaries.js +68 -0
- package/dist/src/function-entries.d.ts +33 -0
- package/dist/src/function-entries.js +84 -0
- package/dist/src/function-inspection.d.ts +4 -2
- package/dist/src/function-inspection.js +94 -59
- package/dist/src/function-results.d.ts +3 -1
- package/dist/src/function-results.js +9 -3
- package/dist/src/inline-script.d.ts +15 -0
- package/dist/src/inline-script.js +62 -0
- package/dist/src/internal-timing.d.ts +3 -0
- package/dist/src/internal-timing.js +52 -0
- package/dist/src/lifecycle.js +14 -4
- package/dist/src/native-inputs.d.ts +1 -1
- package/dist/src/native-inputs.js +1 -1
- package/dist/src/native-queries.js +1 -1
- package/dist/src/persistent-client.d.ts +30 -0
- package/dist/src/persistent-client.js +125 -0
- package/dist/src/queries.js +4 -1
- package/dist/src/query-policy.js +9 -1
- package/dist/src/research-view.d.ts +6 -0
- package/dist/src/research-view.js +9 -0
- package/dist/src/research.d.ts +38 -0
- package/dist/src/research.js +78 -8
- package/dist/src/string-anchors.d.ts +43 -0
- package/dist/src/string-anchors.js +176 -0
- package/dist/src/sync-policy.js +3 -0
- package/dist/src/timed-backend.d.ts +3 -0
- package/dist/src/timed-backend.js +16 -0
- package/dist/src/transport.d.ts +1 -0
- package/dist/src/transport.js +4 -0
- package/dist/src/workspace.js +5 -2
- package/package.json +12 -1
- package/references/native/InsightAnchorScan.java +135 -0
- package/references/native/InsightCodeReferences.java +129 -0
- package/references/native/InsightFunctionBoundaries.java +56 -0
- package/references/native/InsightInlineExecutor.java +143 -0
- package/references/native/InsightInlineScript.java +14 -0
- package/references/native/InsightPeRanges.java +92 -0
- package/references/native/InsightResearchQueries.java +22 -1
- package/references/native/InsightResearchView.java +69 -0
- package/references/native/InsightStringAnchors.java +49 -0
- package/references/persistent-client.mjs +125 -0
- package/references/persistent.md +50 -0
- package/references/query-semantics.md +12 -0
- package/references/recovery.md +36 -0
- package/references/research.md +87 -0
- package/references/setup.md +1 -1
- package/references/sparse-navigation.md +173 -0
- package/runtime/GhidraMCP-7.0.0.jar +0 -0
- package/runtime/GhidraMCP-NOTICE +5 -0
- package/runtime/provenance.json +17 -3
- package/runtime/research-view.provenance.json +28 -0
- package/runtime/toolbox.json +10 -3
- package/skill-bundle/insight-artifacts/SKILL.md +1 -1
- package/skill-bundle/insight-artifacts/references/recovery.md +36 -0
- package/skill-bundle/insight-binary/SKILL.md +1 -1
- package/skill-bundle/insight-capture/SKILL.md +1 -1
- package/skill-bundle/insight-cli/SKILL.md +4 -1
- package/skill-bundle/insight-cli/references/persistent-client.mjs +125 -0
- package/skill-bundle/insight-cli/references/persistent.md +50 -0
- package/skill-bundle/insight-cli/references/recovery.md +36 -0
- package/skill-bundle/insight-cli/references/setup.md +1 -1
- package/skill-bundle/insight-die/SKILL.md +1 -1
- package/skill-bundle/insight-dnspy/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-dump/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-stack/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-trace/SKILL.md +1 -1
- package/skill-bundle/insight-frida/SKILL.md +1 -1
- package/skill-bundle/insight-ghidra/SKILL.md +14 -1
- package/skill-bundle/insight-ghidra/references/persistent-client.mjs +125 -0
- package/skill-bundle/insight-ghidra/references/persistent.md +50 -0
- package/skill-bundle/insight-ghidra/references/query-semantics.md +12 -0
- package/skill-bundle/insight-ghidra/references/recovery.md +36 -0
- package/skill-bundle/insight-ghidra/references/research.md +87 -0
- package/skill-bundle/insight-ghidra/references/setup.md +1 -1
- package/skill-bundle/insight-ghidra/references/sparse-navigation.md +173 -0
- package/skill-bundle/insight-ilspy/SKILL.md +1 -1
- package/skill-bundle/insight-interop/SKILL.md +1 -1
- package/skill-bundle/insight-lldb/SKILL.md +1 -1
- package/skill-bundle/insight-llvm/SKILL.md +1 -1
- package/skill-bundle/insight-managed-heap/SKILL.md +1 -1
- package/skill-bundle/insight-managed-metadata/SKILL.md +1 -1
- package/skill-bundle/insight-mitmproxy/SKILL.md +1 -1
- package/skill-bundle/insight-overview/SKILL.md +1 -1
- package/skill-bundle/insight-procmon/SKILL.md +1 -1
- package/skill-bundle/insight-re/SKILL.md +4 -1
- package/skill-bundle/insight-re/references/persistent-client.mjs +125 -0
- package/skill-bundle/insight-re/references/persistent.md +50 -0
- package/skill-bundle/insight-re/references/recovery.md +36 -0
- package/skill-bundle/insight-sevenzip/SKILL.md +1 -1
- package/skill-bundle/insight-strace/SKILL.md +1 -1
- package/skill-bundle/insight-tshark/SKILL.md +1 -1
- package/skill-bundle/insight-windbg/SKILL.md +1 -1
- package/skill-bundle/manifest.json +1 -1
- package/skills/insight-artifacts/SKILL.md +1 -1
- package/skills/insight-artifacts/references/recovery.md +36 -0
- package/skills/insight-binary/SKILL.md +1 -1
- package/skills/insight-capture/SKILL.md +1 -1
- package/skills/insight-die/SKILL.md +1 -1
- package/skills/insight-dnspy/SKILL.md +1 -1
- package/skills/insight-dotnet-dump/SKILL.md +1 -1
- package/skills/insight-dotnet-stack/SKILL.md +1 -1
- package/skills/insight-dotnet-trace/SKILL.md +1 -1
- package/skills/insight-frida/SKILL.md +1 -1
- package/skills/insight-ghidra/SKILL.md +14 -1
- package/skills/insight-ghidra/references/persistent-client.mjs +125 -0
- package/skills/insight-ghidra/references/persistent.md +50 -0
- package/skills/insight-ghidra/references/query-semantics.md +12 -0
- package/skills/insight-ghidra/references/recovery.md +36 -0
- package/skills/insight-ghidra/references/research.md +87 -0
- package/skills/insight-ghidra/references/setup.md +1 -1
- package/skills/insight-ghidra/references/sparse-navigation.md +173 -0
- package/skills/insight-ilspy/SKILL.md +1 -1
- package/skills/insight-interop/SKILL.md +1 -1
- package/skills/insight-lldb/SKILL.md +1 -1
- package/skills/insight-llvm/SKILL.md +1 -1
- package/skills/insight-managed-heap/SKILL.md +1 -1
- package/skills/insight-managed-metadata/SKILL.md +1 -1
- package/skills/insight-mitmproxy/SKILL.md +1 -1
- package/skills/insight-overview/SKILL.md +1 -1
- package/skills/insight-procmon/SKILL.md +1 -1
- package/skills/insight-re/SKILL.md +4 -1
- package/skills/insight-re/references/persistent-client.mjs +125 -0
- package/skills/insight-re/references/persistent.md +50 -0
- package/skills/insight-re/references/recovery.md +36 -0
- package/skills/insight-sevenzip/SKILL.md +1 -1
- package/skills/insight-strace/SKILL.md +1 -1
- package/skills/insight-tshark/SKILL.md +1 -1
- package/skills/insight-windbg/SKILL.md +1 -1
package/.cli-package-build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":1,"artifact":"
|
|
1
|
+
{"version":1,"artifact":"034bd833a87d3a9c730b486252e2db09f67b3c6da2377c195389b894b26e12f7"}
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: insight-cli
|
|
|
3
3
|
description: Choose and prepare reverse-engineering capabilities from the insight-cli toolbox for binary, managed-code and runtime investigations. Use to discover the appropriate tool or its packaged skill before installing native runtimes.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# insight-cli 0.
|
|
6
|
+
# insight-cli 0.4.0
|
|
7
7
|
|
|
8
8
|
If local help/status reports AppData or IPC access denied, retry that **read-only probe**
|
|
9
9
|
with the agent host's normal permission escalation, then use that route for later authorized
|
|
@@ -15,6 +15,9 @@ request/status recovery, never blind retry.
|
|
|
15
15
|
Read the project's RE instructions and existing findings first. They own tool choices,
|
|
16
16
|
evidence layout, formats and source of truth. These skills fill the remaining gaps.
|
|
17
17
|
|
|
18
|
+
For repeated research calls, retain one [persistent CLI client](references/persistent.md)
|
|
19
|
+
in the agent's long-lived runtime. Reuse it across calls to avoid repeated frontend startup.
|
|
20
|
+
|
|
18
21
|
1. Run `insight-cli tools list --json` to discover the capabilities shipped in this version.
|
|
19
22
|
The catalog works before profile configuration, downloads or IPC startup. A downloaded
|
|
20
23
|
runtime is not needed to discover what a tool does.
|
package/dist/src/arguments.js
CHANGED
|
@@ -23,8 +23,10 @@ export function parsed(p, value) {
|
|
|
23
23
|
fail('invalid', `${flag(p.name)} is outside its supported query range.`);
|
|
24
24
|
if (p.name === 'timeout_seconds' && (result < 1 || result > 1800))
|
|
25
25
|
fail('invalid', 'timeout-seconds must be between 1 and 1800.');
|
|
26
|
-
if (p.name === 'max_scan' && (result < 1 || result > 200000)
|
|
27
|
-
fail('invalid',
|
|
26
|
+
if (p.name === 'max_scan' && (result < 1 || result > 200000))
|
|
27
|
+
fail('invalid', 'max-scan must be between 1 and 200000 visited inputs; result offset does not advance scan coverage.');
|
|
28
|
+
if (p.name === 'max_bytes' && (result < 1 || result > 16777216))
|
|
29
|
+
fail('invalid', 'max-bytes must be between 1 and 16777216 bytes.');
|
|
28
30
|
return result;
|
|
29
31
|
}
|
|
30
32
|
if (['json', 'object', 'array', 'any'].includes(p.type)) {
|
|
@@ -55,8 +57,11 @@ export function option(p) {
|
|
|
55
57
|
const value = p.default;
|
|
56
58
|
const defaultValue = value === undefined || value === '' && !p.allowEmpty ? undefined : parsed(p, value);
|
|
57
59
|
return { flags: `--${flag(p.name)} <${p.type === 'json' ? 'json' : p.type}>`,
|
|
58
|
-
description: p.
|
|
59
|
-
|
|
60
|
+
description: p.name === 'max_scan' ? 'Maximum visited inputs, 1–200000; independent of result offset' :
|
|
61
|
+
p.name === 'max_bytes' ? 'Maximum searched bytes, 1–16777216' :
|
|
62
|
+
p.name === 'offset' && p.default !== '-1' ? 'Skip matching result rows; does not continue an incomplete scan' :
|
|
63
|
+
p.semantic === 'function_ref' ? 'Function name or explicit address' : p.semantic === 'address' ? 'Explicit hexadecimal address' :
|
|
64
|
+
`${flag(p.name)} (${p.type}${p.name === 'limit' ? ', 1–1000' : p.name === 'length' ? ', 1–65536 bytes' : ''})`,
|
|
60
65
|
...(p.required ? { required: true } : {}), ...(defaultValue === undefined ? {} : { defaultValue }),
|
|
61
66
|
...(['json', 'object', 'array', 'any'].includes(p.type) ? {} : { parse: (value) => parsed(p, value) }) };
|
|
62
67
|
}
|
package/dist/src/cli.js
CHANGED
|
@@ -52,6 +52,11 @@ import { RuntimeManager, runtimeCommands, runtimeProcesses } from './runtime-man
|
|
|
52
52
|
import { packageVersion } from './package-version.js';
|
|
53
53
|
import { runtimeSelection } from './runtime-selection.js';
|
|
54
54
|
import { AnalysisStorage, analysisDirectory } from './analysis-storage.js';
|
|
55
|
+
import { codeReferenceCommands } from './code-references.js';
|
|
56
|
+
import { stringAnchorCommands } from './string-anchors.js';
|
|
57
|
+
import { contextDiagnosticCommands } from './context-diagnostics.js';
|
|
58
|
+
import { traceCommand } from './internal-timing.js';
|
|
59
|
+
import { timedBackend } from './timed-backend.js';
|
|
55
60
|
import { readInitialProgress } from './initial-progress.js';
|
|
56
61
|
const bindingFields = ['java', 'ghidraHome', 'mcpJar', 'mcpJarSha256', 'server', 'repository', 'readerUser', 'publisherUser', 'maxContexts', 'debuggerPython', 'windbgDir', 'archiveUrl'];
|
|
57
62
|
class Owner {
|
|
@@ -109,7 +114,7 @@ class Owner {
|
|
|
109
114
|
const result = await selected.backend;
|
|
110
115
|
if (result instanceof GhidraContexts)
|
|
111
116
|
await managedExtensions(selected.workspace.directory);
|
|
112
|
-
return result instanceof GhidraContexts ? result.fork(c) : result;
|
|
117
|
+
return timedBackend(result instanceof GhidraContexts ? result.fork(c) : result);
|
|
113
118
|
};
|
|
114
119
|
const research = new Research(profile.workspace, backend, profile.tasks);
|
|
115
120
|
return { workspace: profile.workspace, research, synchronization: new Synchronization(profile.workspace, research, async () => {
|
|
@@ -231,47 +236,50 @@ function scopedFailures(entry, owner, parents = []) {
|
|
|
231
236
|
const path = [...parents, entry.name.split(' ')[0]], name = path.join(' ');
|
|
232
237
|
const run = entry.run;
|
|
233
238
|
return { ...entry, ...(entry.children ? { children: entry.children.map(child => scopedFailures(child, owner, path)) } : {}), ...(run ? { run: async (input, c) => {
|
|
234
|
-
|
|
235
|
-
let selected;
|
|
239
|
+
const execute = async () => {
|
|
236
240
|
try {
|
|
237
|
-
selected
|
|
238
|
-
|
|
239
|
-
selected =
|
|
241
|
+
let selected;
|
|
242
|
+
try {
|
|
243
|
+
selected = scope(input.options);
|
|
244
|
+
if (selected.session === 'new')
|
|
245
|
+
selected = undefined;
|
|
246
|
+
}
|
|
247
|
+
catch { }
|
|
248
|
+
if (!selected)
|
|
249
|
+
return await run(input, c);
|
|
250
|
+
const services = owner.selected(c);
|
|
251
|
+
// First open of an already known target reserves its context inside Research.open.
|
|
252
|
+
// Existing contexts still use the command boundary to order automatic sync phases.
|
|
253
|
+
if (path[1] === 'open')
|
|
254
|
+
try {
|
|
255
|
+
services.workspace.context(selected);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
if (error instanceof CliError && error.code === 'coordinator.notFound')
|
|
259
|
+
return await run(input, c);
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
return await services.research.boundary(selected, async () => {
|
|
263
|
+
const excluded = ['open', 'merge', 'refresh', 'sync-policy', 'close', 'changes', 'debugger', 'ui', 'version-tracking', 'archive', 'projects', 'repository', 'server', 'formats', 'libraries', 'plugins'];
|
|
264
|
+
const eligible = entry.permission !== Permission.ReadOnly && entry.permission !== undefined && !excluded.includes(path[1]) && !['ghidra types archive-export', 'ghidra documentation transfer'].includes(name);
|
|
265
|
+
return eligible ? services.synchronization.execute(selected, name, { ...input.options, commandArguments: input.args }, async (requestId) => run({ ...input, options: { ...input.options, ...(requestId ? { requestId } : {}) } }, c)) : run(input, c);
|
|
266
|
+
});
|
|
240
267
|
}
|
|
241
|
-
catch {
|
|
242
|
-
|
|
243
|
-
return await run(input, c);
|
|
244
|
-
const services = owner.selected(c);
|
|
245
|
-
// First open of an already known target reserves its context inside Research.open.
|
|
246
|
-
// Existing contexts still use the command boundary to order automatic sync phases.
|
|
247
|
-
if (path[1] === 'open')
|
|
268
|
+
catch (error) {
|
|
269
|
+
let selected;
|
|
248
270
|
try {
|
|
249
|
-
|
|
271
|
+
selected = scope(input.options);
|
|
250
272
|
}
|
|
251
|
-
catch
|
|
252
|
-
if (error instanceof CliError && error.code === 'coordinator.notFound')
|
|
253
|
-
return await run(input, c);
|
|
273
|
+
catch {
|
|
254
274
|
throw error;
|
|
255
275
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
catch (error) {
|
|
263
|
-
let selected;
|
|
264
|
-
try {
|
|
265
|
-
selected = scope(input.options);
|
|
266
|
-
}
|
|
267
|
-
catch {
|
|
268
|
-
throw error;
|
|
276
|
+
if (!(error instanceof CliError))
|
|
277
|
+
throw error;
|
|
278
|
+
const result = error.result && typeof error.result === 'object' && !Array.isArray(error.result) ? error.result : {};
|
|
279
|
+
throw new CliError(error.message, { code: error.code, exitCode: error.exitCode, next: error.next, result: { ...result, ...selected } });
|
|
269
280
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const result = error.result && typeof error.result === 'object' && !Array.isArray(error.result) ? error.result : {};
|
|
273
|
-
throw new CliError(error.message, { code: error.code, exitCode: error.exitCode, next: error.next, result: { ...result, ...selected } });
|
|
274
|
-
}
|
|
281
|
+
};
|
|
282
|
+
return path[1] === 'open' || input.options.session && input.options.target ? traceCommand(join(analysisDirectory(c), 'workspace'), name, execute) : execute();
|
|
275
283
|
} } : {}) };
|
|
276
284
|
}
|
|
277
285
|
export function createDefinition(options = {}) {
|
|
@@ -424,7 +432,7 @@ export function createDefinition(options = {}) {
|
|
|
424
432
|
{ flags: '--loader <name>', description: 'Explicit installed loader name/ID from formats loaders' },
|
|
425
433
|
{ flags: '--loader-options <json>', description: 'Named primitive SDK loader options, requiring --loader' }] }),
|
|
426
434
|
command('strings', 'Read defined strings', [
|
|
427
|
-
command('search', 'Find literal text
|
|
435
|
+
command('search', 'Find literal text in defined string data; undefined literals: analysis search-byte-patterns', ({ options: o }, c) => owner.selected(c).research.strings(scope(o), text(o.contains, 4096), number(o.limit, 100, 1), number(o.offset, 2147483647)), { permission: Permission.ReadOnly, options: [...scoped, { flags: '--contains <text>', description: 'Literal case-insensitive substring', required: true }, ...paged] }),
|
|
428
436
|
]),
|
|
429
437
|
command('xrefs', 'Follow cross references', [
|
|
430
438
|
command('to', 'Find incoming references and their functions', ({ options: o }, c) => owner.selected(c).research.xrefs(scope(o), address(o.address), number(o.limit, 100, 1), number(o.offset, 2147483647)), { permission: Permission.ReadOnly, options: [...scoped, at, ...paged] }),
|
|
@@ -508,7 +516,7 @@ export function createDefinition(options = {}) {
|
|
|
508
516
|
],
|
|
509
517
|
};
|
|
510
518
|
const commands = definition.commands?.map(entry => entry.name === 'ghidra' ?
|
|
511
|
-
scopedFailures({ ...entry, children: extendQueries(extendQueries(extendQueries(entry.children ?? [], queryCommands(c => owner.selected(c).research)), mutationCommands(c => owner.selected(c).research)), [...sdkCommands(c => owner.selected(c).research), ...sdkFileCommands(c => owner.selected(c).sdkFiles), ...trackingCommands(c => owner.selected(c).tracking), ...crossCommands(c => owner.selected(c).research), ...administrationCommands(c => owner.selected(c).administration, options.backend ? undefined : async (c) => { const status = await new RuntimeManager(c).status(); return status.state === 'not_ready' ? { ...status.server, connected: false, state: 'runtime_not_ready', missing: status.missing } : undefined; }),
|
|
519
|
+
scopedFailures({ ...entry, children: extendQueries(extendQueries(extendQueries(entry.children ?? [], queryCommands(c => owner.selected(c).research)), mutationCommands(c => owner.selected(c).research)), [...codeReferenceCommands(c => owner.selected(c).research), ...stringAnchorCommands(c => owner.selected(c).research), ...contextDiagnosticCommands(c => owner.selected(c).research, c => owner.selected(c).workspace), ...sdkCommands(c => owner.selected(c).research), ...sdkFileCommands(c => owner.selected(c).sdkFiles), ...trackingCommands(c => owner.selected(c).tracking), ...crossCommands(c => owner.selected(c).research), ...administrationCommands(c => owner.selected(c).administration, options.backend ? undefined : async (c) => { const status = await new RuntimeManager(c).status(); return status.state === 'not_ready' ? { ...status.server, connected: false, state: 'runtime_not_ready', missing: status.missing } : undefined; }),
|
|
512
520
|
...extensionCommands(c => new Extensions(join(analysisDirectory(c), 'workspace'))), ...extensionInventoryCommands(c => owner.selected(c).backend), ...pluginCommands(c => owner.selected(c).ui), ...synchronizationCommands(c => owner.selected(c).synchronization), ...utilityCommands(), ...runtimeCommands((c, action, quiescent) => owner.runtime(c, action, quiescent)), ...lifecycleCommands(c => owner.selected(c).lifecycle, c => new RuntimeManager(c).status()), ...projectCommands(c => owner.selected(c).projects), ...uiCommands(c => owner.selected(c).ui), ...debuggerCommands(c => owner.selected(c).debugger), ...archiveCommands(c => owner.selected(c).archive)]) }, owner) : entry);
|
|
513
521
|
return { ...definition, commands: [...(commands ?? []).map(e => storage.wrap(e)), storage.commands()] };
|
|
514
522
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type CommandContext } from '@eyeauras/cli-factory';
|
|
2
|
+
import { type Research, type ResearchBackend } from './research.js';
|
|
3
|
+
import type { Scope } from './workspace.js';
|
|
4
|
+
export declare const referenceAlgorithm = "x86-64-code-reference-candidates-v1";
|
|
5
|
+
export declare const referenceForms: readonly ["call-rel32", "lea-rip", "mov-rip", "call-rip-slot"];
|
|
6
|
+
type Form = typeof referenceForms[number];
|
|
7
|
+
export interface ReferenceQuery {
|
|
8
|
+
targets: string[];
|
|
9
|
+
forms: Form[];
|
|
10
|
+
start: string;
|
|
11
|
+
end: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ReferenceBudgets {
|
|
14
|
+
max_bytes: number;
|
|
15
|
+
max_candidates: number;
|
|
16
|
+
timeout_seconds: number;
|
|
17
|
+
limit: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ReferenceRequest {
|
|
20
|
+
query: ReferenceQuery;
|
|
21
|
+
from: string;
|
|
22
|
+
budgets: ReferenceBudgets;
|
|
23
|
+
expectedView?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function referenceRequest(o: Record<string, unknown>, who: Scope): ReferenceRequest;
|
|
26
|
+
/** The deliberately small encoding contract, independently rechecked in the facade. */
|
|
27
|
+
export declare function referenceEncoding(input: Buffer): {
|
|
28
|
+
form: "call-rel32" | "lea-rip" | "mov-rip" | "call-rip-slot";
|
|
29
|
+
length: number;
|
|
30
|
+
displacement: number;
|
|
31
|
+
operand: number;
|
|
32
|
+
bits: number;
|
|
33
|
+
delta: number;
|
|
34
|
+
role: string;
|
|
35
|
+
} | undefined;
|
|
36
|
+
export declare function referenceResult(raw: unknown, r: ReferenceRequest): Record<string, unknown>;
|
|
37
|
+
export declare function inspectCodeReferences(backend: ResearchBackend, who: Scope, r: ReferenceRequest, view: string, observation: unknown): Promise<{
|
|
38
|
+
value: {
|
|
39
|
+
view: {
|
|
40
|
+
identity: string;
|
|
41
|
+
};
|
|
42
|
+
cursor: string | null;
|
|
43
|
+
};
|
|
44
|
+
session: string;
|
|
45
|
+
target: string;
|
|
46
|
+
}>;
|
|
47
|
+
export declare function codeReferenceCommands(research: (c: CommandContext) => Research): import("@eyeauras/cli-factory").CommandDefinition[];
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { command, Permission } from '@eyeauras/cli-factory';
|
|
2
|
+
import { scope, number, address } from './research.js';
|
|
3
|
+
import { fail, object, digest, stable } from './model.js';
|
|
4
|
+
import { parseDomainJson } from './json.js';
|
|
5
|
+
import { selectedOptions } from './arguments.js';
|
|
6
|
+
export const referenceAlgorithm = 'x86-64-code-reference-candidates-v1';
|
|
7
|
+
export const referenceForms = ['call-rel32', 'lea-rip', 'mov-rip', 'call-rip-slot'];
|
|
8
|
+
const defaults = { max_bytes: 4194304, max_candidates: 200000, timeout_seconds: 5, limit: 100 };
|
|
9
|
+
const hex = (v) => { const s = address(v); if (!/^[a-f0-9]{1,16}$/.test(s))
|
|
10
|
+
fail('invalid', 'Code-reference V1 requires default-space unsigned 64-bit addresses.'); return s; };
|
|
11
|
+
const big = (s) => BigInt('0x' + s);
|
|
12
|
+
function query(v) {
|
|
13
|
+
const q = object(v);
|
|
14
|
+
if (!Array.isArray(q.targets) || q.targets.length < 1 || q.targets.length > 64 || !Array.isArray(q.forms) || !q.forms.length || q.forms.length > 4)
|
|
15
|
+
fail('invalid', 'Select 1–64 targets and 1–4 reference forms.');
|
|
16
|
+
const targets = q.targets.map(hex).sort((a, b) => big(a) < big(b) ? -1 : big(a) > big(b) ? 1 : 0), forms = referenceForms.filter(f => q.forms.includes(f));
|
|
17
|
+
if (new Set(targets).size !== targets.length || forms.length !== q.forms.length)
|
|
18
|
+
fail('invalid', 'Targets/forms must be distinct supported values.');
|
|
19
|
+
const start = hex(q.start), end = hex(q.end);
|
|
20
|
+
if (big(start) > big(end))
|
|
21
|
+
fail('invalid', 'scan-through must not precede scan-from.');
|
|
22
|
+
return { targets, forms, start, end };
|
|
23
|
+
}
|
|
24
|
+
function budgets(v, base = defaults) { return { max_bytes: number(v.maxBytes ?? base.max_bytes, 16777216, 15), max_candidates: number(v.maxCandidates ?? base.max_candidates, 1000000, 1), timeout_seconds: number(v.timeoutSeconds ?? base.timeout_seconds, 30, 1), limit: number(v.limit ?? base.limit, 1000, 1) }; }
|
|
25
|
+
export function referenceRequest(o, who) {
|
|
26
|
+
if (o.cursor !== undefined) {
|
|
27
|
+
if (['to', 'forms', 'scanFrom', 'scanThrough'].some(k => o[k] !== undefined))
|
|
28
|
+
fail('invalid', 'Cursor continuation cannot change targets, forms or scan bounds.');
|
|
29
|
+
if (typeof o.cursor !== 'string' || o.cursor.length > 16384 || !/^cr1\.[A-Za-z0-9_-]+$/.test(o.cursor))
|
|
30
|
+
fail('invalid', 'Malformed code-reference cursor.');
|
|
31
|
+
let c;
|
|
32
|
+
try {
|
|
33
|
+
const encoded = o.cursor.slice(4), bytes = Buffer.from(encoded, 'base64url');
|
|
34
|
+
if (bytes.toString('base64url') !== encoded)
|
|
35
|
+
throw Error();
|
|
36
|
+
c = object(parseDomainJson(bytes.toString('utf8')));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
fail('invalid', 'Malformed code-reference cursor.');
|
|
40
|
+
}
|
|
41
|
+
if (c.version !== 1 || c.algorithm !== referenceAlgorithm || stable(c.scope) !== stable(who) || typeof c.view !== 'string' || !/^[a-f0-9]{64}$/.test(c.view))
|
|
42
|
+
fail('invalid', 'Cursor version, scope or view is incompatible.');
|
|
43
|
+
const q = query(c.query), from = hex(c.next), b = object(c.budgets);
|
|
44
|
+
if (big(from) < big(q.start) || big(from) > big(q.end))
|
|
45
|
+
fail('invalid', 'Cursor frontier is outside its range.');
|
|
46
|
+
if (['max_bytes', 'max_candidates', 'timeout_seconds', 'limit'].some(k => !Number.isSafeInteger(b[k])))
|
|
47
|
+
fail('invalid', 'Malformed cursor budgets.');
|
|
48
|
+
const inherited = budgets({ maxBytes: b.max_bytes, maxCandidates: b.max_candidates, timeoutSeconds: b.timeout_seconds, limit: b.limit });
|
|
49
|
+
return { query: q, from, budgets: budgets(o, inherited), expectedView: c.view };
|
|
50
|
+
}
|
|
51
|
+
if (typeof o.to !== 'string' || typeof o.scanFrom !== 'string' || typeof o.scanThrough !== 'string')
|
|
52
|
+
fail('invalid', 'Supply --to, --scan-from and --scan-through, or --cursor.');
|
|
53
|
+
const q = query({ targets: o.to.split(',').map(x => x.trim()), forms: o.forms === undefined ? [...referenceForms] : String(o.forms).split(',').map(x => x.trim()), start: o.scanFrom, end: o.scanThrough });
|
|
54
|
+
return { query: q, from: q.start, budgets: budgets(o) };
|
|
55
|
+
}
|
|
56
|
+
/** The deliberately small encoding contract, independently rechecked in the facade. */
|
|
57
|
+
export function referenceEncoding(input) {
|
|
58
|
+
let p = 0, rex = 0, word = false;
|
|
59
|
+
while (p < input.length && p < 15) {
|
|
60
|
+
const b = input[p];
|
|
61
|
+
if ([0x66, 0x26, 0x2e, 0x36, 0x3e].includes(b)) {
|
|
62
|
+
word ||= b === 0x66;
|
|
63
|
+
rex = 0;
|
|
64
|
+
p++;
|
|
65
|
+
}
|
|
66
|
+
else if (b >= 0x40 && b <= 0x4f) {
|
|
67
|
+
rex = b;
|
|
68
|
+
p++;
|
|
69
|
+
}
|
|
70
|
+
else
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
const op = input[p], m = input[p + 1];
|
|
74
|
+
let form, displacement, length, operand, bits;
|
|
75
|
+
if (op === 0xe8) {
|
|
76
|
+
form = 'call-rel32';
|
|
77
|
+
length = p + 5;
|
|
78
|
+
displacement = p + 1;
|
|
79
|
+
operand = 0;
|
|
80
|
+
bits = 64;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
if (m === undefined || (m & 0xc7) !== 5)
|
|
84
|
+
return;
|
|
85
|
+
if (op === 0x8d) {
|
|
86
|
+
form = 'lea-rip';
|
|
87
|
+
operand = 1;
|
|
88
|
+
}
|
|
89
|
+
else if (op !== undefined && [0x88, 0x89, 0x8a, 0x8b].includes(op)) {
|
|
90
|
+
form = 'mov-rip';
|
|
91
|
+
operand = op === 0x88 || op === 0x89 ? 0 : 1;
|
|
92
|
+
}
|
|
93
|
+
else if (op === 0xff && ((m >> 3) & 7) === 2) {
|
|
94
|
+
form = 'call-rip-slot';
|
|
95
|
+
operand = 0;
|
|
96
|
+
}
|
|
97
|
+
else
|
|
98
|
+
return;
|
|
99
|
+
length = p + 6;
|
|
100
|
+
displacement = p + 2;
|
|
101
|
+
bits = form === 'call-rip-slot' ? 64 : op === 0x88 || op === 0x8a ? 8 : rex & 8 ? 64 : word ? 16 : 32;
|
|
102
|
+
}
|
|
103
|
+
if (length > 15 || input.length < length)
|
|
104
|
+
return;
|
|
105
|
+
return { form, length, displacement, operand, bits, delta: input.readInt32LE(displacement), role: form === 'call-rel32' ? 'direct-call-target' : form === 'call-rip-slot' ? 'pointer-slot' : 'effective-address' };
|
|
106
|
+
}
|
|
107
|
+
export function referenceResult(raw, r) {
|
|
108
|
+
const v = object(raw), s = object(v.scan), q = r.query, b = r.budgets;
|
|
109
|
+
if (v.algorithm !== referenceAlgorithm || typeof v.language !== 'string' || !v.language.startsWith('x86:LE:64:') || stable(v.forms) !== stable(q.forms) || !Array.isArray(v.matches) || v.matches.length > b.limit || object(v.read_only ?? {}).modification_unchanged !== true)
|
|
110
|
+
fail('partial', 'Malformed code-reference result.');
|
|
111
|
+
for (const k of ['bytes_read', 'tested_starts', 'visited_candidates'])
|
|
112
|
+
if (!Number.isSafeInteger(s[k]) || Number(s[k]) < 0)
|
|
113
|
+
fail('partial', 'Malformed reference scan counters.');
|
|
114
|
+
const complete = s.complete;
|
|
115
|
+
if (typeof complete !== 'boolean' || Number(s.bytes_read) > b.max_bytes || Number(s.tested_starts) > Number(s.bytes_read) || Number(s.visited_candidates) > b.max_candidates || Number(s.visited_candidates) < v.matches.length || Number(s.visited_candidates) > Number(s.tested_starts) || !['exhausted', 'byte_limit', 'candidate_limit', 'timeout', 'result_limit'].includes(String(s.reason)) || complete !== (s.reason === 'exhausted') || !Number.isFinite(s.elapsed_ms) || Number(s.elapsed_ms) < 0 || hex(s.from) !== r.from || hex(s.through) !== q.end)
|
|
116
|
+
fail('partial', 'Inconsistent code-reference coverage.');
|
|
117
|
+
const next = complete ? null : hex(s.next_start);
|
|
118
|
+
if (complete && s.next_start !== null || next !== null && (big(next) < big(r.from) || big(next) > big(q.end)) || next !== null && BigInt(Number(s.tested_starts)) > big(next) - big(r.from))
|
|
119
|
+
fail('partial', 'Reference frontier changed.');
|
|
120
|
+
let previous = -1n;
|
|
121
|
+
for (const rawRow of v.matches) {
|
|
122
|
+
const row = object(rawRow), from = hex(row.from), to = hex(row.to), bytes = row.bytes;
|
|
123
|
+
if (typeof bytes !== 'string' || !/^(?:[a-f0-9]{2}){1,15}$/.test(bytes))
|
|
124
|
+
fail('partial', 'Reference omitted its instruction bytes.');
|
|
125
|
+
const encoding = referenceEncoding(Buffer.from(bytes, 'hex'));
|
|
126
|
+
if (!encoding || row.form !== encoding.form || !q.forms.includes(encoding.form) || row.length !== encoding.length || bytes.length !== encoding.length * 2 || row.operand_index !== encoding.operand || row.operand_bits !== encoding.bits || row.target_role !== encoding.role || !q.targets.includes(to) || big(to) !== BigInt.asUintN(64, big(from) + BigInt(encoding.length + encoding.delta)) || big(from) <= previous || big(from) < big(r.from) || big(from) + BigInt(encoding.length - 1) > big(q.end) || next !== null && big(from) >= big(next) || !['listing-confirmed', 'listing-conflict', 'unconfirmed'].includes(String(row.boundary)) || row.validation !== 'decoded' || typeof row.instruction !== 'string' || row.instruction.length > 512)
|
|
127
|
+
fail('partial', 'Reference bytes, target, form or frontier are inconsistent.');
|
|
128
|
+
previous = big(from);
|
|
129
|
+
}
|
|
130
|
+
if (Buffer.byteLength(JSON.stringify(v)) > 1048576)
|
|
131
|
+
fail('limit', 'Code-reference response exceeds one MiB.');
|
|
132
|
+
return v;
|
|
133
|
+
}
|
|
134
|
+
export async function inspectCodeReferences(backend, who, r, view, observation) {
|
|
135
|
+
if (r.expectedView !== undefined && r.expectedView !== view)
|
|
136
|
+
fail('binding', 'Code-reference continuation view changed.', { reason: 'changed_view' });
|
|
137
|
+
const q = r.query, result = await backend.call(who, '/code_references', { targets: q.targets, forms: q.forms, scan_from: r.from, scan_through: q.end, ...r.budgets });
|
|
138
|
+
if (digest(observation) !== digest(await backend.observation(who)))
|
|
139
|
+
fail('binding', 'Native view changed during code-reference scan.', { reason: 'changed_view' });
|
|
140
|
+
const value = referenceResult(result, r), s = object(value.scan);
|
|
141
|
+
const cursor = s.complete ? null : 'cr1.' + Buffer.from(stable({ version: 1, algorithm: referenceAlgorithm, scope: who, query: q, next: hex(s.next_start), view, budgets: r.budgets })).toString('base64url');
|
|
142
|
+
return { ...who, value: { ...value, view: { identity: view }, cursor } };
|
|
143
|
+
}
|
|
144
|
+
export function codeReferenceCommands(research) {
|
|
145
|
+
return [command('analysis', 'Static analysis', [command('code-references', 'Find bounded x86-64 byte reference candidates without changing Listing or xrefs', ({ options: o }, c) => { const who = scope(o); return research(c).codeReferences(who, referenceRequest(o, who)); }, { permission: Permission.ReadOnly, options: [...selectedOptions,
|
|
146
|
+
{ flags: '--to <addresses>', description: '1–64 distinct exact default-space targets, comma separated' },
|
|
147
|
+
{ flags: '--forms <forms>', description: 'call-rel32,lea-rip,mov-rip,call-rip-slot; defaults to all four' },
|
|
148
|
+
{ flags: '--scan-from <address>', description: 'First candidate start (initial request)' }, { flags: '--scan-through <address>', description: 'Inclusive final byte; matches must fit completely' },
|
|
149
|
+
{ flags: '--cursor <token>', description: 'Stateless continuation; excludes targets/forms/range selectors' },
|
|
150
|
+
{ flags: '--max-bytes <count>', description: '15–16777216 bytes read including lookahead; default 4194304' },
|
|
151
|
+
{ flags: '--max-candidates <count>', description: '1–1000000 prefiltered starts including rejected candidates; default 200000' },
|
|
152
|
+
{ flags: '--timeout-seconds <count>', description: '1–30 second cooperative native scan deadline; default 5' },
|
|
153
|
+
{ flags: '--limit <count>', description: '1–1000 matches per call; default 100' }] })])];
|
|
154
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type CommandContext, type CommandDefinition } from '@eyeauras/cli-factory';
|
|
2
|
+
import { type Research } from './research.js';
|
|
3
|
+
import type { Workspace, Scope } from './workspace.js';
|
|
4
|
+
export type StopReason = 'explicit_close' | 'view_replaced' | 'mode_changed' | 'owner_dispose' | 'startup_failed' | 'temporary_complete' | 'legacy_cleanup' | 'unknown';
|
|
5
|
+
type Event = {
|
|
6
|
+
at: string;
|
|
7
|
+
event: 'start' | 'restore' | 'stop';
|
|
8
|
+
reason: string;
|
|
9
|
+
pid: number;
|
|
10
|
+
};
|
|
11
|
+
export declare function recordLifecycle(workspace: Workspace, context: string, event: Event): Promise<void>;
|
|
12
|
+
export declare function lifecycleDiagnostic(workspace: Workspace, selected: Scope): Promise<{
|
|
13
|
+
lifecycle: {
|
|
14
|
+
ownerShutdownCause: string;
|
|
15
|
+
coverage: string;
|
|
16
|
+
latest?: Record<string, unknown> | undefined;
|
|
17
|
+
events: Event[];
|
|
18
|
+
maximumEvents: number;
|
|
19
|
+
};
|
|
20
|
+
session: string;
|
|
21
|
+
target: string;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function navigationResult(raw: unknown, maximum: number): Record<string, unknown>;
|
|
24
|
+
export declare function contextDiagnosticCommands(research: (c: CommandContext) => Research, workspace: (c: CommandContext) => Workspace): CommandDefinition[];
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { command, Permission } from '@eyeauras/cli-factory';
|
|
2
|
+
import { mkdir, readFile, writeFile, rename, realpath } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve, sep } from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { scope, number } from './research.js';
|
|
6
|
+
import { object, fail, stable } from './model.js';
|
|
7
|
+
import { selectedOptions } from './arguments.js';
|
|
8
|
+
const writes = new Map();
|
|
9
|
+
const journal = (workspace, context) => join(workspace.directory, 'diagnostics', 'lifecycle', context + '.json');
|
|
10
|
+
const reasons = new Set(['explicit_close', 'view_replaced', 'mode_changed', 'owner_dispose', 'startup_failed', 'temporary_complete', 'legacy_cleanup', 'unknown', 'native_launch', 'saved_snapshot']);
|
|
11
|
+
async function events(path) {
|
|
12
|
+
try {
|
|
13
|
+
const rows = JSON.parse(await readFile(path, 'utf8'));
|
|
14
|
+
if (!Array.isArray(rows) || rows.length > 128 || rows.some(row => !row || typeof row.at !== 'string' || row.at.length > 64 || !Number.isFinite(Date.parse(row.at)) || !['start', 'restore', 'stop'].includes(row.event) || !reasons.has(row.reason) || !Number.isSafeInteger(row.pid) || row.pid < 1))
|
|
15
|
+
fail('binding', 'Retained lifecycle observations are malformed.');
|
|
16
|
+
return rows.map(({ at, event, reason, pid }) => ({ at, event, reason, pid }));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error.code === 'ENOENT')
|
|
20
|
+
return [];
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function recordLifecycle(workspace, context, event) {
|
|
25
|
+
const path = journal(workspace, context), previous = writes.get(path) ?? Promise.resolve();
|
|
26
|
+
const pending = previous.catch(() => { }).then(async () => { const rows = [...await events(path), event].slice(-128), directory = join(workspace.directory, 'diagnostics', 'lifecycle'); await mkdir(directory, { recursive: true, mode: 0o700 }); const temporary = path + '.new-' + randomUUID(); await writeFile(temporary, JSON.stringify(rows) + '\n', { mode: 0o600, flag: 'wx' }); await rename(temporary, path); });
|
|
27
|
+
writes.set(path, pending);
|
|
28
|
+
try {
|
|
29
|
+
await pending;
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
finally {
|
|
33
|
+
if (writes.get(path) === pending)
|
|
34
|
+
writes.delete(path);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function lifecycleDiagnostic(workspace, selected) {
|
|
38
|
+
const context = workspace.context(selected), root = join(workspace.directory, 'ghidra-native', 'context', context.id);
|
|
39
|
+
let latest;
|
|
40
|
+
try {
|
|
41
|
+
const binding = object(JSON.parse(await readFile(join(root, 'current.json'), 'utf8')));
|
|
42
|
+
if (binding.context !== context.id || binding.target !== selected.target || binding.session !== undefined && binding.session !== selected.session || typeof binding.root !== 'string' || !Number.isSafeInteger(binding.pid) || typeof binding.tokenSecret !== 'string')
|
|
43
|
+
fail('binding', 'Lifecycle receipt belongs to another context.');
|
|
44
|
+
const physical = await realpath(binding.root);
|
|
45
|
+
if (physical !== resolve(binding.root) || !physical.startsWith(resolve(root) + sep))
|
|
46
|
+
fail('binding', 'Lifecycle receipt moved outside its context.');
|
|
47
|
+
const persisted = object(JSON.parse(await readFile(join(physical, 'process.json'), 'utf8')));
|
|
48
|
+
if (stable(persisted) !== stable(binding))
|
|
49
|
+
fail('binding', 'Lifecycle process receipt changed.');
|
|
50
|
+
latest = { pid: binding.pid, state: 'unobserved', reason: 'unknown' };
|
|
51
|
+
try {
|
|
52
|
+
const stopped = object(JSON.parse(await readFile(join(physical, 'stopped.json'), 'utf8')));
|
|
53
|
+
if (stopped.pid === binding.pid && stopped.tokenSecret === binding.tokenSecret)
|
|
54
|
+
latest = { pid: binding.pid, state: 'stopped', reason: reasons.has(String(stopped.reason)) ? stopped.reason : 'unknown', ...(typeof stopped.at === 'string' ? { at: stopped.at } : {}) };
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error.code !== 'ENOENT')
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error.code !== 'ENOENT')
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
return { ...selected, lifecycle: { events: await events(journal(workspace, context.id)), maximumEvents: 128, ...(latest ? { latest } : {}), ownerShutdownCause: 'unknown', coverage: 'Retained facade observations, at most the latest 128 events; absent events and historical causes remain unknown. This read neither starts nor probes a native process.' } };
|
|
66
|
+
}
|
|
67
|
+
export function navigationResult(raw, maximum) {
|
|
68
|
+
const value = object(raw), scan = object(value.scan), functions = object(value.functions);
|
|
69
|
+
const samples = ['functions', 'instructions', 'references', 'definedData'].map(key => object(value[key]));
|
|
70
|
+
if (samples.some(row => !Number.isSafeInteger(row.sampled) || Number(row.sampled) < 0 || Number(row.sampled) > 128 || typeof row.complete !== 'boolean') ||
|
|
71
|
+
!Number.isSafeInteger(scan.visited) || Number(scan.visited) !== samples.reduce((n, row) => n + Number(row.sampled), 0) || Number(scan.visited) > maximum || scan.maximum !== maximum || scan.sampleLimitPerCategory !== 128 || typeof scan.complete !== 'boolean' || scan.complete !== samples.every(row => row.complete) || !['budget', 'sample_limit', 'exhausted'].includes(String(scan.reason)) ||
|
|
72
|
+
!Number.isSafeInteger(functions.oneByteBodies) || Number(functions.oneByteBodies) < 0 || Number(functions.oneByteBodies) > Number(functions.sampled) || !Number.isSafeInteger(functions.entriesWithInstructions) || Number(functions.entriesWithInstructions) < 0 || Number(functions.entriesWithInstructions) > Number(functions.sampled) || !Number.isSafeInteger(functions.bodyBytes) || Number(functions.bodyBytes) < 0 ||
|
|
73
|
+
!Number.isSafeInteger(samples[3].strings) || Number(samples[3].strings) < 0 || Number(samples[3].strings) > Number(samples[3].sampled) || !['sparse-metadata', 'no-code-observed', 'observed-metadata'].includes(String(value.assessment)) || !Array.isArray(value.recommendations) || value.recommendations.some(v => typeof v !== 'string') || object(value.read_only ?? {}).modification_unchanged !== true)
|
|
74
|
+
fail('partial', 'Malformed navigation diagnostic.');
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
export function contextDiagnosticCommands(research, workspace) {
|
|
78
|
+
return [
|
|
79
|
+
command('analysis', 'Analysis observations', [command('navigation', 'Inspect bounded navigation metadata; ready is not analysis completeness', ({ options: o }, c) => research(c).navigation(scope(o), number(o.maxScan ?? 10000, 200000, 1)), { permission: Permission.ReadOnly, options: [...selectedOptions, { flags: '--max-scan <count>', description: 'Maximum visited metadata inputs across all samples, 1–200000', defaultValue: 10000 }] })]),
|
|
80
|
+
command('diagnostics', 'Inspect retained context diagnostics', [command('lifecycle', 'Read retained start/restore/stop reasons without starting Ghidra', ({ options: o }, c) => lifecycleDiagnostic(workspace(c), scope(o)), { permission: Permission.ReadOnly, options: selectedOptions })]),
|
|
81
|
+
];
|
|
82
|
+
}
|
package/dist/src/contexts.d.ts
CHANGED
|
@@ -62,7 +62,19 @@ export declare class GhidraContexts {
|
|
|
62
62
|
private privateCopy;
|
|
63
63
|
open(scope: Scope, request?: string): Promise<GhidraContext>;
|
|
64
64
|
private selected;
|
|
65
|
-
observation(scope: Scope): Promise<
|
|
65
|
+
observation(scope: Scope): Promise<{
|
|
66
|
+
incarnation: string;
|
|
67
|
+
}>;
|
|
68
|
+
inspectObservation(scope: Scope): Promise<{
|
|
69
|
+
incarnation: string;
|
|
70
|
+
modification: string;
|
|
71
|
+
language: unknown;
|
|
72
|
+
compiler: unknown;
|
|
73
|
+
}>;
|
|
74
|
+
inlineScript(scope: Scope, parameters: Record<string, unknown>, requestId: string): Promise<{
|
|
75
|
+
response: Record<string, unknown>;
|
|
76
|
+
safe: boolean;
|
|
77
|
+
}>;
|
|
66
78
|
call(scope: Scope, operation: string, parameters?: Record<string, unknown>): Promise<unknown>;
|
|
67
79
|
saved(scope: Scope): Promise<Artifact>;
|
|
68
80
|
scripts(scope: Scope, filter: string, limit: number, offset: number): Promise<import("./scripts.js").ScriptPage>;
|