Files
git.stella-ops.org/src/Signals/StellaOps.Signals/Services/ISymbolCanonicalizationService.cs
StellaOps Bot 907783f625 Add property-based tests for SBOM/VEX document ordering and Unicode normalization determinism
- Implement `SbomVexOrderingDeterminismProperties` for testing component list and vulnerability metadata hash consistency.
- Create `UnicodeNormalizationDeterminismProperties` to validate NFC normalization and Unicode string handling.
- Add project file for `StellaOps.Testing.Determinism.Properties` with necessary dependencies.
- Introduce CI/CD template validation tests including YAML syntax checks and documentation content verification.
- Create validation script for CI/CD templates ensuring all required files and structures are present.
2025-12-26 15:17:58 +02:00

405 lines
9.7 KiB
C#

namespace StellaOps.Signals.Services;
/// <summary>
/// Symbol canonicalization service interface.
/// Sprint: SPRINT_20251226_010_SIGNALS_runtime_stack
/// Task: STACK-06
///
/// Resolves program counter addresses to canonical (Build-ID, function, offset) tuples.
/// </summary>
public interface ISymbolCanonicalizationService
{
/// <summary>
/// Resolves a program counter address to a canonical symbol.
/// </summary>
/// <param name="address">The program counter address.</param>
/// <param name="buildId">The ELF Build-ID of the binary.</param>
/// <param name="binaryPath">Optional path to the binary for symbol lookup.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The resolved symbol, or null if resolution failed.</returns>
Task<CanonicalSymbol?> ResolveAsync(
ulong address,
string? buildId,
string? binaryPath = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Resolves multiple addresses in batch for efficiency.
/// </summary>
Task<IReadOnlyList<CanonicalSymbol?>> ResolveBatchAsync(
IReadOnlyList<SymbolResolutionRequest> requests,
CancellationToken cancellationToken = default);
/// <summary>
/// Resolves a Java frame using JVMTI metadata.
/// </summary>
Task<CanonicalSymbol?> ResolveJavaFrameAsync(
ulong address,
JavaFrameMetadata metadata,
CancellationToken cancellationToken = default);
/// <summary>
/// Resolves a .NET frame using DAC (Data Access Component).
/// </summary>
Task<CanonicalSymbol?> ResolveDotNetFrameAsync(
ulong address,
DotNetFrameMetadata metadata,
CancellationToken cancellationToken = default);
/// <summary>
/// Resolves a Python frame using interpreter symbols.
/// </summary>
Task<CanonicalSymbol?> ResolvePythonFrameAsync(
ulong address,
PythonFrameMetadata metadata,
CancellationToken cancellationToken = default);
/// <summary>
/// Checks if a Build-ID is in the local symbol cache.
/// </summary>
Task<bool> IsInCacheAsync(string buildId, CancellationToken cancellationToken = default);
/// <summary>
/// Adds symbols for a Build-ID to the cache.
/// </summary>
Task CacheSymbolsAsync(
string buildId,
IReadOnlyList<SymbolEntry> symbols,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Request for symbol resolution.
/// </summary>
public sealed record SymbolResolutionRequest
{
/// <summary>
/// Program counter address.
/// </summary>
public required ulong Address { get; init; }
/// <summary>
/// ELF Build-ID of the binary.
/// </summary>
public string? BuildId { get; init; }
/// <summary>
/// Path to the binary file.
/// </summary>
public string? BinaryPath { get; init; }
/// <summary>
/// Runtime type hint for managed runtimes.
/// </summary>
public RuntimeType RuntimeType { get; init; } = RuntimeType.Native;
}
/// <summary>
/// Runtime type for symbol resolution hints.
/// </summary>
public enum RuntimeType
{
/// <summary>
/// Native code (C, C++, Rust, Go, etc.).
/// </summary>
Native,
/// <summary>
/// Java Virtual Machine.
/// </summary>
Java,
/// <summary>
/// .NET Common Language Runtime.
/// </summary>
DotNet,
/// <summary>
/// Python interpreter.
/// </summary>
Python,
/// <summary>
/// Node.js / V8.
/// </summary>
NodeJs,
/// <summary>
/// Ruby interpreter.
/// </summary>
Ruby,
}
/// <summary>
/// Canonical symbol representation.
/// </summary>
public sealed record CanonicalSymbol
{
/// <summary>
/// Original address that was resolved.
/// </summary>
public required ulong Address { get; init; }
/// <summary>
/// ELF Build-ID of the containing binary.
/// </summary>
public required string BuildId { get; init; }
/// <summary>
/// Demangled function name.
/// </summary>
public required string FunctionName { get; init; }
/// <summary>
/// Offset within the function.
/// </summary>
public required ulong Offset { get; init; }
/// <summary>
/// Module or binary name.
/// </summary>
public string? ModuleName { get; init; }
/// <summary>
/// Source file path (if debug info available).
/// </summary>
public string? SourceFile { get; init; }
/// <summary>
/// Source line number.
/// </summary>
public int? SourceLine { get; init; }
/// <summary>
/// Whether this symbol is from a trusted source.
/// </summary>
public bool IsTrusted { get; init; }
/// <summary>
/// Resolution method used.
/// </summary>
public SymbolResolutionMethod ResolutionMethod { get; init; }
/// <summary>
/// Returns the canonical string format.
/// </summary>
public string ToCanonicalString()
{
return $"{BuildId[..Math.Min(16, BuildId.Length)]}:{FunctionName}+0x{Offset:x}";
}
/// <summary>
/// Parses a canonical string format.
/// </summary>
public static CanonicalSymbol? Parse(string canonical)
{
if (string.IsNullOrWhiteSpace(canonical))
return null;
// Format: "buildid:function+0xoffset"
var colonIdx = canonical.IndexOf(':');
if (colonIdx < 0)
return null;
var buildId = canonical[..colonIdx];
var rest = canonical[(colonIdx + 1)..];
var plusIdx = rest.LastIndexOf('+');
if (plusIdx < 0)
return null;
var functionName = rest[..plusIdx];
var offsetStr = rest[(plusIdx + 1)..];
if (!offsetStr.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
return null;
if (!ulong.TryParse(offsetStr[2..], System.Globalization.NumberStyles.HexNumber, null, out var offset))
return null;
return new CanonicalSymbol
{
Address = 0, // Not recoverable from canonical string
BuildId = buildId,
FunctionName = functionName,
Offset = offset,
ResolutionMethod = SymbolResolutionMethod.Parsed,
};
}
}
/// <summary>
/// Method used to resolve the symbol.
/// </summary>
public enum SymbolResolutionMethod
{
/// <summary>
/// Resolved from ELF symbol table.
/// </summary>
ElfSymtab,
/// <summary>
/// Resolved from DWARF debug info.
/// </summary>
DwarfDebugInfo,
/// <summary>
/// Resolved from local symbol cache.
/// </summary>
LocalCache,
/// <summary>
/// Resolved from debuginfod server.
/// </summary>
Debuginfod,
/// <summary>
/// Resolved from JIT metadata (Java/V8/etc).
/// </summary>
JitMetadata,
/// <summary>
/// Resolved from runtime-specific mechanism.
/// </summary>
RuntimeSpecific,
/// <summary>
/// Parsed from canonical string format.
/// </summary>
Parsed,
/// <summary>
/// Could not resolve, using address only.
/// </summary>
Unresolved,
}
/// <summary>
/// Symbol entry for cache storage.
/// </summary>
public sealed record SymbolEntry
{
/// <summary>
/// Start address of the symbol.
/// </summary>
public required ulong StartAddress { get; init; }
/// <summary>
/// Size of the symbol in bytes.
/// </summary>
public required ulong Size { get; init; }
/// <summary>
/// Symbol name (demangled).
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Symbol type.
/// </summary>
public SymbolType Type { get; init; } = SymbolType.Function;
}
/// <summary>
/// Type of symbol.
/// </summary>
public enum SymbolType
{
Function,
Object,
Unknown,
}
/// <summary>
/// Metadata for Java frame resolution.
/// </summary>
public sealed record JavaFrameMetadata
{
/// <summary>
/// Class name.
/// </summary>
public string? ClassName { get; init; }
/// <summary>
/// Method name.
/// </summary>
public string? MethodName { get; init; }
/// <summary>
/// Method signature.
/// </summary>
public string? Signature { get; init; }
/// <summary>
/// Bytecode index.
/// </summary>
public int? BytecodeIndex { get; init; }
/// <summary>
/// Whether this is a JIT-compiled frame.
/// </summary>
public bool IsJit { get; init; }
}
/// <summary>
/// Metadata for .NET frame resolution.
/// </summary>
public sealed record DotNetFrameMetadata
{
/// <summary>
/// Type name.
/// </summary>
public string? TypeName { get; init; }
/// <summary>
/// Method name.
/// </summary>
public string? MethodName { get; init; }
/// <summary>
/// Method token.
/// </summary>
public uint? MethodToken { get; init; }
/// <summary>
/// IL offset.
/// </summary>
public int? IlOffset { get; init; }
/// <summary>
/// Assembly name.
/// </summary>
public string? AssemblyName { get; init; }
}
/// <summary>
/// Metadata for Python frame resolution.
/// </summary>
public sealed record PythonFrameMetadata
{
/// <summary>
/// Module name.
/// </summary>
public string? ModuleName { get; init; }
/// <summary>
/// Function name.
/// </summary>
public string? FunctionName { get; init; }
/// <summary>
/// Source file path.
/// </summary>
public string? SourceFile { get; init; }
/// <summary>
/// Line number.
/// </summary>
public int? LineNumber { get; init; }
/// <summary>
/// Python version.
/// </summary>
public string? PythonVersion { get; init; }
}