Files
jak-project/game/kernel/klink.cpp
T
2020-08-26 23:07:55 -04:00

539 lines
17 KiB
C++

/*!
* @file klink.cpp
* GOAL Linker for x86-64
* Note - this is significantly different from the MIPS linker because the object file format is
* different.
* DONE!
*/
#include <cstring>
#include <cassert>
#include "klink.h"
#include "fileio.h"
#include "kscheme.h"
#include "kboot.h"
#include "kprint.h"
#include "common/symbols.h"
#include "common/goal_constants.h"
namespace {
// turn on printf's for debugging linking issues.
constexpr bool link_debug_printfs = false;
} // namespace
// space to store a single in-progress linking state.
link_control saved_link_control;
// pointer to GOAL *ultimate-memcpy*, if its loaded.
Ptr<Function> gfunc_774;
void klink_init_globals() {
saved_link_control.reset();
gfunc_774.offset = 0;
}
/*!
* Initialize the link control.
*/
void link_control::begin(Ptr<uint8_t> object_file,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags) {
// save data from call to begin
m_object_data = object_file;
kstrcpy(m_object_name, name);
m_object_size = size;
m_heap = heap;
m_flags = flags;
// initialize link control
m_entry.offset = 0;
m_heap_top = m_heap->top;
m_keep_debug = false;
if (link_debug_printfs) {
char* goal_name = object_file.cast<char>().c();
printf("link %s\n", goal_name);
printf("link_control::begin %c%c%c%c\n", goal_name[0], goal_name[1], goal_name[2],
goal_name[3]);
}
// points to the beginning of the linking data
m_link_block_ptr = object_file + BASIC_OFFSET;
m_code_size = 0;
m_code_start = object_file;
m_state = 0;
m_segment_process = 0;
ObjectFileHeader* ofh = m_link_block_ptr.cast<ObjectFileHeader>().c();
if (link_debug_printfs) {
printf("Object file header:\n");
printf(" GOAL ver %d.%d obj %d len %d\n", ofh->goal_version_major, ofh->goal_version_minor,
ofh->object_file_version, ofh->link_block_length);
printf(" segment count %d\n", ofh->segment_count);
for (int i = 0; i < N_SEG; i++) {
printf(" seg %d link 0x%04x, 0x%04x data 0x%04x, 0x%04x\n", i, ofh->link_infos[i].offset,
ofh->link_infos[i].size, ofh->code_infos[i].offset, ofh->code_infos[i].size);
}
}
m_version = ofh->object_file_version;
if (ofh->object_file_version < 4) {
// three segment file
// seek past the header
m_object_data.offset += ofh->link_block_length;
// todo, set m_code_size
if (m_link_block_ptr.offset < m_heap->base.offset ||
m_link_block_ptr.offset >= m_heap->top.offset) {
// the link block is outside our heap, or in the top of our heap. It's somebody else's
// problem.
if (link_debug_printfs) {
printf("Link block somebody else's problem\n");
}
if (m_heap->base.offset <= m_object_data.offset && // above heap base
m_object_data.offset < m_heap->top.offset && // less than heap top (not needed?)
m_object_data.offset < m_heap->current.offset) { // less than heap current
if (link_debug_printfs) {
printf("Code block in the heap, kicking it out for copy into heap\n");
}
m_heap->current = m_object_data;
}
} else {
// in our heap, we need to move it so we can free up its space later on
if (link_debug_printfs) {
printf("Link block needs to be moved!\n");
}
// allocate space for a new one
auto new_link_block = kmalloc(m_heap, ofh->link_block_length, KMALLOC_TOP, "link-block");
auto old_link_block = m_link_block_ptr - BASIC_OFFSET;
// copy it
ultimate_memcpy(new_link_block.c(), old_link_block.c(), ofh->link_block_length);
m_link_block_ptr = new_link_block + BASIC_OFFSET;
// if we can save some memory here
if (old_link_block.offset < m_heap->current.offset) {
if (link_debug_printfs) {
printf("Kick out old link block\n");
}
m_heap->current = old_link_block;
}
}
} else {
printf("UNHANDLED OBJECT FILE VERSION\n");
assert(false);
}
if ((m_flags & LINK_FLAG_FORCE_DEBUG) && MasterDebug && !DiskBoot) {
m_keep_debug = true;
}
}
/*!
* Make progress on linking.
*/
uint32_t link_control::work() {
auto old_debug_segment = DebugSegment;
if (m_keep_debug) {
DebugSegment = s7.offset + FIX_SYM_TRUE;
}
// set type tag of link block
*((m_link_block_ptr - 4).cast<u32>()) = *((s7 + FIX_SYM_LINK_BLOCK).cast<u32>());
uint32_t rv;
if (m_version == 3) {
rv = work_v3();
} else {
printf("UNHANDLED OBJECT FILE VERSION IN WORK!\n");
assert(false);
return 0;
}
DebugSegment = old_debug_segment;
return rv;
}
/*!
* Link type pointers for a single type in "v3 equivalent" link data
* Returns a pointer to the link table data after the typelinking data.
*/
uint32_t typelink_v3(Ptr<uint8_t> link, Ptr<uint8_t> data) {
// get the name of the type
uint32_t seek = 0;
char sym_name[256];
while (link.c()[seek]) {
sym_name[seek] = link.c()[seek];
seek++;
assert(seek < 256);
}
sym_name[seek] = 0;
seek++;
// determine the number of methods
uint8_t method_count = link.c()[seek++];
// intern the GOAL type, creating the vtable if it doesn't exist.
auto type_ptr = intern_type_from_c(sym_name, method_count);
// prepare to read the locations of the type pointers
Ptr<uint32_t> offsets = link.cast<uint32_t>() + seek;
uint32_t offset_count = *offsets;
offsets = offsets + 4;
seek += 4;
// write the type pointers into memory
for (uint32_t i = 0; i < offset_count; i++) {
*(data + offsets.c()[i]).cast<int32_t>() = type_ptr.offset;
seek += 4;
}
return seek;
}
/*!
* Link symbols (both offsets and pointers) in "v3 equivalent" link data.
* Returns a pointer to the link table data after the linking data for this symbol.
*/
uint32_t symlink_v3(Ptr<uint8_t> link, Ptr<uint8_t> data) {
// get the symbol name
uint32_t seek = 0;
char sym_name[256];
while (link.c()[seek]) {
sym_name[seek] = link.c()[seek];
seek++;
assert(seek < 256);
}
sym_name[seek] = 0;
seek++;
// intern
auto sym = intern_from_c(sym_name);
int32_t sym_offset = sym.cast<u32>() - s7;
uint32_t sym_addr = sym.cast<u32>().offset;
// prepare to read locations of symbol links
Ptr<uint32_t> offsets = link.cast<uint32_t>() + seek;
uint32_t offset_count = *offsets;
offsets = offsets + 4;
seek += 4;
for (uint32_t i = 0; i < offset_count; i++) {
uint32_t offset = offsets.c()[i];
seek += 4;
auto data_ptr = (data + offset).cast<int32_t>();
if (*data_ptr == -1) {
// a "-1" indicates that we should store the address.
*(data + offset).cast<int32_t>() = sym_addr;
} else {
// otherwise store the offset to st. Eventually this should become an s16 instead.
*(data + offset).cast<int32_t>() = sym_offset;
}
}
return seek;
}
/*!
* Link a single pointer.
*/
uint32_t cross_seg_dist_link_v3(Ptr<uint8_t> link,
ObjectFileHeader* ofh,
int current_seg,
int size) {
// target seg, dist into mine, dist into target, patch loc in mine
uint8_t target_seg = *link;
assert(target_seg < ofh->segment_count);
uint32_t* link_data = (link + 1).cast<uint32_t>().c();
int32_t mine = link_data[0] + ofh->code_infos[current_seg].offset;
int32_t tgt = link_data[1] + ofh->code_infos[target_seg].offset;
int32_t diff = tgt - mine;
uint32_t offset_of_patch = link_data[2] + ofh->code_infos[current_seg].offset;
// printf("link object in seg %d diff %d at %d (%d + %d)\n", target_seg, diff, offset_of_patch,
// link_data[2], ofh->code_infos[current_seg].offset);
// both 32-bit and 64-bit pointer links are supported, though 64-bit ones should disappear soon.
if (size == 4) {
*Ptr<int32_t>(offset_of_patch).c() = diff;
} else if (size == 8) {
*Ptr<int64_t>(offset_of_patch).c() = diff;
} else {
throw std::runtime_error("unknown size in cross_seg_dist_link_v3");
}
return 1 + 3 * 4;
}
/*!
* Run the linker. For now, all linking is done in two runs. If this turns out to be too slow,
* this should be modified to do incremental linking over multiple runs.
*/
uint32_t link_control::work_v3() {
ObjectFileHeader* ofh = m_link_block_ptr.cast<ObjectFileHeader>().c();
if (m_state == 0) {
// state 0 <- copying data.
// the actual game does all copying in one shot. I assume this is ok because v3 files are just
// code and always small. Large data which takes too long to copy should use v2.
// loop over segments
for (s32 seg_id = ofh->segment_count - 1; seg_id >= 0; seg_id--) {
// link the infos
ofh->link_infos[seg_id].offset += m_link_block_ptr.offset;
ofh->code_infos[seg_id].offset += m_object_data.offset;
if (seg_id == DEBUG_SEGMENT) {
if (!DebugSegment) {
// clear code info if we aren't going to copy the debug segment.
ofh->code_infos[seg_id].offset = 0;
ofh->code_infos[seg_id].size = 0;
} else {
if (ofh->code_infos[seg_id].size == 0) {
// not actually present
ofh->code_infos[seg_id].offset = 0;
} else {
Ptr<u8> src(ofh->code_infos[seg_id].offset);
ofh->code_infos[seg_id].offset =
kmalloc(kdebugheap, ofh->code_infos[seg_id].size, 0, "debug-segment").offset;
if (ofh->code_infos[seg_id].offset == 0) {
MsgErr("dkernel: unable to malloc %d bytes for debug-segment\n",
ofh->code_infos[seg_id].size);
return 1;
}
ultimate_memcpy(Ptr<u8>(ofh->code_infos[seg_id].offset).c(), src.c(),
ofh->code_infos[seg_id].size);
}
}
} else if (seg_id == MAIN_SEGMENT) {
if (ofh->code_infos[seg_id].size == 0) {
ofh->code_infos[seg_id].offset = 0;
} else {
Ptr<u8> src(ofh->code_infos[seg_id].offset);
ofh->code_infos[seg_id].offset =
kmalloc(m_heap, ofh->code_infos[seg_id].size, 0, "main-segment").offset;
if (ofh->code_infos[seg_id].offset == 0) {
MsgErr("dkernel: unable to malloc %d bytes for main-segment\n",
ofh->code_infos[seg_id].size);
return 1;
}
ultimate_memcpy(Ptr<u8>(ofh->code_infos[seg_id].offset).c(), src.c(),
ofh->code_infos[seg_id].size);
}
} else if (seg_id == TOP_LEVEL_SEGMENT) {
if (ofh->code_infos[seg_id].size == 0) {
ofh->code_infos[seg_id].offset = 0;
} else {
Ptr<u8> src(ofh->code_infos[seg_id].offset);
ofh->code_infos[seg_id].offset =
kmalloc(m_heap, ofh->code_infos[seg_id].size, KMALLOC_TOP, "top-level-segment")
.offset;
if (ofh->code_infos[seg_id].offset == 0) {
MsgErr("dkernel: unable to malloc %d bytes for top-level-segment\n",
ofh->code_infos[seg_id].size);
return 1;
}
ultimate_memcpy(Ptr<u8>(ofh->code_infos[seg_id].offset).c(), src.c(),
ofh->code_infos[seg_id].size);
}
} else {
printf("UNHANDLED SEG ID IN WORK V3 STATE 1\n");
}
}
m_state = 1;
m_segment_process = 0;
return 0;
} else if (m_state == 1) {
// state 1: linking. For now all links are done at once. This is probably going to be fine on a
// modern computer. But the game broke this into multiple steps.
if (m_segment_process < ofh->segment_count) {
Ptr<u8> lp(ofh->link_infos[m_segment_process].offset);
while (*lp) {
switch (*lp) {
case LINK_TABLE_END:
break;
case LINK_SYMBOL_OFFSET:
lp = lp + 1;
lp = lp + symlink_v3(lp, Ptr<u8>(ofh->code_infos[m_segment_process].offset));
break;
case LINK_TYPE_PTR:
lp = lp + 1; // seek past id
lp = lp + typelink_v3(lp, Ptr<u8>(ofh->code_infos[m_segment_process].offset));
break;
case LINK_DISTANCE_TO_OTHER_SEG_64:
lp = lp + 1;
lp = lp + cross_seg_dist_link_v3(lp, ofh, m_segment_process, 8);
break;
case LINK_DISTANCE_TO_OTHER_SEG_32:
lp = lp + 1;
lp = lp + cross_seg_dist_link_v3(lp, ofh, m_segment_process, 4);
break;
default:
printf("unknown link table thing %d\n", *lp);
exit(0);
break;
}
}
m_segment_process++;
} else {
// all done, can set the entry point to the top-level.
m_entry = Ptr<u8>(ofh->code_infos[TOP_LEVEL_SEGMENT].offset) + 4;
return 1;
}
return 0;
}
else {
printf("WORK v3 INVALID STATE\n");
return 1;
}
}
// TODO - work_v2, once v2 objects are created.
/*!
* Complete linking. This will execute the top-level code for v3 object files, if requested.
*/
void link_control::finish() {
CacheFlush(m_code_start.c(), m_code_size);
auto old_debug_segment = DebugSegment;
if (m_keep_debug) {
// note - this probably doesn't work because DebugSegment isn't *debug-segment*.
DebugSegment = s7.offset + FIX_SYM_TRUE;
}
if (m_flags & LINK_FLAG_FORCE_FAST_LINK) {
FastLink = 1;
}
*EnableMethodSet = *EnableMethodSet + m_keep_debug;
ObjectFileHeader* ofh = m_link_block_ptr.cast<ObjectFileHeader>().c();
if (ofh->object_file_version == 3) {
// todo check function type of entry
// execute top level!
if (m_entry.offset && (m_flags & LINK_FLAG_EXECUTE)) {
call_goal(m_entry.cast<Function>(), 0, 0, 0, s7.offset, g_ee_main_mem);
}
// inform compiler that we loaded.
if (m_flags & LINK_FLAG_OUTPUT_LOAD) {
output_segment_load(m_object_name, m_link_block_ptr, m_flags);
}
} else {
printf("UNHANDELD OBJECT FILE VERSION IN FINISH\n");
}
*EnableMethodSet = *EnableMethodSet - m_keep_debug;
FastLink = 0; // nested fast links won't work right.
m_heap->top = m_heap_top;
DebugSegment = old_debug_segment;
}
/*!
* Immediately link and execute an object file.
* DONE, EXACT
*/
Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags) {
link_control lc;
lc.begin(data, name, size, heap, flags);
uint32_t done;
do {
done = lc.work();
} while (!done);
lc.finish();
return lc.m_entry;
}
/*!
* Wrapper so this can be called from GOAL. Not in original game.
*/
u64 link_and_exec_wrapper(u64 data, u64 name, s64 size, u64 heap, u64 flags) {
return link_and_exec(Ptr<u8>(data), Ptr<char>(name).c(), size, Ptr<kheapinfo>(heap), flags)
.offset;
}
/*!
* GOAL exported function for beginning a link with the saved_link_control
* 47 -> output_load, output_true, execute, 8, force fast
* 39 -> no 8 (s7)
*/
uint64_t link_begin(uint64_t object_data,
uint64_t name,
int32_t size,
uint64_t heap,
uint32_t flags) {
saved_link_control.begin(Ptr<u8>(object_data), Ptr<char>(name).c(), size, Ptr<kheapinfo>(heap),
flags);
auto work_result = saved_link_control.work();
// if we managed to finish in one shot, take care of calling finish
if (work_result) {
saved_link_control.finish();
}
return work_result != 0;
}
/*!
* GOAL exported function for doing a small amount of linking work on the saved_link_control
*/
uint64_t link_resume() {
auto work_result = saved_link_control.work();
if (work_result) {
saved_link_control.finish();
}
return work_result != 0;
}
/*!
* The ULTIMATE MEMORY COPY
* IT IS VERY FAST
* but it may use the scratchpad. It is implemented in GOAL, and falls back to normal C memcpy
* if GOAL isn't loaded, or if the alignment isn't good enough.
*/
void* ultimate_memcpy(void* dst, void* src, uint32_t size) {
// only possible if alignment is good.
if (!(u64(dst) & 0xf) && !(u64(src) & 0xf) && !(u64(size) & 0xf)) {
if (!gfunc_774.offset) {
// GOAL function is unknown, lets see if its loaded:
auto sym = find_symbol_from_c("ultimate-memcpy");
if (sym->value == 0) {
return memcpy(dst, src, size);
}
gfunc_774.offset = sym->value;
}
printf("calling goal um\n");
return Ptr<u8>(call_goal(gfunc_774, make_u8_ptr(dst).offset, make_u8_ptr(src).offset, size,
s7.offset, g_ee_main_mem))
.c();
} else {
return memcpy(dst, src, size);
}
}
// The functions below are not ported because they are specific to the MIPS implementation.
// In the MIPS implementation, the c_ functions are used until GOAL loads its GOAL-implemented
// versions of the same functions. The update_goal_fns detects this and causes the linker to use
// the GOAL versions once possible. The GOAL version is much faster, but functionally equivalent to
// the C version. The C version is compiled without optimization, so this isn't too surprising.
// the rellink function is unused.
/*
c_rellink3__FPvP12link_segmentPUc
c_symlink2__FPvUiPUc
c_symlink3__FPvUiPUc
update_goal_fns__Fv
*/