mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-10 23:32:09 +00:00
6d5ffa9f8e
There's still some cleanup to do, but everything seems to be working nicely: `make -j` works, `make distcheck` passes. There is probably plenty of bitrot in the package directories (RPM, debian), though. The vc project files have been removed since those versions are way out of date and quakeforge is pretty much dependent on gcc now anyway. Most of the old Makefile.am files are now Makemodule.am. This should allow for new Makefile.am files that allow local building (to be added on an as-needed bases). The current remaining Makefile.am files are for standalone sub-projects.a The installable bins are currently built in the top-level build directory. This may change if the clutter gets to be too much. While this does make a noticeable difference in build times, the main reason for the switch was to take care of the growing dependency issues: now it's possible to build tools for code generation (eg, using qfcc and ruamoko programs for code-gen).
1475 lines
40 KiB
C
1475 lines
40 KiB
C
/*
|
|
linker.c
|
|
|
|
qc object file linking
|
|
|
|
Copyright (C) 2002 Bill Currie <bill@taniwha.org>
|
|
Copyright (C) 2011 Bill Currie <bill@taniwha.org>
|
|
|
|
Author: Bill Currie <bill@taniwha.org>
|
|
Date: 2002/7/3
|
|
Date: 2011/2/24
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_IO_H
|
|
# include <io.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
|
|
#include "qfalloca.h"
|
|
|
|
#include "QF/alloc.h"
|
|
#include "QF/dstring.h"
|
|
#include "QF/hash.h"
|
|
#include "QF/pakfile.h"
|
|
#include "QF/va.h"
|
|
|
|
#include "tools/qfcc/include/class.h"
|
|
#include "tools/qfcc/include/codespace.h"
|
|
#include "tools/qfcc/include/def.h"
|
|
#include "tools/qfcc/include/defspace.h"
|
|
#include "tools/qfcc/include/diagnostic.h"
|
|
#include "tools/qfcc/include/emit.h"
|
|
#include "tools/qfcc/include/expr.h"
|
|
#include "tools/qfcc/include/linker.h"
|
|
#include "tools/qfcc/include/obj_file.h"
|
|
#include "tools/qfcc/include/obj_type.h"
|
|
#include "tools/qfcc/include/options.h"
|
|
#include "tools/qfcc/include/qfcc.h"
|
|
#include "tools/qfcc/include/reloc.h"
|
|
#include "tools/qfcc/include/strpool.h"
|
|
#include "tools/qfcc/include/type.h"
|
|
|
|
static void linker_internal_error (const char *fmt, ...)
|
|
__attribute__ ((format (printf, 1, 2), noreturn));
|
|
static void linker_error (const char *fmt, ...)
|
|
__attribute__ ((format (printf, 1, 2)));
|
|
static void linker_warning (const char *fmt, ...)
|
|
__attribute__ ((format (printf, 1, 2)));
|
|
static void def_error (qfo_def_t *def, const char *fmt, ...)
|
|
__attribute__ ((format (printf, 2, 3)));
|
|
static void def_warning (qfo_def_t *def, const char *fmt, ...)
|
|
__attribute__ ((used, format (printf, 2, 3)));
|
|
|
|
/** Safe handling of defs in hash tables and other containers.
|
|
|
|
As defs are stored in dynamic arrays, storing pointers to the defs is
|
|
a recipe for disaster when using realloc(). By storing the address of
|
|
the pointer to the array and the index into that that array, realloc
|
|
is allowed to do its magic.
|
|
*/
|
|
typedef struct defref_s {
|
|
struct defref_s *next; ///< if \c merge is true, the next def to
|
|
///< be merged with the main def
|
|
int def; ///< the index of this def
|
|
int space; ///< the space in which this def resides
|
|
int merge; ///< merge def's relocs with the main def
|
|
struct defref_s *merge_list; ///< list of defs to be merged with this
|
|
///< def
|
|
} defref_t;
|
|
|
|
/** Retreive a def from a defref.
|
|
|
|
\param r The defref pointing to the def (defref_t *).
|
|
\return A pointer to the def (qfo_def_t *).
|
|
*/
|
|
#define REF(r) (work->spaces[(r)->space].defs + (r)->def)
|
|
|
|
typedef struct builtin_sym_s {
|
|
const char *name;
|
|
type_t *type;
|
|
unsigned flags;
|
|
defref_t *defref;
|
|
} builtin_sym_t;
|
|
|
|
static builtin_sym_t builtin_symbols[] __attribute__ ((used)) = {
|
|
{".zero", &type_zero, QFOD_NOSAVE | QFOD_GLOBAL},
|
|
{".return", &type_param, QFOD_NOSAVE | QFOD_GLOBAL},
|
|
{".param_0", &type_param, QFOD_NOSAVE | QFOD_GLOBAL},
|
|
{".param_1", &type_param, QFOD_NOSAVE | QFOD_GLOBAL},
|
|
{".param_2", &type_param, QFOD_NOSAVE | QFOD_GLOBAL},
|
|
{".param_3", &type_param, QFOD_NOSAVE | QFOD_GLOBAL},
|
|
{".param_4", &type_param, QFOD_NOSAVE | QFOD_GLOBAL},
|
|
{".param_5", &type_param, QFOD_NOSAVE | QFOD_GLOBAL},
|
|
{".param_6", &type_param, QFOD_NOSAVE | QFOD_GLOBAL},
|
|
{".param_7", &type_param, QFOD_NOSAVE | QFOD_GLOBAL},
|
|
};
|
|
static const unsigned num_builtins = sizeof (builtin_symbols)
|
|
/ sizeof (builtin_symbols[0]);
|
|
|
|
static defref_t *defrefs_freelist;
|
|
|
|
static hashtab_t *extern_data_defs;
|
|
static hashtab_t *defined_data_defs;
|
|
|
|
static hashtab_t *extern_field_defs;
|
|
static hashtab_t *defined_field_defs;
|
|
|
|
static hashtab_t *extern_type_defs;
|
|
static hashtab_t *defined_type_defs;
|
|
static qfo_mspace_t *qfo_type_defs;
|
|
|
|
static qfo_t *work;
|
|
static int work_base[qfo_num_spaces];
|
|
static int work_func_base;
|
|
static defref_t **work_defrefs;
|
|
static unsigned num_work_defrefs;
|
|
static strpool_t *work_strings;
|
|
static codespace_t *work_code;
|
|
static defspace_t *work_near_data;
|
|
static defspace_t *work_far_data;
|
|
static defspace_t *work_entity_data;
|
|
static defspace_t *work_type_data;
|
|
static defspace_t *work_debug_data;
|
|
static qfo_reloc_t *work_loose_relocs;
|
|
static int work_num_loose_relocs;
|
|
|
|
static defspace_t **work_spaces[qfo_num_spaces] = {
|
|
0, 0, 0,
|
|
&work_near_data,
|
|
&work_far_data,
|
|
&work_entity_data,
|
|
&work_type_data,
|
|
&work_debug_data,
|
|
};
|
|
|
|
static dstring_t *linker_current_file;
|
|
|
|
#define QFOSTR(q,s) QFO_GETSTR (q, s)
|
|
#define WORKSTR(s) QFOSTR (work, s)
|
|
#define QFOTYPE(t) ((qfot_type_t *) (char *) \
|
|
(qfo_type_defs->d.data + (t)))
|
|
#define WORKTYPE(t) ((qfot_type_t *) (char *) \
|
|
(work_type_data->data + (t)))
|
|
|
|
/** Produce an error message for defs with mismatched types.
|
|
|
|
\param def The new def.
|
|
\param prev The previous definition.
|
|
*/
|
|
static void
|
|
linker_type_mismatch (qfo_def_t *def, qfo_def_t *prev)
|
|
{
|
|
const char *def_encoding;
|
|
const char *prev_encoding;
|
|
|
|
if ((int) def->type < 0)
|
|
def_encoding = QFO_GETSTR (work, -(int) def->type);
|
|
else
|
|
def_encoding = QFO_TYPESTR (work, def->type);
|
|
if ((int) prev->type < 0)
|
|
prev_encoding = QFO_GETSTR (work, -(int) prev->type);
|
|
else
|
|
prev_encoding = QFO_TYPESTR (work, prev->type);
|
|
def_error (def, "type mismatch for `%s' `%s'", WORKSTR (def->name),
|
|
def_encoding);
|
|
def_error (prev, "previous definition `%s'",
|
|
prev_encoding);
|
|
}
|
|
|
|
/** Create a new def reference.
|
|
|
|
References are required to avoid problems with realloc moving things
|
|
around.
|
|
|
|
The \a def must be in the \a space, and the \a space must be in the
|
|
\c work qfo.
|
|
|
|
\param def The def for which the reference will be created.
|
|
\param space The defspace in \c work which holds \a def.
|
|
\return The new reference.
|
|
*/
|
|
static defref_t *
|
|
get_defref (qfo_def_t *def, qfo_mspace_t *space)
|
|
{
|
|
defref_t *defref;
|
|
|
|
ALLOC (16384, defref_t, defrefs, defref);
|
|
defref->def = def - space->defs;
|
|
defref->space = space - work->spaces;
|
|
return defref;
|
|
}
|
|
|
|
static const char *
|
|
defs_get_key (const void *_r, void *unused)
|
|
{
|
|
defref_t *r = (defref_t *)_r;
|
|
qfo_def_t *d = REF (r);
|
|
return WORKSTR (d->name);
|
|
}
|
|
|
|
/** Add a string to the working qfo string pool.
|
|
|
|
If the string is already in the string pool, the already existing string
|
|
index will be returned instead of adding a second copy of the string.
|
|
|
|
The strings space in the working qfo is kept up to date.
|
|
|
|
\param str The string to add.
|
|
\return The string offset in the working qfo string pool.
|
|
*/
|
|
int
|
|
linker_add_string (const char *str)
|
|
{
|
|
string_t new;
|
|
new = strpool_addstr (work_strings, str);
|
|
work->spaces[qfo_strings_space].d.strings = work_strings->strings;
|
|
work->spaces[qfo_strings_space].data_size = work_strings->size;
|
|
return new;
|
|
}
|
|
|
|
/** Allocate data from the working qfo.
|
|
|
|
\param space The space from which to allocate data.
|
|
\param size The number of words to allocate.
|
|
\return The offset of the allocated data.
|
|
*/
|
|
static int
|
|
alloc_data (int space, int size)
|
|
{
|
|
int offset;
|
|
|
|
if (space < 0 || space >= qfo_num_spaces || !work_spaces[space])
|
|
linker_internal_error ("bad space for alloc_data (): %d", space);
|
|
if (size <= 0)
|
|
linker_internal_error ("bad size for alloc_data (): %d", space);
|
|
offset = defspace_alloc_loc (*work_spaces[space], size);
|
|
work->spaces[space].d.data = (*work_spaces[space])->data;
|
|
work->spaces[space].data_size = (*work_spaces[space])->size;
|
|
return offset;
|
|
}
|
|
|
|
/** Resolve an external def with its global definition.
|
|
|
|
The types of the external def and the global def must match.
|
|
The offset and flags of the global def are copied to the external def.
|
|
|
|
\param ext The external def.
|
|
\param def The global definition.
|
|
*/
|
|
static void
|
|
resolve_external_def (defref_t *ext, defref_t *def)
|
|
{
|
|
qfot_type_t *ext_type;
|
|
qfot_type_t *def_type;
|
|
if (!(REF (ext)->flags & QFOD_EXTERNAL)) {
|
|
def_error (REF (ext), "%s %x", WORKSTR (REF (ext)->name),
|
|
REF (ext)->flags);
|
|
linker_internal_error ("ext is not an external def");
|
|
}
|
|
if (REF (def)->flags & (QFOD_EXTERNAL | QFOD_LOCAL)) {
|
|
def_error (REF (def), "%s %x", WORKSTR (REF (def)->name),
|
|
REF (def)->flags);
|
|
linker_internal_error ("def is an external or local def");
|
|
}
|
|
ext_type = WORKTYPE (REF(ext)->type);
|
|
if (ext_type->meta == ty_alias) {
|
|
ext_type = WORKTYPE (ext_type->alias.aux_type);
|
|
}
|
|
def_type = WORKTYPE (REF (def)->type);
|
|
if (def_type->meta == ty_alias) {
|
|
def_type = WORKTYPE (def_type->alias.aux_type);
|
|
}
|
|
if (ext_type != def_type) {
|
|
linker_type_mismatch (REF (ext), REF (def));
|
|
return;
|
|
}
|
|
REF (ext)->offset = REF (def)->offset;
|
|
REF (ext)->flags = REF (def)->flags;
|
|
ext->merge = 1;
|
|
ext->space = def->space;
|
|
ext->next = def->merge_list;
|
|
def->merge_list = ext;
|
|
}
|
|
|
|
static void
|
|
define_def (defref_t *ref, hashtab_t *extern_tab, hashtab_t *defined_tab)
|
|
{
|
|
defref_t *r;
|
|
const char *name;
|
|
|
|
if (REF (ref)->flags & (QFOD_EXTERNAL | QFOD_LOCAL)) {
|
|
def_error (REF (ref), "%s %x", WORKSTR (REF (ref)->name),
|
|
REF (ref)->flags);
|
|
linker_internal_error ("ref is an external or local def");
|
|
}
|
|
name = WORKSTR (REF (ref)->name);
|
|
if ((r = Hash_Find (defined_tab, name))) {
|
|
if (REF (r)->flags & QFOD_SYSTEM) {
|
|
/// System defs may be redefined only once.
|
|
REF (r)->flags &= ~QFOD_SYSTEM;
|
|
// treat the new def as external
|
|
REF (ref)->flags |= QFOD_EXTERNAL;
|
|
resolve_external_def (ref, r);
|
|
} else {
|
|
def_error (REF (ref), "%s redefined", WORKSTR (REF (ref)->name));
|
|
def_error (REF (r), "previous definition");
|
|
}
|
|
return;
|
|
}
|
|
Hash_Add (defined_tab, ref);
|
|
while ((r = Hash_Del (extern_tab, name)))
|
|
resolve_external_def (r, ref);
|
|
}
|
|
|
|
static void
|
|
extern_def (defref_t *ref, hashtab_t *extern_tab, hashtab_t *defined_tab)
|
|
{
|
|
defref_t *r;
|
|
const char *name;
|
|
|
|
name = WORKSTR (REF (ref)->name);
|
|
r = Hash_Find (defined_tab, name);
|
|
if (!REF (ref)->num_relocs)
|
|
return;
|
|
if (r) {
|
|
resolve_external_def (ref, r);
|
|
} else {
|
|
Hash_Add (extern_tab, ref);
|
|
}
|
|
}
|
|
|
|
static void
|
|
process_def (defref_t *ref, qfo_mspace_t *space,
|
|
hashtab_t *extern_tab, hashtab_t *defined_tab)
|
|
{
|
|
if (REF (ref)->flags & QFOD_EXTERNAL) {
|
|
extern_def (ref, extern_tab, defined_tab);
|
|
} else {
|
|
if (!(REF (ref)->flags & QFOD_LOCAL))
|
|
REF (ref)->offset += space->data_size;
|
|
if (REF (ref)->flags & QFOD_GLOBAL)
|
|
define_def (ref, extern_tab, defined_tab);
|
|
}
|
|
}
|
|
|
|
static void
|
|
process_data_def (defref_t *ref, qfo_mspace_t *space, qfo_def_t *old)
|
|
{
|
|
process_def (ref, space, extern_data_defs, defined_data_defs);
|
|
}
|
|
|
|
static void
|
|
process_field_def (defref_t *ref, qfo_mspace_t *space, qfo_def_t *old)
|
|
{
|
|
process_def (ref, space, extern_field_defs, defined_field_defs);
|
|
}
|
|
|
|
static void
|
|
process_type_def (defref_t *ref, qfo_mspace_t *space, qfo_def_t *old)
|
|
{
|
|
if (REF (ref)->flags & QFOD_EXTERNAL) {
|
|
// The most common (only?) time external type encodings show up is
|
|
// in pointers to incomplete structs. eg, struct foo *bar; with no
|
|
// struct foo {...};
|
|
extern_def (ref, extern_type_defs, defined_type_defs);
|
|
} else {
|
|
const char *name;
|
|
defref_t *r;
|
|
qfot_type_t *old_type;
|
|
qfot_type_t *new_type;
|
|
|
|
old_type = QFOTYPE(old->offset);
|
|
name = WORKSTR (REF (ref)->name);
|
|
if ((r = Hash_Find (defined_type_defs, name))) {
|
|
// The type has been defined already, so treat this def as if it
|
|
// were external
|
|
REF (ref)->flags |= QFOD_EXTERNAL;
|
|
resolve_external_def (ref, r);
|
|
} else {
|
|
// The type is new to the program, so we need to allocate space
|
|
// for it and copy the type encoding. Relocation records will
|
|
// take care of any cross-references.
|
|
|
|
REF (ref)->offset = alloc_data (qfo_type_space, old_type->size);
|
|
new_type = WORKTYPE (REF (ref)->offset);
|
|
memcpy (new_type, old_type, old_type->size * sizeof (pr_type_t));
|
|
define_def (ref, extern_type_defs, defined_type_defs);
|
|
}
|
|
// Save the new address in the old def's type field so relocation
|
|
// records can be updated. Type encoding defs start with no type
|
|
// (type = 0). The old def's offset is not modified because it is used
|
|
// for finding the def when adjusting relocation records that point
|
|
// to fields inside the type encoding.
|
|
old->type = REF (ref)->offset;
|
|
// Mark the old type encoding as having been transfered, and save the
|
|
// new address in the encoding's class field so def and function types
|
|
// can be updated easily.
|
|
old_type->meta = -1;
|
|
old_type->class = REF (ref)->offset;
|
|
}
|
|
}
|
|
|
|
static void
|
|
adjust_reloc_offset (qfo_reloc_t *reloc)
|
|
{
|
|
if (!reloc->space) {
|
|
//FIXME double check
|
|
reloc->offset += work_base[qfo_code_space];
|
|
} else if (reloc->space < qfo_num_spaces) {
|
|
// Relocs in type space are handled by process_type_space, so don't
|
|
// touch them here.
|
|
if (reloc->space != qfo_type_space)
|
|
reloc->offset += work_base[reloc->space];
|
|
}
|
|
}
|
|
|
|
static int
|
|
add_relocs (qfo_t *qfo, int start, int count, int target)
|
|
{
|
|
unsigned size;
|
|
qfo_reloc_t *ireloc;
|
|
qfo_reloc_t *oreloc;
|
|
|
|
size = work->num_relocs + count;
|
|
work->relocs = realloc (work->relocs, size * sizeof (qfo_reloc_t));
|
|
ireloc = qfo->relocs + start;
|
|
oreloc = work->relocs + work->num_relocs;
|
|
for ( ; work->num_relocs < size; ireloc++, oreloc++) {
|
|
*oreloc = *ireloc;
|
|
// Mark the reloc as having been copied and record the new reloc record
|
|
// number in the old reloc's offset
|
|
ireloc->type = -1;
|
|
ireloc->offset = work->num_relocs++;
|
|
if (oreloc->space >= qfo->num_spaces) {
|
|
linker_error ("bad reloc space: %d (%d)", oreloc->space,
|
|
qfo->num_spaces);
|
|
oreloc->type = rel_none;
|
|
continue;
|
|
}
|
|
oreloc->space = qfo->spaces[oreloc->space].id;
|
|
adjust_reloc_offset (oreloc);
|
|
oreloc->target = target;
|
|
}
|
|
return work->num_relocs - count;
|
|
}
|
|
|
|
static int
|
|
add_defs (qfo_t *qfo, qfo_mspace_t *space, qfo_mspace_t *dest_space,
|
|
void (*process) (defref_t *ref, qfo_mspace_t *space, qfo_def_t *old))
|
|
{
|
|
int count = space->num_defs;
|
|
int size;
|
|
int i;
|
|
qfo_def_t *idef;
|
|
qfo_def_t *odef;
|
|
defref_t *ref;
|
|
qfot_type_t *type;
|
|
|
|
size = (num_work_defrefs + count) * sizeof (defref_t *);
|
|
work_defrefs = realloc (work_defrefs, size);
|
|
size = (dest_space->num_defs + count) * sizeof (qfo_def_t);
|
|
dest_space->defs = realloc (dest_space->defs, size);
|
|
idef = space->defs;
|
|
odef = dest_space->defs + dest_space->num_defs;
|
|
for (i = 0; i < count; i++, idef++, odef++) {
|
|
*odef = *idef; // copy the def data
|
|
odef->name = linker_add_string (QFOSTR (qfo, idef->name));
|
|
odef->file = linker_add_string (QFOSTR (qfo, idef->file));
|
|
idef->file = -1; // mark def as copied
|
|
idef->line = num_work_defrefs; // so def can be found
|
|
// In the first passs, process_type_def sets the type meta to -1 and
|
|
// class to the offset of the copied type, but the null type encodiing
|
|
// is not modified. Other defs are processed in the second pass.
|
|
type = QFOTYPE(idef->type);
|
|
if (idef->type && (int) type->meta != -1) {
|
|
linker_internal_error ("reference to type that has not been "
|
|
"relocated");
|
|
}
|
|
// Type encodings have no type (type = 0) so setting the type
|
|
// to the idef type class has no effect.
|
|
odef->type = type->class; // pointer to type in work
|
|
// don't add unused (no attached relocs) external defs to the work
|
|
// defref list so they will not cause unused object files to be
|
|
// pulled in from libraries
|
|
if (odef->flags & QFOD_EXTERNAL && !odef->num_relocs)
|
|
continue;
|
|
ref = get_defref (odef, dest_space);
|
|
work_defrefs[num_work_defrefs++] = ref;
|
|
process (ref, dest_space, idef);
|
|
REF (ref)->relocs = add_relocs (qfo, REF (ref)->relocs,
|
|
REF (ref)->num_relocs,
|
|
REF (ref) - dest_space->defs);
|
|
}
|
|
dest_space->num_defs += count;
|
|
return count;
|
|
}
|
|
|
|
/** Add all the strings in the strings space to the working qfo.
|
|
|
|
\param strings The strings space of the qfo being linked.
|
|
*/
|
|
static void
|
|
add_qfo_strings (qfo_mspace_t *strings)
|
|
{
|
|
const char *str = strings->d.strings;
|
|
|
|
while ((pr_uint_t) (str - strings->d.strings) < strings->data_size) {
|
|
linker_add_string (str);
|
|
while ((pr_uint_t) (str - strings->d.strings) < strings->data_size
|
|
&& *str)
|
|
str++;
|
|
str++; // advance past the terminating nul
|
|
}
|
|
}
|
|
|
|
/** Add the code in the code space to the working qfo.
|
|
|
|
\param code The code space of the qfo being linked.
|
|
*/
|
|
static void
|
|
add_code (qfo_mspace_t *code)
|
|
{
|
|
codespace_addcode (work_code, code->d.code, code->data_size);
|
|
work->spaces[qfo_code_space].d.code = work_code->code;
|
|
work->spaces[qfo_code_space].data_size = work_code->size;
|
|
}
|
|
|
|
/** Add the data in a data space to the working qfo.
|
|
|
|
\param space The space to which the data will be added.
|
|
\param data A data space of the qfo being linked.
|
|
*/
|
|
static void
|
|
add_data (int space, qfo_mspace_t *data)
|
|
{
|
|
if (space < 0 || space >= qfo_num_spaces || !work_spaces[space])
|
|
linker_internal_error ("bad space for add_data (): %d", space);
|
|
if (data->data_size)
|
|
defspace_add_data (*work_spaces[space], data->d.data, data->data_size);
|
|
work->spaces[space].d.data = (*work_spaces[space])->data;
|
|
work->spaces[space].data_size = (*work_spaces[space])->size;
|
|
}
|
|
|
|
/** Add a defspace to the working qfo.
|
|
|
|
\param qfo The qfo being linked.
|
|
\param space The defspace from \a qfo that will be added to the working
|
|
qfo.
|
|
*/
|
|
static void
|
|
add_data_space (qfo_t *qfo, qfo_mspace_t *space)
|
|
{
|
|
qfo_mspace_t *ws;
|
|
if (space->type != qfos_data)
|
|
linker_internal_error ("bad space type for add_data_space (): %d",
|
|
space->type);
|
|
space->id = work->num_spaces++; // so the space in work can be found
|
|
work->spaces = realloc (work->spaces,
|
|
work->num_spaces * sizeof (qfo_mspace_t));
|
|
ws = &work->spaces[space->id];
|
|
memset (ws, 0, sizeof (qfo_mspace_t));
|
|
ws->type = space->type;
|
|
if (space->num_defs)
|
|
add_defs (qfo, space, ws, process_data_def);
|
|
if (space->d.data) {
|
|
int size = space->data_size * sizeof (pr_type_t);
|
|
ws->d.data = malloc (size);
|
|
memcpy (ws->d.data, space->d.data, size);
|
|
}
|
|
ws->data_size = space->data_size;
|
|
ws->id = space->id;
|
|
}
|
|
|
|
static defref_t *
|
|
make_def (int s, const char *name, type_t *type, unsigned flags, void *val)
|
|
{
|
|
qfo_def_t *def;
|
|
defref_t *ref;
|
|
qfo_mspace_t *space;
|
|
defspace_t *def_space;
|
|
|
|
space = &work->spaces[s];
|
|
def_space = *work_spaces[s];
|
|
|
|
space->defs = realloc (space->defs,
|
|
(space->num_defs + 1) * sizeof (qfo_def_t));
|
|
def = space->defs + space->num_defs++;
|
|
memset (def, 0, sizeof (*def));
|
|
def->name = linker_add_string (name);
|
|
def->type = -linker_add_string (type->encoding);
|
|
ref = Hash_Find (defined_type_defs, WORKSTR (-def->type));
|
|
if (ref)
|
|
def->type = REF (ref)->offset;
|
|
def->offset = alloc_data (s, type_size (type));
|
|
def->flags = flags;
|
|
if (val)
|
|
memcpy (&def_space->data[def->offset], val,
|
|
type_size (type) * sizeof (pr_type_t));
|
|
space->d.data = def_space->data;
|
|
space->data_size = def_space->size;
|
|
|
|
ref = get_defref (def, space);
|
|
work_defrefs = realloc (work_defrefs,
|
|
(num_work_defrefs + 1) * sizeof (defref_t *));
|
|
work_defrefs[num_work_defrefs++] = ref;
|
|
if (s == qfo_entity_space) {
|
|
define_def (ref, extern_field_defs, defined_field_defs);
|
|
} else {
|
|
define_def (ref, extern_data_defs, defined_data_defs);
|
|
}
|
|
|
|
return ref;
|
|
}
|
|
|
|
void
|
|
linker_add_def (const char *name, type_t *type, unsigned flags, void *val)
|
|
{
|
|
make_def (qfo_near_data_space, name, type, flags, val);
|
|
}
|
|
|
|
qfo_def_t *
|
|
linker_find_def (const char *name)
|
|
{
|
|
defref_t *r;
|
|
|
|
if ((r = Hash_Find (defined_data_defs, name)))
|
|
return REF (r);
|
|
return 0;
|
|
}
|
|
|
|
/** Initialize the linker state.
|
|
*/
|
|
void
|
|
linker_begin (void)
|
|
{
|
|
unsigned i;
|
|
|
|
linker_current_file = dstring_newstr ();
|
|
|
|
extern_data_defs = Hash_NewTable (16381, defs_get_key, 0, 0, 0);
|
|
defined_data_defs = Hash_NewTable (16381, defs_get_key, 0, 0, 0);
|
|
|
|
extern_field_defs = Hash_NewTable (16381, defs_get_key, 0, 0, 0);
|
|
defined_field_defs = Hash_NewTable (16381, defs_get_key, 0, 0, 0);
|
|
|
|
extern_type_defs = Hash_NewTable (16381, defs_get_key, 0, 0, 0);
|
|
defined_type_defs = Hash_NewTable (16381, defs_get_key, 0, 0, 0);
|
|
|
|
work_strings = strpool_new ();
|
|
work_code = codespace_new ();
|
|
work_near_data = defspace_new (ds_backed);
|
|
work_far_data = defspace_new (ds_backed);
|
|
work_entity_data = defspace_new (ds_virtual);
|
|
work_type_data = defspace_new (ds_backed);
|
|
work_debug_data = defspace_new (ds_backed);
|
|
|
|
pr.strings = work_strings;
|
|
|
|
work = qfo_new ();
|
|
work->spaces = calloc (qfo_num_spaces, sizeof (qfo_mspace_t));
|
|
work->num_spaces = qfo_num_spaces;
|
|
work->spaces[qfo_null_space].type = qfos_null;
|
|
work->spaces[qfo_strings_space].type = qfos_string;
|
|
work->spaces[qfo_strings_space].d.strings = work_strings->strings;
|
|
work->spaces[qfo_strings_space].data_size = work_strings->size;
|
|
work->spaces[qfo_code_space].type = qfos_code;
|
|
work->spaces[qfo_code_space].d.code = work_code->code;
|
|
work->spaces[qfo_code_space].data_size = work_code->size;
|
|
work->spaces[qfo_near_data_space].type = qfos_data;
|
|
work->spaces[qfo_near_data_space].d.data = work_near_data->data;
|
|
work->spaces[qfo_near_data_space].data_size = work_near_data->size;
|
|
work->spaces[qfo_far_data_space].type = qfos_data;
|
|
work->spaces[qfo_far_data_space].d.data = work_far_data->data;
|
|
work->spaces[qfo_far_data_space].data_size = work_far_data->size;
|
|
work->spaces[qfo_entity_space].type = qfos_entity;
|
|
work->spaces[qfo_entity_space].d.data = work_entity_data->data;
|
|
work->spaces[qfo_entity_space].data_size = work_entity_data->size;
|
|
work->spaces[qfo_type_space].type = qfos_type;
|
|
work->spaces[qfo_type_space].d.data = work_type_data->data;
|
|
work->spaces[qfo_type_space].data_size = work_type_data->size;
|
|
work->spaces[qfo_debug_space].type = qfos_debug;
|
|
work->spaces[qfo_debug_space].d.data = work_type_data->data;
|
|
work->spaces[qfo_debug_space].data_size = work_type_data->size;
|
|
for (i = 0; i < qfo_num_spaces; i++)
|
|
work->spaces[i].id = i;
|
|
|
|
alloc_data (qfo_type_space, 4);
|
|
|
|
work->lines = calloc (1, sizeof (pr_lineno_t));
|
|
work->num_lines = 1;
|
|
|
|
if (!options.partial_link) {
|
|
for (i = 0; i < num_builtins; i++) {
|
|
builtin_sym_t *bi = builtin_symbols + i;
|
|
bi->defref = make_def (qfo_near_data_space, bi->name, bi->type,
|
|
bi->flags, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef int (*space_func) (qfo_t *qfo, qfo_mspace_t *space, int pass);
|
|
|
|
static int
|
|
process_null_space (qfo_t *qfo, qfo_mspace_t *space, int pass)
|
|
{
|
|
if (pass != 0)
|
|
return 0;
|
|
if (space->defs || space->num_defs || space->d.data || space->data_size
|
|
|| space->id) {
|
|
linker_error ("non-null null space");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
process_code_space (qfo_t *qfo, qfo_mspace_t *space, int pass)
|
|
{
|
|
if (pass != 1)
|
|
return 0;
|
|
if (space->defs || space->num_defs) {
|
|
linker_error ("defs in code space");
|
|
return 1;
|
|
}
|
|
if (space->id != qfo_code_space)
|
|
linker_warning ("hmm, unexpected code space. *shrug*");
|
|
add_code (space);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
process_data_space (qfo_t *qfo, qfo_mspace_t *space, int pass)
|
|
{
|
|
if (pass != 1)
|
|
return 0;
|
|
if (space->id == qfo_near_data_space) {
|
|
add_defs (qfo, space, work->spaces + qfo_near_data_space,
|
|
process_data_def);
|
|
add_data (qfo_near_data_space, space);
|
|
} else if (space->id == qfo_far_data_space) {
|
|
add_defs (qfo, space, work->spaces + qfo_far_data_space,
|
|
process_data_def);
|
|
add_data (qfo_far_data_space, space);
|
|
} else {
|
|
add_data_space (qfo, space);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
process_strings_space (qfo_t *qfo, qfo_mspace_t *space, int pass)
|
|
{
|
|
if (pass != 0)
|
|
return 0;
|
|
if (space->defs || space->num_defs) {
|
|
linker_error ("defs in strings space");
|
|
return 1;
|
|
}
|
|
add_qfo_strings (space);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
process_entity_space (qfo_t *qfo, qfo_mspace_t *space, int pass)
|
|
{
|
|
if (pass != 1)
|
|
return 0;
|
|
add_defs (qfo, space, work->spaces + qfo_entity_space, process_field_def);
|
|
add_data (qfo_entity_space, space);
|
|
return 0;
|
|
}
|
|
|
|
// NOTE this will likely give weird results for qsort if the defs overlapp.
|
|
// However, that should not happen.
|
|
static int
|
|
type_def_compare (const void *a, const void *b)
|
|
{
|
|
qfo_def_t *ta = (qfo_def_t *) a;
|
|
qfo_def_t *tb = (qfo_def_t *) b;
|
|
|
|
if (ta->offset < tb->offset)
|
|
return -1;
|
|
if (ta->offset >= tb->offset + QFOTYPE (tb->offset)->size)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
update_type_space_reloc (qfo_mspace_t *space, qfo_reloc_t *reloc)
|
|
{
|
|
qfo_def_t dummy;
|
|
qfo_def_t *def;
|
|
|
|
if (reloc->type == -1) {
|
|
// The reloc has been copied, and the record number of the new reloc
|
|
// is in the old reloc's offset.
|
|
reloc = work->relocs + reloc->offset;
|
|
}
|
|
dummy.offset = reloc->offset;
|
|
def = (qfo_def_t *) bsearch (&dummy, space->defs, space->num_defs,
|
|
sizeof (qfo_def_t), type_def_compare);
|
|
if (!def) {
|
|
linker_internal_error ("relocation record with invalid address. "
|
|
"corrupt object file?");
|
|
}
|
|
// The new offset of the type encoding is stored in the def's type field.
|
|
// The old offset is in the def's offset field. The reloc's offset points
|
|
// to somewhere within the type encoding.
|
|
reloc->offset = def->type + (reloc->offset - def->offset);
|
|
}
|
|
|
|
static int
|
|
process_type_space (qfo_t *qfo, qfo_mspace_t *space, int pass)
|
|
{
|
|
unsigned i;
|
|
|
|
if (pass != 0)
|
|
return 0;
|
|
if (qfo_type_defs) {
|
|
linker_error ("type space already defined");
|
|
return 1;
|
|
}
|
|
qfo_type_defs = space;
|
|
add_defs (qfo, space, work->spaces + qfo_type_space, process_type_def);
|
|
// The defs in qfo are no longer needed by the rest of the linker, so
|
|
// we're free to mess around with them.
|
|
|
|
// Sort the defs by addres so they can found using bsearch when adjusting
|
|
// the targets of type encoding relocs. Unfortunately, they will usually
|
|
// be in order, so qsort will likely be pessimistic, but oh well.
|
|
qsort (space->defs, space->num_defs, sizeof (qfo_def_t), type_def_compare);
|
|
|
|
// Update the offsets of all relocation records that point into the type
|
|
// encoding space.
|
|
for (i = 0; i < qfo->num_relocs; i++) {
|
|
qfo_reloc_t *reloc = qfo->relocs + i;
|
|
|
|
if (reloc->space != space->id)
|
|
continue;
|
|
update_type_space_reloc (space, reloc);
|
|
// while we're at it, relocate all references in the type encoding
|
|
// space so the type encodings are always correct.
|
|
if (reloc->type == rel_def_string) {
|
|
string_t str;
|
|
str = linker_add_string (QFOSTR (qfo, reloc->target));
|
|
QFO_STRING (work, reloc->space, reloc->offset) = str;
|
|
} else if (reloc->type == rel_def_def || reloc->type == -1) {
|
|
qfo_def_t *def;
|
|
if (reloc->type == -1) {
|
|
// The reloc has been copied, and the record number of the new
|
|
// reloc is in the old reloc's offset.
|
|
reloc = work->relocs + reloc->offset;
|
|
}
|
|
if (reloc->target >= work->spaces[reloc->space].num_defs) {
|
|
linker_error ("Invalid reloc target def %d / %d.\n",
|
|
reloc->target, qfo->num_defs);
|
|
continue;
|
|
}
|
|
def = work->spaces[reloc->space].defs + reloc->target;
|
|
QFO_INT (work, reloc->space, reloc->offset) = def->offset;
|
|
}
|
|
}
|
|
for (i = 0; i < num_builtins; i++) {
|
|
builtin_sym_t *bi = builtin_symbols + i;
|
|
qfo_def_t *def;
|
|
defref_t *ref;
|
|
|
|
if (!bi->defref)
|
|
continue;
|
|
def = REF (bi->defref);
|
|
if ((int) def->type >= 0)
|
|
continue;
|
|
ref = Hash_Find (defined_type_defs, WORKSTR (-def->type));
|
|
if (!ref)
|
|
continue;
|
|
def->type = REF (ref)->offset;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
process_debug_space (qfo_t *qfo, qfo_mspace_t *space, int pass)
|
|
{
|
|
if (pass != 1)
|
|
return 0;
|
|
if (space->type != qfos_debug) {
|
|
linker_internal_error ("bad space type for add_data_space (): %d",
|
|
space->type);
|
|
}
|
|
add_defs (qfo, space, work->spaces + qfo_debug_space, process_data_def);
|
|
add_data (qfo_debug_space, space);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
process_funcs (qfo_t *qfo)
|
|
{
|
|
unsigned size;
|
|
qfo_func_t *func;
|
|
qfot_type_t *type;
|
|
|
|
size = work->num_funcs + qfo->num_funcs;
|
|
work->funcs = realloc (work->funcs, size * sizeof (qfo_func_t));
|
|
memcpy (work->funcs + work->num_funcs, qfo->funcs,
|
|
qfo->num_funcs * sizeof (qfo_func_t));
|
|
while (work->num_funcs < size) {
|
|
func = work->funcs + work->num_funcs++;
|
|
type = QFOTYPE(func->type);
|
|
func->type = type->class;
|
|
func->name = linker_add_string (QFOSTR (qfo, func->name));
|
|
func->file = linker_add_string (QFOSTR (qfo, func->file));
|
|
if (func->code > 0)
|
|
func->code += work_base[qfo_code_space];
|
|
func->def = qfo->defs[func->def].line; // defref index
|
|
func->locals_space = qfo->spaces[func->locals_space].id;
|
|
if (func->line_info)
|
|
func->line_info += work->num_lines - 1; //FIXME order dependent
|
|
func->relocs = add_relocs (qfo, func->relocs, func->num_relocs,
|
|
func - work->funcs);
|
|
}
|
|
}
|
|
|
|
static void
|
|
process_lines (qfo_t *qfo)
|
|
{
|
|
unsigned size;
|
|
pr_lineno_t *line;
|
|
|
|
if (!qfo->num_lines)
|
|
return;
|
|
size = work->num_lines + qfo->num_lines - 1;
|
|
work->lines = realloc (work->lines, size * sizeof (pr_lineno_t));
|
|
memcpy (work->lines + work->num_lines, qfo->lines + 1,
|
|
(qfo->num_lines - 1) * sizeof (pr_lineno_t));
|
|
while (work->num_lines < size) {
|
|
line = work->lines + work->num_lines++;
|
|
if (line->line)
|
|
line->fa.addr += work_base[qfo_code_space];
|
|
else
|
|
line->fa.func += work_func_base;
|
|
}
|
|
}
|
|
|
|
static void
|
|
process_loose_relocs (qfo_t *qfo)
|
|
{
|
|
int size;
|
|
qfo_reloc_t *reloc;
|
|
|
|
size = work_num_loose_relocs + qfo->num_loose_relocs;
|
|
work_loose_relocs = realloc (work_loose_relocs,
|
|
size * sizeof (qfo_reloc_t));
|
|
memcpy (work_loose_relocs + work_num_loose_relocs,
|
|
qfo->relocs + qfo->num_relocs - qfo->num_loose_relocs,
|
|
qfo->num_loose_relocs * sizeof (qfo_reloc_t));
|
|
while (work_num_loose_relocs < size) {
|
|
reloc = work_loose_relocs + work_num_loose_relocs++;
|
|
if (reloc->space >= qfo->num_spaces) {
|
|
linker_error ("bad reloc space");
|
|
reloc->type = rel_none;
|
|
continue;
|
|
}
|
|
reloc->space = qfo->spaces[reloc->space].id;
|
|
if (reloc->type == rel_def_string) {
|
|
const char *str;
|
|
|
|
if (reloc->target >= qfo->spaces[qfo_strings_space].data_size) {
|
|
linker_error ("bad string reloc at %d:%x", reloc->space,
|
|
reloc->offset);
|
|
reloc->target = 0;
|
|
}
|
|
str = QFOSTR (qfo, reloc->target);
|
|
reloc->target = linker_add_string (str);
|
|
}
|
|
if (reloc->type == rel_def_op)
|
|
reloc->target += work_base[qfo_code_space];
|
|
adjust_reloc_offset (reloc);
|
|
if (reloc->type == rel_def_string) {
|
|
QFO_STRING (work, reloc->space, reloc->offset) = reloc->target;
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
linker_add_qfo (qfo_t *qfo)
|
|
{
|
|
static space_func funcs[] = {
|
|
process_null_space,
|
|
process_code_space,
|
|
process_data_space,
|
|
process_strings_space,
|
|
process_entity_space,
|
|
process_type_space,
|
|
process_debug_space,
|
|
};
|
|
unsigned i;
|
|
int pass;
|
|
qfo_mspace_t *space;
|
|
|
|
qfo_type_defs = 0;
|
|
for (i = 0; i < qfo_num_spaces; i++) {
|
|
work_base[i] = work->spaces[i].data_size;
|
|
}
|
|
work_func_base = work->num_funcs;
|
|
for (pass = 0; pass < 2; pass++) {
|
|
for (i = 0, space = qfo->spaces; i < qfo->num_spaces; i++, space++) {
|
|
if ((int) space->type < 0 || space->type > qfos_debug) {
|
|
linker_error ("bad space type");
|
|
return 1;
|
|
}
|
|
if (funcs[space->type] (qfo, space, pass))
|
|
return 1;
|
|
}
|
|
}
|
|
process_funcs (qfo);
|
|
process_lines (qfo);
|
|
process_loose_relocs (qfo);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
linker_add_object_file (const char *filename)
|
|
{
|
|
qfo_t *qfo;
|
|
|
|
dsprintf (linker_current_file, "%s", filename);
|
|
|
|
qfo = qfo_open (filename);
|
|
if (!qfo) {
|
|
linker_error ("error opening");
|
|
return 1;
|
|
}
|
|
if (qfo->num_spaces < qfo_num_spaces
|
|
|| qfo->spaces[qfo_null_space].type != qfos_null
|
|
|| qfo->spaces[qfo_strings_space].type != qfos_string
|
|
|| qfo->spaces[qfo_code_space].type != qfos_code
|
|
|| qfo->spaces[qfo_near_data_space].type != qfos_data
|
|
|| qfo->spaces[qfo_far_data_space].type != qfos_data
|
|
|| qfo->spaces[qfo_entity_space].type != qfos_entity
|
|
|| qfo->spaces[qfo_type_space].type != qfos_type) {
|
|
linker_error ("missing or mangled standard spaces");
|
|
return 1;
|
|
}
|
|
|
|
if (options.verbosity >= 2)
|
|
fprintf (stderr, "%s\n", filename);
|
|
|
|
linker_add_qfo (qfo);
|
|
|
|
qfo_delete (qfo);
|
|
return 0;
|
|
}
|
|
|
|
typedef struct path_s {
|
|
struct path_s *next;
|
|
const char *path;
|
|
} path_t;
|
|
|
|
static path_t *path_head;
|
|
static path_t **path_tail = &path_head;
|
|
|
|
int
|
|
linker_add_lib (const char *libname)
|
|
{
|
|
pack_t *pack = 0;
|
|
path_t start = {path_head, "."};
|
|
path_t *path = &start;
|
|
const char *path_name = 0;
|
|
int i;
|
|
unsigned j;
|
|
int did_something;
|
|
|
|
if (strncmp (libname, "-l", 2) == 0) {
|
|
while (path) {
|
|
path_name = va ("%s/lib%s.a", path->path, libname + 2);
|
|
pack = pack_open (path_name);
|
|
if (pack)
|
|
break;
|
|
if (errno != ENOENT) {
|
|
if (errno)
|
|
perror (libname);
|
|
return 1;
|
|
}
|
|
path = path->next;
|
|
}
|
|
} else {
|
|
path_name = libname;
|
|
pack = pack_open (path_name);
|
|
}
|
|
|
|
if (!pack) {
|
|
if (errno)
|
|
perror (libname);
|
|
return 1;
|
|
}
|
|
|
|
if (options.verbosity > 1)
|
|
puts (path_name);
|
|
|
|
do {
|
|
did_something = 0;
|
|
for (i = 0; i < pack->numfiles; i++) {
|
|
QFile *f;
|
|
qfo_t *qfo;
|
|
|
|
dsprintf (linker_current_file, "%s(%s)", path_name,
|
|
pack->files[i].name);
|
|
f = Qsubopen (path_name, pack->files[i].filepos,
|
|
pack->files[i].filelen, 1);
|
|
qfo = qfo_read (f);
|
|
Qclose (f);
|
|
|
|
if (!qfo) {
|
|
linker_error ("error opening");
|
|
return 1;
|
|
}
|
|
|
|
for (j = 0; j < qfo->num_defs; j++) {
|
|
qfo_def_t *def = qfo->defs + j;
|
|
if ((def->flags & QFOD_GLOBAL)
|
|
&& !(def->flags & QFOD_EXTERNAL)
|
|
&& Hash_Find (extern_data_defs, QFOSTR (qfo, def->name))) {
|
|
if (options.verbosity >= 2)
|
|
fprintf (stderr, "adding %s because of %s\n",
|
|
pack->files[i].name,
|
|
QFOSTR (qfo, def->name));
|
|
linker_add_qfo (qfo);
|
|
did_something = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
qfo_delete (qfo);
|
|
}
|
|
} while (did_something);
|
|
pack_del (pack);
|
|
return 0;
|
|
}
|
|
|
|
static __attribute__ ((used)) void
|
|
undefined_def (qfo_def_t *def)
|
|
{
|
|
qfo_def_t line_def;
|
|
pr_uint_t i;
|
|
qfo_reloc_t *reloc = work->relocs + def->relocs;
|
|
|
|
for (i = 0; i < def->num_relocs; i++, reloc++) {
|
|
if ((reloc->type == rel_op_a_def
|
|
|| reloc->type == rel_op_b_def
|
|
|| reloc->type == rel_op_c_def
|
|
|| reloc->type == rel_op_a_def_ofs
|
|
|| reloc->type == rel_op_b_def_ofs
|
|
|| reloc->type == rel_op_c_def_ofs)
|
|
&& work->lines) {
|
|
qfo_func_t *func = work->funcs;
|
|
qfo_func_t *best = 0;
|
|
pr_uint_t best_dist;
|
|
pr_lineno_t *line;
|
|
|
|
while (func - work->funcs < work->num_funcs) {
|
|
if (func->code >= 0
|
|
&& (pr_uint_t) func->code <= reloc->offset) {
|
|
if (!best || reloc->offset - func->code < best_dist) {
|
|
best = func;
|
|
best_dist = reloc->offset - func->code;
|
|
}
|
|
}
|
|
func++;
|
|
}
|
|
line = work->lines + best->line_info;
|
|
line_def.file = best->file;
|
|
line_def.line = best->line;
|
|
if (!line->line
|
|
&& line->fa.func == (pr_uint_t) (best - work->funcs)) {
|
|
while (line - work->lines < work->num_lines - 1
|
|
&& line[1].line
|
|
&& line[1].fa.addr <= (pr_uint_t) reloc->offset)
|
|
line++;
|
|
line_def.line = line->line + best->line;
|
|
}
|
|
def_error (&line_def, "undefined symbol %s", WORKSTR (def->name));
|
|
} else {
|
|
def_error (def, "undefined symbol %s", WORKSTR (def->name));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
check_defs (void)
|
|
{
|
|
defref_t **undef_defs, **defref;
|
|
int did_self = 0, did_this = 0;
|
|
|
|
undef_defs = (defref_t **) Hash_GetList (extern_data_defs);
|
|
for (defref = undef_defs; *defref; defref++) {
|
|
qfo_def_t *def = REF (*defref);
|
|
const char *name = WORKSTR (def->name);
|
|
|
|
if (strcmp (name, ".self") == 0 && !did_self) {
|
|
defref_t *_d = Hash_Find (defined_data_defs, "self");
|
|
if (_d) {
|
|
qfo_def_t *d = REF (_d);
|
|
if (QFO_TYPEMETA (work, d->type) == ty_basic
|
|
&& QFO_TYPETYPE (work, d->type) == ev_entity)
|
|
def_warning (d, "@self and self used together");
|
|
}
|
|
linker_add_def (".self", &type_entity, QFOD_GLOBAL, 0);
|
|
did_self = 1;
|
|
} else if (strcmp (name, ".this") == 0 && !did_this) {
|
|
type_t *type;
|
|
int flags;
|
|
defref_t *this_ref;
|
|
|
|
if (!obj_initialized)
|
|
class_init ();
|
|
flags = QFOD_GLOBAL | QFOD_NOSAVE;
|
|
this_ref = make_def (qfo_entity_space, name, &type_id, flags, 0);
|
|
flags |= QFOD_CONSTANT | QFOD_INITIALIZED;
|
|
type = field_type (&type_id);
|
|
linker_add_def (".this", type, flags, &REF (this_ref)->offset);
|
|
did_this = 1;
|
|
}
|
|
}
|
|
free (undef_defs);
|
|
undef_defs = (defref_t **) Hash_GetList (extern_data_defs);
|
|
for (defref = undef_defs; *defref; defref++) {
|
|
qfo_def_t *def = REF (*defref);
|
|
undefined_def (def);
|
|
}
|
|
free (undef_defs);
|
|
}
|
|
|
|
static qfo_t *
|
|
build_qfo (void)
|
|
{
|
|
qfo_t *qfo;
|
|
int size;
|
|
unsigned i, j;
|
|
qfo_reloc_t *reloc;
|
|
qfo_def_t **defs;
|
|
|
|
qfo = qfo_new ();
|
|
qfo->spaces = calloc (work->num_spaces, sizeof (qfo_mspace_t));
|
|
qfo->num_spaces = work->num_spaces;
|
|
for (i = 0; i < work->num_spaces; i++) {
|
|
qfo->spaces[i].type = work->spaces[i].type;
|
|
qfo->spaces[i].id = work->spaces[i].id;
|
|
qfo->spaces[i].d = work->spaces[i].d;
|
|
qfo->spaces[i].data_size = work->spaces[i].data_size;
|
|
}
|
|
// allocate space for all relocs and copy in the loose relocs. bound
|
|
// relocs will be handled with defs and funcs.
|
|
size = work->num_relocs + work_num_loose_relocs;
|
|
qfo->relocs = malloc (size * sizeof (qfo_reloc_t));
|
|
reloc = qfo->relocs;
|
|
memcpy (qfo->relocs + work->num_relocs, work_loose_relocs,
|
|
work_num_loose_relocs * sizeof (qfo_reloc_t));
|
|
qfo->num_relocs = size;
|
|
qfo->num_loose_relocs = work_num_loose_relocs;
|
|
qfo->funcs = work->funcs;
|
|
qfo->num_funcs = work->num_funcs;
|
|
qfo->lines = work->lines;
|
|
qfo->num_lines = work->num_lines;
|
|
// count final defs
|
|
for (i = 0; i < num_work_defrefs; i++) {
|
|
if (work_defrefs[i]->merge)
|
|
continue;
|
|
qfo->num_defs++;
|
|
qfo->spaces[work_defrefs[i]->space].num_defs++;
|
|
}
|
|
qfo->defs = malloc (qfo->num_defs * sizeof (qfo_def_t));
|
|
defs = alloca (qfo->num_spaces * sizeof (qfo_def_t *));
|
|
defs[0] = qfo->defs;
|
|
for (i = 1; i < qfo->num_spaces; i++) {
|
|
defs[i] = defs[i - 1] + qfo->spaces[i - 1].num_defs;
|
|
if (qfo->spaces[i].num_defs)
|
|
qfo->spaces[i].defs = defs[i];
|
|
}
|
|
for (i = 0; i < num_work_defrefs; i++) {
|
|
defref_t *r = work_defrefs[i];
|
|
qfo_def_t *def;
|
|
qfo_def_t d;
|
|
int space;
|
|
if (r->merge)
|
|
continue;
|
|
space = r->space;
|
|
def = REF (r);
|
|
d = *def;
|
|
d.relocs = reloc - qfo->relocs;
|
|
memcpy (reloc, work->relocs + def->relocs,
|
|
def->num_relocs * sizeof (qfo_reloc_t));
|
|
r->def = defs[space] - qfo->defs;
|
|
for (j = 0; j < def->num_relocs; j++) {
|
|
reloc->target = r->def;
|
|
reloc++;
|
|
}
|
|
// copy relocs from merged defs
|
|
for (r = r->merge_list; r; r = r->next) {
|
|
def = REF (r);
|
|
memcpy (reloc, work->relocs + def->relocs,
|
|
def->num_relocs * sizeof (qfo_reloc_t));
|
|
d.num_relocs += def->num_relocs;
|
|
r->space = space;
|
|
r->def = defs[space] - qfo->defs;
|
|
for (j = 0; j < def->num_relocs; j++) {
|
|
reloc->target = r->def;
|
|
reloc++;
|
|
}
|
|
}
|
|
*defs[space]++ = d;
|
|
}
|
|
for (i = 0; i < qfo->num_funcs; i++) {
|
|
qfo_func_t *f = &qfo->funcs[i];
|
|
f->def = work_defrefs[f->def]->def;
|
|
memcpy (reloc, work->relocs + f->relocs,
|
|
f->num_relocs * sizeof (qfo_reloc_t));
|
|
f->relocs = reloc - qfo->relocs;
|
|
for (j = 0; j < f->num_relocs; j++) {
|
|
reloc->target = i;
|
|
reloc++;
|
|
}
|
|
}
|
|
return qfo;
|
|
}
|
|
|
|
qfo_t *
|
|
linker_finish (void)
|
|
{
|
|
int i;
|
|
|
|
if (!options.partial_link) {
|
|
check_defs ();
|
|
if (pr.error_count)
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < work_num_loose_relocs; /**/) {
|
|
if (work_loose_relocs[i].type != rel_none) {
|
|
i++;
|
|
continue;
|
|
}
|
|
memmove (work_loose_relocs + i, work_loose_relocs + i + 1,
|
|
(--work_num_loose_relocs - i) * sizeof (qfo_reloc_t));
|
|
}
|
|
return build_qfo ();
|
|
}
|
|
|
|
void
|
|
linker_add_path (const char *path)
|
|
{
|
|
path_t *p = malloc (sizeof (path_t));
|
|
p->next = 0;
|
|
p->path = path;
|
|
*path_tail = p;
|
|
path_tail = &p->next;
|
|
}
|
|
|
|
static void
|
|
def_error (qfo_def_t *def, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
static dstring_t *string;
|
|
|
|
if (!string)
|
|
string = dstring_new ();
|
|
|
|
va_start (args, fmt);
|
|
dvsprintf (string, fmt, args);
|
|
va_end (args);
|
|
|
|
pr.source_file = def->file;
|
|
pr.source_line = def->line;
|
|
error (0, "%s", string->str);
|
|
}
|
|
|
|
static void
|
|
def_warning (qfo_def_t *def, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
static dstring_t *string;
|
|
|
|
if (!string)
|
|
string = dstring_new ();
|
|
|
|
va_start (args, fmt);
|
|
dvsprintf (string, fmt, args);
|
|
va_end (args);
|
|
|
|
pr.source_file = def->file;
|
|
pr.source_line = def->line;
|
|
warning (0, "%s", string->str);
|
|
}
|
|
|
|
static void
|
|
linker_warning (const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
fprintf (stderr, "%s: warning: ", linker_current_file->str);
|
|
|
|
va_start (args, fmt);
|
|
vfprintf (stderr, fmt, args);
|
|
va_end (args);
|
|
|
|
fputs ("\n", stderr);
|
|
|
|
if (options.warnings.promote)
|
|
pr.error_count++;
|
|
}
|
|
|
|
static void
|
|
linker_error (const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
fprintf (stderr, "%s: ", linker_current_file->str);
|
|
|
|
va_start (args, fmt);
|
|
vfprintf (stderr, fmt, args);
|
|
va_end (args);
|
|
|
|
fputs ("\n", stderr);
|
|
|
|
pr.error_count++;
|
|
}
|
|
|
|
static void
|
|
linker_internal_error (const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
fprintf (stderr, "%s: ", linker_current_file->str);
|
|
|
|
va_start (args, fmt);
|
|
vfprintf (stderr, fmt, args);
|
|
va_end (args);
|
|
|
|
fputs ("\n", stderr);
|
|
|
|
abort ();
|
|
}
|