Skip to content

Latest commit

 

History

History
777 lines (532 loc) · 24.9 KB

api_reference.md

File metadata and controls

777 lines (532 loc) · 24.9 KB

Rust Client API Reference

Comprehensive reference for the libghidra Rust crate -- a synchronous HTTP client for Ghidra program databases via the LibGhidraHost RPC layer.

Comprehensive method coverage across 10 service areas, plus auto-pagination helpers.

Quick Start

use libghidra as ghidra;

fn main() -> Result<(), ghidra::Error> {
    let client = ghidra::connect("http://127.0.0.1:18080");

    let status = client.get_status()?;
    println!("{} v{}", status.service_name, status.service_version);

    let funcs = client.list_functions(0, u64::MAX, 10, 0)?;
    for f in &funcs.functions {
        println!("0x{:x}  {}", f.entry_address, f.name);
    }

    if let Some(f) = funcs.functions.first() {
        let dec = client.get_decompilation(f.entry_address, 30000)?;
        if let Some(d) = &dec.decompilation {
            println!("{}", d.pseudocode);
        }
    }
    Ok(())
}

Installation

Add to Cargo.toml:

[dependencies]
libghidra = { path = "../libghidra/rust" }

Requires a running LibGhidraHost instance.

Factory & Options

ghidra::connect(url) -> GhidraClient

Create a client with default options.

let client = ghidra::connect("http://127.0.0.1:18080");

ClientOptions

use libghidra::{ClientOptions, GhidraClient};
use std::time::Duration;

let client = GhidraClient::new(ClientOptions {
    base_url: "http://127.0.0.1:18080".into(),
    auth_token: String::new(),
    connect_timeout: Duration::from_secs(3),
    read_timeout: Duration::from_secs(15),
    max_retries: 0,
    initial_backoff: Duration::from_millis(100),
    max_backoff: Duration::from_secs(5),
    jitter: true,
});

Error Handling

All methods return Result<T, ghidra::Error>.

use libghidra::{Error, ErrorCode};

match client.get_function(0xdeadbeef) {
    Ok(resp) => { /* ... */ }
    Err(e) => {
        eprintln!("Code: {:?}, Message: {}", e.code, e);
        if e.code.is_retryable() {
            // retry logic
        }
    }
}

ErrorCode variants

ConnectionFailed, Timeout, TransportError, BadRequest, Unauthorized, Forbidden, NotFound, Conflict, TooManyRequests, InternalError, BadGateway, ServiceUnavailable, GatewayTimeout, HttpError, EncodeError, ParseError, ApiError, NotSupported, ConfigError, Other.


Health (2 methods)

fn get_status(&self) -> Result<HealthStatus>

Check host health and program state.

let status = client.get_status()?;
println!("{} v{} (mode: {})", status.service_name, status.service_version, status.host_mode);

Returns: HealthStatus { ok, service_name, service_version, host_mode, modification_number, warnings }.

fn get_capabilities(&self) -> Result<Vec<Capability>>

List backend capabilities.

let caps = client.get_capabilities()?;
for c in &caps {
    println!("{}: {}", c.id, c.status);
}

Session (13 methods)

fn open_project(&self, request: &OpenProjectRequest) -> Result<OpenProjectResponse>

let resp = client.open_project(&ghidra::OpenProjectRequest {
    project_path: "C:/ghidra_projects".into(),
    project_name: "firmware".into(),
    create: true,
    ..Default::default()
})?;
println!("Project: {} created={}", resp.project_name, resp.created);

fn close_project(&self, policy: ShutdownPolicy) -> Result<CloseProjectResponse>

client.close_project(ghidra::ShutdownPolicy::Save)?;

fn list_project_files(&self, request: ListProjectFilesRequest) -> Result<ListProjectFilesResponse>

let files = client.list_project_files(ghidra::ListProjectFilesRequest {
    programs_only: true,
    ..Default::default()
})?;
for file in &files.files {
    println!("{}", file.path);
}

fn import_program(&self, request: &ImportProgramRequest) -> Result<ImportProgramResponse>

Import a binary into the active project. This does not automatically make it the active program; call open_program with the returned project path.

