mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-19 02:00:28 +02:00
708 lines
29 KiB
Python
708 lines
29 KiB
Python
"""Conservative progress corrections for proven objdiff false negatives.
|
|
|
|
Entries in config/semantic_matches.json are never trusted on their own. Each
|
|
entry is re-verified against the current target and rebuilt COFF objects before
|
|
its function is credited. This keeps the ordinary objdiff report authoritative
|
|
except where a stricter semantic relocation comparison proves exact equality.
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List
|
|
|
|
from .coff_compare import (
|
|
CoffError,
|
|
IMAGE_SCN_CNT_CODE,
|
|
image_symbol_addresses,
|
|
load,
|
|
section_info,
|
|
section_info_by_number,
|
|
section_info_resolved,
|
|
section_info_source_relative,
|
|
section_infos_equal,
|
|
)
|
|
|
|
|
|
class SemanticProgressError(RuntimeError):
|
|
pass
|
|
|
|
|
|
def _verify_local_label_continuation(target, base, label_name, owner_name):
|
|
"""Prove that an objdiff ``$L`` pseudo-function is an exact continuation.
|
|
|
|
csplit can expose a compiler-local jump-table label as an external,
|
|
function-typed symbol while the rebuilt COFF emits a differently named
|
|
static label at the same offset. The label is creditable only when the
|
|
complete owning COMDAT is semantically exact, both labels are unique at
|
|
that offset, and an internal relocation in the owner proves the offset is
|
|
an actual encoded destination. Any ambiguity fails closed.
|
|
"""
|
|
if not label_name.startswith("$"):
|
|
raise SemanticProgressError(
|
|
f"local-label match does not name a local label: {label_name}")
|
|
|
|
target_labels = [
|
|
item for item in target["symbols"]
|
|
if item["name"] == label_name and item["section"] > 0
|
|
]
|
|
if len(target_labels) != 1:
|
|
raise SemanticProgressError(
|
|
f"expected one target local label {label_name}, found {len(target_labels)}")
|
|
target_label = target_labels[0]
|
|
if target_label["value"] <= 0:
|
|
raise SemanticProgressError(
|
|
f"target local label is not a continuation: {label_name}")
|
|
|
|
target_owners = [
|
|
item for item in target["symbols"]
|
|
if item["name"] == owner_name and item["section"] > 0
|
|
]
|
|
base_owners = [
|
|
item for item in base["symbols"]
|
|
if item["name"] == owner_name and item["section"] > 0
|
|
]
|
|
if len(target_owners) != 1 or len(base_owners) != 1:
|
|
raise SemanticProgressError(
|
|
f"expected unique local-label owner {owner_name}, found "
|
|
f"{len(target_owners)}/{len(base_owners)}")
|
|
target_owner = target_owners[0]
|
|
base_owner = base_owners[0]
|
|
if target_owner["value"] != 0 or base_owner["value"] != 0:
|
|
raise SemanticProgressError(
|
|
f"local-label owner is not a COMDAT entry: {owner_name}")
|
|
if target_label["section"] != target_owner["section"]:
|
|
raise SemanticProgressError(
|
|
f"target local label is outside owner {owner_name}: {label_name}")
|
|
|
|
target_info = section_info(target, owner_name)
|
|
base_info = section_info(base, owner_name)
|
|
if not section_infos_equal(target_info, base_info):
|
|
raise SemanticProgressError(
|
|
f"local-label owner is no longer exact: {owner_name}")
|
|
|
|
offset = target_label["value"]
|
|
base_labels = [
|
|
item for item in base["symbols"]
|
|
if item["section"] == base_owner["section"]
|
|
and item["value"] == offset
|
|
and item["name"].startswith("$")
|
|
]
|
|
if len(base_labels) != 1:
|
|
raise SemanticProgressError(
|
|
f"expected one base local destination at {offset:#x}, found {len(base_labels)}")
|
|
if not any(
|
|
relocation["target"] == ["internal", offset]
|
|
for relocation in target_info["relocations"]
|
|
):
|
|
raise SemanticProgressError(
|
|
f"target local label has no proven internal relocation: {label_name}")
|
|
|
|
return target_info
|
|
|
|
|
|
def _percent(numerator: int, denominator: int) -> float:
|
|
return 100.0 * numerator / denominator if denominator else 0.0
|
|
|
|
|
|
def _credit(measures: Dict[str, Any], code_bytes: int) -> None:
|
|
measures["matched_code"] = int(measures.get("matched_code", 0)) + code_bytes
|
|
measures["matched_functions"] = int(measures.get("matched_functions", 0)) + 1
|
|
measures["matched_code_percent"] = _percent(
|
|
measures["matched_code"], int(measures.get("total_code", 0))
|
|
)
|
|
measures["matched_functions_percent"] = _percent(
|
|
measures["matched_functions"], int(measures.get("total_functions", 0))
|
|
)
|
|
|
|
|
|
def _debit(measures: Dict[str, Any], code_bytes: int) -> None:
|
|
measures["matched_code"] = int(measures.get("matched_code", 0)) - code_bytes
|
|
measures["matched_functions"] = int(measures.get("matched_functions", 0)) - 1
|
|
if measures["matched_code"] < 0 or measures["matched_functions"] < 0:
|
|
raise SemanticProgressError("semantic rejection would make progress negative")
|
|
measures["matched_code_percent"] = _percent(
|
|
measures["matched_code"], int(measures.get("total_code", 0))
|
|
)
|
|
measures["matched_functions_percent"] = _percent(
|
|
measures["matched_functions"], int(measures.get("total_functions", 0))
|
|
)
|
|
|
|
|
|
def _revoke_completion(
|
|
measures: Dict[str, Any], code_bytes: int, data_bytes: int
|
|
) -> None:
|
|
"""Remove one previously complete unit from aggregate progress measures."""
|
|
measures["complete_code"] = int(measures.get("complete_code", 0)) - code_bytes
|
|
measures["complete_data"] = int(measures.get("complete_data", 0)) - data_bytes
|
|
measures["complete_units"] = int(measures.get("complete_units", 0)) - 1
|
|
if (
|
|
measures["complete_code"] < 0
|
|
or measures["complete_data"] < 0
|
|
or measures["complete_units"] < 0
|
|
):
|
|
raise SemanticProgressError("semantic rejection would make completion negative")
|
|
measures["complete_code_percent"] = _percent(
|
|
measures["complete_code"], int(measures.get("total_code", 0))
|
|
)
|
|
measures["complete_data_percent"] = _percent(
|
|
measures["complete_data"], int(measures.get("total_data", 0))
|
|
)
|
|
|
|
|
|
def _credit_data(measures: Dict[str, Any], data_bytes: int) -> None:
|
|
measures["matched_data"] = int(measures.get("matched_data", 0)) + data_bytes
|
|
total_data = int(measures.get("total_data", 0))
|
|
if measures["matched_data"] > total_data:
|
|
raise SemanticProgressError("semantic data credit exceeds total data")
|
|
measures["matched_data_percent"] = _percent(
|
|
measures["matched_data"], total_data)
|
|
|
|
|
|
def revoke_incomplete_units(report: Dict[str, Any]) -> List[str]:
|
|
"""Revoke config-level completion when measured content is incomplete.
|
|
|
|
``metadata.complete`` originates from the manually maintained Matching
|
|
label. It must not grant linked-object credit when the current report,
|
|
after strict semantic corrections, still contains unmatched functions or
|
|
data. This is deliberately a one-way safety gate: it can revoke a stale
|
|
label, but it never promotes an object to complete.
|
|
"""
|
|
categories = {item["id"]: item for item in report.get("categories", [])}
|
|
revoked = []
|
|
|
|
for report_unit in report.get("units", []):
|
|
if not report_unit.get("metadata", {}).get("complete", False):
|
|
continue
|
|
|
|
unit_measures = report_unit.get("measures", {})
|
|
matched_functions = int(unit_measures.get("matched_functions", 0))
|
|
total_functions = int(unit_measures.get("total_functions", 0))
|
|
matched_data = int(unit_measures.get("matched_data", 0))
|
|
total_data = int(unit_measures.get("total_data", 0))
|
|
missing_functions = total_functions - matched_functions
|
|
missing_data = total_data - matched_data
|
|
if missing_functions < 0 or missing_data < 0:
|
|
raise SemanticProgressError(
|
|
f"unit progress exceeds totals: {report_unit.get('name', '<unknown>')}"
|
|
)
|
|
if missing_functions == 0 and missing_data == 0:
|
|
continue
|
|
|
|
if int(unit_measures.get("complete_units", 0)) != 1:
|
|
raise SemanticProgressError(
|
|
f"complete unit has inconsistent completion measures: "
|
|
f"{report_unit.get('name', '<unknown>')}"
|
|
)
|
|
|
|
complete_code = int(unit_measures.get("complete_code", 0))
|
|
complete_data = int(unit_measures.get("complete_data", 0))
|
|
_revoke_completion(report["measures"], complete_code, complete_data)
|
|
|
|
progress_categories = report_unit.get("metadata", {}).get(
|
|
"progress_categories", []
|
|
)
|
|
if isinstance(progress_categories, str):
|
|
progress_categories = [progress_categories]
|
|
for category_id in progress_categories:
|
|
if category_id not in categories:
|
|
raise SemanticProgressError(
|
|
f"progress category not found: {category_id}"
|
|
)
|
|
_revoke_completion(
|
|
categories[category_id]["measures"], complete_code, complete_data
|
|
)
|
|
|
|
unit_measures["complete_code"] = 0
|
|
unit_measures["complete_data"] = 0
|
|
unit_measures["complete_units"] = 0
|
|
unit_measures["complete_code_percent"] = 0.0
|
|
unit_measures["complete_data_percent"] = 0.0
|
|
report_unit["metadata"]["complete"] = False
|
|
revoked.append(
|
|
f"{report_unit['name']} ({missing_functions} unmatched functions, "
|
|
f"{missing_data} unmatched data bytes)"
|
|
)
|
|
|
|
return revoked
|
|
|
|
|
|
def _section_ownership_snapshot(obj: Dict[str, Any], section_name: str) -> Dict[str, Any]:
|
|
sections = [section for section in obj["sections"] if section["name"] == section_name]
|
|
if len(sections) != 1:
|
|
raise SemanticProgressError(
|
|
f"expected one {section_name} section, found {len(sections)}"
|
|
)
|
|
|
|
section = sections[0]
|
|
info = section_info_by_number(obj, int(section["index"]))
|
|
symbols = sorted(
|
|
(
|
|
{
|
|
"name": symbol["name"],
|
|
"value": int(symbol["value"]),
|
|
"type": int(symbol["type"]),
|
|
"storage": int(symbol["storage"]),
|
|
}
|
|
for symbol in obj["symbols"]
|
|
if int(symbol["section"]) == int(section["index"])
|
|
and symbol["name"] != section_name
|
|
),
|
|
key=lambda symbol: (symbol["value"], symbol["name"]),
|
|
)
|
|
return {
|
|
"size": int(section["size"]),
|
|
"flags": int(section["flags"]),
|
|
"relocation_count": int(info["relocation_count"]),
|
|
"normalized_sha256": info["normalized_sha256"],
|
|
"symbols": symbols,
|
|
}
|
|
|
|
|
|
def require_symbol_ownership_snapshots(
|
|
project_root: Path,
|
|
manifest_path: Path,
|
|
objdiff_config_path: Path,
|
|
) -> List[str]:
|
|
"""Require exact COFF section ownership for admission-sensitive data.
|
|
|
|
Zero-filled BSS can compare byte-exact even when symbols move or change
|
|
linkage. This manifest records the complete named-symbol set for a
|
|
section and validates both the csplit target and rebuilt object. It is a
|
|
pure safety gate: it grants no progress credit and any drift fails closed.
|
|
"""
|
|
if not manifest_path.is_file():
|
|
return []
|
|
|
|
entries = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
objdiff = json.loads(objdiff_config_path.read_text(encoding="utf-8"))
|
|
config_units = {unit["name"]: unit for unit in objdiff.get("units", [])}
|
|
validated = []
|
|
|
|
for entry in entries:
|
|
unit_name = entry["unit"]
|
|
section_name = entry["section"]
|
|
if unit_name not in config_units:
|
|
raise SemanticProgressError(
|
|
f"ownership snapshot unit not found: {unit_name}"
|
|
)
|
|
config_unit = config_units[unit_name]
|
|
try:
|
|
target = load(project_root / config_unit["target_path"])
|
|
base = load(project_root / config_unit["base_path"])
|
|
target_snapshot = _section_ownership_snapshot(target, section_name)
|
|
base_snapshot = _section_ownership_snapshot(base, section_name)
|
|
except (CoffError, KeyError, OSError) as error:
|
|
raise SemanticProgressError(
|
|
f"cannot verify ownership snapshot {unit_name}:{section_name}: {error}"
|
|
) from error
|
|
|
|
expected = entry.get("snapshot", {})
|
|
if target_snapshot != expected:
|
|
raise SemanticProgressError(
|
|
f"target ownership snapshot changed: {unit_name}:{section_name}"
|
|
)
|
|
if base_snapshot != expected:
|
|
raise SemanticProgressError(
|
|
f"rebuilt ownership snapshot changed: {unit_name}:{section_name}"
|
|
)
|
|
|
|
validated.append(
|
|
f"{unit_name}:{section_name} ({len(expected.get('symbols', []))} symbols)"
|
|
)
|
|
|
|
return validated
|
|
|
|
|
|
def apply_semantic_rejections(
|
|
report: Dict[str, Any],
|
|
semantic_report_path: Path,
|
|
) -> List[str]:
|
|
"""Remove objdiff credits rejected by the stricter COFF-shape audit."""
|
|
if not semantic_report_path.is_file():
|
|
return []
|
|
|
|
semantic_report = json.loads(semantic_report_path.read_text(encoding="utf-8"))
|
|
report_units = {unit["name"]: unit for unit in report.get("units", [])}
|
|
categories = {item["id"]: item for item in report.get("categories", [])}
|
|
rejected = []
|
|
revoked_units = set()
|
|
|
|
for entry in semantic_report.get("ordinary_rejected", []):
|
|
unit_name = entry["unit"]
|
|
function_name = entry["function"]
|
|
report_unit = report_units.get(unit_name)
|
|
if report_unit is None:
|
|
raise SemanticProgressError(f"semantic rejection unit not found: {unit_name}")
|
|
functions = [
|
|
function for function in report_unit.get("functions", [])
|
|
if function.get("name") == function_name
|
|
]
|
|
if len(functions) != 1 or functions[0].get("fuzzy_match_percent") != 100.0:
|
|
raise SemanticProgressError(
|
|
f"semantic rejection is not an objdiff exact function: "
|
|
f"{unit_name}:{function_name}"
|
|
)
|
|
|
|
code_bytes = int(functions[0]["size"])
|
|
_debit(report["measures"], code_bytes)
|
|
_debit(report_unit["measures"], code_bytes)
|
|
|
|
progress_categories = report_unit.get("metadata", {}).get(
|
|
"progress_categories", []
|
|
)
|
|
if isinstance(progress_categories, str):
|
|
progress_categories = [progress_categories]
|
|
for category_id in progress_categories:
|
|
if category_id not in categories:
|
|
raise SemanticProgressError(f"progress category not found: {category_id}")
|
|
_debit(categories[category_id]["measures"], code_bytes)
|
|
|
|
if unit_name not in revoked_units and report_unit.get("metadata", {}).get(
|
|
"complete", False
|
|
):
|
|
unit_measures = report_unit["measures"]
|
|
complete_code = int(unit_measures.get("complete_code", 0))
|
|
complete_data = int(unit_measures.get("complete_data", 0))
|
|
_revoke_completion(report["measures"], complete_code, complete_data)
|
|
for category_id in progress_categories:
|
|
_revoke_completion(
|
|
categories[category_id]["measures"], complete_code, complete_data
|
|
)
|
|
unit_measures["complete_code"] = 0
|
|
unit_measures["complete_data"] = 0
|
|
unit_measures["complete_units"] = 0
|
|
unit_measures["complete_code_percent"] = 0.0
|
|
unit_measures["complete_data_percent"] = 0.0
|
|
report_unit["metadata"]["complete"] = False
|
|
revoked_units.add(unit_name)
|
|
|
|
rejected.append(
|
|
f"{unit_name}:{function_name} (-{code_bytes} code bytes, -1 function)"
|
|
)
|
|
|
|
return rejected
|
|
|
|
|
|
def apply_semantic_matches(
|
|
report: Dict[str, Any],
|
|
project_root: Path,
|
|
manifest_path: Path,
|
|
objdiff_config_path: Path,
|
|
) -> List[str]:
|
|
"""Verify and credit manifest entries that objdiff did not count exactly.
|
|
|
|
Returns human-readable notes for credited entries. Missing, ambiguous, or
|
|
non-equal evidence raises instead of silently inflating progress.
|
|
"""
|
|
if not manifest_path.is_file():
|
|
return []
|
|
|
|
entries = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
objdiff = json.loads(objdiff_config_path.read_text(encoding="utf-8"))
|
|
report_units = {unit["name"]: unit for unit in report.get("units", [])}
|
|
config_units = {unit["name"]: unit for unit in objdiff.get("units", [])}
|
|
categories = {item["id"]: item for item in report.get("categories", [])}
|
|
credited = []
|
|
|
|
for entry in entries:
|
|
unit_name = entry["unit"]
|
|
function_name = entry["function"]
|
|
if unit_name not in report_units or unit_name not in config_units:
|
|
raise SemanticProgressError(f"semantic match unit not found: {unit_name}")
|
|
|
|
report_unit = report_units[unit_name]
|
|
functions = [
|
|
function for function in report_unit.get("functions", [])
|
|
if function.get("name") == function_name
|
|
]
|
|
if len(functions) != 1:
|
|
raise SemanticProgressError(
|
|
f"expected one report function {unit_name}:{function_name}, found {len(functions)}"
|
|
)
|
|
function = functions[0]
|
|
if function.get("fuzzy_match_percent") == 100.0:
|
|
continue
|
|
|
|
config_unit = config_units[unit_name]
|
|
try:
|
|
target = load(project_root / config_unit["target_path"])
|
|
base = load(project_root / config_unit["base_path"])
|
|
owner_function = entry.get("owner_function")
|
|
if owner_function:
|
|
target_info = _verify_local_label_continuation(
|
|
target, base, function_name, owner_function)
|
|
base_info = target_info
|
|
else:
|
|
target_info = section_info(target, function_name)
|
|
# csplit may expose a source/SDK function under an anonymous
|
|
# image name while VC7 emits its authentic public name. An
|
|
# explicit per-unit alias is safe only because the unchanged
|
|
# strict comparator below still proves the complete function
|
|
# shape, including relocation destinations and addends.
|
|
base_info = section_info(
|
|
base, entry.get("base_function", function_name))
|
|
except (CoffError, KeyError, OSError) as error:
|
|
raise SemanticProgressError(
|
|
f"cannot verify semantic match {unit_name}:{function_name}: {error}"
|
|
) from error
|
|
if not section_infos_equal(target_info, base_info):
|
|
raise SemanticProgressError(
|
|
f"semantic match is no longer exact: {unit_name}:{function_name}"
|
|
)
|
|
|
|
code_bytes = int(function["size"])
|
|
_credit(report["measures"], code_bytes)
|
|
_credit(report_unit["measures"], code_bytes)
|
|
|
|
progress_categories = report_unit.get("metadata", {}).get("progress_categories", [])
|
|
if isinstance(progress_categories, str):
|
|
progress_categories = [progress_categories]
|
|
for category_id in progress_categories:
|
|
if category_id not in categories:
|
|
raise SemanticProgressError(f"progress category not found: {category_id}")
|
|
_credit(categories[category_id]["measures"], code_bytes)
|
|
|
|
credited.append(
|
|
f"{unit_name}:{function_name} (+{code_bytes} code bytes, +1 function)"
|
|
)
|
|
|
|
return credited
|
|
|
|
|
|
def _unique_defined_symbol(obj, name, description):
|
|
matches = [
|
|
item for item in obj["symbols"]
|
|
if item["name"] == name and item["section"] > 0
|
|
]
|
|
if len(matches) != 1:
|
|
raise CoffError(
|
|
f"expected one {description} {name!r}, found {len(matches)}")
|
|
return matches[0]
|
|
|
|
|
|
def _semantic_data_member_snapshot(owner, section, info):
|
|
if int(section["flags"]) & IMAGE_SCN_CNT_CODE:
|
|
raise CoffError(
|
|
f"semantic data owner {owner['name']!r} names code")
|
|
alignment_code = (int(section["flags"]) >> 20) & 0xF
|
|
if alignment_code == 0:
|
|
alignment = 1
|
|
elif 1 <= alignment_code <= 14:
|
|
alignment = 1 << (alignment_code - 1)
|
|
else:
|
|
raise CoffError(
|
|
f"invalid COFF section alignment code {alignment_code}")
|
|
padded_size = (int(section["size"]) + alignment - 1) & ~(alignment - 1)
|
|
return {
|
|
"section": section["name"],
|
|
"size": info["size"],
|
|
"padded_size": padded_size,
|
|
"flags": int(section["flags"]),
|
|
"relocation_count": info["relocation_count"],
|
|
"normalized_sha256": info["normalized_sha256"],
|
|
"owner": {
|
|
"value": int(owner["value"]),
|
|
"type": int(owner["type"]),
|
|
"storage": int(owner["storage"]),
|
|
},
|
|
}
|
|
|
|
|
|
def apply_semantic_data_matches(
|
|
report: Dict[str, Any],
|
|
project_root: Path,
|
|
manifest_path: Path,
|
|
objdiff_config_path: Path,
|
|
symbol_manifest_path: Path,
|
|
) -> List[str]:
|
|
"""Verify and credit executable-split data relocation aliases.
|
|
|
|
A manifest entry is accepted only when the target and rebuilt data
|
|
sections have identical normalized bytes, relocation locations/types,
|
|
and independently resolved destinations. A grouped entry additionally
|
|
snapshots every member's flags, alignment-derived padded extent, and
|
|
producer-specific owner. Its members must account for the unit's entire
|
|
remaining unmatched data. An incomplete unit requires an explicit
|
|
manifest opt-in so partial spans are never credited accidentally.
|
|
"""
|
|
if not manifest_path.is_file():
|
|
return []
|
|
|
|
entries = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
objdiff = json.loads(objdiff_config_path.read_text(encoding="utf-8"))
|
|
symbol_entries = json.loads(symbol_manifest_path.read_text(encoding="utf-8"))
|
|
symbol_addresses = image_symbol_addresses(symbol_entries)
|
|
|
|
report_units = {unit["name"]: unit for unit in report.get("units", [])}
|
|
config_units = {unit["name"]: unit for unit in objdiff.get("units", [])}
|
|
categories = {item["id"]: item for item in report.get("categories", [])}
|
|
credited = []
|
|
|
|
for entry in entries:
|
|
unit_name = entry["unit"]
|
|
if unit_name not in report_units or unit_name not in config_units:
|
|
raise SemanticProgressError(
|
|
f"semantic data match unit not found: {unit_name}")
|
|
report_unit = report_units[unit_name]
|
|
config_unit = config_units[unit_name]
|
|
if (
|
|
not config_unit.get("metadata", {}).get("complete")
|
|
and not entry.get("allow_incomplete_unit", False)
|
|
):
|
|
raise SemanticProgressError(
|
|
f"semantic data unit is not marked complete: {unit_name}")
|
|
|
|
members = entry.get("members")
|
|
if members:
|
|
section_label = entry.get("group", "data-section-group")
|
|
target = load(project_root / config_unit["target_path"])
|
|
base = load(project_root / config_unit["base_path"])
|
|
credited_size = 0
|
|
target_section_numbers = set()
|
|
base_section_numbers = set()
|
|
grouped_sections = {}
|
|
|
|
for member in members:
|
|
target_symbol = member["symbol"]
|
|
base_symbol = member.get("base_symbol", target_symbol)
|
|
source_function = member.get("source_function")
|
|
base_source_function = member.get(
|
|
"base_source_function", source_function)
|
|
|
|
try:
|
|
if source_function:
|
|
target_info = section_info_source_relative(
|
|
target, target_symbol, source_function)
|
|
base_info = section_info_source_relative(
|
|
base, base_symbol, base_source_function)
|
|
else:
|
|
target_info = section_info(target, target_symbol)
|
|
base_info = section_info(base, base_symbol)
|
|
|
|
target_owner = _unique_defined_symbol(
|
|
target, target_symbol, "semantic data target owner")
|
|
base_owner = _unique_defined_symbol(
|
|
base, base_symbol, "semantic data base owner")
|
|
target_section = target["sections"][
|
|
target_owner["section"] - 1]
|
|
base_section = base["sections"][base_owner["section"] - 1]
|
|
except (CoffError, KeyError, OSError) as error:
|
|
raise SemanticProgressError(
|
|
f"cannot verify semantic data group member "
|
|
f"{unit_name}:{target_symbol}: {error}") from error
|
|
|
|
if target_owner["section"] in target_section_numbers \
|
|
or base_owner["section"] in base_section_numbers:
|
|
raise SemanticProgressError(
|
|
f"semantic data group repeats a section: "
|
|
f"{unit_name}:{target_symbol}")
|
|
target_section_numbers.add(target_owner["section"])
|
|
base_section_numbers.add(base_owner["section"])
|
|
|
|
if not section_infos_equal(target_info, base_info):
|
|
raise SemanticProgressError(
|
|
f"semantic data group member is no longer exact: "
|
|
f"{unit_name}:{target_symbol}")
|
|
|
|
target_snapshot = _semantic_data_member_snapshot(
|
|
target_owner, target_section, target_info)
|
|
base_snapshot = _semantic_data_member_snapshot(
|
|
base_owner, base_section, base_info)
|
|
for key in ("section", "size", "padded_size", "flags"):
|
|
if target_snapshot[key] != base_snapshot[key]:
|
|
raise SemanticProgressError(
|
|
f"semantic data group member layout differs: "
|
|
f"{unit_name}:{target_symbol} ({key})")
|
|
|
|
expected = member.get("measurements", {})
|
|
snapshot = {
|
|
"target": target_snapshot,
|
|
"base": base_snapshot,
|
|
}
|
|
if expected != snapshot:
|
|
raise SemanticProgressError(
|
|
f"semantic data group member snapshot changed: "
|
|
f"{unit_name}:{target_symbol}")
|
|
credited_size += target_snapshot["padded_size"]
|
|
grouped_sections[target_snapshot["section"]] = (
|
|
grouped_sections.get(target_snapshot["section"], 0)
|
|
+ target_snapshot["padded_size"])
|
|
|
|
unmatched_sections = {}
|
|
for report_section in report_unit.get("sections", []):
|
|
if report_section.get("name") == ".text" \
|
|
or float(report_section.get(
|
|
"fuzzy_match_percent", 0.0)) == 100.0:
|
|
continue
|
|
name = report_section["name"]
|
|
unmatched_sections[name] = (
|
|
unmatched_sections.get(name, 0)
|
|
+ int(report_section.get("size", 0)))
|
|
if grouped_sections != unmatched_sections:
|
|
raise SemanticProgressError(
|
|
f"semantic data group does not cover the reported "
|
|
f"unmatched sections: {unit_name}:{section_label}")
|
|
else:
|
|
section_symbol = entry["symbol"]
|
|
section_label = section_symbol
|
|
try:
|
|
target_info = section_info_resolved(
|
|
load(project_root / config_unit["target_path"]),
|
|
section_symbol,
|
|
symbol_addresses,
|
|
)
|
|
base_info = section_info_resolved(
|
|
load(project_root / config_unit["base_path"]),
|
|
section_symbol,
|
|
symbol_addresses,
|
|
)
|
|
except (CoffError, KeyError, OSError) as error:
|
|
raise SemanticProgressError(
|
|
f"cannot verify semantic data match "
|
|
f"{unit_name}:{section_symbol}: {error}") from error
|
|
if target_info != base_info:
|
|
raise SemanticProgressError(
|
|
f"semantic data match is no longer exact: "
|
|
f"{unit_name}:{section_symbol}")
|
|
|
|
expected = entry.get("measurements", {})
|
|
snapshot = {
|
|
key: target_info[key]
|
|
for key in ("size", "relocation_count", "normalized_sha256")
|
|
}
|
|
if expected != snapshot:
|
|
raise SemanticProgressError(
|
|
f"semantic data target snapshot changed: "
|
|
f"{unit_name}:{section_symbol}")
|
|
credited_size = target_info["size"]
|
|
|
|
unit_measures = report_unit["measures"]
|
|
unmatched_data = (
|
|
int(unit_measures.get("total_data", 0))
|
|
- int(unit_measures.get("matched_data", 0))
|
|
)
|
|
if unmatched_data == 0:
|
|
continue
|
|
if unmatched_data != credited_size:
|
|
raise SemanticProgressError(
|
|
f"semantic data section does not cover all unmatched data: "
|
|
f"{unit_name}:{section_label} covers {credited_size}, "
|
|
f"remaining {unmatched_data}")
|
|
|
|
_credit_data(report["measures"], unmatched_data)
|
|
_credit_data(unit_measures, unmatched_data)
|
|
progress_categories = report_unit.get("metadata", {}).get(
|
|
"progress_categories", [])
|
|
if isinstance(progress_categories, str):
|
|
progress_categories = [progress_categories]
|
|
for category_id in progress_categories:
|
|
if category_id not in categories:
|
|
raise SemanticProgressError(
|
|
f"semantic data category not found: {category_id}")
|
|
_credit_data(categories[category_id]["measures"], unmatched_data)
|
|
|
|
credited.append(
|
|
f"{unit_name}:{section_label} (+{unmatched_data} data bytes)")
|
|
|
|
return credited
|