From Diagnosis to the Smallest Faithful Intervention

Once FIRMENSTEIN has identified the dependency and the state constraint behind a roadblock, it still has to decide what to change. This chapter focuses on that decision: when should the environment be prepared, when should behavior be synthesized inside the emulator, and how broad should an intervention be? The answer supports a practical judgment about “minimal” repair: choose the smallest unit that preserves the diagnosed semantics, not merely the fewest instructions that suppress the visible error.

That distinction matters because one firmware service can encounter several roadblocks at once. They may touch the same device resource, depend on a shared global, or exchange values through a chain of wrappers. Generating a separate patch for each symptom risks creating mutually inconsistent substitutes. FIRMENSTEIN therefore treats synthesis as a problem of preserving semantic coupling before it treats it as a problem of generating code.

Group the roadblocks before choosing the remedy

A roadblock report is a useful local explanation, but it is not necessarily a safe unit of intervention. Two reports may refer to different APIs that access the same resource. Several NVRAM-related operations may rely on a shared file descriptor or global state. A value produced by one roadblock may become an input to another. If those cases are synthesized independently, each intervention can appear reasonable on its own while the combined service sees contradictory state.

FIRMENSTEIN aggregates reports through three kinds of relationship. Shared-resource access links roadblocks that operate on the same device path or other environment resource. Global-state dependency links functions that read or modify the same persistent or process-wide state. Variable overlap identifies reports whose critical variables intersect. The ADG supplies shared-state relationships, while the reports supply the variables and constraints that make the relationship meaningful. Aggregation is therefore not just a way to batch model calls. It is a consistency step: the synthesis process sees the semantic context that must remain coherent across related repairs.

The next choice is where the missing behavior belongs. An Environment Intervention prepares the surroundings in which the service runs. It may create the required filesystem state, configure resources, or launch a dependent daemon. This is the natural direction when the dependency is an external process or configuration relationship whose real behavior is available and important. Letting the service interact with a prepared dependency can preserve more of the original relationship than replacing every exchange with a local answer.

An Execution Intervention, by contrast, is hook code that changes how an API behaves while the service is being emulated. It is useful when the relevant behavior can be modeled at a particular function or operation and when preparing the original environment would be impractical or unnecessarily expensive. The hook can manipulate the registers, memory, arguments, or return value needed to satisfy the diagnosed constraint. It must still preserve the relationship the native code expects; “inside the emulator” does not mean “free from semantics.”

The categories are complementary rather than ranked. A complex daemon-mediated dependency may be best addressed by preparing and launching the daemon, while a simple interaction can be abstracted at the API boundary. A missing device operation may need a pseudo-file model, whereas a self-contained helper that translates configuration into a value may be replaced as a complete function. The right question is not whether environment setup or code generation is philosophically purer. It is which boundary owns the state and which form can reproduce the behavior the service uses.