279 lines
10 KiB
C
279 lines
10 KiB
C
/*
|
|
* routines that scan and load a (host) Executable and Linkable Format (ELF) file
|
|
* into the (emulated) memory.
|
|
*/
|
|
|
|
#include "elf.h"
|
|
#include "string.h"
|
|
#include "riscv.h"
|
|
#include "spike_interface/spike_utils.h"
|
|
|
|
typedef struct elf_info_t {
|
|
spike_file_t *f;
|
|
struct process *p;
|
|
} elf_info;
|
|
|
|
//
|
|
// the implementation of allocater. allocates memory space for later segment loading
|
|
//
|
|
static void *elf_alloc_mb(elf_ctx *ctx, uint64 elf_pa, uint64 elf_va, uint64 size) {
|
|
// directly returns the virtual address as we are in the Bare mode in lab1
|
|
return (void *)elf_va;
|
|
}
|
|
|
|
//
|
|
// actual file reading, using the spike file interface.
|
|
//
|
|
static uint64 elf_fpread(elf_ctx *ctx, void *dest, uint64 nb, uint64 offset) {
|
|
elf_info *msg = (elf_info *)ctx->info;
|
|
// call spike file utility
|
|
return spike_file_pread(msg->f, dest, nb, offset);
|
|
}
|
|
|
|
//
|
|
// init elf_ctx, a data structure that loads the elf.
|
|
//
|
|
elf_status elf_init(elf_ctx *ctx, void *info) {
|
|
ctx->info = info;
|
|
|
|
// load the elf header
|
|
if (elf_fpread(ctx, &ctx->ehdr, sizeof(ctx->ehdr), 0) != sizeof(ctx->ehdr)) return EL_EIO;
|
|
|
|
// check the signature (magic value) of the elf
|
|
if (ctx->ehdr.magic != ELF_MAGIC) return EL_NOTELF;
|
|
|
|
return EL_OK;
|
|
}
|
|
// leb128 (little-endian base 128) is a variable-length
|
|
// compression algoritm in DWARF
|
|
void read_uleb128(uint64 *out, char **off) {
|
|
uint64 value = 0; int shift = 0; uint8 b;
|
|
for (;;) {
|
|
b = *(uint8 *)(*off); (*off)++;
|
|
value |= ((uint64)b & 0x7F) << shift;
|
|
shift += 7;
|
|
if ((b & 0x80) == 0) break;
|
|
}
|
|
if (out) *out = value;
|
|
}
|
|
void read_sleb128(int64 *out, char **off) {
|
|
int64 value = 0; int shift = 0; uint8 b;
|
|
for (;;) {
|
|
b = *(uint8 *)(*off); (*off)++;
|
|
value |= ((uint64_t)b & 0x7F) << shift;
|
|
shift += 7;
|
|
if ((b & 0x80) == 0) break;
|
|
}
|
|
if (shift < 64 && (b & 0x40)) value |= -(1 << shift);
|
|
if (out) *out = value;
|
|
}
|
|
// Since reading below types through pointer cast requires aligned address,
|
|
// so we can only read them byte by byte
|
|
void read_uint64(uint64 *out, char **off) {
|
|
*out = 0;
|
|
for (int i = 0; i < 8; i++) {
|
|
*out |= (uint64)(**off) << (i << 3); (*off)++;
|
|
}
|
|
}
|
|
void read_uint32(uint32 *out, char **off) {
|
|
*out = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
*out |= (uint32)(**off) << (i << 3); (*off)++;
|
|
}
|
|
}
|
|
void read_uint16(uint16 *out, char **off) {
|
|
*out = 0;
|
|
for (int i = 0; i < 2; i++) {
|
|
*out |= (uint16)(**off) << (i << 3); (*off)++;
|
|
}
|
|
}
|
|
/*
|
|
* analyzis the data in the debug_line section
|
|
*
|
|
* the function needs 3 parameters: elf context, data in the debug_line section
|
|
* and length of debug_line section
|
|
*
|
|
* make 3 arrays:
|
|
* "process->dir" stores all directory paths of code files
|
|
* "process->file" stores all code file names of code files and their directory path index of array "dir"
|
|
* "process->line" stores all relationships map instruction addresses to code line numbers
|
|
* and their code file name index of array "file"
|
|
*/
|
|
void make_addr_line(elf_ctx *ctx, char *debug_line, uint64 length) {
|
|
process *p = ((elf_info *)ctx->info)->p;
|
|
p->debugline = debug_line;
|
|
// directory name char pointer array
|
|
p->dir = (char **)((((uint64)debug_line + length + 7) >> 3) << 3); int dir_ind = 0, dir_base;
|
|
// file name char pointer array
|
|
p->file = (code_file *)(p->dir + 64); int file_ind = 0, file_base;
|
|
// table array
|
|
p->line = (addr_line *)(p->file + 64); p->line_ind = 0;
|
|
char *off = debug_line;
|
|
while (off < debug_line + length) { // iterate each compilation unit(CU)
|
|
debug_header *dh = (debug_header *)off; off += sizeof(debug_header);
|
|
dir_base = dir_ind; file_base = file_ind;
|
|
// get directory name char pointer in this CU
|
|
while (*off != 0) {
|
|
p->dir[dir_ind++] = off; while (*off != 0) off++; off++;
|
|
}
|
|
off++;
|
|
// get file name char pointer in this CU
|
|
while (*off != 0) {
|
|
p->file[file_ind].file = off; while (*off != 0) off++; off++;
|
|
uint64 dir; read_uleb128(&dir, &off);
|
|
p->file[file_ind++].dir = dir - 1 + dir_base;
|
|
read_uleb128(NULL, &off); read_uleb128(NULL, &off);
|
|
}
|
|
off++; addr_line regs; regs.addr = 0; regs.file = 1; regs.line = 1;
|
|
// simulate the state machine op code
|
|
for (;;) {
|
|
uint8 op = *(off++);
|
|
switch (op) {
|
|
case 0: // Extended Opcodes
|
|
read_uleb128(NULL, &off); op = *(off++);
|
|
switch (op) {
|
|
case 1: // DW_LNE_end_sequence
|
|
if (p->line_ind > 0 && p->line[p->line_ind - 1].addr == regs.addr) p->line_ind--;
|
|
p->line[p->line_ind] = regs; p->line[p->line_ind].file += file_base - 1;
|
|
p->line_ind++; goto endop;
|
|
case 2: // DW_LNE_set_address
|
|
read_uint64(®s.addr, &off); break;
|
|
// ignore DW_LNE_define_file
|
|
case 4: // DW_LNE_set_discriminator
|
|
read_uleb128(NULL, &off); break;
|
|
}
|
|
break;
|
|
case 1: // DW_LNS_copy
|
|
if (p->line_ind > 0 && p->line[p->line_ind - 1].addr == regs.addr) p->line_ind--;
|
|
p->line[p->line_ind] = regs; p->line[p->line_ind].file += file_base - 1;
|
|
p->line_ind++; break;
|
|
case 2: { // DW_LNS_advance_pc
|
|
uint64 delta; read_uleb128(&delta, &off);
|
|
regs.addr += delta * dh->min_instruction_length;
|
|
break;
|
|
}
|
|
case 3: { // DW_LNS_advance_line
|
|
int64 delta; read_sleb128(&delta, &off);
|
|
regs.line += delta; break; } case 4: // DW_LNS_set_file
|
|
read_uleb128(®s.file, &off); break;
|
|
case 5: // DW_LNS_set_column
|
|
read_uleb128(NULL, &off); break;
|
|
case 6: // DW_LNS_negate_stmt
|
|
case 7: // DW_LNS_set_basic_block
|
|
break;
|
|
case 8: { // DW_LNS_const_add_pc
|
|
int adjust = 255 - dh->opcode_base;
|
|
int delta = (adjust / dh->line_range) * dh->min_instruction_length;
|
|
regs.addr += delta; break;
|
|
}
|
|
case 9: { // DW_LNS_fixed_advanced_pc
|
|
uint16 delta; read_uint16(&delta, &off);
|
|
regs.addr += delta;
|
|
break;
|
|
}
|
|
// ignore 10, 11 and 12
|
|
default: { // Special Opcodes
|
|
int adjust = op - dh->opcode_base;
|
|
int addr_delta = (adjust / dh->line_range) * dh->min_instruction_length;
|
|
int line_delta = dh->line_base + (adjust % dh->line_range);
|
|
regs.addr += addr_delta;
|
|
regs.line += line_delta;
|
|
if (p->line_ind > 0 && p->line[p->line_ind - 1].addr == regs.addr) p->line_ind--;
|
|
p->line[p->line_ind] = regs; p->line[p->line_ind].file += file_base - 1;
|
|
p->line_ind++; break;
|
|
}
|
|
}
|
|
}
|
|
endop:;
|
|
}
|
|
// for (int i = 0; i < p->line_ind; i++)
|
|
// sprint("%p %d %d\n", p->line[i].addr, p->line[i].line, p->line[i].file);
|
|
}
|
|
//
|
|
// load the elf segments to memory regions as we are in Bare mode in lab1
|
|
//
|
|
elf_status elf_load(elf_ctx *ctx) {
|
|
elf_prog_header ph_addr;
|
|
int i, off;
|
|
// traverse the elf program segment headers
|
|
for (i = 0, off = ctx->ehdr.phoff; i < ctx->ehdr.phnum; i++, off += sizeof(ph_addr)) {
|
|
// read segment headers
|
|
if (elf_fpread(ctx, (void *)&ph_addr, sizeof(ph_addr), off) != sizeof(ph_addr)) return EL_EIO;
|
|
|
|
if (ph_addr.type != ELF_PROG_LOAD) continue;
|
|
if (ph_addr.memsz < ph_addr.filesz) return EL_ERR;
|
|
if (ph_addr.vaddr + ph_addr.memsz < ph_addr.vaddr) return EL_ERR;
|
|
|
|
// allocate memory before loading
|
|
void *dest = elf_alloc_mb(ctx, ph_addr.vaddr, ph_addr.vaddr, ph_addr.memsz);
|
|
|
|
// actual loading
|
|
if (elf_fpread(ctx, dest, ph_addr.memsz, ph_addr.off) != ph_addr.memsz)
|
|
return EL_EIO;
|
|
}
|
|
|
|
return EL_OK;
|
|
}
|
|
|
|
typedef union {
|
|
uint64 buf[MAX_CMDLINE_ARGS];
|
|
char *argv[MAX_CMDLINE_ARGS];
|
|
} arg_buf;
|
|
|
|
//
|
|
// returns the number (should be 1) of string(s) after PKE kernel in command line.
|
|
// and store the string(s) in arg_bug_msg.
|
|
//
|
|
static size_t parse_args(arg_buf *arg_bug_msg) {
|
|
// HTIFSYS_getmainvars frontend call reads command arguments to (input) *arg_bug_msg
|
|
long r = frontend_syscall(HTIFSYS_getmainvars, (uint64)arg_bug_msg,
|
|
sizeof(*arg_bug_msg), 0, 0, 0, 0, 0);
|
|
kassert(r == 0);
|
|
|
|
size_t pk_argc = arg_bug_msg->buf[0];
|
|
uint64 *pk_argv = &arg_bug_msg->buf[1];
|
|
|
|
int arg = 1; // skip the PKE OS kernel string, leave behind only the application name
|
|
for (size_t i = 0; arg + i < pk_argc; i++)
|
|
arg_bug_msg->argv[i] = (char *)(uintptr_t)pk_argv[arg + i];
|
|
|
|
//returns the number of strings after PKE kernel in command line
|
|
return pk_argc - arg;
|
|
}
|
|
|
|
//
|
|
// load the elf of user application, by using the spike file interface.
|
|
//
|
|
void load_bincode_from_host_elf(struct process *p) {
|
|
arg_buf arg_bug_msg;
|
|
|
|
// retrieve command line arguements
|
|
size_t argc = parse_args(&arg_bug_msg);
|
|
if (!argc) panic("You need to specify the application program!\n");
|
|
|
|
sprint("Application: %s\n", arg_bug_msg.argv[0]);
|
|
|
|
//elf loading
|
|
elf_ctx elfloader;
|
|
elf_info info;
|
|
|
|
info.f = spike_file_open(arg_bug_msg.argv[0], O_RDONLY, 0);
|
|
info.p = p;
|
|
if (IS_ERR_VALUE(info.f)) panic("Fail on openning the input application program.\n");
|
|
|
|
// init elfloader
|
|
if (elf_init(&elfloader, &info) != EL_OK)
|
|
panic("fail to init elfloader.\n");
|
|
|
|
// load elf
|
|
if (elf_load(&elfloader) != EL_OK) panic("Fail on loading elf.\n");
|
|
|
|
// entry (virtual) address
|
|
p->trapframe->epc = elfloader.ehdr.entry;
|
|
|
|
// close host file
|
|
spike_file_close( info.f );
|
|
|
|
sprint("Application program entry point (virtual address): 0x%lx\n", p->trapframe->epc);
|
|
}
|