My original intention in using a blog to record the process of doing PA was to record some experiences, lessons, and interesting things summarized during the personal project. But so far, everything has been going relatively smoothly, without any points that troubled me too much. Therefore, in this article, I will simply introduce the functions accomplished so far.
So far…
Up to now, the hardware and software involved in my system are still relatively simple: the hardware aspect mainly simulates a simple implementation of registers and memory; the software level mainly implements the first half of a simple debugger.
CPU
The CPU (Central Processing Unit) is the important computing center of a computer. The various functions of the CPU can be called by software through the instruction set, and different instruction sets also correspond to different hardware architectures. In PA, the teachers very kindly provided the freedom to choose one of three different CPU instruction set architectures (x86, mips32, Risc-V32(64)) to implement, and I chose the Risc-V32 Instruction Set Architecture (ISA). It’s worth noting that ISA is neither hardware nor software, but a specification manual; the design of hardware and the writing of software both need to refer to the ISA.
However, for the current task (simple debugger), the choice of ISA is not that important, so I haven’t implemented any functions yet won’t go into more detail for now.
Registers and Memory
Registers are storage devices in computers used to store values related to computing as well as some calculation states. Generally speaking, registers have extremely fast read and write speeds, but their storage capacity is also very small. A register is a highly structured storage component: there are many storage locations holding special meanings, so in PA, we use a struct to implement a register.
Memory is a storage device used to store the data and instructions required during the computer’s running process. It has relatively fast read and write speeds, but also relatively small storage capacity (comparatively speaking). Memory is also a continuous physical storage space in the computer (excluding virtual memory), so in PA, we use an array to simulate memory.
I wonder if classmates who haven’t studied digital circuits have ever been curious about a question: what is the process like when a computer executes a program? Simply put, when we double-click a program on the computer, its binary program is loaded into memory.
A binary program is composed of many instructions, each of which can be further divided into two parts of information: opcode and operand. The opcode defines an instruction that needs to be executed by the CPU, and the operand is the object upon which the CPU executes the instruction, which can be an immediate value or an address. The specific format of the instruction and the specific definition and quantity of opcodes follow the design of the ISA, while how the CPU specifically executes these instructions is the content of the hardware designer’s work.
In brief, software programs, ISA, and hardware can be seen as having the following relationships.
Precisely because ISA encapsulates hardware functions into interfaces, programmers can design code that runs on all machines. At the same time, this also explains why an executable program compiled on an x86 platform cannot run on an arm platform.
After the binary program is loaded into memory, a special register–the PC (Program Counter)–will record the progress of the program’s execution, and will point to the position of the next instruction after one instruction finishes executing. The computer can then execute instructions one by one, thus running the programs we wrote.
It is worth mentioning that in the simple debugger project, the code provides a default program, and this program is quite hard to find. I looked for it for a long time and felt very broken annoyed. Let it be known.
SDB (Simple DeBugger)
SDB is the main program needing to be implemented in PA1. Debugging can help programmers specifically expose problems in the code, and its functions include: breakpoints, single-step execution, stepping in, stepping out, etc. Therefore, for PA, implementing a simple debugger is also very important.
In SDB, we mainly need to reproduce the following GDB instructions:
| Command | Format | Description |
|---|---|---|
| Help | help |
Print help information |
| Continue | c |
Continue running the paused program |
| Quit | q |
Exit NEMU |
| Step | si [N] |
Step through N instructions and then pause execution. If N is omitted, defaults to 1. |
| Info Registers | info r |
Print register states |
| Scan Memory | x N EXPR |
Evaluate EXPR, use result as starting memory address, and output N consecutive 4-byte values in hexadecimal |
We can directly read the binary form of the running instructions by reading the memory that stores the instructions, and thereby verify the correctness of the x command implementation. This is why I had to find the program loaded by default in SDB :-(