ELF support and other stuff

not working still
This commit is contained in:
Minecon724 2024-10-19 11:50:02 +02:00
parent 8467142c3e
commit 3864104e67
Signed by: Minecon724
GPG key ID: 3CCC4D267742C8E8
11 changed files with 219 additions and 34 deletions

View file

@ -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
View 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`

View file

@ -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`

View 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

Binary file not shown.

Binary file not shown.

BIN
programs/return2.elf Executable file

Binary file not shown.

137
src/elf_program_loader.c Normal file
View 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;
}

View file

@ -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;
} }

View file

@ -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;
} }

View file

@ -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,6 +22,10 @@ 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);
@ -16,6 +33,10 @@ int load_to_rom(const char filename[], AddressSpace *addressSpace) {
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;
} }