A binary program verification method and system for RISC-V architecture
By converting RISC-V assembly instructions into C language code and using CBMC for verification, the problem that existing tools cannot fully cover binary file errors under the RISC-V architecture is solved, efficient and automated verification is achieved, and the reliability of the software is improved Sex and safety.
Patent Information
- Application Number
- CN202411377628.3
- Authority / Receiving Office
- CN · China
- Patent Type
- Patents(China)
- Current Assignee / Owner
- Filing Date
- 2024-09-30
- Publication Date
- 2025-01-14
- Estimated Expiration
- 2044-09-30
AI Technical Summary
Existing software verification tools cannot effectively support assembly language and passive code binary files under the RISC-V architecture, especially in application scenarios with high security and reliability requirements, and cannot fully cover all potential errors of the program.
Using a binary program verification method for RISC-V architecture, the assembly instructions are converted into C language code through the ELF file processor, RISC-V instruction decoder, assembly to C converter and verification logic generator, and the assembly instructions are converted into C language code and utilized CBMC The model inspector performs verification and generates verification results.
It realizes efficient, comprehensive and automated verification of RISC-V assembler and binary files, improving the reliability and security of the software before actual deployment.
Smart Images

Figure CN118916886B_ABST
Abstract
Description
Technical Field
[0001] The present invention relates to the technical field of computer software verification, and in particular to a binary program verification method and system for a RISC-V architecture. Background Art
[0002] RISC-V (Reduced Instruction Set Computing - V) is an open source instruction set architecture (ISA) that is widely used in custom microprocessor and embedded system development due to its high configurability and modularity. Although the RISC-V architecture provides great flexibility in software and hardware development, existing software verification tools often cannot effectively support assembly language and binary files without source code, especially in application scenarios with extremely high security and reliability requirements, such as embedded systems and operating systems. The shortcomings of current tools are mainly reflected in the lack of understanding of the underlying language and compiled code, and cannot fully cover all potential errors in these programs.
[0003] In the field of software verification, static analysis, dynamic testing, theorem proving, and model checking each offer unique advantages and application approaches. These techniques are key tools for understanding and ensuring that program behavior is as expected, especially when dealing with low-level languages such as assembly code and binary files. While each method has its own application scenarios, they exhibit their own advantages and disadvantages when dealing with specific types of errors and verifying specific aspects of program correctness. The following describes in detail how these methods operate and their practical applications. (1) Static analysis: Static analysis tools analyze assembly code or binary files without executing the program to predict problems that may occur when the program is running. These tools deeply examine the syntax and structure of the assembly program, paying special attention to the correct use of instructions, register allocation, data flow, and control flow. Static analysis can identify problems such as uninitialized register access, incorrect jump instructions, and illegal memory access. In addition, it can also check for security vulnerabilities such as buffer overflows and permission errors. (2) Dynamic testing: Dynamic testing involves executing assembly or binary programs in real or virtual hardware environments. By monitoring the behavior of the program in real time, this method can capture errors that only appear under specific running conditions, including runtime errors, resource access conflicts, and performance bottlenecks. Dynamic testing usually requires carefully designed test cases to simulate various possible execution paths and user interactions. This method is particularly important for verifying the compatibility and behavior of programs on actual hardware, especially in complex system integration and multi-tasking operation environments. (3) Theorem proving: In the verification of assembly programs or binary files, theorem proving uses formal logic and mathematical methods to prove that the program conforms to predefined specifications. Using formal languages such as Isabelle or Coq, experts can construct precise mathematical models of assembly programs and apply Hall logic to deduce and verify the correctness of program behavior. This method is very suitable for verifying the implementation of key algorithms, such as cryptographic protocols and secure communications. Assembler (assembly language program) is a low-level programming language that allows programmers to directly control hardware by using easy-to-remember symbolic codes (rather than binary codes). Binary files contain a series of machine language instructions that can be directly executed by a computer processor. These files are usually compiled programs, such as executable files and library files. (4) Model checking: Model checking systematically explores all possible program execution paths by building a state machine model of the assembly or binary program. This includes verification specifications described using temporal logic to check whether the program satisfies properties such as deadlock freedom, event correctness, etc. In addition, model checking can also convert state machine models and formal specifications into Boolean expressions and solve them using SAT (Satisfiability) / SMT (Satisfiability Modulo Theories) solvers.SAT solvers are used to determine whether a set of logic formulas has a satisfying solution, while SMT solvers are an extension of SAT solvers that can handle logic formulas that contain theories such as integer arithmetic, real number arithmetic, arrays, etc. These techniques enable model checking to automatically verify the correctness of specifications.
[0004] Common challenges in existing methods for verifying assembly programs and binaries include technical limitations, high complexity, and reliance on expertise. Although static analysis, dynamic testing, theorem proving, and model checking each have their own unique application advantages, they also face the difficulties of not being able to fully cover all execution paths, handling complex dependencies and environmental interactions, and application limitations in the absence of source code. These methods often fail to fully meet the requirements of high security and reliability standards in practical applications, especially when faced with verification challenges at the assembly and binary levels. Therefore, it has become an urgent need for the industry to develop a more efficient and comprehensive verification method to ensure the stability and security of software products in complex environments. Specific technical problems and defects encountered by each method in practical applications. (1) Static analysis: Static analysis tools are mainly designed to analyze high-level programming languages. For assembly code, although they can perform certain error detection and process analysis, they usually show great limitations when analyzing binary files. Since static analysis does not execute code, it usually cannot fully and accurately capture dynamic behavior and environmental dependency issues that may occur at runtime. In addition, these tools often lack an understanding of the underlying logic and data flow in assembly code, and may miss critical errors or generate false positives, especially when faced with complex system interactions and peripheral device operations. (2) Dynamic testing: Dynamic testing detects errors in assembly and binary files by actually executing the program. It can directly observe the runtime behavior of the program, including the detection of resource conflicts, runtime errors, and performance issues. However, the effectiveness of this method is extremely dependent on the comprehensiveness and depth of the test cases. It is very difficult to design test cases that can cover all potential execution paths, especially for large applications with complex inputs and multi-path execution. In addition, dynamic testing is usually time-consuming and costly, and may not be executed without a proper hardware environment. (3) Theorem proving: Theorem proving methods use mathematical logic to prove that a program conforms to a specific specification. For assembly programs, theorem proving can provide extremely high verification accuracy, especially for verifying the core algorithms of critical systems and components with high safety requirements. However, it requires highly specialized knowledge and is time-consuming and complex to construct formal proofs, especially for complex assembly programs or binary files without source code, where it is very challenging to accurately express their logic as mathematical models. (4) Model checking: Model checking verifies the correctness of a program by building a behavioral model of an assembly program or binary file and exploring all possible states. This approach is particularly suitable for program verification that deals with complex interactive behaviors because it can systematically analyze all possible program states. The automation capability of model checking makes it a powerful tool in modern software verification, especially when it is necessary to verify the correctness of a program's response to various inputs and events.However, the success of model checking depends heavily on the accuracy and completeness of the model, which requires careful design of the model to ensure that all key program behaviors and possible state changes are covered.
[0005] In summary, the shortcomings of existing verification methods and tools mainly lie in the insufficient understanding of the underlying language and compiled code, and they cannot fully cover all potential errors in these programs. Summary of the invention
[0006] Technical problem to be solved by the present invention: In view of the above-mentioned problems in the prior art, a binary program verification method and system for RISC-V architecture are provided. The present invention aims to verify the correctness of RISC-V assembler programs and binary files in an efficient, comprehensive and automated manner, thereby improving the reliability and security of the software before actual deployment.
[0007] In order to solve the above technical problems, the technical solution adopted by the present invention is:
[0008] A binary program verification method for RISC-V architecture, comprising the following steps:
[0009] S101, obtaining an input source file, wherein the source file is an ELF binary file;
[0010] S102, parsing the input source file using an ELF file processor and loading it into a simulator, and providing a runtime simulation of an actual hardware environment for the loaded source file through the simulator;
[0011] S103, using the RISC-V instruction decoder to extract binary instructions of corresponding length from the memory, and decoding them according to the RISC-V encoding rules to obtain specific assembly instructions and their operands;
[0012] S104, converting the decoded assembly instructions and their operands into a C language code file using an assembly-to-C converter;
[0013] S105, using a verification logic generator to perform verification tool compatibility processing on the converted C language code file;
[0014] S106, using a C program model checker CBMC to verify the C language code file and generate a verification result;
[0015] Before step S101, the method further includes compiling a source code file in the form of an assembler, a high-level language inline assembler language program, or a high-level language program into an ELF binary file; when compiling the source code file in the form of an assembler, a high-level language inline assembler language program, or a high-level language program into an ELF binary file, the method includes processing assertion statements:
[0016] S201, obtaining an input source code file;
[0017] S202, read the source code file line by line, if the reading is completed, it is determined that the assertion statement processing is completed and exit; otherwise, jump to step S203;
[0018] S203, determining whether there is an uncommented assertion statement in the line, if there is an uncommented assertion statement, jumping to step S204; otherwise, jumping to step S202 to continue reading the source code file line by line;
[0019] S204, record the serial number i of the assertion statement in the line, declare the i-th global variable assert_reti to be 0, replace the assert statement with an if statement, and set the value of the i-th global variable assert_reti to 1 in the if statement to indicate that the conditional expression in the assert statement is established.
[0020] Optionally, after step S101 and before step S102, the following step of performing global variable processing on a source file in the form of an ELF binary file is also included:
[0021] For the input source file in the form of an ELF binary file, declare a structure array gv to store the global variable information of the source file in the form of an ELF binary file, including variable name, variable value and variable address;
[0022] Parse and read the global variables in the source file in the form of ELF binary file one by one. If the global variables in the source file in the form of ELF binary file are successfully read, the process continues; otherwise, the process ends; if all global variables are read, the reading ends and the name and memory address information of the global variables are saved in the structure array gv.
[0023] Optionally, when the input source file is parsed by the ELF file processor and loaded into the simulator in step S102, the memory allocation area of the simulator includes the simulator reserved area, the ELF file program segment, the ELF file stack space and the ELF file heap space, and the data segment .data, the code segment .text and the read-only data segment .rodata obtained by parsing the input source file by the ELF file processor are loaded into the ELF file program segment respectively, and the position in the virtual address space is determined by the base address Base+entry address Entry, wherein the base address Base is the base address of the starting space representing the ELF file operation, and the entry address Entry is the offset of the segment starting point of the data segment .data, the code segment .text and the read-only data segment .rodata relative to the beginning of the file.
[0024] Optionally, in step S103, a RISC-V instruction decoder is used to extract a binary instruction of a corresponding length from the memory, and a specific assembly instruction and its operands are obtained by decoding according to the encoding rules of RISC-V, including:
[0025] S301, obtain the instruction word from the program counter PC and store it in the instruction register to prepare for decoding;
[0026] S302, extracting the operation code Opcode of the instruction word from the instruction register and identifying the instruction format;
[0027] S303, determining the instruction type according to the operation code Opcode of the instruction word;
[0028] S304, extracting the fields of the instruction according to the instruction type, including the function code, source register, target register and immediate value;
[0029] S305, obtaining a specific assembly instruction and its operands according to the operation code Opcode and the extracted fields.
[0030] Optionally, in step S104, using an assembly-to-C converter to convert the decoded assembly instructions and their operands into a C language code file includes:
[0031] S401, setting the program counter PC to point to the position of the base address Base + entry address Entry to prepare to read the assembly instruction, wherein the base address Base is the base address of the starting space representing the ELF file operation, and the entry address Entry is the offset of the segment starting point of the data segment .data, the code segment .text and the read-only data segment .rodata relative to the beginning of the file;
[0032] S402, if the value of the program counter PC is 0, it is determined that the conversion is completed and jumps to step S105; otherwise, a 16-bit or 32-bit binary instruction is read from the address pointed to by the program counter PC, wherein the length of the 16-bit or 32-bit data read is determined according to whether the instruction is a compressed instruction;
[0033] S403, decoding the read 16-bit or 32-bit binary instruction using the RISC-V instruction decoder to obtain a specific assembly instruction and its operands;
[0034] S404, check whether the program has reached the end. If the program has reached the end, the process ends and jumps to step S105; otherwise, jumps to step S405;
[0035] S405, determine whether the decoded assembly instruction is a jalr or ecall instruction, if it is a jalr or ecall instruction, jump to step S406; otherwise, the assembly-to-C converter converts the assembly instruction and its operands into C language code and appends them to the buffer string to simulate execution through the simulator, adds 2 or 4 to the value of the program counter PC to point to the next instruction, and jumps to step S402;
[0036] S406, determine whether the decoded assembly instruction is an ecall instruction, if it is an ecall instruction, jump to step S407; otherwise, determine it as a processing system call of the real machine, append the system call function to the buffer string, add 2 or 4 to the value of the program counter PC to point to the next instruction, and jump to step S402;
[0037] S407, append the C function end string to the buffer string;
[0038] S408, output the buffered string to the C language code file; clear the buffered string, write out the initial string, add 2 or 4 to the value of the program counter PC to make it point to the next instruction, and jump to step S402.
[0039] Optionally, in step S405, the assembly-to-C converter converts the assembly instruction and its operands into C language code, including:
[0040] S501, initializing a data structure, the data structure including a register structure and a structure array addr_val for storing global variable addresses and values;
[0041] S502, identifying the type of the assembly instruction, where the type of the assembly instruction is storage, load, operation, direct jump, indirect jump or system call;
[0042] S503, converting the assembly instruction and its operands into C language code based on the type of the assembly instruction:
[0043] If the type of the assembly instruction is a storage instruction, converting the assembly instruction and its operands into C language code includes: step a: reading the target address of the instruction operation of the assembly instruction; step b: if the target address is in the stack space, converting the assembly instruction and its operands into C language code for data storage operation, ending and exiting; otherwise jumping to step c; step c: if the target address is the storage address of a global or static variable, converting the assembly instruction and its operands into C language code for data storage operation, ending and exiting; otherwise jumping to step d; step d: determining whether the target address is passed through a parameter register, if so, performing a data storage operation, ending and exiting; otherwise throwing an exception; the data storage operation is used to write corresponding data into the memory pointed to by the address, and the throwing of the exception is used to avoid runtime errors caused by illegal memory access;
[0044] If the type of the assembly instruction is a load instruction, converting the assembly instruction and its operands into C language code includes: step a: reading the target address of the instruction operation of the assembly instruction; step b: if the target address is located in the stack space, converting the assembly instruction and its operands into C language code for data loading operation, ending and exiting; otherwise jumping to step c; step c: if the target address is the storage address of a global or static variable, converting the assembly instruction and its operands into C language code for loading data from a global variable, ending and exiting; otherwise jumping to step d; step d: determining whether the target address is passed through a parameter register, if so, performing a data storage operation, ending and exiting; otherwise throwing an exception;
[0045] If the type of the assembly instruction is a direct jump instruction JAL, the direct jump instruction JAL is used to implement a function call, and jumps to a target address through a command form of JAL ra, offset, wherein offset is a target address offset, and the return address is saved in a return address register ra; converting the assembly instruction and its operands into C language code comprises: step a: reading an operation register of the direct jump instruction JAL; step b: determining whether the operation register of the direct jump instruction JAL is a return address register ra, if it is a return address register ra, calculating and recording the return address and saving it in a register of a simulated return address register ra, and pushing the return address into a function call stack for use when the function returns; otherwise, jumping to step c; step c: adding the value of the program counter PC to the target address offset offset, and converting the jump logic of the direct jump instruction JAL into a goto target_address statement in C language;
[0046] If the type of the assembly instruction is an indirect jump instruction JALR, the indirect jump instruction JALR is used to realize function return through the pseudo instruction ret, converting the assembly instruction and its operands into C language code includes: step a: reading the operation field of the indirect jump instruction JALR, including registers and immediate values; step b: judging whether the indirect jump instruction JALR is a function return instruction, if it is a function return instruction, popping the most recently saved return address from the function call stack, updating the popped address to the ra register variable in the simulator, and converting the return operation into a goto ra statement in the C language; otherwise, calculating the jump address, the jump address is the base register plus an offset, and saved in a temporary variable target_address, generating a goto target_address statement in the C language to execute the actual jump to the address of the temporary variable target_address;
[0047] If the type of the assembly instruction is an operation or a system call, the operation or the system call is directly converted into C language code according to the conversion relationship table.
[0048] Optionally, in step S106, using the verification logic generator to perform verification tool compatibility processing on the converted C language code file includes:
[0049] S601, declare and initialize the global variables identified and recorded in the preprocessing stage, declare and assign values to the global variables at the beginning of the C file, and ensure that they can be correctly identified and used by the verification tool;
[0050] S602, declare and initialize basic memory resources and variables, including general registers and stack space, initialize the stack pointer register to point to the end address of the stack space, and initialize all other registers to zero;
[0051] S603, encapsulate the C program converted by the assembly-to-C converter into a function with no return value type, and add it here;
[0052] S604, adding a main function main in the converted C code to call the converted function;
[0053] S605, insert an assertion statement into the main function main, the conditional expression is whether the value of the global variable corresponding to the assertion statement assert is 1, and ensure that the number of assertion statements assert is consistent with that in the assembly file.
[0054] In addition, the present invention also provides a binary program verification system for RISC-V architecture, comprising a microprocessor and a memory connected to each other, wherein the microprocessor is programmed or configured to execute the binary program verification method for RISC-V architecture.
[0055] In addition, the present invention also provides a computer-readable storage medium, which stores a computer program or instruction, and the computer program or instruction is programmed or configured to execute the binary program verification method for the RISC-V architecture through a processor.
[0056] In addition, the present invention also provides a computer program product, including a computer program or instructions, which are programmed or configured to execute the binary program verification method for the RISC-V architecture through a processor.
[0057] Compared with the prior art, the present invention mainly has the following advantages:
[0058] 1. The present invention comprises parsing an input source file by using an ELF file processor, loading the file into a simulator and simulating execution; extracting binary instructions of corresponding length from a memory by using a RISC-V instruction decoder, decoding the instructions to obtain specific assembly instructions and their operands; converting the decoded assembly instructions and their operands into C language code files by using an assembly-to-C converter; performing verification tool compatibility processing on the converted C language code files by using a verification logic generator; and verifying the C language code files by using a model checker CBMC of a C program and generating verification results. The present invention can verify the correctness of RISC-V assemblers and binary files in an efficient, comprehensive and automated manner, thereby improving the reliability and security of the software before actual deployment.
[0059] 2. The present invention can support the verification of C language, RISC-V architecture assembly language, C and assembly mixed source code, and ELF binary files. In theory, it can also support program verification of more compiled languages through simple extensions. For example, by adding specific language parsers and adapters, the tool can easily adapt to other programming languages such as Rust and Fortran. BRIEF DESCRIPTION OF THE DRAWINGS
[0060] Figure 1 Schematic diagram of the basic flow of the method of the embodiment of the present invention.
[0061] Figure 2 Schematic diagram of the basic module structure of the verification tool in an embodiment of the present invention.
[0062] Figure 3 The figure is a basic flow chart of processing assertion statements in an embodiment of the present invention.
[0063] Figure 4 4 is a flowchart of processing global variables in an embodiment of the present invention.
[0064] Figure 5 Schematic diagram of simulator memory allocation in an embodiment of the present invention.
[0065] Figure 6 This is a workflow diagram of the RISC-V decoder in an embodiment of the present invention.
[0066] Figure 7 The following is a flowchart of the assembly-to-C converter in an embodiment of the present invention.
[0067] Figure 8 The following is a simplified diagram of the conversion process of six types of assembly instructions into C code in an embodiment of the present invention.
[0068] Fig. 9 The figure is a conversion flow chart of storage instructions in an embodiment of the present invention.
[0069] Fig.10 The figure is a conversion flow chart of a load instruction in an embodiment of the present invention.
[0070] Fig.11 4 is a conversion flow chart of a direct jump instruction in an embodiment of the present invention.
[0071] Fig.12 4 is a conversion flow chart of an indirect jump instruction in an embodiment of the present invention.
[0072] Fig.13 The following is a flowchart of the verification logic generator in an embodiment of the present invention. DETAILED DESCRIPTION
[0073] like Figure 1 and Figure 2 As shown, the binary program verification method for the RISC-V architecture in this embodiment includes the following steps:
[0074] S101, obtaining an input source file, wherein the source file is an ELF binary file;
[0075] S102, parsing the input source file using an ELF file processor and loading it into a simulator, and providing a runtime simulation of an actual hardware environment for the loaded source file through the simulator;
[0076] S103, using the RISC-V instruction decoder to extract binary instructions of corresponding length from the memory, and decoding them according to the RISC-V encoding rules to obtain specific assembly instructions and their operands;
[0077] S104, converting the decoded assembly instructions and their operands into a C language code file using an assembly-to-C converter;
[0078] S105, using a verification logic generator to perform verification tool compatibility processing on the converted C language code file;
[0079] S106, using the C program model checker CBMC to verify the C language code file and generate a verification result.
[0080] Figure 1 The workflow of the improved software verification tool proposed in this application is shown. The entire process is specifically for verifying programs or codes of the RISC-V architecture. Source file: The code file to be verified, which can be compiled into an executable program, or an executable program that is already in the ELF (Executable and Linkable Format) format. ELF file processor: The function of the ELF identifier is to convert the provided source file into an executable ELF format file. It further parses the ELF file, determines the instruction set type used by the file, identifies the various segments of the program and their addresses in the memory, and is responsible for loading these segments into the simulator for subsequent processing. Simulator: The simulator is written in C language, which accurately simulates the hardware components required for program execution, such as registers, to provide a runtime simulation close to the actual hardware environment. RISC-V instruction decoder: The tool at this stage is responsible for extracting binary instructions of corresponding length from the memory, and decoding them according to the encoding rules of RISC-V to obtain specific assembly instructions and their operands. Assembly to C converter: While the simulator executes the instructions, the converter converts the executing assembly instructions into corresponding C language codes in real time and saves them. Verification Logic Generator: Further processes and optimizes the converted C code to ensure that it is compatible with the verification tool, thereby achieving effective verification of the program. CBMC: This stands for C Bounded Model Checker, which is the core verification component in the process and an existing verification tool. It uses various model checking techniques to confirm whether the program meets specific specifications and properties to ensure the correctness of the code. Figure 2 The detailed working mechanism of the verification tool designed in this embodiment is described in depth. Figure 1 Supplement and refine the overall architecture. Input: The process accepts two forms of input, which can be source code files or executable files that have been compiled into ELF format. Core tools: This process involves multiple core modules, including ELF parser, RISC-V instruction decoder, and assembly to C converter. These tools work together to ensure that the source code can be correctly converted to C language representation. Output: After formal verification by CBMC, the verification results are output to indicate whether the code meets the predefined specifications. See Figure 2 In this embodiment, before step S101, the process also includes compiling a source code file in the form of an assembler program, a high-level language inline assembly language program, or a high-level language program into an ELF binary file.
[0081] Figure 3The figure shows the processing flow of assert statements, which shows different stages such as inputting the program file to be verified, reading the file line by line, replacing the assert statement, recording the serial number i of the assert statement, checking the uncommented assert statement, and declaring global variables. Each stage represents the specific operation that the processing program needs to perform when processing the assert statement. These stages are interconnected through a series of conditional judgments and variable settings to ensure that the assert statement can be accurately processed and recorded. Input the program file to be verified: the user inputs the program file to be verified. Read the file line by line: read the program file content entered by the user to be verified line by line. Whether there is an uncommented assert statement in the line: determine whether the line exists or whether the assert statement is valid. If it is valid, record it. If it does not exist or is invalid, read the next line. Record the serial number i of the assert statement: record the serial number of the assert statement currently being processed, that is, the number of the assert language in the current file. Declare the global variable assert_ret1=0: After convenient conversion to a verifiable C program, the global variable assert_reti is used to determine whether the assertion is true. The if statement replaces the assert statement. In the if statement, the global variable assert_reti=1 is set, indicating that the conditional expression in the assert statement is true and the global variable corresponding to the assert statement is modified.
[0082] In this embodiment, when compiling a source code file in the form of an assembler, a high-level language inline assembly language program, or a high-level language program into an ELF binary file, the following steps are performed to process assert statements:
[0083] S201, obtaining an input source code file;
[0084] S202, read the source code file line by line, if the reading is completed, it is determined that the assertion statement processing is completed and exit; otherwise, jump to step S203;
[0085] S203, determining whether there is an uncommented assertion statement in the line, if there is an uncommented assertion statement, jumping to step S204; otherwise, jumping to step S202 to continue reading the source code file line by line;
[0086] S204, record the serial number i of the assertion statement in the line, declare the i-th global variable assert_reti to be 0, replace the assert statement with an if statement, and set the value of the i-th global variable assert_reti to 1 in the if statement to indicate that the conditional expression in the assert statement is established.
[0087] Figure 4 The process of processing global variables in ELF files is presented. Figure 4In the figure, different stages such as input ELF binary file, declaration of global variable type and structure array, reading global variables in ELF file, and preserving the name and memory address of global variables are shown. Each stage represents the specific operation required for processing program to carry out when processing global variables in ELF binary file. These stages are interconnected by a series of conditional judgments and data storage, guaranteeing that global variables can be accurately read and preserved. After the step S101 of the present embodiment, before S102, also include the following steps of carrying out global variable processing for the source file in ELF binary file form:
[0088] For the input source file in the form of an ELF binary file, declare a structure array gv to store the global variable information of the source file in the form of an ELF binary file, including variable name, variable value and variable address;
[0089] Parse and read the global variables in the source file in the form of ELF binary file one by one. If the global variables in the source file in the form of ELF binary file are successfully read, the process continues; otherwise, the process ends; if all global variables are read, the reading ends and the name and memory address information of the global variables are saved in the structure array gv.
[0090] Figure 5 This is a schematic diagram of simulator memory allocation in this embodiment. Figure 5 The mapping mechanism of ELF files in memory is presented in detail, showing various data segments in ELF files, such as .data, .text, and .rodata, which represent different types of program data. These segments will be mapped to the virtual address space of the process through the mmap system call. At the base address (Base) of the virtual address space, this represents the starting space for ELF program operations. After the base address, the specific locations of the different segments of the ELF file in memory are determined by Base+Entry, where Entry represents the offset of the segment starting point relative to the beginning of the file. Figure 5The banner at the bottom provides an overview of the virtual address space, showing the layout of the reserved area, program segment, stack and heap space, and each arrow indicates the mapping path of the ELF file segment to the virtual address space. In step S102 of this embodiment, when the input source file is parsed and loaded into the simulator using the ELF file processor, the memory allocation area of the simulator includes the simulator reserved area, the ELF file program segment, the ELF file stack space and the ELF file heap space, and the data segment .data, the code segment .text and the read-only data segment .rodata obtained by parsing the input source file using the ELF file processor are loaded into the ELF file program segment respectively, and the position in the virtual address space is determined by the base address Base+entry address Entry, wherein the base address Base is the base address of the starting space representing the ELF file operation, and the entry address Entry is the offset of the segment starting point of the data segment .data, the code segment .text and the read-only data segment .rodata relative to the beginning of the file.
[0091] Figure 6 This is the workflow diagram of the RISC-V decoder in this embodiment. Figure 6 The decoding process of RISC-V instructions is presented in detail, showing different stages such as instruction acquisition, instruction decoding, register reading, execution, memory access and writeback. Each stage represents the specific operation that the processor needs to perform when executing instructions. These stages are interconnected by a series of signals and control logic to ensure that instructions can be accurately decoded and executed. Through detailed step-by-step analysis, the figure clearly shows the processing mechanism of instructions in the RISC-V architecture. Figure 6 As shown, in step S103 of this embodiment, a RISC-V instruction decoder is used to extract a binary instruction of corresponding length from the memory, and the specific assembly instruction and its operands are decoded according to the RISC-V encoding rules, including:
[0092] S301, obtain the instruction word from the program counter PC and store it in the instruction register to prepare for decoding;
[0093] S302, extracting the operation code Opcode of the instruction word from the instruction register and identifying the instruction format;
[0094] S303, determining the instruction type according to the operation code Opcode of the instruction word;
[0095] S304, extracting the fields of the instruction according to the instruction type, including the function code, source register, target register and immediate value;
[0096] S305, obtaining a specific assembly instruction and its operands according to the operation code Opcode and the extracted fields.
[0097] Figure 7 The specific process of the assembly-to-C converter when processing instructions is shown in detail. The figure is divided into multiple steps, including setting the program counter (PC), reading instructions, decoding instructions, determining the instruction type, and performing corresponding operations. Each stage represents the specific operations that the simulator needs to perform when processing and executing instructions. The simulator PC register points to the Base+Entry address: Set the program counter (PC) to point to the location of the base address plus the entry address, and prepare to read the instruction. Read the 16 / 32-bit binary number pointed to by the PC: Read a 16-bit or 32-bit binary instruction from the address pointed to by the program counter. The data length is determined based on whether the instruction is a compressed instruction. Decode to obtain specific assembly instructions: Decode the read binary instructions using the RISC-V instruction decoder to obtain specific assembly instructions. Program end judgment: Check whether the program has reached the end. If so, the process ends. Is it a jalr or ecall instruction: Determine whether the decoded instruction is a jalr or ecall instruction. If so, enter the relevant processing branch; otherwise, continue processing other instructions. The assembly to C converter converts and appends it to the buf string, PC+=2 / 4: converts the assembly instruction into C language code and appends it to the buf string, and adds 2 or 4 to the PC to point to the next instruction. Clear the buf string and write out the initial string, PC+=2 / 4: clear the buf string and write out the initial string, and add 2 or 4 to the PC to point to the next instruction. Is it an ecall instruction: determine whether the current instruction is a system call instruction. If so, enter the system call processing branch; otherwise, enter other system call processing. Append the system call function to the buf string, PC+=2 / 4: according to the identified system call type, append the system call function to the buf string, and add 2 or 4 to the PC to point to the next instruction. Real machine processes system calls: The actual machine processes the system call and returns the processing result to the simulator. Such as Figure 7 As shown, in step S104 of this embodiment, using the assembly-to-C converter to convert the decoded assembly instructions and their operands into a C language code file includes:
[0098] S401, setting the program counter PC to point to the position of the base address Base + entry address Entry to prepare to read the assembly instruction, wherein the base address Base is the base address of the starting space representing the ELF file operation, and the entry address Entry is the offset of the segment starting point of the data segment .data, the code segment .text and the read-only data segment .rodata relative to the beginning of the file;
[0099] S402, if the value of the program counter PC is 0, it is determined that the conversion is completed and jumps to step S105; otherwise, a 16-bit or 32-bit binary instruction is read from the address pointed to by the program counter PC, wherein the length of the 16-bit or 32-bit data read is determined according to whether the instruction is a compressed instruction;
[0100] S403, decoding the read 16-bit or 32-bit binary instruction using the RISC-V instruction decoder to obtain a specific assembly instruction and its operands;
[0101] S404, check whether the program has reached the end. If the program has reached the end, the process ends and jumps to step S105; otherwise, jumps to step S405;
[0102] S405, determine whether the decoded assembly instruction is a jalr or ecall instruction, if it is a jalr or ecall instruction, jump to step S406; otherwise, the assembly-to-C converter converts the assembly instruction and its operands into C language code and appends them to the buffer string to simulate execution through the simulator, adds 2 or 4 to the value of the program counter PC to point to the next instruction, and jumps to step S402;
[0103] S406, determine whether the decoded assembly instruction is an ecall instruction, if it is an ecall instruction, jump to step S407; otherwise, determine it as a processing system call of the real machine, append the system call function to the buffer string, add 2 or 4 to the value of the program counter PC to point to the next instruction, and jump to step S402;
[0104] S407, append the C function end string to the buffer string;
[0105] S408, output the buffered string to the C language code file; clear the buffered string, write out the initial string, add 2 or 4 to the value of the program counter PC to make it point to the next instruction, and jump to step S402.
[0106] Figure 8This is a simplified flow chart of the conversion of six types of assembly instructions into C code in this embodiment, which shows in detail the general flow of the conversion of six types of assembly instructions into C code. The figure is divided into multiple steps, including initialization, identification of instruction type, generation of corresponding C code, simulation execution, and update of program counter (PC). Create data group, register structure, addr_val structure array: initialize data structure, the data structure includes register structure and structure array addr_val for storing global variable address and value. Identify instruction type: Identify the type of instruction, including storage, load, operation, direct jump, indirect jump, system call, etc. Generate corresponding C code: Generate corresponding C code for the identified and processed instructions. Simulator simulation execution: The simulator simulates the execution of the corresponding assembly instruction. pc = pc + 2 / 4: Update the value of the program counter PC to point to the next instruction (compressed instruction: pc = pc + 2, ordinary instruction: pc = pc + 4); *pc ! = 0: Check whether the PC value is 0, that is, whether all instructions have been processed. If it is 0, end the process; otherwise, continue to execute the next instruction. Specifically, in step S405 of this embodiment, the assembly-to-C converter converts the assembly instruction and its operands into C language code, including:
[0107] S501, initializing a data structure, the data structure including a register structure and a structure array addr_val for storing global variable addresses and values;
[0108] S502, identifying the type of the assembly instruction, where the type of the assembly instruction is storage, load, operation, direct jump, indirect jump or system call;
[0109] S503: Convert the assembly instruction and its operands into C language code based on the type of the assembly instruction.
[0110] In step S503 of this embodiment, when the assembly instruction and its operands are converted into C language code based on the type of the assembly instruction:
[0111] like Fig. 9As shown, if the type of the assembly instruction is a storage instruction, converting the assembly instruction and its operands into C language code includes: step a: reading the target address of the instruction operation of the assembly instruction; step b: if the target address is located in the stack space, converting the assembly instruction and its operands into C language code for data storage operation, ending and exiting; otherwise jumping to step c; step c: if the target address is the storage address of a global or static variable, converting the assembly instruction and its operands into C language code for data storage operation, ending and exiting; otherwise jumping to step d; step d: determining whether the target address is passed through a parameter register, if so, performing a data storage operation, ending and exiting; otherwise throwing an exception; the data storage operation is used to write corresponding data into the memory pointed to by the address, and the throwing of the exception is used to avoid runtime errors caused by illegal memory access.
[0112] like Fig.10 As shown, if the type of the assembly instruction is a load instruction, converting the assembly instruction and its operands into C language code includes: step a: reading the target address of the instruction operation of the assembly instruction; step b: if the target address is in the stack space, executing the data loading operation, converting the assembly instruction and its operands into C language code for the data loading operation, ending and exiting; otherwise jumping to step c; step c: if the target address is the storage address of a global or static variable, executing loading data from the global variable, converting the assembly instruction and its operands into C language code for loading data from the global variable, ending and exiting; otherwise jumping to step d; step d: determining whether the target address is passed through the parameter register, if so, performing the data storage operation, ending and exiting; otherwise throwing an exception.
[0113] Fig.11 The specific process of converting JAL instructions into C code in the compiler is shown in detail, such as Fig.11As shown, if the type of the assembly instruction is a direct jump instruction JAL, the direct jump instruction JAL is used to implement a function call, and jumps to the target address in the form of a command of JALra, offset, where offset is the target address offset, and the return address is saved in the return address register ra; converting the assembly instruction and its operands into C language code includes: step a: reading the operation register of the direct jump instruction JAL; step b: judging whether the operation register of the direct jump instruction JAL is the return address register ra (referred to as ra register in the figure), if it is the return address register ra, then calculating and recording the return address and saving it in the register of the simulated return address register ra, and pushing the return address into the function call stack for use when the function returns; otherwise, jump to step c; step c: adding the value of the program counter PC to the target address offset offset, and converting the jump logic of the direct jump instruction JAL into the goto target_address statement in the C language; the JAL instruction implements a function call by jumping to the target address in the form of JAL ra, offset, and saving the return address in the ra register. Identifying the JAL instruction operation register: detecting the type of the current JAL instruction operation register. If the register operated by the JAL instruction is the ra register (return address register ra), the return address (pc+2 / 4) is recorded and saved in the variable simulating the ra register, and then the return address is pushed into the function call stack for use when the function returns. Finally, the value of the program counter PC is added to the target address offset offset, and the jump logic of the direct jump instruction JAL is converted into the goto target_address statement in the C language.
[0114] like Fig.12 As shown, if the type of the assembly instruction is an indirect jump instruction JALR, the indirect jump instruction JALR is used to realize function return through the pseudo instruction ret, and converting the assembly instruction and its operands into C language code includes: step a: reading the operation field of the indirect jump instruction JALR, including registers and immediate values; step b: judging whether the indirect jump instruction JALR is a function return instruction, if it is a function return instruction, popping the most recently saved return address from the function call stack, updating the popped address to the ra register variable in the simulator, and converting the return operation into a goto ra statement in the C language; otherwise, calculating the jump address, the jump address is the base register plus an offset, and saved in a temporary variable target_address, generating a goto target_address statement in the C language to execute the actual jump to the address of the temporary variable target_address; Fig.12The specific processing flow when converting JALR instructions to C code in the compiler is shown in detail. JALR instructions are often used to implement function returns (through the pseudo-instruction ret) and other jump operations. Read JALR instruction operation fields: Read the registers and immediate values operated by the current JALR instruction. Is it a function return pseudo-instruction (ret): Determine whether the JALR instruction is a function return instruction JALR x0, 0(ra). Pop the function call stack: Pop the most recently saved return address from the function call stack. Update ra register: Update the popped address to the ra register variable in the simulator. Generate goto ra statement: Convert the return operation to the goto ra statement in C language. Calculate the jump address: Calculate the jump address, usually the base register plus an offset, and save it in the temporary variable target_address. Generate gototarget_address statement: Generate the goto target_address statement in C language to perform the actual jump.
[0115] If the type of the assembly instruction is an operation or a system call, the operation or the system call is directly converted into C language code according to the conversion relationship table.
[0116] Fig.13 The specific processing flow of the verification logic generator when processing the program converted from assembly to C code is shown in detail. The main function of the verification logic generator is to further process and expand the converted C code so that these codes not only maintain the integrity of their original logic, but also adapt to and make full use of the verification tool for system verification. Declare and initialize global variables: declare and initialize the global variables identified and recorded in the preprocessing stage. Declare and assign global variables at the beginning of the C file to ensure that they can be correctly identified and used by the verification tool. Declare basic memory resources and variables: declare and initialize basic memory resources and variables, including general registers and stack space. Initialize the stack pointer register to point to the end address of the stack space, and initialize all other registers to zero. Add converted C code function: encapsulate the C program converted by the assembly to C converter into a function with no return value type and add it here. Add main function: add the main function in the converted C code to call the converted function. Insert assert statement: insert the assert statement in the main function, and the conditional expression is whether the value of the global variable corresponding to the assert statement is 1. Ensure that the number of assert statements is consistent with that in the original assembly file. For example Fig.13 As shown, in step S106, using the verification logic generator to perform verification tool compatibility processing on the converted C language code file includes:
[0117] S601, declare and initialize the global variables identified and recorded in the preprocessing stage, declare and assign values to the global variables at the beginning of the C file, and ensure that they can be correctly identified and used by the verification tool;
[0118] S602, declare and initialize basic memory resources and variables, including general registers and stack space, initialize the stack pointer register to point to the end address of the stack space, and initialize all other registers to zero;
[0119] S603, encapsulate the C program converted by the assembly-to-C converter into a function with no return value type, and add it here;
[0120] S604, adding a main function main in the converted C code to call the converted function;
[0121] S605, insert an assertion statement into the main function main, the conditional expression is whether the value of the global variable corresponding to the assertion statement assert is 1, and ensure that the number of assertion statements assert is consistent with that in the assembly file.
[0122] This embodiment uses CBMC (C Bounded Model Checker) as the core verification tool to provide formal code verification, ensure the correctness of code logic and effectively predict potential bugs. This function is particularly valuable when developing system software that needs to comply with strict safety standards, and helps to discover hidden programming errors and potential security vulnerabilities. CBMC is a model checker for C programs that specifically checks for errors such as array out-of-bounds, pointer errors, and data races in programs. CBMC's formal verification method can predict potential runtime errors and verify the correctness of program logic.
[0123] In summary, the core function of the verification tool in the method of this embodiment is to convert the source code to be verified into a verifiable C language code through a series of rigorous analysis and processing steps, and then use CBMC (C Bounded ModelChecker) for formal verification. At present, this tool already supports the verification of C language, RISC-V architecture assembly language, C and assembly mixed source code, and ELF binary files. In theory, this tool can support program verification of more compiled languages through simple extensions. For example, by adding specific language parsers and adapters, the tool can easily adapt to other programming languages such as Rust and Fortran. Next, we will use Figure 1 As the core, the details and logic of each design step are elaborated.
[0124] 1. Input: This tool supports multiple file types as input, including C language files, assembly language files, and ELF files. It has the ability to automatically identify file types and delegate the corresponding files to a dedicated RISC-V instruction compiler for processing. Specifically, depending on the type of input file, the tool will automatically compile these files into binary files in ELF format. If additional static or dynamic libraries need to be linked during the compilation process, the compiler will automatically add the necessary library files. In addition, in order to support formal verification, this tool allows assert statements to be written directly in the C language code to define verification property specifications. These assert statements will be automatically recognized by the tool and integrated into the verifiable C language code so that the subsequent verification process can check these specifications.
[0125] 2. Preprocessing: Preprocessing is mainly aimed at C language and C and assembly language mixed programming. Its core purpose is to process specific structures in C language to optimize the subsequent verification process. First, the preprocessing stage will identify the assert statement in the C code and convert it into an if expression. The necessity of this conversion stems from the fact that the assert statement originally relied on assert.h in the C standard library, which contains a large number of system calls, which become extremely cumbersome and complicated after being compiled and expanded into assembly language. In contrast, the logical nature of the assert statement is relatively simple, so converting it into an if expression in the preprocessing stage can greatly simplify the subsequent processing flow. The specific implementation of this conversion process can be found in Figure 3 The process shown in . Secondly, the preprocessing stage also involves the processing of global variables. Before the program runs, global variables are usually saved in the .bss segment and the .data segment. When the high-level language is compiled into assembly language, the assembler will operate these memory addresses to load and write data. From the perspective of the converted program, writing data to an unknown memory address is an illegal operation. Therefore, the preprocessing needs to identify all declared global variables in the C code and establish a mapping relationship between their memory addresses to ensure legal access and operation of the data. For details on the specific implementation process of global variable mapping, see Figure 4 As shown. In addition, during the preprocessing stage, the tool will also identify and analyze all functions in the file to be verified to record the address information of each function. This includes searching and confirming whether there is a main function (main). If the main function is detected, the preprocessing will take special measures to set the simulator's program counter (PC) register to the starting address of the main function. This step ensures that the simulator can correctly start execution from the main function at startup and simulate the normal operation flow of the entire program. Through these preprocessing steps, it can be ensured that the source code is properly converted and simplified, thus laying the foundation for subsequent formal verification.
[0126] 3, ELF file conversion: in this process, the program code that has completed pretreatment will be sent to the corresponding RISC-V compiler for compilation, and the executable file of ELF format is generated. In the ELF file parsing stage, the processing flow includes compiling the pretreated code into a relocatable file of ELF format. Usually, these programs to be verified do not include main function, because what the present embodiment method pays attention to is the behavior of a certain part of the program code rather than the execution of the whole application program. Therefore, after compiling and generating the relocatable file, it is necessary to carry out specific post-processing to ensure that the file is converted into a legal ELF executable file. This conversion involves modifying the header information and segment mapping of the ELF file, and the specific operation is to set the first instruction address of the .text segment to the entry point address of the ELF file, because it defines the starting execution position when the operating system loads and starts to execute the program. By manually setting the entry point, the starting behavior of the program execution can be controlled to ensure the accurate construction of the verification environment and the correct start of the execution process.
[0127] 4. Simulator loading: The simulator loading stage is to load the compiled ELF executable file into the simulator so that it can simulate the normal operation of the program. The main purpose of this step is to solve the problem of location calculation of jump instructions in the program. Since the target addresses of many jump instructions are calculated based on the program counter (PC), it is quite complicated to process these jump logics when directly converted into verifiable C code. Therefore, the simulator needs to simulate the execution of each instruction and accurately calculate the specific destination of each jump to facilitate subsequent program conversion and verification. After obtaining the ELF executable file, the simulator loads the various segments of the ELF file into the corresponding virtual memory of the simulator through the mmap function based on the detailed information of the ELF file. At the same time, the simulator will also configure reasonable stack space and heap space. The detailed structure of these memory mappings can be referred to. Figure 5 The architecture of the simulator is as follows Figure 2 As described in the , it is divided into two parts: the computing unit and the memory unit. The computing unit contains a PC register that points to the address of the currently executed instruction; 32 general registers that are used to store data during the execution of instructions; and a flag register that records the reason for each PC register jump. The memory unit consists of the stack space and heap space required during program execution. When the simulator is initialized, the PC register is set to point to the address of the first instruction, and the stack pointer register (SP) points to the top of the stack to prepare for program execution.
[0128] 5. RISC-V instruction decoder decoding: The main task of the RISC-V instruction decoder is to convert the binary instruction sequence in the memory into specific operations that the processor can understand and execute. For the specific decoding process, please refer to Figure 6. The decoding process begins by reading the instruction from the address pointed to by the program counter (PC). The decoder first analyzes the lowest 7 bits of the instruction, namely the opcode, to preliminarily identify the type of instruction, such as R-Type, I-Type, S-Type, etc. The lowest two bits of the opcode are particularly important because they indicate the length of the instruction: the standard 32-bit instruction opcode ends with 11, while the 16-bit compressed instruction does not end with 11. For the 16-bit compressed instruction, the decoder decodes and expands it according to the rules of the RISC-V C extension, which includes mapping the shorter instruction format to the standard 32-bit instruction format, involving extended immediate values, adjusting register indexes, etc. Once the compressed instruction is identified and expanded, the decoder will continue to parse other key fields, such as register addresses and immediate values, according to the format of standard instructions. In addition, for J-Type and B-Type instructions involving program flow control, the decoder also needs to calculate the jump or branch target address, usually by adding the parsed immediate value to the current PC value to determine the destination of the jump. After all these steps are completed, the decoded instruction and its related operands and target information are sent to the execution unit for specific operation execution.
[0129] Assembly to C Converter Conversion: After the instruction decoder successfully decodes and determines the instruction type, the assembly to C converter processing flow will enter two key stages. First, depending on the specific semantics of the instruction, this stage may involve simulated execution in a simulator. This step is mainly used to verify and execute instructions, especially for jump instructions, simulated execution is necessary because it allows the system to record and determine the specific target address of the jump. The second stage involves converting the decoded assembly instructions into corresponding C language code. This conversion process relies on the semantic parsing of the instructions to map the assembly instructions into equivalent C language expressions. This includes not only basic calculations and logical operations, but also complex control flow structures and memory operations to ensure that the converted C program can reflect the behavior and intent of the original assembly program. The tool flow for this stage is as follows Figure 7As shown. The verification tool in this embodiment roughly divides RISC-V instructions into six categories for easy processing and conversion. These six categories include storage instructions, load instructions, operation instructions, direct jump instructions, indirect jump instructions and system call instructions. Storage instructions are mainly responsible for transferring data from registers to memory, while load instructions retrieve data from memory to registers. Operation instructions include various arithmetic and logical operations used to perform basic computing tasks. Direct jump instructions and indirect jump instructions control the flow of the program, where direct jump instructions provide a clear jump address, while indirect jumps rely on the value in the register to determine the jump target. Finally, system call instructions are used to implement interaction between programs and operating systems, such as requesting system services. Such classification helps the tool to more effectively identify and process different types of instructions during the conversion process, thereby ensuring that the converted C program can accurately simulate the behavior of the original assembly program. The conversion method and steps for each type of instruction will be described in detail below. The rough conversion process of the six-process instructions is as follows Figure 8 shown.
[0130] (1) Storage instructions: Storage instructions in RISC-V include SB (store one byte), SH (store halfword, i.e. two bytes), SW (store one word, i.e. four bytes), and SD (store doubleword, i.e. eight bytes in 64-bit mode). These instructions are responsible for reading data from a specific register and storing it in the memory location obtained through address operation. Different instructions have different processing data widths. The conversion process of storage instructions is as follows: Fig. 9 As shown. The main function of the storage instruction is to store data to the specified memory address. It is relatively simple to execute such instructions in the simulator because the memory layout of the program has been planned in advance. However, it is more complicated to convert these storage instructions into C language programs because writing data to unknown addresses is usually illegal in C language. In the process of converting storage instructions into C code, it is first necessary to determine the nature of the target memory address. If this address is located in the stack space or is a valid address passed through the parameter register (such as a0 to a7), then the data storage operation can be performed directly. However, if the target address is an undefined "strange address", it is necessary to further check whether this address is used as the storage of global or static variables. If the address is confirmed to be valid and associated with a defined global or static variable, then a normal storage operation is performed; if not, an exception should be thrown to avoid runtime errors caused by illegal memory access.
[0131] (2) Load instructions: In the RISC-V instruction set, load instructions are mainly responsible for reading data from memory and loading it into registers. These instructions include LB (Load Byte) and LBU (Load Byte Unsigned) for loading one byte, LH (Load Halfword) and LHU (Load Halfword Unsigned) for loading two bytes, LW (Load Word) and LWU (Load Word Unsigned) for loading four bytes, and LD (LoadDoubleword) for loading eight bytes in 64-bit mode. These load operations select appropriate instructions based on the data width and whether sign extension is required, such as LB and LH for signed extension, while LBU and LHU for unsigned extension, ensuring that the program can flexibly process the data in memory according to specific needs. The conversion process of the load instruction is as follows: Fig.10 As shown. When converting a load instruction, you first need to confirm whether the target address is associated with a valid variable. If the address is in the stack space, you can load data directly from this address, because the addresses in the stack space are usually associated with local variables or temporary data, and their validity is easier to verify. If the target address is not in the stack space, you need to further check all declared global variables to see if the load address matches the address of a global variable. If no matching address is found in the global variable, it means that an illegal address is being accessed, which should be treated as an exception or error, because accessing an illegal address may cause program runtime errors or data inconsistencies. On the contrary, if the address is valid and matches a global variable, then the value of the global variable should be loaded from the address to the specified register.
[0132] (3) Operation instructions: In the RISC-V instruction set, operation instructions are responsible for performing various arithmetic and logical operations, including basic addition (ADD, ADDI), subtraction (SUB), multiplication (MUL), and division (DIV, DIVU). These instructions enable the processor to perform a variety of operations from simple arithmetic to complex mathematical calculations. In addition, operation instructions also include logical operations such as bitwise AND, bitwise OR, bitwise XOR, and comparison operations (SLT, SLTU), which are used to perform basic logical judgments and data comparisons. Shift instructions such as left shift (SLL, SLLI) and right shift (SRL, SRLI, SRA, SRAI) are used for bit operations, supporting logical right shift and arithmetic right shift. Converting such operation instructions to C language programs is a relatively straightforward process. Since these instructions are very similar to the basic operations and logical operations in C language, they can be easily mapped directly to the corresponding operations in C language. For example, RISC-V addition, subtraction, multiplication and logical operations can be directly mapped to operators such as +, -, *, &&, || and ^ in C language. This direct correspondence simplifies the instruction conversion process, making it an efficient and low-error task to translate these instructions into C language code. Therefore, for this type of instruction, the method of this embodiment does not need to perform complex processing or conversion strategies, and can keep the conversion simple and intuitive.
[0133] (4) Direct jump instructions: In the RISC-V instruction set, direct jump instructions are key instructions for controlling the execution flow of programs. They allow the program to unconditionally jump to a specified code address for execution. This type of instruction is mainly JAL (Jump and Link). The JAL instruction performs an unconditional jump and saves the address of the next instruction after the jump to the link register. It is usually used for function calls so that the function can return to the call point after execution. In contrast, the JALR instruction allows the jump address to be determined by a base register plus an immediate offset, providing greater flexibility and supporting register-based dynamic address calculation. It is often used to return function calls and implement more complex control flow structures such as virtual function calls. The conversion process of direct jump instructions is as follows: Fig.11As shown. The common use of the JAL instruction in the compiler is to implement a function call (i.e., pseudo-instruction call), which is JAL ra, offset, which makes the program jump to the address specified by pc+offset for execution, and saves the address of the next instruction after the jump (pc+4) in ra (return address register). Therefore, the core task of converting this instruction is to record the return address value (pc+4), save it in a variable that simulates the ra register, then calculate the target address target_address = pc + offset, and convert this jump logic into the goto target_address statement in C language. Direct conversion of the JAL instruction may encounter problems, especially in the case of nested function calls, the address saved in the ra register may be overwritten by the new call, resulting in failure to correctly return to the original call location. To solve this problem, this tool designs a function call stack to record the return address after each program call. Whenever a function call is identified, the return address is pushed into the call stack. When it is detected that the function has been executed and needs to return, a pop operation is performed to ensure that the program position before the call can be accurately returned. For the case where the ra register is not involved in the JAL instruction, the instruction can be regarded as a simple unconditional jump instruction. In this case, the conversion process is very direct, and it only needs to be converted to gototarget_address to achieve an unconditional program execution flow jump.
[0134] (5) Indirect jump instruction: In the RISC-V instruction set, the indirect jump instruction JALR (Jump and Link Register) enables the program to execute a jump based on a calculated address, providing the flexibility to implement dynamic function calls or complex control flow structures. The JALR instruction sets the program counter (PC) to the value of the source register (usually rs1) plus an immediate offset, and saves the address of the next instruction (pc+4) to the target register (usually ra). The conversion process of the indirect jump instruction is as follows: Fig.11As shown in the figure. In the compiler, one of the common uses of the JALR instruction is to implement function returns, which is usually expressed by the pseudo-instruction ret, and its specific operation is JALR x0, 0(ra). This operation obtains the return address from the ra register and executes the return. Considering that in the case of nested function calls, the value in the ra register may be overwritten by subsequent calls. In order to ensure that it can correctly return to the position before the call, specific stack operations are required before converting such instructions. Specifically, it is necessary to pop the most recently saved address from the function call stack and update this address to the ra register variable in the simulator. After completing this step, the conversion process involves converting this return operation into a gotora statement in C language. This processing ensures that each function return can accurately return to the correct call point, thereby maintaining the continuity and correctness of the program execution flow. For other types of JALR instructions, the first step to be performed is to calculate the jump address, which usually involves adding an offset from a base register. After the address calculation is completed, the address after the current instruction (i.e., the return address) must be saved, and the actual jump must be performed through the goto statement in C language. This process ensures that the program can jump to the calculated target address in the predetermined logical order, while preserving the return path so that future execution flow can correctly return to the location before the jump.
[0135] (6) System call instructions: In the RISC-V instruction set, system call instructions act as a communication bridge between applications and the operating system, allowing applications to request services provided by the operating system. This includes two main instructions: ecall and ebreak. ecall is an environment call instruction used to initiate system service requests from the application layer, such as process control or file operations. The specific service is specified by the parameters in the register. After execution, control is transferred to the operating system to handle the request. ebreak is mainly used for debugging. When this instruction is executed, the program enters an interrupt state, allowing developers to check and modify the program status. In this tool, the method of converting system call instructions is as follows: Figure 6As shown in the figure. Since the simulator cannot process system calls directly, whenever a system call instruction is encountered, this tool will forward it to the real machine for processing, and then return the result to the virtual machine after processing. However, considering that the verification tool CBMC has relatively limited support for system call instructions, this tool currently only supports a few system calls, such as write, read, and exit. The conversion process first involves detecting the system call number stored in the a7 register, and then determining the specific system call type based on the call number. Based on the determined type, the number of parameters to be passed is identified and organized into the form of a function call. The return value of the system call will be saved in the a0 register. For example, when calling the exit function, if the expression is a0=exit(a0), it means using the exit system call, which accepts one parameter and stores the return value in the a0 register. In theory, by adopting a similar approach, this tool can support almost all system calls in RISC-V. However, given the limitations of the verification tool, this tool prefers to verify programs that do not involve system calls to ensure the efficiency and accuracy of the conversion and verification process. In addition, since the ebreak instruction is mainly used in the debugging process, it allows developers to generate a breakpoint in the program execution, so as to perform status checks or modify the execution flow. However, for program verification, the ebreak instruction has no direct effect because it does not affect the correctness or performance of the program logic. Therefore, in the design of this tool, the method of this embodiment chooses to ignore the conversion of the ebreak instruction. This decision simplifies the conversion process, allowing the method of this embodiment to focus more resources on instructions that have a direct impact on program execution, ensuring the efficiency and accuracy of the verification tool. Through the above detailed introduction, it is demonstrated how to effectively convert the six major categories of instructions in the RISC-V instruction set into corresponding C code. These conversion methods cover everything from basic operations to complex jumps and system calls, greatly improving the accuracy and consistency of the conversion. In addition, considering the characteristics and limitations of verification tools such as CBMC, the method of this embodiment simplifies the processing of system calls, making the execution in the verification environment more efficient and accurate.
[0136] (7) Verification logic generator generation: The main function of the verification logic generator is to further process and expand the code converted by the assembly to C code adapter, so that these converted programs not only maintain the integrity of their original logic, but also can be directly adapted to and fully utilize the verification tools for system verification. This module inserts specific verification logic and assertions to ensure that each part of the program can be thoroughly checked during the verification process, thereby improving the reliability and security of the code. The introduction of the verification logic generator enables the original assembly program to be thoroughly analyzed by the verification tool CBMC in the form of C code, thereby identifying potential errors and problem points and enhancing the stability and robustness of the program. After conversion by the assembly to C converter, the method of this embodiment usually obtains a section of C code with a complex structure and difficult to understand directly. The original assembly program may contain assert statements for verification, but in the preprocessing stage, all assert statements are converted to if statements, and the corresponding global variables are set to 1 when if is true. However, the converted C program often lacks clear declarations of these global variables. Therefore, the first task of the verification logic generator is to clearly declare and initialize these global variables. During the preprocessing phase, the tool has identified and recorded all the global variables in the program to be verified and stored them in a dedicated structure. This structure contains the address, name and initial value of the global variables. Based on this information, the verification logic generator declares and assigns these variables one by one at the beginning of the converted C code file to ensure that these global variables can be correctly identified and used during the subsequent verification process. The next steps involve declaring and initializing the basic memory resources and variables required by the converted code. This includes declaring all general registers used during the program execution phase and setting up a byte array representing the stack space with a length of 1024 bytes. During the initialization process, the stack pointer register (sp) is set to point to the end of this array to ensure that memory is allocated from the top of the stack. At the same time, the program will initialize the values of all other registers to zero to ensure that all registers are in a known initial state when the program starts executing. The last step involves adding a main function to the converted code and embedding assert statements in it. The main function will be placed at the end of the code and its main responsibility is to call the converted function. Next, according to the assert statements in the original assembly file, the method of this embodiment will add corresponding assert statements one by one in the main function to verify whether the associated global variable is one. The number of assert statements added will completely match the number of assert statements in the original file. So far, the key steps of the verification logic generator have been described in detail, including the declaration and initialization of global variables, the configuration of memory resources and registers, and the addition of the main function and the insertion of assert statements.These steps together ensure that the converted C code not only maintains logical consistency with the original assembly code, but can also be effectively verified by standard verification tools.
[0137] (8) CBMC verification: The CBMC verification stage is the final step of the entire conversion and verification process. Its main function is to use the CBMC verification tool to perform in-depth verification of the code generated by the verification logic generator. It is worth noting that the converted C program no longer contains loop statements. Therefore, when using CBMC for verification, it is necessary to manually specify the appropriate number of loop unrolling. This step is necessary because even though there is no loop structure in the traditional sense in the code, CBMC still needs to analyze possible execution paths through unrolling to ensure complete coverage. Correctly setting the number of loop unrolling is crucial to accurately verify the behavior of the program and ensure the accuracy and effectiveness of the verification results. Through the above detailed introduction and step demonstration, the method of this embodiment successfully realizes the conversion from RISC-V assembly instructions to C code, and its subsequent verification logic generation and CBMC formal verification. This tool not only effectively completes the verification of mixed source code, assembly code and high-level language code, but also supports the verification of dynamic or static library files containing invisible source code. This enhances its wide application and flexibility, and ensures the functional and logical correctness of the converted program. The key improvements of the method of this embodiment include: 1. Assert conversion mechanism: In the preprocessing stage, this tool adopts a unique method to convert all assert statements into if expressions, and uses global variables to track the status of assertions. This method avoids the problems of code conversion and interruption process that may be caused by directly calling the assert function at runtime, and improves the efficiency and flexibility of assertion execution. Through this preprocessing, assertion detection becomes more efficient and adaptable. 2. Global variable mapping mechanism: In the preprocessing stage, this tool creates a structure containing all global variable information for dynamic management of the address, name and initial value of global variables. This mapping mechanism allows more flexible memory address processing, especially in conversion scenarios where the address space may change frequently, ensuring the correctness and accessibility of global variables. 3. Dynamic function call stack management: In the conversion stage of the assembly to C converter, in order to handle complex function calls and returns, this tool implements a custom function call stack to manage the return address, ensuring the accuracy of complex function calls and return operations. 4. Dynamic execution drives conversion. The tool of the method of this embodiment adopts a strategy of conversion while executing, that is, the simulator converts the instruction into C code accordingly when executing each assembly instruction step. Compared with traditional direct instruction conversion, this method can more accurately reflect the execution flow of the program. Especially when processing jump instructions, since the direct conversion method usually cannot accurately determine the specific location of the jump, this tool dynamically determines the jump target through simulated execution, thereby ensuring the logical accuracy and coherence of the converted C code.
[0138] In addition, this embodiment also provides a binary program verification system for the RISC-V architecture, including a microprocessor and a memory connected to each other, and the microprocessor is programmed or configured to execute the binary program verification method for the RISC-V architecture.
[0139] In addition, this embodiment also provides a computer-readable storage medium, which stores a computer program or instructions, and the computer program or instructions are programmed or configured to execute the binary program verification method for the RISC-V architecture through a processor.
[0140] In addition, this embodiment also provides a computer program product, including a computer program or instructions, which are programmed or configured to execute the binary program verification method for the RISC-V architecture through a processor.
[0141] The above is only a preferred embodiment of the present invention, and the protection scope of the present invention is not limited to the above embodiments. All technical solutions under the concept of the present invention belong to the protection scope of the present invention. It should be pointed out that for ordinary technicians in this technical field, some improvements and modifications without departing from the principle of the present invention should also be regarded as the protection scope of the present invention.
Claims
1. A binary program verification method for RISC-V architecture, characterized in that: The steps include: S101, obtaining an input source file, wherein the source file is an ELF binary file; S102, parsing the input source file using an ELF file processor and loading it into a simulator, and providing a runtime simulation of an actual hardware environment for the loaded source file through the simulator; S103, using the RISC-V instruction decoder to extract binary instructions of corresponding length from the memory, and decoding them according to the RISC-V encoding rules to obtain specific assembly instructions and their operands; S104, using an assembly-to-C converter to convert the decoded assembly instructions and their operands into a C language code file; S105, using a verification logic generator to perform verification tool compatibility processing on the converted C language code file; S106, using a C program model checker CBMC to verify the C language code file and generate a verification result; Before step S101, the method further includes compiling a source code file in the form of an assembler, a high-level language inline assembler language program, or a high-level language program into an ELF binary file; when compiling the source code file in the form of an assembler, a high-level language inline assembler language program, or a high-level language program into an ELF binary file, the method includes processing assertion statements: S201, obtaining an input source code file; S202, reading the source code file line by line, if the reading is completed, it is determined that the assertion statement processing is completed, and exit; Otherwise, jump to step S203; S203, determine whether there is an uncommented assertion statement in the line, if there is an uncommented assertion statement, jump to step S204; Otherwise, jump to step S202 to continue reading the source code file line by line; S204, record the serial number i of the assertion statement in the line, declare the i-th global variable assert_reti to be 0, replace the assert statement with an if statement, and set the value of the i-th global variable assert_reti to 1 in the if statement to indicate that the conditional expression in the assert statement is established.
2. The binary program verification method for RISC-V architecture according to claim 1, characterized in that: After step S101 and before step S102, the following steps are also included for processing global variables for a source file in the form of an ELF binary file: For the input source file in the form of an ELF binary file, declare a structure array gv to store the global variable information of the source file in the form of an ELF binary file, including variable name, variable value and variable address; Parse and read the global variables in the source file in the form of ELF binary file one by one. If the global variables in the source file in the form of ELF binary file are successfully read, the process continues; otherwise, the process ends; If all global variables have been read, the reading ends and the names and memory address information of the global variables are saved in the structure array gv.
3. The binary program verification method for RISC-V architecture according to claim 1, characterized in that: When the input source file is parsed by the ELF file processor and loaded into the simulator in step S102, the memory allocation area of the simulator includes the simulator reserved area, the ELF file program segment, the ELF file stack space and the ELF file heap space, and the data segment .data, the code segment .text and the read-only data segment .rodata obtained by parsing the input source file by the ELF file processor are loaded into the ELF file program segment respectively, and the position in the virtual address space is determined by the base address Base+entry address Entry, wherein the base address Base is the base address of the starting space representing the ELF file operation, and the entry address Entry is the offset of the segment starting point of the data segment .data, the code segment .text and the read-only data segment .rodata relative to the beginning of the file.
4. The binary program verification method for RISC-V architecture according to claim 1, characterized in that: In step S103, the RISC-V instruction decoder is used to extract binary instructions of corresponding length from the memory, and the specific assembly instructions and their operands are decoded according to the RISC-V encoding rules, including: S301, obtain the instruction word from the program counter PC and store it in the instruction register to prepare for decoding; S302, extracting the operation code Opcode of the instruction word from the instruction register and identifying the instruction format; S303, determining the instruction type according to the operation code Opcode of the instruction word; S304, extracting the fields of the instruction according to the instruction type, including the function code, source register, target register and immediate value; S305, obtaining a specific assembly instruction and its operands according to the operation code Opcode and the extracted fields.
5. The binary program verification method for RISC-V architecture according to claim 1, characterized in that: In step S104, the assembly-to-C converter is used to convert the decoded assembly instructions and their operands into a C language code file, including: S401, setting the program counter PC to point to the position of the base address Base + entry address Entry to prepare to read the assembly instruction, wherein the base address Base is the base address of the starting space representing the ELF file operation, and the entry address Entry is the offset of the segment starting point of the data segment .data, the code segment .text and the read-only data segment .rodata relative to the beginning of the file; S402, if the value of the program counter PC is 0, it is determined that the conversion is completed and jumps to step S105; otherwise, a 16-bit or 32-bit binary instruction is read from the address pointed to by the program counter PC, wherein the length of the 16-bit or 32-bit data read is determined according to whether the instruction is a compressed instruction; S403, decoding the read 16-bit or 32-bit binary instruction using the RISC-V instruction decoder to obtain a specific assembly instruction and its operands; S404, check whether the program has reached the end. If the program has reached the end, the process ends and jumps to step S105; otherwise, jumps to step S405; S405, determine whether the decoded assembly instruction is a jalr or ecall instruction, if it is a jalr or ecall instruction, jump to step S406; otherwise, the assembly-to-C converter converts the assembly instruction and its operands into C language code and appends them to the buffer string to simulate execution through the simulator, adds 2 or 4 to the value of the program counter PC to point to the next instruction, and jumps to step S402; S406, determine whether the decoded assembly instruction is an ecall instruction, if it is an ecall instruction, jump to step S407; otherwise, determine it as a processing system call of the real machine, append the system call function to the buffer string, add 2 or 4 to the value of the program counter PC to point to the next instruction, and jump to step S402; S407, append the C function end string to the buffer string; S408, output the buffered string to the C language code file; clear the buffered string, write out the initial string, add 2 or 4 to the value of the program counter PC to make it point to the next instruction, and jump to step S402.
6. The binary program verification method for RISC-V architecture according to claim 5, characterized in that: In step S405, the assembly-to-C converter converts the assembly instruction and its operands into C language code, including: S501, initializing a data structure, the data structure including a register structure and a structure array addr_val for storing global variable addresses and values; S502, identifying the type of the assembly instruction, where the type of the assembly instruction is storage, load, operation, direct jump, indirect jump or system call; S503, converting the assembly instruction and its operands into C language code based on the type of the assembly instruction: If the type of the assembly instruction is a storage instruction, converting the assembly instruction and its operands into C language code includes: step a: reading the target address of the instruction operation of the assembly instruction; step b: if the target address is in the stack space, converting the assembly instruction and its operands into C language code for data storage operation, ending and exiting; otherwise jumping to step c; step c: if the target address is the storage address of a global or static variable, converting the assembly instruction and its operands into C language code for data storage operation, ending and exiting; otherwise jumping to step d; step d: determining whether the target address is passed through a parameter register, if so, performing a data storage operation, ending and exiting; otherwise throwing an exception; the data storage operation is used to write corresponding data into the memory pointed to by the address, and the throwing of the exception is used to avoid runtime errors caused by illegal memory access; If the type of the assembly instruction is a load instruction, converting the assembly instruction and its operands into C language code includes: step a: reading the target address of the instruction operation of the assembly instruction; step b: if the target address is located in the stack space, converting the assembly instruction and its operands into C language code for data loading operation, ending and exiting; otherwise jumping to step c; step c: if the target address is the storage address of a global or static variable, converting the assembly instruction and its operands into C language code for loading data from a global variable, ending and exiting; otherwise jumping to step d; step d: determining whether the target address is passed through a parameter register, if so, performing a data storage operation, ending and exiting; otherwise throwing an exception; If the type of the assembly instruction is a direct jump instruction JAL, the direct jump instruction JAL is used to implement a function call, and jumps to a target address in the form of a command of JAL ra, offset, wherein offset is the target address offset, and the return address is saved in a return address register ra; converting the assembly instruction and its operands into C language code comprises: step a: reading an operation register of the direct jump instruction JAL; step b: determining whether the operation register of the direct jump instruction JAL is the return address register ra, if it is the return address register ra, calculating and recording the return address and saving it in a register of the simulated return address register ra, and pushing the return address into a function call stack for use when the function returns; otherwise, jumping to step c; step c: adding the value of the program counter PC to the target address offset offset, and converting the jump logic of the direct jump instruction JAL into a goto target_address statement in the C language; If the type of the assembly instruction is an indirect jump instruction JALR, the indirect jump instruction JALR is used to realize function return through the pseudo instruction ret, converting the assembly instruction and its operands into C language code includes: step a: reading the operation field of the indirect jump instruction JALR, including registers and immediate values; step b: judging whether the indirect jump instruction JALR is a function return instruction, if it is a function return instruction, popping the most recently saved return address from the function call stack, updating the popped address to the ra register variable in the simulator, and converting the return operation into a goto ra statement in the C language; otherwise, calculating the jump address, the jump address is the base register plus an offset, and saved in a temporary variable target_address, generating a goto target_address statement in the C language to execute the actual jump to the address of the temporary variable target_address; If the type of the assembly instruction is an operation or a system call, the operation or the system call is directly converted into C language code according to the conversion relationship table.
7. The binary program verification method for RISC-V architecture according to claim 1, characterized in that: In step S106, using the verification logic generator to perform verification tool compatibility processing on the converted C language code file includes: S601, declare and initialize the global variables identified and recorded in the preprocessing stage, declare and assign values to the global variables at the beginning of the C file, and ensure that they can be correctly identified and used by the verification tool; S602, declare and initialize basic memory resources and variables, including general registers and stack space, initialize the stack pointer register to point to the end address of the stack space, and initialize all other registers to zero; S603, encapsulate the C program converted by the assembly-to-C converter into a function with no return value type, and add it here; S604, adding a main function main in the converted C code to call the converted function; S605, insert an assertion statement into the main function main, the conditional expression is whether the value of the global variable corresponding to the assertion statement assert is 1, and ensure that the number of assertion statements assert is consistent with that in the assembly file.
8. A binary program verification system for RISC-V architecture, comprising a microprocessor and a memory connected to each other, characterized in that: The microprocessor is programmed or configured to execute the binary program verification method for the RISC-V architecture as described in any one of claims 1 to 7.
9. A computer-readable storage medium having a computer program or instruction stored therein, characterized in that: The computer program or instruction is programmed or configured to execute the binary program verification method for the RISC-V architecture as described in any one of claims 1 to 7 through a processor.
10. A computer program product comprising a computer program or instructions, characterized in that The computer program or instruction is programmed or configured to execute the binary program verification method for the RISC-V architecture as described in any one of claims 1 to 7 through a processor.
Citation Information
Patent Citations
Realization method and device for restoring source code through reverse engineering and electronic equipment
CN116501378A
NP microcode instruction verification analysis method and system, terminal and medium
CN117744112A