383 lines
12 KiB
C
383 lines
12 KiB
C
// Copyright (c) 2013-2018 Bluespec, Inc. All Rights Reserved
|
|
|
|
// This program reads an ELF file and outputs a Verilog hex memory
|
|
// image file (suitable for reading using $readmemh).
|
|
|
|
// Modifications for CHERI as well as compiling on APPLE devices:
|
|
|
|
/*-
|
|
* Copyright (c) 2022 Jonathan Woodruff
|
|
* Copyright (c) 2022 Franz Fuchs
|
|
* All rights reserved.
|
|
*
|
|
* This software was developed by the University of Cambridge
|
|
* Department of Computer Science and Technology under the
|
|
* SIPP (Secure IoT Processor Platform with Remote Attestation)
|
|
* project funded by EPSRC: EP/S030868/1
|
|
*
|
|
* @BERI_LICENSE_HEADER_START@
|
|
*
|
|
* Licensed to BERI Open Systems C.I.C. (BERI) under one or more contributor
|
|
* license agreements. See the NOTICE file distributed with this work for
|
|
* additional information regarding copyright ownership. BERI licenses this
|
|
* file to you under the BERI Hardware-Software License, Version 1.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at:
|
|
*
|
|
* http://www.beri-open-systems.org/legal/license-1-0.txt
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, Work distributed
|
|
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations under the License.
|
|
*
|
|
* @BERI_LICENSE_HEADER_END@
|
|
*/
|
|
|
|
// ================================================================
|
|
// Standard C includes
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <gelf.h>
|
|
|
|
#ifdef __APPLE__
|
|
#include <vector>
|
|
#endif
|
|
|
|
// ================================================================
|
|
// Memory buffer into which we load the ELF file before
|
|
// writing it back out to the output file.
|
|
|
|
// 1 Gigabyte size
|
|
// #define MAX_MEM_SIZE (((uint64_t) 0x400) * ((uint64_t) 0x400) * ((uint64_t) 0x400))
|
|
#define MAX_MEM_SIZE ((uint64_t) 0x90000000)
|
|
|
|
#define BASE_ADDR_B 0x80000000lu
|
|
|
|
// For 16 MB memory at 0x_8000_0000
|
|
#define MIN_MEM_ADDR_16MB BASE_ADDR_B
|
|
#define MAX_MEM_ADDR_16MB (BASE_ADDR_B + 0x1000000lu)
|
|
|
|
// For 1 GB memory at 0x_8000_0000
|
|
#define MIN_MEM_ADDR_1GB BASE_ADDR_B
|
|
#define MAX_MEM_ADDR_1GB (BASE_ADDR_B + 0x40000000lu)
|
|
|
|
uint8_t *mem_buf;
|
|
|
|
// Features of the ELF binary
|
|
int bitwidth;
|
|
uint64_t min_addr;
|
|
uint64_t max_addr;
|
|
|
|
uint64_t pc_start; // Addr of label '_start'
|
|
uint64_t pc_exit; // Addr of label 'exit'
|
|
uint64_t tohost_addr; // Addr of label 'tohost'
|
|
|
|
// ================================================================
|
|
// Load an ELF file.
|
|
|
|
void c_mem_load_elf (char *elf_filename,
|
|
const char *start_symbol,
|
|
const char *exit_symbol,
|
|
const char *tohost_symbol)
|
|
{
|
|
int fd;
|
|
// int n_initialized = 0;
|
|
Elf *e;
|
|
|
|
// Default start, exit and tohost symbols
|
|
if (start_symbol == NULL)
|
|
start_symbol = "_start";
|
|
if (exit_symbol == NULL)
|
|
exit_symbol = "exit";
|
|
if (tohost_symbol == NULL)
|
|
tohost_symbol = "tohost";
|
|
|
|
// Verify the elf library version
|
|
if (elf_version (EV_CURRENT) == EV_NONE) {
|
|
fprintf (stderr, "ERROR: c_mem_load_elf: Failed to initialize the libelfg library!\n");
|
|
exit (1);
|
|
}
|
|
|
|
// Open the file for reading
|
|
fd = open (elf_filename, O_RDONLY, 0);
|
|
if (fd < 0) {
|
|
fprintf (stderr, "ERROR: c_mem_load_elf: could not open elf input file: %s\n", elf_filename);
|
|
exit (1);
|
|
}
|
|
|
|
// Initialize the Elf pointer with the open file
|
|
e = elf_begin (fd, ELF_C_READ, NULL);
|
|
if (e == NULL) {
|
|
fprintf (stderr, "ERROR: c_mem_load_elf: elf_begin() initialization failed!\n");
|
|
exit (1);
|
|
}
|
|
|
|
// Verify that the file is an ELF file
|
|
if (elf_kind (e) != ELF_K_ELF) {
|
|
elf_end (e);
|
|
fprintf (stderr, "ERROR: c_mem_load_elf: specified file '%s' is not an ELF file!\n", elf_filename);
|
|
exit (1);
|
|
}
|
|
|
|
// Get the ELF header
|
|
GElf_Ehdr ehdr;
|
|
if (gelf_getehdr (e, & ehdr) == NULL) {
|
|
elf_end (e);
|
|
fprintf (stderr, "ERROR: c_mem_load_elf: get_getehdr() failed: %s\n", elf_errmsg(-1));
|
|
exit (1);
|
|
}
|
|
|
|
// Is this a 32b or 64 ELF?
|
|
if (gelf_getclass (e) == ELFCLASS32) {
|
|
fprintf (stdout, "c_mem_load_elf: %s is a 32-bit ELF file\n", elf_filename);
|
|
bitwidth = 32;
|
|
}
|
|
else if (gelf_getclass (e) == ELFCLASS64) {
|
|
fprintf (stdout, "c_mem_load_elf: %s is a 64-bit ELF file\n", elf_filename);
|
|
bitwidth = 64;
|
|
}
|
|
else {
|
|
fprintf (stderr, "ERROR: c_mem_load_elf: ELF file '%s' is not 32b or 64b\n", elf_filename);
|
|
elf_end (e);
|
|
exit (1);
|
|
}
|
|
|
|
// Verify we are dealing with a RISC-V ELF
|
|
if (ehdr.e_machine != 243) { // EM_RISCV is not defined, but this returns 243 when used with a valid elf file.
|
|
elf_end (e);
|
|
fprintf (stderr, "ERROR: c_mem_load_elf: %s is not a RISC-V ELF file\n", elf_filename);
|
|
exit (1);
|
|
}
|
|
|
|
// Verify we are dealing with a little endian ELF
|
|
if (ehdr.e_ident[EI_DATA] != ELFDATA2LSB) {
|
|
elf_end (e);
|
|
fprintf (stderr,
|
|
"ERROR: c_mem_load_elf: %s is a big-endian 64-bit RISC-V executable which is not supported\n",
|
|
elf_filename);
|
|
exit (1);
|
|
}
|
|
|
|
// Grab the string section index
|
|
size_t shstrndx;
|
|
shstrndx = ehdr.e_shstrndx;
|
|
|
|
// Iterate through each of the sections looking for code that should be loaded
|
|
Elf_Scn *scn = 0;
|
|
GElf_Shdr shdr;
|
|
|
|
min_addr = 0xFFFFFFFFFFFFFFFFllu;
|
|
max_addr = 0x0000000000000000llu;
|
|
pc_start = 0xFFFFFFFFFFFFFFFFllu;
|
|
pc_exit = 0xFFFFFFFFFFFFFFFFllu;
|
|
tohost_addr = 0xFFFFFFFFFFFFFFFFllu;
|
|
|
|
while ((scn = elf_nextscn (e,scn)) != NULL) {
|
|
// get the header information for this section
|
|
gelf_getshdr (scn, & shdr);
|
|
|
|
char *sec_name = elf_strptr (e, shstrndx, shdr.sh_name);
|
|
fprintf (stdout, "Section %-16s: ", sec_name);
|
|
|
|
Elf_Data *data = 0;
|
|
// If we find a code/data section, load it into the model
|
|
if ( ((shdr.sh_type == SHT_PROGBITS)
|
|
|| (shdr.sh_type == SHT_NOBITS)
|
|
|| (shdr.sh_type == SHT_INIT_ARRAY)
|
|
|| (shdr.sh_type == SHT_FINI_ARRAY))
|
|
&& ((shdr.sh_flags & SHF_WRITE)
|
|
|| (shdr.sh_flags & SHF_ALLOC)
|
|
|| (shdr.sh_flags & SHF_EXECINSTR))) {
|
|
data = elf_getdata (scn, data);
|
|
|
|
// n_initialized += data->d_size;
|
|
if (shdr.sh_addr < min_addr)
|
|
min_addr = shdr.sh_addr;
|
|
if (max_addr < (shdr.sh_addr + data->d_size - 1)) // shdr.sh_size + 4))
|
|
max_addr = shdr.sh_addr + data->d_size - 1; // shdr.sh_size + 4;
|
|
|
|
if (max_addr >= MAX_MEM_SIZE) {
|
|
fprintf (stdout, "INTERNAL ERROR: max_addr (0x%0" PRIx64 ") > buffer size (0x%0" PRIx64 ")\n",
|
|
max_addr, MAX_MEM_SIZE);
|
|
fprintf (stdout, " Please increase the #define in this program, recompile, and run again\n");
|
|
fprintf (stdout, " Abandoning this run\n");
|
|
exit (1);
|
|
}
|
|
|
|
if (shdr.sh_type != SHT_NOBITS && shdr.sh_addr!=0) {
|
|
memcpy (& (mem_buf [shdr.sh_addr-MIN_MEM_ADDR_1GB]), data->d_buf, data->d_size);
|
|
}
|
|
fprintf (stdout, "addr %16" PRIx64 " to addr %16" PRIx64 "; size 0x%8lx (= %0ld) bytes\n",
|
|
shdr.sh_addr, shdr.sh_addr + data->d_size, data->d_size, data->d_size);
|
|
|
|
}
|
|
|
|
// If we find the symbol table, search for symbols of interest
|
|
else if (shdr.sh_type == SHT_SYMTAB) {
|
|
fprintf (stdout, "Searching for addresses of '%s', '%s' and '%s' symbols\n",
|
|
start_symbol, exit_symbol, tohost_symbol);
|
|
|
|
// Get the section data
|
|
data = elf_getdata (scn, data);
|
|
|
|
// Get the number of symbols in this section
|
|
int symbols = shdr.sh_size / shdr.sh_entsize;
|
|
|
|
// search for the uart_default symbols we need to potentially modify.
|
|
GElf_Sym sym;
|
|
int i;
|
|
for (i = 0; i < symbols; ++i) {
|
|
// get the symbol data
|
|
gelf_getsym (data, i, &sym);
|
|
|
|
// get the name of the symbol
|
|
char *name = elf_strptr (e, shdr.sh_link, sym.st_name);
|
|
|
|
// Look for, and remember PC of the start symbol
|
|
if (strcmp (name, start_symbol) == 0) {
|
|
pc_start = sym.st_value;
|
|
}
|
|
// Look for, and remember PC of the exit symbol
|
|
else if (strcmp (name, exit_symbol) == 0) {
|
|
pc_exit = sym.st_value;
|
|
}
|
|
// Look for, and remember addr of 'tohost' symbol
|
|
else if (strcmp (name, tohost_symbol) == 0) {
|
|
tohost_addr = sym.st_value;
|
|
}
|
|
}
|
|
|
|
FILE *fp_symbol_table = fopen ("symbol_table.txt", "w");
|
|
if (fp_symbol_table != NULL) {
|
|
fprintf (stdout, "Writing symbols to: symbol_table.txt\n");
|
|
if (pc_start == -1)
|
|
fprintf (stdout, " No '_start' label found\n");
|
|
else
|
|
fprintf (fp_symbol_table, "_start 0x%0" PRIx64 "\n", pc_start);
|
|
|
|
if (pc_exit == -1)
|
|
fprintf (stdout, " No 'exit' label found\n");
|
|
else
|
|
fprintf (fp_symbol_table, "exit 0x%0" PRIx64 "\n", pc_exit);
|
|
|
|
if (tohost_addr == -1)
|
|
fprintf (stdout, " No 'tohost' symbol found\n");
|
|
else
|
|
fprintf (fp_symbol_table, "tohost 0x%0" PRIx64 "\n", tohost_addr);
|
|
|
|
fclose (fp_symbol_table);
|
|
}
|
|
}
|
|
else {
|
|
fprintf (stdout, "Ignored\n");
|
|
}
|
|
}
|
|
|
|
elf_end (e);
|
|
|
|
fprintf (stdout, "Min addr: %16" PRIx64 " (hex)\n", min_addr);
|
|
fprintf (stdout, "Max addr: %16" PRIx64 " (hex)\n", max_addr);
|
|
}
|
|
|
|
// ================================================================
|
|
|
|
// Write out from word containing addr1 to word containing addr2
|
|
void write_mem_hex_file (FILE *fp, uint64_t addr1, uint64_t addr2)
|
|
{
|
|
const uint64_t bits_per_raw_mem_word = 256;
|
|
uint64_t bytes_per_raw_mem_word = bits_per_raw_mem_word / 8; // 32
|
|
uint64_t raw_mem_word_align_mask = (~ ((uint64_t) (bytes_per_raw_mem_word - 1)));
|
|
|
|
fprintf (stdout, "Subtracting 0x%08" PRIx64 " base from addresses\n", BASE_ADDR_B);
|
|
|
|
// Align the start and end addrs to raw mem words
|
|
uint64_t a1 = (addr1 & raw_mem_word_align_mask);
|
|
uint64_t a2 = ((addr2 + bytes_per_raw_mem_word - 1) & raw_mem_word_align_mask);
|
|
|
|
fprintf (fp, "@%07" PRIx64 " // raw_mem addr; byte addr: %08" PRIx64 "\n",
|
|
((a1 - BASE_ADDR_B) / bytes_per_raw_mem_word),
|
|
a1 - BASE_ADDR_B);
|
|
|
|
uint64_t addr;
|
|
for (addr = a1; addr < a2; addr += bytes_per_raw_mem_word) {
|
|
for (int j = (bytes_per_raw_mem_word - 1); j >= 0; j--)
|
|
fprintf (fp, "%02x", mem_buf [addr+j-MIN_MEM_ADDR_1GB]);
|
|
fprintf (fp, " // raw_mem addr %08" PRIx64 "; byte addr %08" PRIx64 "\n",
|
|
((addr - BASE_ADDR_B) / bytes_per_raw_mem_word),
|
|
(addr - BASE_ADDR_B));
|
|
}
|
|
|
|
// Write last word, if necessary, to avoid warnings about missing locations
|
|
if (addr < (MAX_MEM_ADDR_1GB - bytes_per_raw_mem_word)) {
|
|
addr = MAX_MEM_ADDR_1GB - bytes_per_raw_mem_word;
|
|
fprintf (fp, "@%07" PRIx64 " // last raw_mem addr; byte addr: %08" PRIx64 "\n",
|
|
((addr - BASE_ADDR_B) / bytes_per_raw_mem_word),
|
|
addr - BASE_ADDR_B);
|
|
for (int j = (bytes_per_raw_mem_word - 1); j >= 0; j--)
|
|
fprintf (fp, "%02x", 0);
|
|
fprintf (fp, " // raw_mem addr %08" PRIx64 "; byte addr %08" PRIx64 "\n",
|
|
((addr - BASE_ADDR_B) / bytes_per_raw_mem_word),
|
|
(addr - BASE_ADDR_B));
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
|
|
void print_usage (FILE *fp, int argc, char *argv [])
|
|
{
|
|
fprintf (fp, "Usage:\n");
|
|
fprintf (fp, " %s --help\n", argv [0]);
|
|
fprintf (fp, " %s <ELF filename> <mem hex filename>\n", argv [0]);
|
|
fprintf (fp, "Reads ELF file and writes a Verilog Hex Memory image file\n");
|
|
fprintf (fp, "ELF file should have addresses within this range:\n");
|
|
fprintf (fp, "< Max: 0x%8" PRIx64 "\n", MAX_MEM_ADDR_1GB);
|
|
fprintf (fp, ">= Min: 0x%8" PRIx64 "\n", MIN_MEM_ADDR_1GB);
|
|
}
|
|
|
|
// ================================================================
|
|
|
|
int main (int argc, char *argv [])
|
|
{
|
|
if ((argc == 2) && (strcmp (argv [1], "--help") == 0)) {
|
|
print_usage (stdout, argc, argv);
|
|
return 0;
|
|
}
|
|
else if (argc != 3) {
|
|
print_usage (stderr, argc, argv);
|
|
return 1;
|
|
}
|
|
|
|
// Zero out the memory buffer before loading the ELF file
|
|
mem_buf = (uint8_t *)(calloc(MAX_MEM_SIZE, sizeof(uint8_t)));
|
|
if (mem_buf == NULL) {
|
|
fprintf (stderr, "ERROR: unable to allocate %lxMB for mem_buf\n", MAX_MEM_SIZE / 1024 / 1024);
|
|
return 1;
|
|
}
|
|
|
|
c_mem_load_elf (argv [1], "_start", "exit", "tohost");
|
|
|
|
if ((min_addr < BASE_ADDR_B) || (MAX_MEM_ADDR_1GB <= max_addr)) {
|
|
print_usage (stderr, argc, argv);
|
|
exit (1);
|
|
}
|
|
|
|
FILE *fp_out = fopen (argv [2], "w");
|
|
if (fp_out == NULL) {
|
|
fprintf (stderr, "ERROR: unable to open file '%s' for output\n", argv [2]);
|
|
return 1;
|
|
}
|
|
|
|
fprintf (stdout, "Writing mem hex to file '%s'\n", argv [2]);
|
|
write_mem_hex_file (fp_out, BASE_ADDR_B, max_addr);
|
|
// write_mem_hex_file (fp_out, BASE_ADDR_B, MAX_MEM_ADDR_1GB);
|
|
|
|
free(mem_buf);
|
|
fclose (fp_out);
|
|
}
|