A question about binary translation/simulation
Posted: Thu May 13, 2021 1:54 pm
So, I've had this absolutely crazy theory/idea in my mind for a while that I was going to add to my OS if its feasible. But its not about OS theory or OS development, so it didn't make sense to put this there.
The idea is kinda like QEMU user-mode emulation, except a bit different. The idea is to create a framework that could one day become a universal binary execution engine for user-mode (not kernel-mode) programs. To illustrate this, lets say you wanted to run a RISC-V program on your x86 machine. On Linux you'd need to register binary formats with the kernel and then run qemu-system-riscv32 or qemu-system-riscv64 with the appropriate options to load your program and run it. However, using my theoretical framework, here's what might happen:
1. The kernel loads and parses the ELF program.
2. The kernel checks the architecture field and determines that its not native to the one the kernel is running on.
3. Instead of "rejecting" the binary like Linux might if you loaded a program that didn't have a registered binary handler, the kernel sends the code to the framework, which does the following:
a. The framework disassembles the program, converting it into a raw instruction stream.
b. The framework iterates through the instruction stream. For each instruction:
I. The framework reads the opcode and operands.
II. The framework attempts to locate an equivalent opcode for the architecture that the processor knows, in this case x86.
III. Once the opcode is found, the framework performs register allocation and completes the instruction, taking into account any bits (e.g.: the AQLR bit on RISC-V atomic instructions) and prepending things like lock prefixes. If there is no equivalent instruction in the native opcode set, the opcode is simulated using the appropriate instructions.
IV. The newly generated instructions are added to a "new" instruction stream and this process continues.
c. Once the framework has scanned the complete instruction stream, it reassembles the binary in memory, alters any fields and sections as necessary, and returns the new binary to the kernel.
4. The kernel runs the binary.
As you can imagine, there are lots of problems to this idea. For one, I've no idea how to detect functions in an instruction stream, so I'd need to find a way of figuring that out to account for ABI differences. For two, architectues like ARM/AArch64 have instructions like HVC and SMC for which there is no equivalent in other architectures. However, my idea was to start with something a lot simpler like RISC-V and then to allow others to help expand it in future. Do you guys have any other ideas on how something like this might work and any problems that I might encounter and how to solve them? Is it even a good idea to try something like this, or should I just leave it up to QEMU, assuming I even try this at all?
The idea is kinda like QEMU user-mode emulation, except a bit different. The idea is to create a framework that could one day become a universal binary execution engine for user-mode (not kernel-mode) programs. To illustrate this, lets say you wanted to run a RISC-V program on your x86 machine. On Linux you'd need to register binary formats with the kernel and then run qemu-system-riscv32 or qemu-system-riscv64 with the appropriate options to load your program and run it. However, using my theoretical framework, here's what might happen:
1. The kernel loads and parses the ELF program.
2. The kernel checks the architecture field and determines that its not native to the one the kernel is running on.
3. Instead of "rejecting" the binary like Linux might if you loaded a program that didn't have a registered binary handler, the kernel sends the code to the framework, which does the following:
a. The framework disassembles the program, converting it into a raw instruction stream.
b. The framework iterates through the instruction stream. For each instruction:
I. The framework reads the opcode and operands.
II. The framework attempts to locate an equivalent opcode for the architecture that the processor knows, in this case x86.
III. Once the opcode is found, the framework performs register allocation and completes the instruction, taking into account any bits (e.g.: the AQLR bit on RISC-V atomic instructions) and prepending things like lock prefixes. If there is no equivalent instruction in the native opcode set, the opcode is simulated using the appropriate instructions.
IV. The newly generated instructions are added to a "new" instruction stream and this process continues.
c. Once the framework has scanned the complete instruction stream, it reassembles the binary in memory, alters any fields and sections as necessary, and returns the new binary to the kernel.
4. The kernel runs the binary.
As you can imagine, there are lots of problems to this idea. For one, I've no idea how to detect functions in an instruction stream, so I'd need to find a way of figuring that out to account for ABI differences. For two, architectues like ARM/AArch64 have instructions like HVC and SMC for which there is no equivalent in other architectures. However, my idea was to start with something a lot simpler like RISC-V and then to allow others to help expand it in future. Do you guys have any other ideas on how something like this might work and any problems that I might encounter and how to solve them? Is it even a good idea to try something like this, or should I just leave it up to QEMU, assuming I even try this at all?