ELF support and other stuff
not working still
This commit is contained in:
parent
8467142c3e
commit
3864104e67
11 changed files with 219 additions and 34 deletions
4
Makefile
4
Makefile
|
@ -9,9 +9,9 @@ CC := gcc
|
||||||
# -O3: Optimize code (level 3)
|
# -O3: Optimize code (level 3)
|
||||||
# -Wno-unused-variable: Disable warnings for unused variables
|
# -Wno-unused-variable: Disable warnings for unused variables
|
||||||
ifeq ($(DEBUG),1)
|
ifeq ($(DEBUG),1)
|
||||||
CFLAGS := -Wall -Wextra -std=gnu23 -I include -O0 -g
|
CFLAGS := -Wall -Wextra -std=gnu23 -I include -O0 -g -lelf
|
||||||
else
|
else
|
||||||
CFLAGS := -Wall -Wextra -std=gnu23 -I include -O3
|
CFLAGS := -Wall -Wextra -std=gnu23 -I include -O3 -lelf
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Directory for build outputs
|
# Directory for build outputs
|
||||||
|
|
29
README.md
Normal file
29
README.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
RISC-V (rv32i) emulator in C \
|
||||||
|
This is just for me to understand how all this works, and to learn something new. \
|
||||||
|
So don't use it.
|
||||||
|
|
||||||
|
Example programs:
|
||||||
|
- `return.bin` returns -1094647826 (puts it to register 10)
|
||||||
|
- `return2.elf` returns a different value with more steps. if it returns DADD, something's wrong
|
||||||
|
|
||||||
|
Emulator exit codes:
|
||||||
|
- `-1` - reached end of address space
|
||||||
|
- `0` - never happens
|
||||||
|
- `1` - invalid opcode
|
||||||
|
- `2` - illegal instruction argument (like funct3)
|
||||||
|
|
||||||
|
Compile:
|
||||||
|
0. Requirements: `libelf`
|
||||||
|
1. `make` - should display no warnings
|
||||||
|
2. Executable is `build/criscv`
|
||||||
|
|
||||||
|
Compile programs:
|
||||||
|
0. Get the toolchain obviously
|
||||||
|
1. ```
|
||||||
|
riscv32-unknown-elf-gcc -ffreestanding -nostdlib -Ttext=0x0 -e main -O0 -o program.elf program.c
|
||||||
|
```
|
||||||
|
3. `program.bin` is the binary file with the program, pass it as an argument
|
||||||
|
|
||||||
|
rv32i, ilp32 compatible toolchain for 64bit Linux: https://lfs.m724.eu/toolchain.tar.zst `adaa74f263dcba430da588b1109bc3b90bd90a84c67b06213bd03a7bbacd1a2a` \
|
||||||
|
Or just the stuff necessary to make a binary file: https://lfs.m724.eu/toolchainlite.tar.zst `55e79dff7ba4093dedb8151461508fc157525ad89615d49d737845af03d1643f` \
|
||||||
|
Those were compiled with `./configure --prefix=$(pwd)/../toolchain --with-arch=rv32i --with-abi=ilp32` and `make`
|
23
README.txt
23
README.txt
|
@ -1,23 +0,0 @@
|
||||||
RISC-V (rv32i) emulator in C
|
|
||||||
This is just for me to understand how all this works, and to learn something new.
|
|
||||||
So don't use it.
|
|
||||||
|
|
||||||
Example programs:
|
|
||||||
- return.bin: returns -1094647826 (puts it to register 10)
|
|
||||||
- return2.bin: returns a different value with more steps. if it returns DADD, something's wrong
|
|
||||||
|
|
||||||
Emulator exit codes:
|
|
||||||
- <0 = OK
|
|
||||||
- 0 = never happens
|
|
||||||
- 1 - invalid opcode
|
|
||||||
- 2 - illegal instruction argument (like funct3)
|
|
||||||
|
|
||||||
To compile stuff:
|
|
||||||
0. Get the toolchain obviously
|
|
||||||
1. riscv32-unknown-elf-gcc -ffreestanding -nostdlib -nostartfiles -Wl,--no-relax -Ttext=0x0 -e main -O0 -o program.elf program.c
|
|
||||||
2. riscv32-unknown-elf-objcopy -O binary program.elf program.bin
|
|
||||||
3. program.bin is the binary file with the program, pass it as an argument
|
|
||||||
|
|
||||||
rv32i, ilp32 compatible toolchain for 64bit Linux: https://lfs.m724.eu/toolchain.tar.zst adaa74f263dcba430da588b1109bc3b90bd90a84c67b06213bd03a7bbacd1a2a
|
|
||||||
Or just the stuff necessary to make a binary file: https://lfs.m724.eu/toolchainlite.tar.zst 55e79dff7ba4093dedb8151461508fc157525ad89615d49d737845af03d1643f
|
|
||||||
Those were compiled with `./configure --prefix=$(pwd)/../toolchain --with-arch=rv32i --with-abi=ilp32` and `make`
|
|
8
include/elf_program_loader.h
Normal file
8
include/elf_program_loader.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef ELF_PROGRAM_LOADER_H
|
||||||
|
#define ELF_PROGRAM_LOADER_H
|
||||||
|
|
||||||
|
#include "cpu.h"
|
||||||
|
|
||||||
|
int load_elf_to_cpu_and_rom(const char filename[], CPU *cpu);
|
||||||
|
|
||||||
|
#endif
|
BIN
programs/return.elf
Executable file
BIN
programs/return.elf
Executable file
Binary file not shown.
Binary file not shown.
BIN
programs/return2.elf
Executable file
BIN
programs/return2.elf
Executable file
Binary file not shown.
137
src/elf_program_loader.c
Normal file
137
src/elf_program_loader.c
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
#include <gelf.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "cpu.h"
|
||||||
|
|
||||||
|
static inline int process_elf(Elf *elf, CPU *cpu) {
|
||||||
|
AddressSpace *addressSpace = cpu->addressSpace;
|
||||||
|
|
||||||
|
if (elf_kind(elf) != ELF_K_ELF) {
|
||||||
|
fprintf(stderr, "File is not a valid ELF file\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
GElf_Ehdr ehdr;
|
||||||
|
if (gelf_getehdr(elf, &ehdr) == NULL) {
|
||||||
|
fprintf(stderr, "gelf_getehdr() failed: %s\n", elf_errmsg(-1));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ehdr.e_type != ET_EXEC) {
|
||||||
|
printf("ELF is not an executable, proceeding anyway.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ehdr.e_machine != EM_RISCV) {
|
||||||
|
fprintf(stderr, "ELF is not for RISC-V. Machine: 0x%X\n", ehdr.e_machine);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Elf_Scn *scn = NULL;
|
||||||
|
GElf_Shdr shdr;
|
||||||
|
|
||||||
|
int warnedArch = 0;
|
||||||
|
|
||||||
|
size_t shdrstrndx; // Section HeaDeR STRing table iNDeX
|
||||||
|
if (elf_getshdrstrndx(elf, &shdrstrndx) != 0) {
|
||||||
|
fprintf(stderr, "elf_getshdrstrndx error: %s\n", elf_errmsg(-1));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((scn = elf_nextscn(elf, scn)) != NULL) {
|
||||||
|
if (gelf_getshdr(scn, &shdr) != &shdr) {
|
||||||
|
fprintf(stderr, "getshdr() failed: %s\n", elf_errmsg(-1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *sectionName = elf_strptr(elf, shdrstrndx, shdr.sh_name);
|
||||||
|
|
||||||
|
if (shdr.sh_type == SHT_RISCV_ATTRIBUTES) {
|
||||||
|
Elf_Data *data = elf_getdata(scn, NULL);
|
||||||
|
|
||||||
|
if (data == NULL || data->d_size == 0) {
|
||||||
|
fprintf(stderr, "elf_getdata() failed: %s\n", elf_errmsg(-1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this is highly fragile
|
||||||
|
// https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
|
||||||
|
char *content = (char *)data->d_buf;
|
||||||
|
content += 19; // first 17 chars are subsection headers, and I have to +2 to make strcmp match
|
||||||
|
if (strcmp(content, "rv32i2p1") == 0) {
|
||||||
|
warnedArch = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else if (strcmp(sectionName, ".text") == 0) {
|
||||||
|
Elf_Data *data = elf_getdata(scn, NULL);
|
||||||
|
|
||||||
|
if (data == NULL || data->d_size == 0) {
|
||||||
|
fprintf(stderr, "elf_getdata() failed: %s\n", elf_errmsg(-1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *content = (char *)data->d_buf;
|
||||||
|
uint32_t contentSize = data->d_size;
|
||||||
|
|
||||||
|
if (contentSize > addressSpace->romSize) {
|
||||||
|
fprintf(stderr, "Code has %d bytes, and doesn't fit in ROM of capacity %d bytes\n", contentSize, addressSpace->romSize);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(addressSpace->rom, content, contentSize);
|
||||||
|
|
||||||
|
printf("Loaded %d bytes or %d instructions from an ELF file\n", contentSize, contentSize / 4);
|
||||||
|
} else {
|
||||||
|
printf("Unrecognized section: %s\n", sectionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warnedArch == 0) {
|
||||||
|
printf("I couldn't verify whether the ELF was compiled for the correct instruction set!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu->programCounter = ehdr.e_entry;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int load_elf_to_cpu_and_rom(const char filename[], CPU *cpu) {
|
||||||
|
if (elf_version(EV_CURRENT) == EV_NONE) {
|
||||||
|
fprintf(stderr, "Invalid ELF version: %s\n", elf_errmsg(-1)); // TODO can perror be used?
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int code = 0;
|
||||||
|
|
||||||
|
int fd = open(filename, O_RDONLY, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
perror("Failed opening file");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Elf *elf = elf_begin(fd, ELF_C_READ, NULL);
|
||||||
|
if (elf == NULL) {
|
||||||
|
fprintf(stderr, "Error reading ELF file: %s\n", elf_errmsg(-1));
|
||||||
|
if (close(fd) < 0) {
|
||||||
|
perror("Also failed closing file");
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((code = process_elf(elf, cpu)) != 0) {
|
||||||
|
fprintf(stderr, "Failed processing ELF\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elf_end(elf) < 0) {
|
||||||
|
fprintf(stderr, "Failed closing ELF: %s\n", elf_errmsg(-1)); // TODO can perror be used?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (close(fd) < 0) {
|
||||||
|
perror("Failed closing file"); // TODO perhaps make one close for all the errors above
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
|
@ -255,7 +255,7 @@ int execute_instruction_on_cpu(CPU *cpu, uint32_t instruction) { // TODO conside
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
// TODO illegal instruction, proper error handling
|
// TODO illegal instruction, proper error handling
|
||||||
fprintf(stderr, "Unrecognized opcode!");
|
fprintf(stderr, "Unrecognized opcode: 0x%X", opcode);
|
||||||
return 1;
|
return 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
25
src/main.c
25
src/main.c
|
@ -2,8 +2,10 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "program_loader.h"
|
#include "program_loader.h"
|
||||||
|
#include "elf_program_loader.h"
|
||||||
#include "cpu.h"
|
#include "cpu.h"
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
@ -15,16 +17,23 @@ int main(int argc, char *argv[]) {
|
||||||
AddressSpace *addressSpace = create_address_space(256, 256);
|
AddressSpace *addressSpace = create_address_space(256, 256);
|
||||||
printf("Address space: %dB ROM, %dB RAM\n", addressSpace->romSize, addressSpace->ramSize);
|
printf("Address space: %dB ROM, %dB RAM\n", addressSpace->romSize, addressSpace->ramSize);
|
||||||
|
|
||||||
if (load_to_rom(argv[1], addressSpace) != 0) {
|
CPU cpu = create_cpu(addressSpace);
|
||||||
|
cpu.registers[1] = addressSpace->romSize + addressSpace->ramSize; // make jumping to x1 end the program
|
||||||
|
|
||||||
|
|
||||||
|
int lres = load_to_rom(argv[1], addressSpace);
|
||||||
|
|
||||||
|
if (lres == -1) {
|
||||||
|
lres = load_elf_to_cpu_and_rom(argv[1], &cpu);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lres != 0) {
|
||||||
fprintf(stderr, "Error loading program\n");
|
fprintf(stderr, "Error loading program\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
CPU cpu = create_cpu(addressSpace);
|
|
||||||
cpu.registers[1] = addressSpace->romSize + addressSpace->ramSize; // make jumping to x1 end the program
|
|
||||||
cpu.programCounter = 0x70;
|
|
||||||
|
|
||||||
printf("\n----- Start of program -----");
|
printf("\n----- Start of program (0x%X) -----", cpu.programCounter);
|
||||||
|
|
||||||
int code;
|
int code;
|
||||||
uint32_t cycles = 0;
|
uint32_t cycles = 0;
|
||||||
|
@ -33,12 +42,14 @@ int main(int argc, char *argv[]) {
|
||||||
//sleep(1);
|
//sleep(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\n\n----- End of program -----\n");
|
printf("\n\n----- End of program (0x%X) -----\n", cpu.programCounter);
|
||||||
|
|
||||||
printf("\n Emulator exit code: \033[1m%d\033[0m\n", code);
|
printf("\n Emulator exit code: \033[1m%d\033[0m\n", code);
|
||||||
printf(" Program exit code: \033[1m%d\033[0m (x10)\n", cpu.registers[10]);
|
printf(" Program exit code: \033[1m%d\033[0m (x10)\n", cpu.registers[10]);
|
||||||
printf(" Cycles: \033[1m%u\033[0m\n\n", cycles);
|
printf(" Cycles: \033[1m%u\033[0m\n\n", cycles);
|
||||||
|
|
||||||
|
// TODO don't
|
||||||
|
|
||||||
int cols[8] = {0};
|
int cols[8] = {0};
|
||||||
int total = 0;
|
int total = 0;
|
||||||
|
|
||||||
|
@ -91,5 +102,7 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
|
printf("The register dump might have some visual glitches, the data itself is ok\n"); // TODO fix
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
|
@ -1,6 +1,19 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "address_space.h"
|
#include "address_space.h"
|
||||||
|
|
||||||
|
static const unsigned char elf_magic[] = {0x7F, 0x45, 0x4c, 0x46};
|
||||||
|
|
||||||
|
static inline bool check_elf(FILE *file) { // TODO maybe move
|
||||||
|
uint8_t header[4];
|
||||||
|
fread(header, 4, 1, file);
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
|
||||||
|
return memcmp(header, elf_magic, 4) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
int load_to_rom(const char filename[], AddressSpace *addressSpace) {
|
int load_to_rom(const char filename[], AddressSpace *addressSpace) {
|
||||||
FILE *file = fopen(filename, "rb");
|
FILE *file = fopen(filename, "rb");
|
||||||
|
|
||||||
|
@ -9,13 +22,21 @@ int load_to_rom(const char filename[], AddressSpace *addressSpace) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (check_elf(file)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// size_t is not used because a program can't be larger than the 32bit address space
|
// size_t is not used because a program can't be larger than the 32bit address space
|
||||||
int romSize = addressSpace->romSize;
|
int romSize = addressSpace->romSize;
|
||||||
int bytesRead = fread(addressSpace->rom, 1, romSize, file);
|
int bytesRead = fread(addressSpace->rom, 1, romSize, file);
|
||||||
|
|
||||||
fseek(file, 0, SEEK_END);
|
fseek(file, 0, SEEK_END);
|
||||||
int fileLen = ftell(file);
|
int fileLen = ftell(file);
|
||||||
|
|
||||||
|
if (fileLen % 4 != 0) {
|
||||||
|
printf("Suspicious file size of %d bytes, not divisible by 4\n", fileLen); // TODO maybe handle it in fread
|
||||||
|
}
|
||||||
|
|
||||||
if (fileLen > romSize) {
|
if (fileLen > romSize) {
|
||||||
fprintf(stderr, "File has %d bytes, and doesn't fit in ROM of capacity %d bytes\n", fileLen, romSize);
|
fprintf(stderr, "File has %d bytes, and doesn't fit in ROM of capacity %d bytes\n", fileLen, romSize);
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -24,7 +45,7 @@ int load_to_rom(const char filename[], AddressSpace *addressSpace) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("%s has %d bytes or %d instructions\n", filename, fileLen, fileLen / 4);
|
printf("%s has %d bytes or %d instructions from a binary file\n", filename, fileLen, fileLen / 4);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
Loading…
Reference in a new issue