A formal verification method for LLVMIR assembly level language
Patent Information
- Application Number
- CN202611540636.4
- Authority / Receiving Office
- CN · China
- Patent Type
- Applications(China)
- Current Assignee / Owner
- Filing Date
- 2026-09-30
- Publication Date
- 2026-11-03
AI Technical Summary
Existing technologies lack a unified and reusable formal semantic modeling mechanism for LLVMIR programs. The complex relationship between property reduction and program execution state leads to the risk of incomplete models and erroneous verification during the verification process.
By performing structured parsing on the LLVMIR program, a program model is established that includes the usage definition relationship of value objects and the memory state divided by objects. Symbolic execution is performed using a pre-established instruction semantic mapping to generate verification conditions, and the SMT solver is used for automatic solution.
It achieves unified formal processing of LLVMIR programs, reduces the workload of repeatedly building formal semantics, improves the automation of the verification process and the traceability of results, and reduces the risk of false verification passing.
Smart Images

Figure CN123030482A_ABST
Abstract
Description
TECHNICAL FIELD
[0001] The present application relates to the technical field of program formal verification, and particularly relates to a formal verification method for LLVMIR assembly level language. BACKGROUND
[0002] LLVM (Compiler Infrastructure) is one of the widely used compiler infrastructures at present, which adopts a multi-layer intermediate representation architecture, a compiler front end converts a high-level language source program into LLVMIR (Low Level Virtual Machine Intermediate Representation), a middle end performs optimization and transformation which is relatively independent of a target platform at the LLVMIR layer, and a back end converts the processed LLVMIR into target machine code; the LLVMIR is at the intermediate layer of the compilation process, adopts a static single assignment form, and retains type information, functions, basic blocks, instruction sequences and control flow structures, so as to be able to more completely reflect data dependency relationships and execution behaviors of a program at a low level representation layer; existing formal verification technologies have formed various technical routes around the LLVMIR, for example, Coq (an interactive theorem proving tool) or Isabelle / HOL (an interactive theorem proving system based on high-order logic) is used to establish program semantics and perform interactive theorem proving, a KLEE (a symbolic execution tool) or the like is used to perform symbolic execution at the LLVMIR layer, and an SMT (Satisfiability Modulo Theories) solver is used to automatically solve program paths and safety properties, in addition, there is a technical solution of converting the LLVMIR into a Boogie (an intermediate verification language) or the like and then performing verification.
[0003] Although the existing technologies can realize program analysis and formal verification for specific programs or specific verification targets, there are still deficiencies when establishing a general and reusable automatic verification mechanism for LLVMIR programs; on the one hand, the LLVMIR semantic coding in the existing solutions is usually closely combined with specific verification objects or verification frameworks, there is a lack of a general processing mechanism of uniformly converting types, global symbols, functions, basic blocks, instructions and memory states in the LLVMIR into a structured program model which can be directly used for subsequent verification, and verification personnel usually still need to reprocess parsing, semantic modeling and memory modeling when facing new LLVMIR programs; on the other hand, an abstract mapping usually needs to be additionally established between the property specification and the program model in the existing solutions, and the instruction semantics of arithmetic, comparison, memory reading, memory writing and control flow in the LLVMIR lack a unified formal representation which can be directly reused, so that the specification writing, program state association and verification condition generation processes are relatively complex, and when encountering unsupported instructions, unknown states or bounded path exploration, it is also necessary to avoid forming a false verification passing conclusion due to an incomplete model. SUMMARY
[0004] The application aims to solve the problems in the prior art that the LLVMIR program lacks a unified and reusable formal semantic modeling mechanism, the property specification is associated with the program execution state in a complex manner, and the incomplete model may cause the risk of false verification.
[0005] To solve the problems in the prior art, the application adopts the following technical scheme: A formal verification method for an LLVMIR assembly-level language, comprising: Obtaining a low-level virtual machine intermediate representation program to be verified and parsing the program to obtain structured program information including type information, functions, basic blocks and instruction sequences; Building a program model including usage definition relationships between value objects and memory states divided according to objects according to the structured program information; Identifying property specifications from the low-level virtual machine intermediate representation program, performing symbolic execution on the program model according to the control flow, calling pre-established instruction semantic mappings when executing instructions to generate symbolic values and safety conditions, and forming verification conditions according to path conditions and the property specifications or the safety conditions; Solving the verification conditions and outputting a verification result of pass, fail or unknown according to the solving result.
[0006] Preferably, the obtaining of the low-level virtual machine intermediate representation program to be verified and the parsing of the program comprise: Obtaining a bit code file of the low-level virtual machine intermediate representation program; Using a tool chain having the same main version as a compiler front end that generates the bit code file to disassemble the bit code file into a text representation file; Calling a low-level virtual machine intermediate representation parsing interface to parse the text representation file into a module object; Extracting type information, global symbols, functions, basic blocks and instruction sequences from the module object to form the structured program information; When the bit code file fails to be disassembled, the text representation file fails to be parsed or the parsed instructions do not have corresponding instruction semantic mappings established, terminating the verification process.
[0007] Preferably, the building of the program model according to the structured program information comprises: Establishing type objects according to the type information; Establishing value objects according to the global symbols, function parameters, constants and instructions; Causing the value objects corresponding to each instruction to save references to the value objects corresponding to their operands to form the usage definition relationships; During symbolic execution, the symbolic value corresponding to the operand of the current instruction is obtained according to the defined relationship.
[0008] Preferably, the memory state divided by object includes: Memory objects are created for allocated objects in stack memory, allocated objects in heap memory, and global variables. Each memory object includes an object identifier, an object size, and a byte region. For pointers that can locate the corresponding memory object using the object identifier, establish an object identifier and an offset within the object; The accessed memory object is determined based on the object identifier of the pointer, and the access position in the memory object is determined based on the offset within the object. Reading or writing is then performed in the corresponding byte region.
[0009] Preferably, performing a read or write operation in the corresponding byte region includes: The access length is determined based on the type of data accessed. The access range in the memory object is determined based on the offset within the object and the access length; Determine whether there is a symbolic state where the access range exceeds the object size limit of the memory object based on the current path conditions; When the symbol state exists, a safety condition representing a memory out-of-bounds error is generated, and the safety condition is used to form the verification condition.
[0010] Preferably, the pre-established instruction semantic mapping corresponds at least to integer arithmetic and bitwise operation instructions, integer comparison instructions, memory read instructions, memory write instructions, stack memory allocation instructions, control flow branch instructions, function return instructions, phi (instruction to select input value by predecessor basic block) instructions, and function call instructions with function bodies or registered semantic stubs; during symbolic execution, the corresponding instruction semantic mapping is called according to the current instruction to obtain the symbolic value, memory state change, or security condition generated by the current instruction, and the obtained symbolic value or memory state change is used for subsequent instruction execution, and the obtained security condition is used to form the verification condition.
[0011] Preferably, the step of identifying property reductions from the low-level virtual machine intermediate representation program and performing symbolic execution according to the control flow includes: Identify path hypothesis primitives, assertion primitives to be proved, proposition combination primitives, and nondeterministic input primitives that are compiled into the intermediate representation program of the low-level virtual machine along with the source program; Add the conditions corresponding to the path hypothesis primitives to the current path conditions; Different path conditions are formed based on the conditional branches, and execution continues according to the control flow between basic blocks; Identify loops and perform bounded expansion according to a preset expansion depth; When the verifiable assertion primitive is executed within the preset expansion depth, the verification condition is formed based on the current path condition and the verifiable condition corresponding to the verifiable assertion primitive.
[0012] Preferably, solving for the verification conditions and outputting the verification results includes: Solve for the violation states of each verification condition separately; When the corresponding violation state cannot be satisfied, the verification condition is recorded as verified. When the corresponding violation state can be satisfied, the verification condition is recorded as a verification failure, and a counterexample model is output. When it cannot be determined whether the corresponding violation state can be satisfied, the verification condition is recorded as unknown. If a verification condition fails, output "Overall verification failed"; if no verification failed but an unknown verification condition exists, output "Overall unknown". For a low-level virtual machine intermediate representation program containing loops, only when all verification conditions formed within a preset expansion depth are verified as passed, will the overall verification be output corresponding to the preset expansion depth, and the preset expansion depth be recorded. When the low-level virtual machine intermediate representation program fails to parse or there is an instruction whose corresponding instruction semantic mapping is not established, the verification process is terminated and no verification pass is output.
[0013] Compared with the prior art, the beneficial effects of the present invention are: 1. This invention performs structured parsing of the LLVMIR program to be verified and establishes a program model based on the parsing results, including the definition relationship of value objects and the division of memory states by object. This enables types, functions, basic blocks, instructions, and memory accesses in LLVMIR to participate in subsequent symbolic execution according to a unified data structure. On this basis, it uses a pre-established instruction semantic mapping to formally process supported instructions such as integer arithmetic, comparison, memory read, memory write, control flow, and function calls. This allows different LLVMIR programs to reuse existing semantic models, reducing the workload of repeatedly establishing formal semantics for a single program. At the same time, it uses object identifiers, offsets within objects, object sizes, and byte regions to establish memory access relationships, which is beneficial for realizing memory read / write state transmission and out-of-bounds checks within objects.
[0014] 2. This invention further incorporates path hypothesis primitives, assertion primitives to be proven, propositional combination primitives, and nondeterministic input primitives into the LLVMIR program during source code compilation. This allows property reduction to be directly associated with the current path conditions and program state during symbolic execution. Verification conditions are then formed based on the safety conditions generated by the property reduction or instruction execution, and automatically solved using the SMT solver. By distinguishing between verification success, verification failure, and unknown results, and outputting a counterexample model when verification fails, while prohibiting the output of verification success when parsing fails, instructions are not supported, or the solution is unknown, this invention helps reduce the risk of erroneous verification conclusions due to incomplete program models or uncertain solution states. Simultaneously, it improves the automation, reusability, and traceability of the formal verification process of the LLVMIR program. Attached Figure Description
[0015] The accompanying drawings, which are included to provide a further understanding of the invention and form part of this application, illustrate exemplary embodiments of the invention and, together with their description, serve to explain the invention and do not constitute an undue limitation thereof. In the drawings: Figure 1 This is a flowchart illustrating a formal verification method for LLVMIR assembly-level languages according to one or more embodiments. Detailed Implementation
[0016] The technical solutions of the present invention will be clearly and completely described below with reference to the accompanying drawings of the embodiments of the present invention. Obviously, the described embodiments are only some embodiments of the present invention, and not all embodiments.
[0017] The low-level virtual machine intermediate representation program referred to in this embodiment is the LLVMIR program. The LLVMIR program adopts a static single assignment form and includes program structures such as types, functions, basic blocks, and instruction sequences. This embodiment performs formal verification on a subset of LLVMIR support for which a program model and instruction semantic mapping have been established.
[0018] It should be noted that this implementation adopts a verification principle prioritizing integrity. When the input LLVMIR program exhibits issues such as bitcode disassembly failure, LLVMIR parsing failure, incomplete program model construction, lack of corresponding instruction semantic mapping for the current instruction, or unreliable execution semantics for function calls, the current verification process is terminated. Verification success is not achieved by ignoring the corresponding program structure, arbitrarily assigning values to unknown instructions, or skipping unknown function calls. Similarly, if the SMT solver returns UNKNOWN (unknown), the verification should not be considered successful.
[0019] For LLVMIR programs containing loops, this implementation uses a preset unrolling depth for bounded symbolic execution. Once the preset unrolling depth is reached, subsequent loop iterations beyond that depth are no longer explored. Therefore, a successful verification for a program containing loops indicates that all verification conditions actually formed within the corresponding preset unrolling depth do not contain any violating states that can be satisfied, but does not indicate that unbounded correctness has been proven for any loop execution exceeding the preset unrolling depth.
[0020] Example 1: Overall Implementation Process of Formal Verification of LLVMIR Program Combination Figure 1 As shown in the figure, this embodiment illustrates the overall processing procedure from the input of the LLVMIR program to be verified to the output of the verification result.
[0021] Step S101: Obtain the LLVMIR program to be verified and normalize it.
[0022] Obtain the LLVMIR bitcode file corresponding to the program to be verified. In this embodiment, the LLVMIR bitcode file in the format of ".bc" (bitcode file extension) is used as the standard input. The bitcode file is generated by the compiler front-end that can generate LLVMIR.
[0023] Considering the potential differences between different LLVM major versions in terms of opaque pointers, type text formats, and instruction attributes, the compiler frontend that generates the bitcode file to be verified and the LLVM toolchain that performs the verification use the same major version. In one practical implementation, the LLVM toolchain version used is LLVM 17.0.4.
[0024] When a bitcode file contains multiple functions, and the current verification task only targets one of the specified functions, the bitcode file can be extracted based on the target function name, so that subsequent parsing and symbolic execution revolve around the extracted LLVMIR program.
[0025] For the obtained LLVMIR bitcode file, call llvm-dis (bitcode disassembler), which has the same main version as the compiler frontend that generated the bitcode file, to disassemble the ".bc" bitcode file into a normalized LLVMIR text representation file, i.e., a ".ll" file (the middle part indicates the text file extension).
[0026] When the input to be processed is initially in the form of ".ll" text, in order to reduce the differences in printing format and version representation of LLVMIR text from different sources, the ".ll" file can be assembled into a ".bc" bit code file using llvm-as (intermediate representation assembler) of the same major version, and then a normalized ".ll" text representation file can be generated by llvm-dis.
[0027] When llvm-dis cannot complete bit code disassembly, the current input cannot enter the subsequent structured parsing process. At this time, the verification process is terminated and the program model construction stage is not entered.
[0028] Step S102: Parse the LLVMIR program and form structured program information.
[0029] The standardized LLVMIR text representation file formed in step S101 is processed by calling the official LLVM parsing interface. In this embodiment, IRReader (intermediate representation reading component) in LLVM C++ (a programming language) API (application programming interface) is used, and parseIRFile (intermediate representation file parsing function) is called to parse the ".ll" text representation file to obtain the corresponding Module object.
[0030] This implementation does not use a self-built LLVMIR text syntax parser as the primary parsing method, but rather identifies types, global symbols, functions, basic blocks, and instructions in program modules through the official LLVM parsing interface.
[0031] If parseIRFile fails to parse, the current verification process is terminated. Upon successfully obtaining a Module object, the program structure within the Module object is traversed to form structured program information used by the subsequent Python (a programming language) program model. This structured program information includes at least type information, global symbols, functions, basic blocks, and instruction sequences.
[0032] Scan the type declarations in the LLVMIR program, as well as the type information carried by functions, variables, and instructions, and establish type registration information accordingly.
[0033] For integer types, extract the corresponding integer bit width. For example, i1 (one-bit integer type) corresponds to an integer type with a bit width of 1, i8 (eight-bit integer type) corresponds to an integer type with a bit width of 8, i16 (sixteen-bit integer type) corresponds to an integer type with a bit width of 16, i32 (thirty-two-bit integer type) corresponds to an integer type with a bit width of 32, and i64 (sixty-four-bit integer type) corresponds to an integer type with a bit width of 64.
[0034] For the opaque pointer used in modern LLVM, it is uniformly recognized as a pointer type, and it is not required to obtain the type of the element it points to from the pointer type itself.
[0035] For array types, extract the element type and number of elements. For struct types, extract the field type sequence. For function types, extract the return type and parameter type sequence. For void (no value type) and label (basic block label type) types, establish corresponding type categories respectively.
[0036] The core technical solution of this application does not take the complete ABI (Application Binary Interface) data layout algorithm under any target platform conditions as a necessary technical feature. When the reliable formal execution of a certain LLVMIR instruction requires data layout information that cannot be determined by the current program model, it does not continue to execute by presuming the unknown layout, but treats the instruction as an instruction whose semantic mapping has not been established by the current verifier.
[0037] Scan the global variables and global constants in the Module object, and obtain at least the symbol name, data type, initial value, and available link attributes for each global symbol.
[0038] When a global variable has an initial value, that value is used for the initialization of subsequent global memory objects. For string constants that can be reliably parsed, their byte content is saved as the initialization data for the corresponding global constant.
[0039] For a function with a function definition, obtain the function name, return type, formal parameters, and basic blocks within the function body. For each basic block, obtain the basic block label and the instruction sequence within the basic block, while maintaining the instruction order determined in the LLVMIR program.
[0040] For the instructions that the current verifier can support, corresponding structured instruction information is generated. The structured instruction information includes at least the instruction category, instruction result type, instruction operands, and the basic block to which it belongs.
[0041] The LLVM official parsing interface's ability to successfully recognize the syntax of a given instruction does not necessarily mean that the current verifier has established the formal execution semantics of that instruction. When the parsed instruction lacks a reliable semantic mapping, the current verification process terminates, without skipping the instruction or arbitrarily creating an unconstrained result value for it. Similarly, if unknown constants, unrecognizable values, or other situations arise that prevent the complete formation of structured program information, subsequent verification also stops.
[0042] For metadata in Module objects, information related to verification location is retained according to the verifier's emission mechanism. Debugging built-in calls in the form of `dbg.declare` (declaring a debug built-in function for variable storage location) and `dbg.value` (recording a debug built-in function for variable value) are not executed as ordinary instructions that change the program's execution state. When the debug location of an instruction is available, the corresponding debug location information is stored in the debug location data. When the subsequent verification result is FAIL, the corresponding LLMVR location and the associated source program location can be obtained based on this debug location data.
[0043] Step S103: Establish a Python program model based on the structured program information.
[0044] The structured program information obtained in step S102 is emitted as a Python program model that can be directly referenced by the verification execution program. The Python program model includes at least type objects, value objects, usage definition relationships between value objects, function objects, basic block objects, and memory states divided by objects.
[0045] Set LLVMType (the middle one represents the base class of the type) as the base type of the type object, and create the corresponding type object based on the type information in the structured program information.
[0046] Integer types are represented by IntegerType objects. Pointer types are represented by PointerType objects. Array types are represented by ArrayType objects. Structure types are represented by StructType objects. Function types are represented by FunctionType objects. Corresponding type objects are created for void and label types. The bit width stored in the integer type object is used to establish the sign value of the subsequent SMT bit vector. Type information can also be used to determine the data length of currently reliably modeled memory read or write operations, and to determine the type of function parameters or Instruction results.
[0047] The precise offsets of complex structure fields and the corresponding complex pointer calculations, which can only be reliably determined by data layout algorithms that are not currently fully disclosed, are not considered as essential technical steps in the core method of this implementation.
[0048] Set LLVMValue (intermediate representation of the base class of values) as the basic type of LLVMIR value objects, and create integer constant objects, null constant objects, array constant objects, structure constant objects, global variable objects, function parameter objects, Instruction objects, and Function objects based on the parsed structured program information.
[0049] Because LLVMIR uses SSA (Static Single Assignment), the local values generated by LLVMIR instructions are represented by corresponding Instruction objects. Each Instruction object stores at least the current instruction type, instruction result type, instruction operands, and the basic block to which it belongs.
[0050] When constructing an Instruction object, each operand in that Instruction object directly references its corresponding LLVMValue object, thus establishing the usage definition relationship between LLVMIR value objects. For example, when the operand of the current instruction is generated by the previous integer arithmetic instruction, the current Instruction object stores a reference to the previous Instruction object. When the operand comes from a function parameter, a reference to the corresponding function parameter object is stored. When the operand is a constant, a reference to the corresponding constant object is stored.
[0051] During subsequent symbol execution, the corresponding LLVMValue object is obtained based on the operand reference stored in the current Instruction object, and the symbol value corresponding to the LLVMValue object is obtained based on the current symbol state, so that the usage definition relationship established in step S103 can be directly used for instruction semantic execution in step S105.
[0052] Step S104: Establish memory state partitioned by object.
[0053] This implementation establishes memory states divided by object for memory objects whose ownership can be reliably identified, and does not uniformly represent all program memory as a unique global flat byte array.
[0054] For allocated objects in stack memory, allocated objects in heap memory, and global variables, create separate memory objects in the form of Reference (memory objects). Each Reference must include at least an object identifier, object size, and byte range. For pointers that can locate the corresponding Reference through the object identifier, create Pointer (pointer object) objects. Each Pointer must include at least an object identifier and an offset within the object.
[0055] The symbolic execution context stores the set of currently accessible References. When performing a memory read or write operation, the Accessed Reference is determined from the Reference set based on the object identifier in the Pointer, and the access location within the Reference is determined based on the object offset in the Pointer. If the current program model cannot reliably determine the corresponding Reference based on the object identifier in the Pointer, it is not assumed that the pointer access has been correctly modeled; instead, the process of generating a valid verification result based on this uncertain memory access stops.
[0056] When the alloca (stack memory allocation instruction) instruction is executed, a new Reference is created based on the allocation type that the alloca instruction can determine, and an object identifier is assigned to the Reference that is different from the existing memory object.
[0057] The object size of the Reference is determined based on the known allocation type, and a corresponding byte region is established. A Pointer is also generated, whose object identifier matches that of the newly created Reference, with a zero offset within the object. The Pointer is saved as the SSA symbol result corresponding to the current alloca instruction. Subsequent load (memory read) or store (memory write) instructions referencing the alloca result access the same Reference through this Pointer. Different alloca instructions generate different object identifiers by default; therefore, Pointers directly generated by different alloca instructions without altering object ownership can locate different References.
[0058] A global reference is created for each global variable that can be reliably modeled by the current program model. When a global variable has an initial value, that initial value is used to initialize the byte region of the corresponding reference. The global reference is stored in the current program memory state and remains visible during the execution of the relevant functions.
[0059] When a `call` instruction invokes a heap memory allocation function that has been registered with allocation semantic stubs, a new `Reference` is created based on the allocation size that can be determined by the allocation call. A new object identifier, object size, and byte region are created for the `Reference`, and a `Pointer` with a zero offset within the object is created and returned as the allocation call value.
[0060] When the external memory allocation function does not have an executable function body and there is no registered allocation semantic stub, it is not assumed that the call has generated valid heap memory. Instead, the current verification process is terminated as if the function call semantics cannot be reliably established.
[0061] Step S105: Execute instruction semantics according to control flow.
[0062] This implementation pre-establishes instruction semantic mappings for LLVMIR instructions whose inputs, symbol state changes, and outputs can be clearly determined. The instruction semantic mappings implemented in this embodiment primarily correspond to at least integer arithmetic and bitwise operation instructions, integer comparison instructions, memory read instructions, memory write instructions, stack memory allocation instructions, control flow branch instructions, function return instructions, phi instructions, and function call instructions with function bodies or registered semantic stubs.
[0063] During symbolic execution, the corresponding instruction semantic mapping is invoked according to the instruction category represented by the current Instruction object. The instruction semantic mapping obtains the symbol value, memory state changes, or security conditions generated by the current instruction based on the input of the current instruction and the current path state.
[0064] The symbolic value generated by the instruction semantic mapping is saved as the SSA symbolic value corresponding to the Instruction object, and is used by subsequent instructions that reference the Instruction object. The memory state changes generated by instruction execution are saved as the current path memory state, and are used by subsequent memory access instructions. The security conditions generated by the instruction semantic mapping are used to form the verification conditions in step S110.
[0065] In LLVMIR, integer values are represented by SMT bit vectors based on the corresponding integer type bit width. When executing integer arithmetic or bitwise operation instructions, the sign values of each operand are obtained based on the operand references of the current Instruction object and the defined relationships, and the corresponding bit vectors are created for the operation based on the current instruction type. For example, when the current instruction is an integer addition instruction, the sign values of the two operands with equal bit widths are obtained, the corresponding bit vectors are used to perform the addition, and the result is saved as the SSA sign value corresponding to the current Instruction object.
[0066] When other instructions use the result of the addition, the aforementioned SSA symbol value is obtained according to the usage definition relationship, thereby making the symbol result of the current integer operation directly used as the input of subsequent instructions.
[0067] For integer operations carrying the nuw (no unsigned overflow flag) or nsw (no signed overflow flag) flags, enhanced security checks to prevent the corresponding overflow can be enabled or disabled based on the verification configuration. When the corresponding check is enabled, the corresponding security condition is generated. When disabled, fixed-width integer wrap-around operations are performed. This implementation does not include the complete poison propagation and precise semantics of the freeze instruction in all LLVM instructions as necessary implementations of the current method.
[0068] When performing integer division or modulo operations, the dividend and the corresponding bit vector sign value of the divisor are obtained from the operand references stored in the current Instruction object, and the corresponding integer division or modulo operation sign result is established.
[0069] When division-by-zero safety checks are enabled, it is determined whether a symbolic state exists that would result in division by zero, based on the current path conditions and the divisor sign value. If a symbolic state exists that satisfies the current path conditions and results in division by zero, a safety condition representing a division-by-zero violation is generated, and this safety condition is used in step S110 to form a division-by-zero verification condition. Normal symbolic results generated by integer division or modulo operations are saved as the SSA symbolic value corresponding to the current Instruction for use in subsequent instruction execution.
[0070] For the icmp (integer comparison instruction) instruction, the corresponding integer bit vector comparison relation is selected based on the comparison predicate. The comparison relations include at least equal, not equal, unsigned greater than, unsigned greater than or equal to, unsigned less than, unsigned less than or equal to, signed greater than, signed greater than or equal to, signed less than, and signed less than or equal to.
[0071] The current comparison result is generated based on the sign values of the two operands, and the comparison result is saved as the sign value corresponding to the current ICMP Instruction. When subsequent branch instructions (br) or other supported instructions reference the ICMP result, the comparison result is used as the corresponding condition input.
[0072] When executing the alloca instruction, a new Reference and corresponding Pointer are created according to step S104. The object identifier in the Pointer points to the corresponding Reference, and the offset within the object is zero. This Pointer is saved as the SSA result corresponding to the current alloca Instruction. When the alloca result is referenced in subsequent load or store operations, the corresponding Reference is accessed through the corresponding Pointer.
[0073] When executing the load instruction, the pointer operand of the load instruction is obtained according to the usage definition. The object identifier in the Pointer is used to determine the reference being accessed from the current memory state, and the offset within the object is used to determine the starting position of the read within the reference. The current access length is determined according to the data type corresponding to the load result, and the current read access range is determined by combining the offset within the Pointer object.
[0074] When object boundary checking is enabled, the system determines whether there are any out-of-bounds symbol states where the read access range exceeds the size limit of the corresponding Reference object, based on the current path conditions. Out-of-bounds symbol states include instances where the offset within the object is below the lower bound of the object's allowed range, or instances where the access end position, determined by the offset within the object and the access length, exceeds the size of the Reference object.
[0075] When the aforementioned read out-of-bounds sign state exists, a safety condition representing the read out-of-bounds condition is generated. This safety condition is used in step S110 to form the memory read out-of-bounds verification condition. For read operations that the current model can execute, the corresponding bytes are obtained from the Reference byte region based on the Pointer's object offset, and combined into a symbol value corresponding to the load result type. The obtained symbol value is saved as the SSA symbol value corresponding to the current load Instruction. Subsequent Instructions obtain the SSA symbol value corresponding to this load result using the defined relationship. The load instruction itself does not modify the byte region of the target Reference.
[0076] When executing the store instruction, the symbol value to be written and the target Pointer are obtained. The corresponding Reference is located from the current memory state based on the object identifier in the target Pointer. The write start position is determined based on the offset within the object, and the access length is determined based on the data type to be written. The current write access range is determined based on the offset within the object and the access length. When object boundary checking is enabled, it is determined whether there are symbol states where the write access range exceeds the size limit of the Reference object based on the current path conditions. When there are symbol states where the write position is below the allowed lower bound of the object, or the write end position exceeds the object size, a safety condition indicating write out of bounds is generated, and this safety condition is used in step S110 to form a memory write out of bounds verification condition.
[0077] For the actual write operation performed on the current path, the symbol value to be written is updated to the corresponding byte area of the Reference based on the offset within the object, and the updated Reference is saved as the memory state of the current path. When a Pointer with the same object identifier performs a load or store operation subsequently, the updated Reference state is used, thus forming a continuous transfer of memory state.
[0078] For unconditional `br` instructions, the current execution position is changed according to the target block specified by the instruction, and execution of the corresponding block continues. For conditional `br` instructions, the symbol value corresponding to the condition operand is obtained, and two successor paths are formed based on the current path conditions before entering the `br` instruction. One successor path is constrained to meet the branch condition and enters the corresponding first target block. The other successor path is constrained to not meet the branch condition and enters the second target block.
[0079] Each executing path at least saves the current path conditions, the current basic block, the predecessor basic block that enters the current basic block, the SSA symbol state, and the current memory state, so that different control flow paths can continue to execute under their respective path condition constraints.
[0080] When executing a switch (multi-path branching) instruction, the symbol value corresponding to the switch condition is obtained. Based on the values of each case (branch matching item), path conditions that satisfy the corresponding case conditions are established, and the corresponding target block is entered. For symbol states that do not satisfy any of the explicit case conditions, execution continues according to the path corresponding to the default branch.
[0081] When the current path enters a basic block containing a PHI instruction, the corresponding PHI input is determined based on the predecessor basic block information stored in the path. If the current path originates from the first predecessor basic block, the input value from the PHI instruction corresponding to the first predecessor basic block is selected. If it originates from the second predecessor basic block, the input value corresponding to the second predecessor basic block is selected. The symbol value corresponding to the selected input value is saved as the SSA symbol result corresponding to the current PHI Instruction.
[0082] When multiple predecessor inputs can be uniformly represented by corresponding path conditions, a symbolic expression of condition selection form is formed based on each predecessor path condition and each input value. This implementation does not consider implementing a complex, general memory state merging algorithm for all References at the control flow merging location as a necessary technical feature of the currently required protection method.
[0083] When the `ret` (function return instruction) instruction is executed, the current function path is terminated. When the `ret` instruction carries a return value, the symbolic value corresponding to the return value is obtained according to the usage definition relationship, and this symbolic value is used as the return result of the current function path. The memory state in the current path serves as the current state after function execution, for subsequent reliable modeling of function call return processes or verification result processing.
[0084] For `call` instructions with a identifiable target function and a function body, inline symbolic execution is employed. The symbolic values corresponding to the actual arguments of the `call` instruction are obtained, the caller's current local execution environment is saved, and a local execution environment corresponding to the called function is established. The symbolic values of the actual arguments are bound to the corresponding formal parameters of the called function, and execution begins from the entry block of the called function according to the same set of instruction semantic mappings. When the called function reaches the `ret` instruction, its return value is obtained, and memory state changes resulting from reliably locatable shared references during the call process are preserved.
[0085] After restoring the caller's local execution environment, the return value of the called function is saved as the SSA symbol value corresponding to the call Instruction, and the caller's subsequent instructions continue to be executed. For runtime functions that have already registered semantic stubs, the corresponding symbol state is updated directly according to the semantic stub. When the target function of the call instruction cannot be determined, or the target function does not have a function body and there is no registered semantic stub, or the current verifier cannot reliably establish the corresponding call semantics, the current verification process is terminated, and the call instruction is not skipped as a side-effect-free call.
[0086] Step S106: Identify property specifications.
[0087] Property specifications are added to the source program by the verification engineer using restricted specification primitives and are compiled into the LLVMIR program along with the source program.
[0088] The property specifications identified in this implementation include path assumption primitives, assertion primitives to be proved, proposition combination primitives, and nondeterministic input primitives. In one implementation, the path assumption primitive is LIVE_assume, the assertion primitive to be proved is LIVE_assert, the proposition combination primitives are LIVE_and, LIVE_or, and LIVE_implies, and the nondeterministic input primitives are the nondet family of primitives.
[0089] When LIVE_assume is executed, the symbolic logic value corresponding to the primitive input condition is obtained and added to the current path condition. The resulting new path condition is used to constrain symbolic execution after LIVE_assume. For example, when the integer parameter n of the function to be verified should satisfy the non-negativity condition, the corresponding LIVE_assume can be set at the source program entry point. After this reduction is executed, subsequent symbolic execution only considers symbolic states that satisfy the entry assumption.
[0090] When LIVE_assert is executed, it is not ignored as a normal function call without side effects. Instead, the current path conditions and the conditions to be verified corresponding to LIVE_assert are obtained.
[0091] Verification conditions are formed based on the current path conditions and the conditions to be verified. These verification conditions are used to determine whether there exists a symbolic state that satisfies the current path conditions and makes the conditions to be verified false. Thus, the verification conditions corresponding to the property specification are formed by the path conditions and the properties to be verified, without requiring each assertion verification condition to simultaneously contain instruction-level security conditions.
[0092] LIVE_and generates a symbolic result of logical AND form based on the input conditions. LIVE_or generates a symbolic result of logical OR form based on the input conditions. LIVE_implies generates a symbolic result of logical implication form based on the input conditions. The symbolic results generated by the above propositional combinations can be used as input for LIVE_assume, LIVE_assert, or other propositional combination primitives.
[0093] When executing a nondet primitive, a new symbolic value is created based on the primitive's return type. The symbolic value is not assigned a fixed specific input during creation; its possible values are jointly limited by subsequent LIVE_assume, program control flow branch conditions, and constraints arising from supported LLVMIR instructions. This implementation does not automatically interpret arbitrary source program functions or arbitrary Python ASTs (Abstract Syntax Trees) as property reductions; it only recognizes restricted primitives whose corresponding reduction semantics have been pre-established.
[0094] Step S107: Establish the initial symbol execution state.
[0095] Before entering the symbolic execution of the target function, initial symbolic values that can be reliably represented are established based on the function's formal parameters.
[0096] For integer parameters, an SMT bit vector sign value is established based on the corresponding integer bit width. The initial path condition is set to true, and the execution context used by the current verification process is established. The execution context at least stores the current function, the current basic block, the predecessor basic block, the SSA sign value mapping, the reference set, the current path condition, the verification condition set, error logs, call stack information, and loop unrolling counts. After the initial state is established, execution begins from the entry basic block of the target function and follows the LLMIR control flow.
[0097] Step S108: Execute the path symbol.
[0098] Upon entering the current basic block, the instruction semantic mapping corresponding to each instruction is executed according to the order of the LLVMIR instructions within that basic block.
[0099] For instructions that generate symbolic values, the symbolic values are saved as the SSA symbolic result of the corresponding Instruction object. For instructions that cause changes in memory state, the corresponding changed Reference is saved to the current path's memory state.
[0100] For instructions that generate safety conditions, the corresponding safety conditions are saved for later use in forming verification conditions. For control flow instructions, the current execution path is updated based on the corresponding branch conditions and the target basic block.
[0101] After conditional branches form different path conditions, each subsequent path is executed under its corresponding path condition. When entering a basic block with a phi instruction, the corresponding phi input is selected based on the predecessor basic block actually corresponding to the current path. Thus, this implementation follows the actual control flow progression symbol execution of the LLMVR program, without requiring the entire control flow graph to be linearized before execution in one go.
[0102] Step S109: Identify the loop and perform bounded expansion.
[0103] For LLVMIR programs containing loops, the corresponding loop head and loop exit are determined based on the loop identification information.
[0104] When the current symbol path enters the loop head, record the number of times the path has entered the current loop head. If the current entry count has not reached the preset expansion depth, continue executing the loop condition, loop body, and subsequent control flow according to the current path conditions. When entering the same loop head again, update the corresponding entry count. When the current entry count reaches the preset expansion depth, stop exploring subsequent loop iterations in the current path that exceed the preset expansion depth.
[0105] For paths that have reached `LIVE_assert` within the preset expansion depth, verification conditions are formed based on the current path conditions and the corresponding conditions to be verified. For paths that have generated safe conditions such as memory overflow or division by zero within the preset expansion depth, verification conditions are formed based on the current path conditions and the corresponding safe conditions. For subsequent program behaviors that are not explored further due to reaching the preset expansion depth, no verification conclusion is formed for the portion exceeding the preset expansion depth. Therefore, a `SUCCESS` result for a program containing loops only indicates that none of the verification conditions actually formed within the corresponding preset expansion depth are in a state that can be satisfied and violated.
[0106] The verification report also records the preset expansion depth used in this verification to clarify the bounded execution conditions corresponding to the verification results.
[0107] Step S110: Form verification conditions based on path conditions and property specifications or security conditions.
[0108] The verification conditions formed during symbol execution include those generated by property reduction and those generated by instruction security checks.
[0109] When LIVE_assert is executed, the verification conditions for the assertion to be verified are formed based on the current path conditions and the corresponding conditions to be verified. The verification conditions are used to determine whether there exists a symbol state that satisfies the current path conditions and makes the conditions to be verified false.
[0110] When executing a load instruction with object boundary checking enabled, the system determines whether there is a symbolic state where the access range exceeds the size limit of the target Reference object, based on the Pointer object's offset, access length, target Reference object size, and current path conditions. If a symbolic state exists, a safety condition representing a memory read out of bounds is generated, and an out-of-bounds verification condition is formed based on the current path conditions and the safety condition.
[0111] When executing a store instruction with object boundary checking enabled, the system determines whether there is a symbolic state where the write access range exceeds the size limit of the target Reference object, based on the Pointer object's offset, access length, target Reference object size, and current path conditions. If a symbolic state exists, a safety condition representing a memory write out of bounds is generated, and a write out-of-bounds verification condition is formed based on the current path conditions and the safety condition.
[0112] When executing integer division or modulo instructions, with division-by-zero safety checks enabled, the system determines whether a sign state exists where the divisor is zero based on the current path conditions and the sign value of the divisor. If a sign state exists, a safety condition representing division by zero is generated, and a division-by-zero verification condition is formed based on the current path conditions and the safety condition.
[0113] For the current validator, which has established reliable semantics and actually enabled integer security enhancement checks, corresponding verification conditions can be formed based on the current path conditions and corresponding security conditions. This implementation does not include the currently undisclosed LLVM complete poison propagation mechanism as a necessary technical content.
[0114] Step S111: Solve for the verification conditions.
[0115] In this embodiment, Z3 (a satisfiability model theory solver) is used as the SMT solver to solve each verification condition actually formed in step S110. For each verification condition, it is determined whether there is a model that satisfies the current path constraint for its corresponding violation state.
[0116] When the corresponding verification condition cannot be satisfied, it means that there is no symbol state that satisfies the current path condition and causes the corresponding property or security condition to be verified to be violated. The verification condition is then recorded as SUCCESS, meaning the verification is successful.
[0117] When the violation state of the corresponding verification condition can be satisfied, it indicates that there exists a program state that satisfies the current path condition and can cause the corresponding property to be violated. This verification condition is recorded as FAIL, meaning verification failed. Simultaneously, the model output by the SMT solver is saved as a counterexample. The counterexample model can contain the function input that caused the property to fail, the values of symbolic variables, and other information that can help locate the problem.
[0118] When the SMT solver cannot determine whether a corresponding violation condition can be satisfied due to timeout, resource limitations, or other reasons, it records the verification condition as UNKNOWN. UNKNOWN must not be converted to SUCCESS.
[0119] Step S112: Aggregate the verification results and output a verification report.
[0120] Record the overall validation result as FAIL if at least one validation condition is recorded as FAIL. Record the overall validation result as UNKNOWN if no FAIL is recorded and at least one validation condition is recorded as UNKNOWN.
[0121] For LLVMIR programs without loops, the overall verification result is recorded as SUCCESS when all verification conditions actually formed in the current verification process are recorded as SUCCESS. For LLVMIR programs containing loops, the overall verification result is recorded as SUCCESS corresponding to the preset unwrapping depth only when all verification conditions actually formed within the preset unwrapping depth are recorded as SUCCESS, and the preset unwrapping depth used in this verification report is recorded. Program behavior that does not continue exploring after reaching the preset unwrapping depth is not included in the unbounded correctness conclusion of this bounded SUCCESS.
[0122] If LLVMIR program parsing fails during verification, or if an instruction lacks a corresponding semantic mapping, the verification process terminates, and no verification pass is output. Similarly, if the program model cannot be fully constructed or function calls cannot reliably establish semantics, the SUCCESS result aggregation branch must not be entered. When the verification result is FAIL, the verification report saves the failure verification conditions, counterexample models, corresponding LLVMIR locations, and the source program locations obtainable if debug locations exist. When the verification result is UNKNOWN, the verification report records the corresponding UNKNOWN state and the obtainable solution information.
[0123] Example 2: This example further illustrates the processing relationship between the symbolic value, security conditions, and verification conditions corresponding to integer instructions.
[0124] Suppose the function to be verified contains an integer division instruction. During the parsing phase, the instruction type, dividend operand reference, divisor operand reference, integer types of the two operands, result type, and the underlying block to which it belongs are obtained.
[0125] When constructing the Python program model, the integer division Instruction stores references to both the dividend LLVMValue object and the divisor LLVMValue object.
[0126] Upon entering the symbolic execution phase, the dividend and divisor's current corresponding bit vector symbol values are obtained based on the defined relationship. The semantic mapping of the corresponding integer division instruction is invoked to establish the bit vector representation of the division result, and the bit vector representation is saved as the SSA symbol value corresponding to the current integer division instruction.
[0127] When division-by-zero safety checks are enabled, the system determines whether a divisor sign of zero exists based on the current path conditions. For example, if LIVE_assume has been executed before the division instruction arrives, and LIVE_assume adds a divisor greater than zero condition to the current path conditions, subsequent division-by-zero violations are also constrained by this path condition.
[0128] When no symbolic state with a division by zero exists under the current path conditions, the state violating the division-by-zero verification condition is unsatisfactory and is recorded as SUCCESS. When a symbolic state exists that satisfies the current path conditions and makes the division by zero, the SMT solver returns a satisfactory state, records the division-by-zero verification condition as FAIL, and saves the corresponding symbolic input as a counterexample model. When the SMT solver cannot determine the condition, it records the corresponding verification condition as UNKNOWN.
[0129] Example 3: This example illustrates the processing relationship between the alloca, store, and load instructions and the memory state partitioned by object.
[0130] Suppose that there exists an alloca instruction in the LLVMIR program that can reliably determine the allocation type. When the alloca instruction is executed, a new Reference is created, and an object identifier A, object size, and corresponding byte region are set for this Reference. At the same time, a Pointer P is created, with the object identifier of Pointer P being A, and the offset within the object being zero. Pointer P is then saved as the SSA result corresponding to the alloca Instruction.
[0131] When a subsequent store instruction references Pointer P, Pointer P is retrieved based on the usage definition. The corresponding stack memory reference is located from the current Reference set using the object identifier A in Pointer P, and the write start position is determined based on the object offset in Pointer P.
[0132] The current access length is determined based on the type of the value to be written, and the write access range is formed based on the offset within the object and the access length. When object boundary checking is enabled, it is determined whether there is a symbolic state where the write access range exceeds the size limit of the Reference object based on the current path conditions. When a corresponding symbolic state exists, a write out-of-bounds safety condition is generated, and a corresponding verification condition is formed.
[0133] For the actual write operation performed on the current path, the symbol value to be written is updated to the corresponding byte area of the Reference, and the updated Reference is saved as the memory state of the current path. When a subsequent load instruction references Pointer P again, the updated Reference is obtained based on the object identifier A, and the read access range is determined based on the offset within the object and the type of the load result.
[0134] When object boundary checking is enabled, it determines whether there is a symbol state where the read access range exceeds the size limit of the Reference object based on the current path conditions, and forms a read out-of-bounds safety condition if the corresponding state exists. For reads that the current model can execute, the corresponding symbol byte is obtained from the updated Reference byte area and combined into a load result symbol value, which is then saved as the SSA symbol result corresponding to the load Instruction.
[0135] Subsequent instructions can continue to use the load result by using the defined relationship, thereby allowing the memory state changes generated by the store to be directly used by subsequent loads, while making memory safety conditions independent verification conditions.
[0136] Example 4: Conditional branching and phi instruction examples.
[0137] Suppose that an integer comparison instruction is executed in the current basic block, and this instruction generates a sign condition `cond` (condition sign), followed by the execution of the condition `br`. When executing the `br` instruction, paths where `cond` is true and paths where `cond` is false are formed based on the current path condition.
[0138] A path where `cond` is true adds a `cond` true constraint to the original path conditions and enters the first target basic block. A path where `cond` is false adds a `cond` false constraint to the original path conditions and enters the second target basic block. The two paths execute the instructions in their respective basic blocks and each saves its current path conditions, current SSA symbol state, and current memory state.
[0139] When two control flow paths subsequently enter a basic block containing PHI instructions, the corresponding PHI input is determined based on the predecessor basic block saved in the current execution path. If the current path originates from the first predecessor basic block, the input value corresponding to the first predecessor basic block is selected. If the current path originates from the second predecessor basic block, the input value corresponding to the second predecessor basic block is selected.
[0140] The symbolic value corresponding to the selected input value is saved as the SSA symbolic result corresponding to the phi Instruction. In this way, the phi result is constrained by the actual control flow source and can be obtained by subsequent instructions using the defined relationship.
[0141] Example 5: Function call example.
[0142] Assume function A contains a `call` instruction for function B, and the resolution phase determines that function B has a function body. When executing the `call` instruction in function A, the symbolic values of each actual argument are obtained according to the usage definition relationship. The local execution context of function A is saved, and the local execution context of function B is established. The symbolic values of each actual argument are bound to the corresponding formal parameters of function B, and execution begins from the entry block of function B.
[0143] Integer operations, memory accesses, control flow, and other instructions belonging to the currently supported instruction set in function B continue to be executed according to their corresponding instruction semantic mappings. When function B executes the ret instruction, it retrieves the symbolic value corresponding to the return value of function B, while preserving the memory state changes generated during the call for reliably locatable shared references.
[0144] After restoring the local execution environment of function A, the return value of function B is saved as the SSA result corresponding to the call instruction in function A, and subsequent instructions in function A continue to be executed. When the target function has no ordinary function body, but the verifier has already registered the corresponding semantic stub, the symbolic state is updated according to the semantic stub. When the target function cannot be determined, or the target function has no function body and no registered semantic stub exists, the current verifier cannot establish reliable call semantics. In this case, the verification process terminates, and verification is not continued without ignoring unknown calls.
[0145] Example 6: An example of property specification forming verification conditions.
[0146] Suppose that LIVE_assume is set at the entry point of the source program to be verified. LIVE_assume is used to ensure that the integer parameter n meets the predetermined entry conditions. After the source program is compiled into LLVMIR, the verification executor identifies the corresponding LIVE_assume call, obtains the symbolic logical value corresponding to its condition, and adds the symbolic logical value to the current path condition.
[0147] Subsequent LLVMIR instructions are executed under the updated path condition constraints. A `LIVE_assert` is set at a critical point in the program or before returning; each `LIVE_assert` corresponds to a condition to be verified. When this `LIVE_assert` is executed, the current path conditions and the condition to be verified are obtained, and the verification conditions for the assertion to be verified are formed based on these conditions. The SMT solver then solves for the violation states of the conditions to be verified.
[0148] If no symbolic state exists that satisfies the current path conditions and makes the condition to be proved false, the corresponding LIVE_assert is recorded as SUCCESS. If a symbolic state exists, the corresponding LIVE_assert is recorded as FAIL, and the corresponding counterexample model is output.
[0149] When the solver cannot determine the answer, it records UNKNOWN. When the condition to be proved consists of multiple propositions, LIVE_and, LIVE_or, and LIVE_implies can be used to form corresponding proposition combination symbolic values, which are then used as input to LIVE_assert or LIVE_assume. When the source program uses nondet to generate input, the verification executor creates new symbolic values, and the feasible values of these symbolic values are subsequently restricted by LIVE_assume, program control flow conditions, and instruction constraints.
[0150] Example 7: Example of verifying bounded cycles.
[0151] Suppose the function to be verified contains a loop, and the loop identification information can determine the corresponding loop head.
[0152] The system stores the number of times the current symbol path enters the loop head. If the number of entries has not reached the preset expansion depth, the current loop condition and loop body continue execution. When the loop body jumps back to the loop head, the number of entries into the loop head for the current path is updated, and the loop execution path is re-evaluated based on the current path condition. When the number of entries reaches the preset expansion depth, subsequent loop iterations exceeding the expansion depth are no longer explored.
[0153] For paths that have reached LIVE_assert within the preset expansion depth, verification conditions are formed based on the current path conditions and the conditions to be verified in LIVE_assert. For paths within the preset expansion depth where safe conditions are generated by load, store, or integer division, verification conditions are formed based on the current path conditions and the corresponding safe conditions. For program behaviors that can only occur after exceeding the current preset expansion depth, this verification does not form an unbounded verification conclusion exceeding that expansion depth.
[0154] When all verification conditions formed within the preset unfolding depth are SUCCESS, the overall verification result is recorded as SUCCESS corresponding to the preset unfolding depth, and the preset unfolding depth is recorded in the verification report.
[0155] Example 8: Validation result aggregation and counterexample feedback example.
[0156] Suppose that multiple verification conditions are formed during the current verification process, including the assertion verification condition to be proved, the memory safety verification condition, and the division by zero verification condition.
[0157] The violation status of each verification condition is submitted to the SMT solver for solution. When the violation status of any verification condition can be satisfied, the verification condition is recorded as FAIL, and the overall verification result is also recorded as FAIL. Even if all other verification conditions are SUCCESS, the overall verification result is still FAIL as long as at least one FAIL exists. When there are no FAILs, but at least one UNKNOWN exists, the overall verification result is recorded as UNKNOWN. For programs that do not contain loops, the overall verification result is recorded as SUCCESS only if all verification conditions formed by the current verification process are SUCCESS.
[0158] For programs containing loops, the overall verification is passed only if all verification conditions formed within the preset unwrapping depth are SUCCESS, and the preset unwrapping depth is recorded. When a verification condition is FAIL, the counterexample model generated by the solver is saved. If a debug location exists for the corresponding LLVMIR instruction, the corresponding LLVMIR location and the associated source code location are further determined based on the debug location data, thereby assisting verification personnel in analyzing the input state that leads to the violation of the property to be verified or the security property.
[0159] Example 9: Example of handling unsupported instructions and abnormal verification status.
[0160] During the LLVMIR program processing, continuous supportive judgments are made regarding the current structure and current instructions.
[0161] The verification process terminates when the bitcode file cannot be properly disassembled. The verification process terminates when the normalized LLVMIR text cannot be successfully parsed by parseIRFile. Model construction terminates when the structured program information contains unknown constants or values for which corresponding LLVMValue objects cannot be created. The verification process terminates when the parsed LLVMIR instructions do not have corresponding reliable instruction semantic mappings. The verification process also terminates when the call instruction cannot determine the target function and there is no reliably executable function body or registered semantic stub.
[0162] No termination scenario described above shall be recorded as SUCCESS simply because a previously executed path did not find a FAIL. For verification conditions where the SMT solver returns UNKNOWN, the condition shall be explicitly recorded as UNKNOWN, and the overall UNKNOWN or FAIL shall be determined according to the verification result aggregation rules. UNKNOWN shall not be converted to verification pass.
[0163] The above description is only a preferred embodiment of the present invention, but the scope of protection of the present invention is not limited thereto. Any equivalent substitutions or modifications made by those skilled in the art within the scope of the technology disclosed in the present invention, based on the technical solution and inventive concept of the present invention, should be covered within the scope of protection of the present invention.
Claims
1. A formal verification method for LLVMIR assembly-level language, characterized in that, Includes the following steps: The low-level virtual machine intermediate representation program to be verified is obtained and parsed to obtain structured program information containing type information, functions, basic blocks and instruction sequences; Based on the structured program information, construct a program model that includes the usage definition relationships between value objects and the memory state divided by object; The property specification is identified from the intermediate representation program of the low-level virtual machine, and symbolic execution is performed on the program model according to the control flow. When executing instructions, the pre-established instruction semantic mapping is invoked to generate symbol values and security conditions, and verification conditions are formed according to path conditions and the property specification or the security conditions. The verification conditions are solved, and the verification result (verification passed, verification failed, or unknown) is output based on the solution.
2. The formal verification method for LLVMIR assembly-level languages according to claim 1, characterized in that, The process of obtaining and parsing the low-level virtual machine intermediate representation program to be verified includes: Obtain the bitcode file of the low-level virtual machine intermediate representation program; Using a toolchain with the same major version as the compiler frontend that generated the bitcode file, the bitcode file is disassembled into a text representation file; The text representation file is parsed into a module object by calling the low-level virtual machine intermediate representation parsing interface; Extract type information, global symbols, functions, basic blocks, and instruction sequences from the module object to form the structured program information; The verification process terminates when the bit code file disassembly fails, the text representation file parsing fails, or the parsed instructions do not establish a corresponding instruction semantic mapping.
3. The formal verification method for LLVMIR assembly-level languages according to claim 2, characterized in that, Constructing a program model based on the structured program information includes: Create a type object based on the type information; A value object is created based on the global symbols, function parameters, constants, and instructions. Each instruction's corresponding value object stores a reference to the value object corresponding to its operand, thus forming the usage definition relationship. During symbolic execution, the symbolic value corresponding to the operand of the current instruction is obtained according to the defined relationship.
4. The formal verification method for LLVMIR assembly-level languages according to claim 1, characterized in that, The memory state divided by object includes: Memory objects are created for allocated objects in stack memory, allocated objects in heap memory, and global variables. Each memory object includes an object identifier, an object size, and a byte region. For pointers that can locate the corresponding memory object using the object identifier, establish an object identifier and an offset within the object; The accessed memory object is determined based on the object identifier of the pointer, and the access position in the memory object is determined based on the offset within the object. Reading or writing is then performed in the corresponding byte region.
5. The formal verification method for LLVMIR assembly-level languages according to claim 4, characterized in that, The step of performing a read or write operation in the corresponding byte region includes: The access length is determined based on the type of data accessed. The access range in the memory object is determined based on the offset within the object and the access length; Determine whether there is a symbolic state where the access range exceeds the object size limit of the memory object based on the current path conditions; When the symbol state exists, a safety condition representing a memory out-of-bounds error is generated, and the safety condition is used to form the verification condition.
6. The formal verification method for LLVMIR assembly-level languages according to claim 1, characterized in that, The pre-established instruction semantic mapping corresponds at least to integer arithmetic and bitwise operation instructions, integer comparison instructions, memory read instructions, memory write instructions, stack memory allocation instructions, control flow branch instructions, function return instructions, phi instructions, and function call instructions with function bodies or registered semantic stubs; During symbolic execution, the corresponding instruction semantic mapping is invoked according to the current instruction to obtain the symbolic value, memory state change or security condition generated by the current instruction, and the obtained symbolic value or memory state change is used for subsequent instruction execution, and the obtained security condition is used to form the verification condition.
7. The formal verification method for LLVMIR assembly-level languages according to claim 1, characterized in that, Identifying property reductions from the intermediate representation program of the low-level virtual machine and performing symbolic execution on the program model according to the control flow, including: Identify path hypothesis primitives, assertion primitives to be proved, proposition combination primitives, and nondeterministic input primitives that are compiled into the intermediate representation program of the low-level virtual machine along with the source program; Add the conditions corresponding to the path hypothesis primitives to the current path conditions; Different path conditions are formed based on the conditional branches, and execution continues according to the control flow between basic blocks; Identify loops and perform bounded expansion according to a preset expansion depth; When the verifiable assertion primitive is executed within the preset expansion depth, the verification condition is formed based on the current path condition and the verifiable condition corresponding to the verifiable assertion primitive.
8. The formal verification method for LLVMIR assembly-level languages according to claim 1, characterized in that, The verification conditions are solved, and the verification result (verification passed, verification failed, or unknown) is output based on the solution result, including: Solve for the violation states of each verification condition separately; When the corresponding violation state cannot be satisfied, the verification condition is recorded as verified. When the corresponding violation state can be satisfied, the verification condition is recorded as verification failure, and a counterexample model is output; When it cannot be determined whether the corresponding violation state can be satisfied, the verification condition is recorded as unknown. If a verification condition fails, output "Overall verification failed"; if no verification failed but an unknown verification condition exists, output "Overall unknown". For a low-level virtual machine intermediate representation program containing loops, only when all verification conditions formed within a preset expansion depth are verified as passed, will the overall verification be output corresponding to the preset expansion depth, and the preset expansion depth be recorded. When the low-level virtual machine intermediate representation program fails to parse or there is an instruction whose corresponding instruction semantic mapping is not established, the verification process is terminated and no verification pass is output.