let imported = client.import_program(&ghidra::ImportProgramRequest {
    source_path: "C:/samples/picture_decoder.pe".into(),
    overwrite: true,
    analyze: true,
    ..Default::default()
})?;
println!("{}", imported.primary_program_path);

fn open_program(&self, request: &OpenProgramRequest) -> Result<OpenProgramResponse>

let req = ghidra::OpenProgramRequest {
    project_path: "C:/ghidra_projects".into(),
    project_name: "firmware".into(),
    program_path: "/picture_decoder.pe".into(),
    ..Default::default()
};
let resp = client.open_program(&req)?;
println!("Opened: {} (base=0x{:x})", resp.program_name, resp.image_base);

Returns: OpenProgramResponse { program_name, language_id, compiler_spec, image_base }.

fn close_program(&self, policy: ShutdownPolicy) -> Result<CloseProgramResponse>

client.close_program(ghidra::ShutdownPolicy::Save)?;

fn save_program(&self) -> Result<SaveProgramResponse>

let resp = client.save_program()?;
assert!(resp.saved);

fn discard_program(&self) -> Result<DiscardProgramResponse>

client.discard_program()?;

fn get_revision(&self) -> Result<RevisionResponse>

let rev = client.get_revision()?;
println!("Program: {}, modification: {}", rev.program_id, rev.modification_number);

fn shutdown(&self, policy: ShutdownPolicy) -> Result<ShutdownResponse>

client.shutdown(ghidra::ShutdownPolicy::Save)?;

fn list_program_options(&self, category: &str, name_filter: &str) -> Result<Vec<ProgramOptionRecord>>

List the current program's options, every category or one exact category ("Analyzers", "Program Information", ...); name_filter is a case-insensitive substring. A pure read. type_name is the Ghidra option type in lower case (boolean, int, enum, ...); allowed_values lists enum constants.

fn set_program_option(&self, category: &str, name: &str, value: &str) -> Result<SetProgramOptionResponse>

Typed write of one option; a value the type rejects, a read-only category or a non-scalar option is an invalid_argument error. Only Analyzers options are settable.

client.set_program_option("Analyzers", "Objective-C 2 Class", "false")?;

fn list_transactions(&self) -> Result<Vec<TransactionRecord>>

Undo history (kind == "undo", position 1 = most recent), undone entries ("redo"), and the open transaction ("open", position 0).

ImportProgramRequest::analyzers_off / analyzers_on switch analyzer toggles for every loaded program before analysis; ImportProgramResponse::analyzer_matches reports what each pattern matched.


Analysis (3 methods)

A job owns the program while it runs: every other program call fails with analysis_running; poll list_analysis_jobs.

fn start_analysis(&self, mode: &str) -> Result<AnalysisJobRecord>

"all" re-runs every enabled analyzer; "changed" runs only the work queued by edits since the program was opened. Returns at once with a running job.

fn list_analysis_jobs(&self) -> Result<Vec<AnalysisJobRecord>>

Jobs of the current program with state running / done / error / cancelled and their timing.

fn cancel_analysis(&self, job_id: u64) -> Result<bool>

Cancel a job (0 = whichever is running); work already done is kept.


Decompiler (3 methods)

fn get_decompilation(&self, address: u64, timeout_ms: u32) -> Result<GetDecompilationResponse>

Against the live Ghidra host, pseudocode is the unchanged DecompiledFunction.getC() string. Tokens and locals are separate fields and are not inserted into it. Require completed && !is_fallback for an exact successful result. A fallback may contain a synthetic diagnostic comment, with the native reason in error_message.

let resp = client.get_decompilation(0x140001000, 30000)?;
if let Some(d) = &resp.decompilation {
    if d.completed && !d.is_fallback {
        println!("{}", d.pseudocode);
    }
}

Returns: GetDecompilationResponse { decompilation: Option<DecompilationRecord> }. Record fields: function_entry_address, function_name, prototype, pseudocode, completed, is_fallback, error_message.

fn list_decompilations(&self, range_start: u64, range_end: u64, limit: i32, offset: i32, timeout_ms: u32) -> Result<ListDecompilationsResponse>

let resp = client.list_decompilations(0, u64::MAX, 50, 0, 60000)?;

fn get_pcode(&self, address: u64, maturity: PcodeMaturity, timeout_ms: u32) -> Result<GetPcodeResponse>

