DECISION-NATIVE-TOOLCHAIN-401: Native Lifter and Demangler Selection

Status: Published Version: 1.0.0 Published: 2025-12-13 Owners: Scanner Guild, Platform Guild Unblocks: SCANNER-NATIVE-401-015, SCAN-REACH-401-009

Part of the Stella Ops Contracts index. Audience: Scanner and Platform implementers building native binary analysis, and reviewers checking that the chosen toolchain preserves determinism and the offline/air-gap posture.

Decision Summary

This decision record fixes the toolchain selection for native binary analysis in Stella Ops — the parsers, demanglers, disassemblers, and callgraph strategy used to extract native symbols, callgraphs, and demangled names from ELF/PE/Mach-O binaries. The guiding constraints are determinism, portability, and zero runtime dependency on external tools so that analysis runs identically in CI and in air-gapped deployments.


1. Component Decisions

1.1 ELF Parser

Decision: Use custom pure-C# ELF parser

Rationale:

Implementation: src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Native/Internal/Elf/

1.2 PE Parser

Decision: Use custom pure-C# PE parser

Rationale:

Implementation: src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Native/Internal/Pe/

1.3 Mach-O Parser

Decision: Use custom pure-C# Mach-O parser

Rationale:

Implementation: src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Native/Internal/MachO/

1.4 Symbol Demangler

Decision: Use per-language managed demanglers with native fallback

LanguagePrimary DemanglerFallback
C++ (Itanium ABI)Demangler.Net (NuGet)llvm-cxxfilt via P/Invoke
C++ (MSVC)UnDecorateSymbolName wrapperNone (Windows-specific)
Rustrustc-demangle portrustfilt via P/Invoke
Swiftswift-demangle portNone
Ddlang-demangler portNone

Rationale:

NuGet packages:

<PackageReference Include="Demangler.Net" Version="1.0.0" />

1.5 Disassembler (Optional, for heuristic analysis)

Decision: Use Iced (x86/x64) + Capstone.NET (ARM/others)

ArchitectureLibraryNuGet Package
x86/x64IcedIced
ARM/ARM64Capstone.NETCapstone.NET
OtherSkip disassemblyN/A

Rationale:

1.6 Callgraph Extraction

Decision: Static analysis only (no dynamic execution)

Methods:

  1. Relocation-based: Extract call targets from relocations
  2. Import/Export: Map import references to exports
  3. Symbol-based: Direct and indirect call targets from symbol table
  4. CFG heuristics: Basic block boundary detection (x86 only)

No dynamic analysis: Avoids execution risks, portable.


2. CI Toolchain Requirements

2.1 Build Requirements

ComponentRequirementNotes
.NET SDK10.0+Required for all builds
Native libs (optional)Capstone 4.0+Only for ARM disassembly
Test binariesPre-built fixturesNo compiler dependency in CI

2.2 Test Fixture Strategy

Decision: Ship pre-built binary fixtures, not source + compiler

Rationale:

Fixture locations:

tests/Binary/fixtures/
  elf-x86_64/
    binary.elf           # Pre-built
    expected.json        # Expected graph
    expected-hashes.txt  # Determinism check
  pe-x64/
    binary.exe
    expected.json
  macho-arm64/
    binary.dylib
    expected.json

2.3 Fixture Generation (Offline)

Fixtures are generated offline by maintainers:

# Generate ELF fixture (run once, commit result)
cd tools/fixtures
./generate-elf-fixture.sh

# Verify hashes match
./verify-fixtures.sh

3. Demangling Contract

3.1 Output Format

Demangled names follow this format:

{
  "symbol": {
    "mangled": "_ZN4Curl7Session4readEv",
    "demangled": "Curl::Session::read()",
    "source": "itanium-abi",
    "confidence": 1.0
  }
}

3.2 Demangling Sources

SourceDescriptionConfidence
itanium-abiItanium C++ ABI (GCC/Clang)1.0
msvcMicrosoft Visual C++1.0
rustRust mangling1.0
swiftSwift mangling1.0
fallbackNative tool fallback0.9
heuristicPattern-based guess0.6
noneNo demangling available0.3

3.3 Failed Demangling

When demangling fails:

{
  "symbol": {
    "mangled": "_Z15unknown_format",
    "demangled": null,
    "source": "none",
    "confidence": 0.3,
    "demangling_error": "Unrecognized mangling scheme"
  }
}

4. Callgraph Edge Types

4.1 Edge Type Enumeration

TypeDescriptionConfidence
callDirect call instruction1.0
pltPLT/GOT indirect call0.95
indirectIndirect call (vtable, function pointer)0.6
init_arrayFrom init_array to function1.0
tls_callbackTLS callback invocation1.0
exceptionException handler target0.8
switchSwitch table target0.7
heuristicCFG-based heuristic0.4

4.2 Unknown Targets

When call target cannot be resolved:

{
  "unknowns": [
    {
      "id": "unknown:call:0x12345678",
      "type": "unresolved_call_target",
      "source_id": "sym:binary:abc...",
      "call_site": "0x12345678",
      "reason": "Indirect call through register"
    }
  ]
}

5. Performance Constraints

5.1 Size Limits

MetricLimitAction on Exceed
Binary size100 MBWarn, proceed
Symbol count1M symbolsChunk processing
Edge count10M edgesChunk output
Memory usage4 GBStream processing

5.2 Timeout Constraints

OperationTimeoutAction on Exceed
ELF parse60sFail with partial
Demangle all120sTruncate results
CFG analysis300sSkip heuristics
Total analysis600sFail gracefully

6. Integration Points

6.1 Scanner Plugin Interface

public interface INativeAnalyzer : IAnalyzerPlugin
{
    Task<NativeObservationDocument> AnalyzeAsync(
        Stream binaryStream,
        NativeAnalyzerOptions options,
        CancellationToken ct);
}

6.2 RichGraph Integration

Native analysis results feed into RichGraph:

NativeObservation → NativeReachabilityGraph → RichGraph nodes/edges

6.3 Signals Integration

Native symbols with runtime hits:

Signals runtime-facts + RichGraph → ReachabilityFact with confidence

7. Implementation Checklist

TaskStatusOwner
ELF parserDoneScanner Guild
PE parserDoneScanner Guild
Mach-O parserIn ProgressScanner Guild
C++ demanglerDoneScanner Guild
Rust demanglerPendingScanner Guild
Callgraph builderDoneScanner Guild
Test fixturesPartialQA Guild
CI integrationPendingDevOps Guild


Changelog

VersionDateAuthorChanges
1.0.02025-12-13Platform GuildInitial toolchain decision