let resp = client.get_pcode(
    0x140001000,
    ghidra::models::PcodeMaturity::Raw,
    30000,
)?;
if let Some(pcode) = &resp.pcode {
    for op in &pcode.ops {
        println!("{} {} @ {}", op.seq, op.op,
            if op.has_address { format!("{:#x}", op.addr) } else { "<synthetic>".into() });
    }
}

PcodeRecord carries function_entry_address, ops, completed, error_message, and the returned maturity. Each PcodeOpRecord carries seq, op, addr, has_address, has_output, output, and inputs. has_address distinguishes a real source address zero from an op with no machine address.


Functions (5 methods)

fn get_function(&self, address: u64) -> Result<GetFunctionResponse>

let resp = client.get_function(0x140001000)?;
if let Some(f) = &resp.function {
    println!("{} ({} bytes)", f.name, f.size);
}

Returns: FunctionRecord { entry_address, name, start_address, end_address, size, namespace_name, prototype, is_thunk, parameter_count }.

fn list_functions(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<ListFunctionsResponse>

let funcs = client.list_functions(0, u64::MAX, 20, 0)?;

fn list_leaf_functions(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<ListFunctionsResponse>

Functions that make no call, from Ghidra's reference index. LocalClient reads the same index out of a Ghidra project, so both backends agree; on a bare binary it returns ErrorCode::NotSupported.

let leaves = client.list_leaf_functions(0, u64::MAX, 0, 0)?;

fn rename_function(&self, address: u64, new_name: &str) -> Result<RenameFunctionResponse>

let resp = client.rename_function(0x140001000, "init_app")?;
println!("Renamed: {}", resp.name);

fn list_basic_blocks(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<ListBasicBlocksResponse>

let blocks = client.list_basic_blocks(func.start_address, func.end_address, 100, 0)?;
for b in &blocks.blocks {
    println!("  0x{:x}-0x{:x}  in={} out={}", b.start_address, b.end_address, b.in_degree, b.out_degree);
}

Returns: BasicBlockRecord { function_entry, start_address, end_address, in_degree, out_degree }.

fn list_cfg_edges(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<ListCFGEdgesResponse>

let edges = client.list_cfg_edges(func.start_address, func.end_address, 100, 0)?;
for e in &edges.edges {
    println!("  0x{:x} -> 0x{:x}  ({})", e.src_block_start, e.dst_block_start, e.edge_kind);
}

Returns: CFGEdgeRecord { function_entry, src_block_start, dst_block_start, edge_kind }.


Memory (4 methods)

fn read_bytes(&self, address: u64, length: u32) -> Result<ReadBytesResponse>

let resp = client.read_bytes(0x140000000, 64)?;
for (i, byte) in resp.data.iter().enumerate() {
    print!("{:02x} ", byte);
    if (i + 1) % 16 == 0 { println!(); }
}

fn write_bytes(&self, address: u64, data: &[u8]) -> Result<WriteBytesResponse>

client.write_bytes(0x140000000, &[0x90, 0x90, 0x90, 0x90])?;

fn patch_bytes_batch(&self, patches: &[BytePatch]) -> Result<PatchBytesBatchResponse>

let patches = vec![
    ghidra::BytePatch { address: 0x140001000, data: vec![0xcc] },
    ghidra::BytePatch { address: 0x140001010, data: vec![0x90, 0x90] },
];
let resp = client.patch_bytes_batch(&patches)?;
println!("{} patches, {} bytes", resp.patch_count, resp.bytes_written);

fn list_memory_blocks(&self, limit: i32, offset: i32) -> Result<ListMemoryBlocksResponse>

let blocks = client.list_memory_blocks(100, 0)?;
for b in &blocks.blocks {
    let perms = format!("{}{}{}",
        if b.is_read { "r" } else { "-" },
        if b.is_write { "w" } else { "-" },
        if b.is_execute { "x" } else { "-" },
    );
    println!("  {}  0x{:x}  {}  {} bytes", b.name, b.start_address, perms, b.size);
}

Returns: MemoryBlockRecord { name, start_address, end_address, size, is_read, is_write, is_execute, is_volatile, is_initialized, source_name, comment }.


Symbols (4 methods)

fn get_symbol(&self, address: u64) -> Result<GetSymbolResponse>

let resp = client.get_symbol(0x140001000)?;
if let Some(s) = &resp.symbol {
    println!("{} ({})", s.name, s.r#type);
}

Returns: SymbolRecord { symbol_id, address, name, full_name, r#type, namespace_name, source, is_primary, is_external, is_dynamic, is_external_entry_point }.

fn list_symbols(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<ListSymbolsResponse>

let syms = client.list_symbols(0, u64::MAX, 20, 0)?;

fn rename_symbol(&self, address: u64, new_name: &str) -> Result<RenameSymbolResponse>

client.rename_symbol(0x140001000, "my_func")?;

fn delete_symbol(&self, address: u64, name_filter: &str) -> Result<DeleteSymbolResponse>

client.delete_symbol(0x140001000, "old_label")?;

Types (31 methods)

Query methods (7)

fn get_type(&self, path: &str) -> Result<GetTypeResponse>

let resp = client.get_type("/int")?;
if let Some(t) = &resp.r#type {
    println!("{}: {} ({} bytes)", t.name, t.kind, t.length);
}

Returns: TypeRecord { type_id, name, path_name, category_path, display_name, kind, length, is_not_yet_defined, source_archive, universal_id }.

fn list_types(&self, query: &str, limit: i32, offset: i32) -> Result<ListTypesResponse>

fn list_type_aliases(&self, query: &str, limit: i32, offset: i32) -> Result<ListTypeAliasesResponse>

Returns: TypeAliasRecord { type_id, path_name, name, target_type, declaration }.

fn list_type_unions(&self, query: &str, limit: i32, offset: i32) -> Result<ListTypeUnionsResponse>

Returns: TypeUnionRecord { type_id, path_name, name, size, declaration }.

fn list_type_enums(&self, query: &str, limit: i32, offset: i32) -> Result<ListTypeEnumsResponse>

Returns: TypeEnumRecord { type_id, path_name, name, width, is_signed, declaration }.

fn list_type_enum_members(&self, type_id_or_path: &str, limit: i32, offset: i32) -> Result<ListTypeEnumMembersResponse>

Returns: TypeEnumMemberRecord { type_id, type_path_name, type_name, ordinal, name, value }.

fn list_type_members(&self, type_id_or_path: &str, limit: i32, offset: i32) -> Result<ListTypeMembersResponse>

Returns: TypeMemberRecord { parent_type_id, parent_type_path_name, parent_type_name, ordinal, name, member_type, offset, size }.

Struct CRUD (5)

fn create_type(&self, name: &str, kind: &str, size: u64) -> Result<CreateTypeResponse>

client.create_type("my_struct", "struct", 32)?;

fn delete_type(&self, type_id_or_path: &str) -> Result<DeleteTypeResponse>

fn rename_type(&self, type_id_or_path: &str, new_name: &str) -> Result<RenameTypeResponse>

fn add_type_member(&self, parent: &str, name: &str, member_type: &str, size: u64) -> Result<AddTypeMemberResponse>

client.add_type_member("my_struct", "flags", "int", 4)?;

fn delete_type_member(&self, parent: &str, ordinal: u64) -> Result<DeleteTypeMemberResponse>

Struct member mutation (2)

fn rename_type_member(&self, parent: &str, ordinal: u64, new_name: &str) -> Result<RenameTypeMemberResponse>

fn set_type_member_type(&self, parent: &str, ordinal: u64, member_type: &str) -> Result<SetTypeMemberTypeResponse>

Alias CRUD (3)

fn create_type_alias(&self, name: &str, target_type: &str) -> Result<CreateTypeAliasResponse>

client.create_type_alias("HANDLE", "void *")?;

fn delete_type_alias(&self, type_id_or_path: &str) -> Result<DeleteTypeAliasResponse>

fn set_type_alias_target(&self, type_id_or_path: &str, target_type: &str) -> Result<SetTypeAliasTargetResponse>

Enum CRUD (6)

fn create_type_enum(&self, name: &str, width: u64, is_signed: bool) -> Result<CreateTypeEnumResponse>

client.create_type_enum("error_code", 4, false)?;

fn delete_type_enum(&self, type_id_or_path: &str) -> Result<DeleteTypeEnumResponse>

fn add_type_enum_member(&self, type_id_or_path: &str, name: &str, value: i64) -> Result<AddTypeEnumMemberResponse>

fn delete_type_enum_member(&self, type_id_or_path: &str, ordinal: u64) -> Result<DeleteTypeEnumMemberResponse>

fn rename_type_enum_member(&self, type_id_or_path: &str, ordinal: u64, new_name: &str) -> Result<RenameTypeEnumMemberResponse>

fn set_type_enum_member_value(&self, type_id_or_path: &str, ordinal: u64, value: i64) -> Result<SetTypeEnumMemberValueResponse>

Signatures (8)

fn get_function_signature(&self, address: u64) -> Result<GetFunctionSignatureResponse>

let resp = client.get_function_signature(0x140001000)?;
if let Some(sig) = &resp.signature {
    println!("Prototype: {}", sig.prototype);
    println!("Return: {}, Convention: {}", sig.return_type, sig.calling_convention);
    for p in &sig.parameters {
        println!("  param[{}]: {} {}", p.ordinal, p.data_type, p.name);
    }
}

Returns: FunctionSignatureRecord { function_entry_address, function_name, prototype, return_type, has_var_args, calling_convention, parameters }. Each ParameterRecord { ordinal, name, data_type, formal_data_type, is_auto_parameter, is_forced_indirect }.

fn list_function_signatures(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<ListFunctionSignaturesResponse>

fn set_function_signature(&self, address: u64, prototype: &str, calling_convention: &str) -> Result<SetFunctionSignatureResponse>

client.set_function_signature(0x140001000, "int main(int argc, char **argv)", "")?;

fn rename_function_parameter(&self, address: u64, ordinal: i32, new_name: &str) -> Result<RenameFunctionParameterResponse>

fn set_function_parameter_type(&self, address: u64, ordinal: i32, data_type: &str) -> Result<SetFunctionParameterTypeResponse>

fn rename_function_local(&self, address: u64, local_id: &str, new_name: &str) -> Result<RenameFunctionLocalResponse>

fn set_function_local_type(&self, address: u64, local_id: &str, data_type: &str) -> Result<SetFunctionLocalTypeResponse>

fn apply_data_type(&self, address: u64, data_type: &str) -> Result<ApplyDataTypeResponse>

client.apply_data_type(0x140010000, "dword")?;

Listing (21 methods)

Instructions (3)

fn get_instruction(&self, address: u64) -> Result<GetInstructionResponse>

let resp = client.get_instruction(0x140001000)?;
if let Some(i) = &resp.instruction {
    println!("0x{:x}  {}  ({} bytes)", i.address, i.disassembly, i.length);
}

Returns: InstructionRecord { address, mnemonic, operand_text, disassembly, length }.

fn list_instructions(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<ListInstructionsResponse>

fn list_instruction_operands(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<ListInstructionOperandsResponse>

Returns per-operand records with address, operand_index, text, type_name, and ref_type.

Comments (3)

fn set_comment(&self, address: u64, kind: CommentKind, text: &str) -> Result<SetCommentResponse>

client.set_comment(0x140001000, ghidra::CommentKind::Eol, "entry point")?;

CommentKind variants: Eol, Pre, Post, Plate, Repeatable.

fn get_comments(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<GetCommentsResponse>

Returns: CommentRecord { address, kind, text }.

fn delete_comment(&self, address: u64, kind: CommentKind) -> Result<DeleteCommentResponse>

Data Items (3)

fn list_data_items(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<ListDataItemsResponse>

Returns: DataItemRecord { address, end_address, name, data_type, size, value_repr }.

fn rename_data_item(&self, address: u64, new_name: &str) -> Result<RenameDataItemResponse>

fn delete_data_item(&self, address: u64) -> Result<DeleteDataItemResponse>

Bookmarks (3)

fn list_bookmarks(&self, range_start: u64, range_end: u64, limit: i32, offset: i32, type_filter: &str, category_filter: &str) -> Result<ListBookmarksResponse>

Returns: BookmarkRecord { address, r#type, category, comment }.

fn add_bookmark(&self, address: u64, r#type: &str, category: &str, comment: &str) -> Result<AddBookmarkResponse>

fn delete_bookmark(&self, address: u64, r#type: &str, category: &str) -> Result<DeleteBookmarkResponse>

Breakpoints (8)

fn list_breakpoints(&self, range_start: u64, range_end: u64, limit: i32, offset: i32, kind_filter: &str, group_filter: &str) -> Result<ListBreakpointsResponse>

Returns: BreakpointRecord { address, enabled, kind, size, condition, group }.

fn add_breakpoint(&self, address: u64, kind: &str, size: u64, enabled: bool, condition: &str, group: &str) -> Result<AddBreakpointResponse>

fn set_breakpoint_enabled(&self, address: u64, enabled: bool) -> Result<SetBreakpointEnabledResponse>

fn set_breakpoint_kind(&self, address: u64, kind: &str) -> Result<SetBreakpointKindResponse>

fn set_breakpoint_size(&self, address: u64, size: u64) -> Result<SetBreakpointSizeResponse>

fn set_breakpoint_condition(&self, address: u64, condition: &str) -> Result<SetBreakpointConditionResponse>

fn set_breakpoint_group(&self, address: u64, group: &str) -> Result<SetBreakpointGroupResponse>

fn delete_breakpoint(&self, address: u64) -> Result<DeleteBreakpointResponse>

Strings (1)

fn list_defined_strings(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<ListDefinedStringsResponse>

Returns: DefinedStringRecord { address, value, length, data_type, encoding }.


Cross-References (1 method)

fn list_xrefs(&self, range_start: u64, range_end: u64, limit: i32, offset: i32) -> Result<ListXrefsResponse>

let xrefs = client.list_xrefs(0, u64::MAX, 20, 0)?;
for x in &xrefs.xrefs {
    println!("0x{:x} -> 0x{:x}  {}", x.from_address, x.to_address, x.ref_type);
}

Returns: XrefRecord { from_address, to_address, operand_index, ref_type, is_primary, source, symbol_id, is_external, is_memory, is_flow }.


Pagination

The paginate module provides helpers for auto-fetching all pages from paginated list RPCs.

fetch_all(fetch_fn) -> Result<Vec<T>>

Convenience function that drains all pages into a single Vec.

use ghidra::paginate::fetch_all;

let all_funcs = fetch_all(|limit, offset| {
    let resp = client.list_functions(0, u64::MAX, limit, offset)?;
    Ok(resp.functions)
})?;
println!("Total functions: {}", all_funcs.len());

Paginator<T, F>

Lazy iterator that fetches pages on demand. Implements Iterator<Item = Result<Vec<T>>>.

use ghidra::paginate::Paginator;

// Default page size (100)
let pages: Result<Vec<Vec<_>>, _> = Paginator::new(|limit, offset| {
    let resp = client.list_symbols(0, u64::MAX, limit, offset)?;
    Ok(resp.symbols)
}).collect();

// Custom page size
let pages = Paginator::new(|limit, offset| {
    let resp = client.list_types("", limit, offset)?;
    Ok(resp.types)
}).page_size(25);

for page in pages {
    let items = page?;
    println!("Got {} types", items.len());
}

The closure receives (limit: i32, offset: i32) and should return Result<Vec<T>> from the list response. Iteration stops when a page returns fewer items than the page size.


Appendix

Type aliases

The crate exports short aliases for common types:

Alias Full Type
Function FunctionRecord
Symbol SymbolRecord
Decompilation DecompilationRecord
Instruction InstructionRecord
Xref XrefRecord
Type TypeRecord
Comment CommentRecord
MemoryBlock MemoryBlockRecord
BasicBlock BasicBlockRecord
CFGEdge CFGEdgeRecord
DataItem DataItemRecord
Bookmark BookmarkRecord
Breakpoint BreakpointRecord
Parameter ParameterRecord
Signature FunctionSignatureRecord
DefinedString DefinedStringRecord
TypeMember TypeMemberRecord
TypeEnum TypeEnumRecord
TypeEnumMember TypeEnumMemberRecord
TypeAlias TypeAliasRecord
TypeUnion TypeUnionRecord
Client GhidraClient
ConnectOptions ClientOptions

ShutdownPolicy

Variant Effect
Unspecified Server decides
Save Save before closing
Discard Discard unsaved changes
None Close without saving