nasm/output/outobj.c
Victor van den Elzen f568613c2a Fix BR #916647
nasm.c should respect the default debug format of the output format,
instead of replacing it with the first format in the list.
This is cleaner and allows the list to be sorted normally.

This commit rewrites commit 116994111b which was very fragile.
2008-11-05 14:19:15 +01:00

2540 lines
75 KiB
C

/* outobj.c output routines for the Netwide Assembler to produce
* .OBJ object files
*
* The Netwide Assembler is copyright (C) 1996 Simon Tatham and
* Julian Hall. All rights reserved. The software is
* redistributable under the license given in the file "LICENSE"
* distributed in the NASM archive.
*/
#include "compiler.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <inttypes.h>
#include "nasm.h"
#include "nasmlib.h"
#include "stdscan.h"
#include "outform.h"
#ifdef OF_OBJ
/*
* outobj.c is divided into two sections. The first section is low level
* routines for creating obj records; It has nearly zero NASM specific
* code. The second section is high level routines for processing calls and
* data structures from the rest of NASM into obj format.
*
* It should be easy (though not zero work) to lift the first section out for
* use as an obj file writer for some other assembler or compiler.
*/
/*
* These routines are built around the ObjRecord data struture. An ObjRecord
* holds an object file record that may be under construction or complete.
*
* A major function of these routines is to support continuation of an obj
* record into the next record when the maximum record size is exceeded. The
* high level code does not need to worry about where the record breaks occur.
* It does need to do some minor extra steps to make the automatic continuation
* work. Those steps may be skipped for records where the high level knows no
* continuation could be required.
*
* 1) An ObjRecord is allocated and cleared by obj_new, or an existing ObjRecord
* is cleared by obj_clear.
*
* 2) The caller should fill in .type.
*
* 3) If the record is continuable and there is processing that must be done at
* the start of each record then the caller should fill in .ori with the
* address of the record initializer routine.
*
* 4) If the record is continuable and it should be saved (rather than emitted
* immediately) as each record is done, the caller should set .up to be a
* pointer to a location in which the caller keeps the master pointer to the
* ObjRecord. When the record is continued, the obj_bump routine will then
* allocate a new ObjRecord structure and update the master pointer.
*
* 5) If the .ori field was used then the caller should fill in the .parm with
* any data required by the initializer.
*
* 6) The caller uses the routines: obj_byte, obj_word, obj_rword, obj_dword,
* obj_x, obj_index, obj_value and obj_name to fill in the various kinds of
* data required for this record.
*
* 7) If the record is continuable, the caller should call obj_commit at each
* point where breaking the record is permitted.
*
* 8) To write out the record, the caller should call obj_emit2. If the
* caller has called obj_commit for all data written then he can get slightly
* faster code by calling obj_emit instead of obj_emit2.
*
* Most of these routines return an ObjRecord pointer. This will be the input
* pointer most of the time and will be the new location if the ObjRecord
* moved as a result of the call. The caller may ignore the return value in
* three cases: It is a "Never Reallocates" routine; or The caller knows
* continuation is not possible; or The caller uses the master pointer for the
* next operation.
*/
#define RECORD_MAX (1024-3) /* maximal size of any record except type+reclen */
#define OBJ_PARMS 3 /* maximum .parm used by any .ori routine */
#define FIX_08_LOW 0x8000 /* location type for various fixup subrecords */
#define FIX_16_OFFSET 0x8400
#define FIX_16_SELECTOR 0x8800
#define FIX_32_POINTER 0x8C00
#define FIX_08_HIGH 0x9000
#define FIX_32_OFFSET 0xA400
#define FIX_48_POINTER 0xAC00
enum RecordID { /* record ID codes */
THEADR = 0x80, /* module header */
COMENT = 0x88, /* comment record */
LINNUM = 0x94, /* line number record */
LNAMES = 0x96, /* list of names */
SEGDEF = 0x98, /* segment definition */
GRPDEF = 0x9A, /* group definition */
EXTDEF = 0x8C, /* external definition */
PUBDEF = 0x90, /* public definition */
COMDEF = 0xB0, /* common definition */
LEDATA = 0xA0, /* logical enumerated data */
FIXUPP = 0x9C, /* fixups (relocations) */
FIXU32 = 0x9D, /* 32-bit fixups (relocations) */
MODEND = 0x8A, /* module end */
MODE32 = 0x8B /* module end for 32-bit objects */
};
enum ComentID { /* ID codes for comment records */
dEXTENDED = 0xA1, /* tells that we are using translator-specific extensions */
dLINKPASS = 0xA2, /* link pass 2 marker */
dTYPEDEF = 0xE3, /* define a type */
dSYM = 0xE6, /* symbol debug record */
dFILNAME = 0xE8, /* file name record */
dCOMPDEF = 0xEA /* compiler type info */
};
typedef struct ObjRecord ObjRecord;
typedef void ORI(ObjRecord * orp);
struct ObjRecord {
ORI *ori; /* Initialization routine */
int used; /* Current data size */
int committed; /* Data size at last boundary */
int x_size; /* (see obj_x) */
unsigned int type; /* Record type */
ObjRecord *child; /* Associated record below this one */
ObjRecord **up; /* Master pointer to this ObjRecord */
ObjRecord *back; /* Previous part of this record */
uint32_t parm[OBJ_PARMS]; /* Parameters for ori routine */
uint8_t buf[RECORD_MAX + 3];
};
static void obj_fwrite(ObjRecord * orp);
static void ori_ledata(ObjRecord * orp);
static void ori_pubdef(ObjRecord * orp);
static void ori_null(ObjRecord * orp);
static ObjRecord *obj_commit(ObjRecord * orp);
static bool obj_uppercase; /* Flag: all names in uppercase */
static bool obj_use32; /* Flag: at least one segment is 32-bit */
/*
* Clear an ObjRecord structure. (Never reallocates).
* To simplify reuse of ObjRecord's, .type, .ori and .parm are not cleared.
*/
static ObjRecord *obj_clear(ObjRecord * orp)
{
orp->used = 0;
orp->committed = 0;
orp->x_size = 0;
orp->child = NULL;
orp->up = NULL;
orp->back = NULL;
return (orp);
}
/*
* Emit an ObjRecord structure. (Never reallocates).
* The record is written out preceeded (recursively) by its previous part (if
* any) and followed (recursively) by its child (if any).
* The previous part and the child are freed. The main ObjRecord is cleared,
* not freed.
*/
static ObjRecord *obj_emit(ObjRecord * orp)
{
if (orp->back) {
obj_emit(orp->back);
nasm_free(orp->back);
}
if (orp->committed)
obj_fwrite(orp);
if (orp->child) {
obj_emit(orp->child);
nasm_free(orp->child);
}
return (obj_clear(orp));
}
/*
* Commit and Emit a record. (Never reallocates).
*/
static ObjRecord *obj_emit2(ObjRecord * orp)
{
obj_commit(orp);
return (obj_emit(orp));
}
/*
* Allocate and clear a new ObjRecord; Also sets .ori to ori_null
*/
static ObjRecord *obj_new(void)
{
ObjRecord *orp;
orp = obj_clear(nasm_malloc(sizeof(ObjRecord)));
orp->ori = ori_null;
return (orp);
}
/*
* Advance to the next record because the existing one is full or its x_size
* is incompatible.
* Any uncommited data is moved into the next record.
*/
static ObjRecord *obj_bump(ObjRecord * orp)
{
ObjRecord *nxt;
int used = orp->used;
int committed = orp->committed;
if (orp->up) {
*orp->up = nxt = obj_new();
nxt->ori = orp->ori;
nxt->type = orp->type;
nxt->up = orp->up;
nxt->back = orp;
memcpy(nxt->parm, orp->parm, sizeof(orp->parm));
} else
nxt = obj_emit(orp);
used -= committed;
if (used) {
nxt->committed = 1;
nxt->ori(nxt);
nxt->committed = nxt->used;
memcpy(nxt->buf + nxt->committed, orp->buf + committed, used);
nxt->used = nxt->committed + used;
}
return (nxt);
}
/*
* Advance to the next record if necessary to allow the next field to fit.
*/
static ObjRecord *obj_check(ObjRecord * orp, int size)
{
if (orp->used + size > RECORD_MAX)
orp = obj_bump(orp);
if (!orp->committed) {
orp->committed = 1;
orp->ori(orp);
orp->committed = orp->used;
}
return (orp);
}
/*
* All data written so far is commited to the current record (won't be moved to
* the next record in case of continuation).
*/
static ObjRecord *obj_commit(ObjRecord * orp)
{
orp->committed = orp->used;
return (orp);
}
/*
* Write a byte
*/
static ObjRecord *obj_byte(ObjRecord * orp, uint8_t val)
{
orp = obj_check(orp, 1);
orp->buf[orp->used] = val;
orp->used++;
return (orp);
}
/*
* Write a word
*/
static ObjRecord *obj_word(ObjRecord * orp, unsigned int val)
{
orp = obj_check(orp, 2);
orp->buf[orp->used] = val;
orp->buf[orp->used + 1] = val >> 8;
orp->used += 2;
return (orp);
}
/*
* Write a reversed word
*/
static ObjRecord *obj_rword(ObjRecord * orp, unsigned int val)
{
orp = obj_check(orp, 2);
orp->buf[orp->used] = val >> 8;
orp->buf[orp->used + 1] = val;
orp->used += 2;
return (orp);
}
/*
* Write a dword
*/
static ObjRecord *obj_dword(ObjRecord * orp, uint32_t val)
{
orp = obj_check(orp, 4);
orp->buf[orp->used] = val;
orp->buf[orp->used + 1] = val >> 8;
orp->buf[orp->used + 2] = val >> 16;
orp->buf[orp->used + 3] = val >> 24;
orp->used += 4;
return (orp);
}
/*
* All fields of "size x" in one obj record must be the same size (either 16
* bits or 32 bits). There is a one bit flag in each record which specifies
* which.
* This routine is used to force the current record to have the desired
* x_size. x_size is normally automatic (using obj_x), so that this
* routine should be used outside obj_x, only to provide compatibility with
* linkers that have bugs in their processing of the size bit.
*/
static ObjRecord *obj_force(ObjRecord * orp, int x)
{
if (orp->x_size == (x ^ 48))
orp = obj_bump(orp);
orp->x_size = x;
return (orp);
}
/*
* This routine writes a field of size x. The caller does not need to worry at
* all about whether 16-bits or 32-bits are required.
*/
static ObjRecord *obj_x(ObjRecord * orp, uint32_t val)
{
if (orp->type & 1)
orp->x_size = 32;
if (val > 0xFFFF)
orp = obj_force(orp, 32);
if (orp->x_size == 32) {
ObjRecord *nxt = obj_dword(orp, val);
nxt->x_size = 32; /* x_size is cleared when a record overflows */
return nxt;
}
orp->x_size = 16;
return (obj_word(orp, val));
}
/*
* Writes an index
*/
static ObjRecord *obj_index(ObjRecord * orp, unsigned int val)
{
if (val < 128)
return (obj_byte(orp, val));
return (obj_word(orp, (val >> 8) | (val << 8) | 0x80));
}
/*
* Writes a variable length value
*/
static ObjRecord *obj_value(ObjRecord * orp, uint32_t val)
{
if (val <= 128)
return (obj_byte(orp, val));
if (val <= 0xFFFF) {
orp = obj_byte(orp, 129);
return (obj_word(orp, val));
}
if (val <= 0xFFFFFF)
return (obj_dword(orp, (val << 8) + 132));
orp = obj_byte(orp, 136);
return (obj_dword(orp, val));
}
/*
* Writes a counted string
*/
static ObjRecord *obj_name(ObjRecord * orp, const char *name)
{
int len = strlen(name);
uint8_t *ptr;
orp = obj_check(orp, len + 1);
ptr = orp->buf + orp->used;
*ptr++ = len;
orp->used += len + 1;
if (obj_uppercase)
while (--len >= 0) {
*ptr++ = toupper(*name);
name++;
} else
memcpy(ptr, name, len);
return (orp);
}
/*
* Initializer for an LEDATA record.
* parm[0] = offset
* parm[1] = segment index
* During the use of a LEDATA ObjRecord, parm[0] is constantly updated to
* represent the offset that would be required if the record were split at the
* last commit point.
* parm[2] is a copy of parm[0] as it was when the current record was initted.
*/
static void ori_ledata(ObjRecord * orp)
{
obj_index(orp, orp->parm[1]);
orp->parm[2] = orp->parm[0];
obj_x(orp, orp->parm[0]);
}
/*
* Initializer for a PUBDEF record.
* parm[0] = group index
* parm[1] = segment index
* parm[2] = frame (only used when both indexes are zero)
*/
static void ori_pubdef(ObjRecord * orp)
{
obj_index(orp, orp->parm[0]);
obj_index(orp, orp->parm[1]);
if (!(orp->parm[0] | orp->parm[1]))
obj_word(orp, orp->parm[2]);
}
/*
* Initializer for a LINNUM record.
* parm[0] = group index
* parm[1] = segment index
*/
static void ori_linnum(ObjRecord * orp)
{
obj_index(orp, orp->parm[0]);
obj_index(orp, orp->parm[1]);
}
/*
* Initializer for a local vars record.
*/
static void ori_local(ObjRecord * orp)
{
obj_byte(orp, 0x40);
obj_byte(orp, dSYM);
}
/*
* Null initializer for records that continue without any header info
*/
static void ori_null(ObjRecord * orp)
{
(void)orp; /* Do nothing */
}
/*
* This concludes the low level section of outobj.c
*/
static char obj_infile[FILENAME_MAX];
static efunc error;
static evalfunc evaluate;
static ldfunc deflabel;
static FILE *ofp;
static int32_t first_seg;
static bool any_segs;
static int passtwo;
static int arrindex;
#define GROUP_MAX 256 /* we won't _realistically_ have more
* than this many segs in a group */
#define EXT_BLKSIZ 256 /* block size for externals list */
struct Segment; /* need to know these structs exist */
struct Group;
struct LineNumber {
struct LineNumber *next;
struct Segment *segment;
int32_t offset;
int32_t lineno;
};
static struct FileName {
struct FileName *next;
char *name;
struct LineNumber *lnhead, **lntail;
int index;
} *fnhead, **fntail;
static struct Array {
struct Array *next;
unsigned size;
int basetype;
} *arrhead, **arrtail;
#define ARRAYBOT 31 /* magic number for first array index */
static struct Public {
struct Public *next;
char *name;
int32_t offset;
int32_t segment; /* only if it's far-absolute */
int type; /* only for local debug syms */
} *fpubhead, **fpubtail, *last_defined;
static struct External {
struct External *next;
char *name;
int32_t commonsize;
int32_t commonelem; /* element size if FAR, else zero */
int index; /* OBJ-file external index */
enum {
DEFWRT_NONE, /* no unusual default-WRT */
DEFWRT_STRING, /* a string we don't yet understand */
DEFWRT_SEGMENT, /* a segment */
DEFWRT_GROUP /* a group */
} defwrt_type;
union {
char *string;
struct Segment *seg;
struct Group *grp;
} defwrt_ptr;
struct External *next_dws; /* next with DEFWRT_STRING */
} *exthead, **exttail, *dws;
static int externals;
static struct ExtBack {
struct ExtBack *next;
struct External *exts[EXT_BLKSIZ];
} *ebhead, **ebtail;
static struct Segment {
struct Segment *next;
int32_t index; /* the NASM segment id */
int32_t obj_index; /* the OBJ-file segment index */
struct Group *grp; /* the group it beint32_ts to */
uint32_t currentpos;
int32_t align; /* can be SEG_ABS + absolute addr */
enum {
CMB_PRIVATE = 0,
CMB_PUBLIC = 2,
CMB_STACK = 5,
CMB_COMMON = 6
} combine;
bool use32; /* is this segment 32-bit? */
struct Public *pubhead, **pubtail, *lochead, **loctail;
char *name;
char *segclass, *overlay; /* `class' is a C++ keyword :-) */
ObjRecord *orp;
} *seghead, **segtail, *obj_seg_needs_update;
static struct Group {
struct Group *next;
char *name;
int32_t index; /* NASM segment id */
int32_t obj_index; /* OBJ-file group index */
int32_t nentries; /* number of elements... */
int32_t nindices; /* ...and number of index elts... */
union {
int32_t index;
char *name;
} segs[GROUP_MAX]; /* ...in this */
} *grphead, **grptail, *obj_grp_needs_update;
static struct ImpDef {
struct ImpDef *next;
char *extname;
char *libname;
unsigned int impindex;
char *impname;
} *imphead, **imptail;
static struct ExpDef {
struct ExpDef *next;
char *intname;
char *extname;
unsigned int ordinal;
int flags;
} *exphead, **exptail;
#define EXPDEF_FLAG_ORDINAL 0x80
#define EXPDEF_FLAG_RESIDENT 0x40
#define EXPDEF_FLAG_NODATA 0x20
#define EXPDEF_MASK_PARMCNT 0x1F
static int32_t obj_entry_seg, obj_entry_ofs;
struct ofmt of_obj;
/* The current segment */
static struct Segment *current_seg;
static int32_t obj_segment(char *, int, int *);
static void obj_write_file(int debuginfo);
static int obj_directive(char *, char *, int);
static void obj_init(FILE * fp, efunc errfunc, ldfunc ldef, evalfunc eval)
{
ofp = fp;
error = errfunc;
evaluate = eval;
deflabel = ldef;
first_seg = seg_alloc();
any_segs = false;
fpubhead = NULL;
fpubtail = &fpubhead;
exthead = NULL;
exttail = &exthead;
imphead = NULL;
imptail = &imphead;
exphead = NULL;
exptail = &exphead;
dws = NULL;
externals = 0;
ebhead = NULL;
ebtail = &ebhead;
seghead = obj_seg_needs_update = NULL;
segtail = &seghead;
grphead = obj_grp_needs_update = NULL;
grptail = &grphead;
obj_entry_seg = NO_SEG;
obj_uppercase = false;
obj_use32 = false;
passtwo = 0;
current_seg = NULL;
of_obj.current_dfmt->init(&of_obj, NULL, fp, errfunc);
}
static int obj_set_info(enum geninfo type, char **val)
{
(void)type;
(void)val;
return 0;
}
static void obj_cleanup(int debuginfo)
{
obj_write_file(debuginfo);
of_obj.current_dfmt->cleanup();
fclose(ofp);
while (seghead) {
struct Segment *segtmp = seghead;
seghead = seghead->next;
while (segtmp->pubhead) {
struct Public *pubtmp = segtmp->pubhead;
segtmp->pubhead = pubtmp->next;
nasm_free(pubtmp->name);
nasm_free(pubtmp);
}
nasm_free(segtmp->segclass);
nasm_free(segtmp->overlay);
nasm_free(segtmp);
}
while (fpubhead) {
struct Public *pubtmp = fpubhead;
fpubhead = fpubhead->next;
nasm_free(pubtmp->name);
nasm_free(pubtmp);
}
while (exthead) {
struct External *exttmp = exthead;
exthead = exthead->next;
nasm_free(exttmp);
}
while (imphead) {
struct ImpDef *imptmp = imphead;
imphead = imphead->next;
nasm_free(imptmp->extname);
nasm_free(imptmp->libname);
nasm_free(imptmp->impname); /* nasm_free won't mind if it's NULL */
nasm_free(imptmp);
}
while (exphead) {
struct ExpDef *exptmp = exphead;
exphead = exphead->next;
nasm_free(exptmp->extname);
nasm_free(exptmp->intname);
nasm_free(exptmp);
}
while (ebhead) {
struct ExtBack *ebtmp = ebhead;
ebhead = ebhead->next;
nasm_free(ebtmp);
}
while (grphead) {
struct Group *grptmp = grphead;
grphead = grphead->next;
nasm_free(grptmp);
}
}
static void obj_ext_set_defwrt(struct External *ext, char *id)
{
struct Segment *seg;
struct Group *grp;
for (seg = seghead; seg; seg = seg->next)
if (!strcmp(seg->name, id)) {
ext->defwrt_type = DEFWRT_SEGMENT;
ext->defwrt_ptr.seg = seg;
nasm_free(id);
return;
}
for (grp = grphead; grp; grp = grp->next)
if (!strcmp(grp->name, id)) {
ext->defwrt_type = DEFWRT_GROUP;
ext->defwrt_ptr.grp = grp;
nasm_free(id);
return;
}
ext->defwrt_type = DEFWRT_STRING;
ext->defwrt_ptr.string = id;
ext->next_dws = dws;
dws = ext;
}
static void obj_deflabel(char *name, int32_t segment,
int64_t offset, int is_global, char *special)
{
/*
* We have three cases:
*
* (i) `segment' is a segment-base. If so, set the name field
* for the segment or group structure it refers to, and then
* return.
*
* (ii) `segment' is one of our segments, or a SEG_ABS segment.
* Save the label position for later output of a PUBDEF record.
* (Or a MODPUB, if we work out how.)
*
* (iii) `segment' is not one of our segments. Save the label
* position for later output of an EXTDEF, and also store a
* back-reference so that we can map later references to this
* segment number to the external index.
*/
struct External *ext;
struct ExtBack *eb;
struct Segment *seg;
int i;
bool used_special = false; /* have we used the special text? */
#if defined(DEBUG) && DEBUG>2
fprintf(stderr,
" obj_deflabel: %s, seg=%ld, off=%ld, is_global=%d, %s\n",
name, segment, offset, is_global, special);
#endif
/*
* If it's a special-retry from pass two, discard it.
*/
if (is_global == 3)
return;
/*
* First check for the double-period, signifying something
* unusual.
*/
if (name[0] == '.' && name[1] == '.' && name[2] != '@') {
if (!strcmp(name, "..start")) {
obj_entry_seg = segment;
obj_entry_ofs = offset;
return;
}
error(ERR_NONFATAL, "unrecognised special symbol `%s'", name);
}
/*
* Case (i):
*/
if (obj_seg_needs_update) {
obj_seg_needs_update->name = name;
return;
} else if (obj_grp_needs_update) {
obj_grp_needs_update->name = name;
return;
}
if (segment < SEG_ABS && segment != NO_SEG && segment % 2)
return;
if (segment >= SEG_ABS || segment == NO_SEG) {
/*
* SEG_ABS subcase of (ii).
*/
if (is_global) {
struct Public *pub;
pub = *fpubtail = nasm_malloc(sizeof(*pub));
fpubtail = &pub->next;
pub->next = NULL;
pub->name = nasm_strdup(name);
pub->offset = offset;
pub->segment = (segment == NO_SEG ? 0 : segment & ~SEG_ABS);
}
if (special)
error(ERR_NONFATAL, "OBJ supports no special symbol features"
" for this symbol type");
return;
}
/*
* If `any_segs' is still false, we might need to define a
* default segment, if they're trying to declare a label in
* `first_seg'.
*/
if (!any_segs && segment == first_seg) {
int tempint; /* ignored */
if (segment != obj_segment("__NASMDEFSEG", 2, &tempint))
error(ERR_PANIC, "strange segment conditions in OBJ driver");
}
for (seg = seghead; seg && is_global; seg = seg->next)
if (seg->index == segment) {
struct Public *loc = nasm_malloc(sizeof(*loc));
/*
* Case (ii). Maybe MODPUB someday?
*/
*seg->pubtail = loc;
seg->pubtail = &loc->next;
loc->next = NULL;
loc->name = nasm_strdup(name);
loc->offset = offset;
if (special)
error(ERR_NONFATAL,
"OBJ supports no special symbol features"
" for this symbol type");
return;
}
/*
* Case (iii).
*/
if (is_global) {
ext = *exttail = nasm_malloc(sizeof(*ext));
ext->next = NULL;
exttail = &ext->next;
ext->name = name;
/* Place by default all externs into the current segment */
ext->defwrt_type = DEFWRT_NONE;
/* 28-Apr-2002 - John Coffman
The following code was introduced on 12-Aug-2000, and breaks fixups
on code passed thru the MSC 5.1 linker (3.66) and MSC 6.00A linker
(5.10). It was introduced after FIXUP32 was added, and may be needed
for 32-bit segments. The following will get 16-bit segments working
again, and maybe someone can correct the 'if' condition which is
actually needed.
*/
#if 0
if (current_seg) {
#else
if (current_seg && current_seg->use32) {
if (current_seg->grp) {
ext->defwrt_type = DEFWRT_GROUP;
ext->defwrt_ptr.grp = current_seg->grp;
} else {
ext->defwrt_type = DEFWRT_SEGMENT;
ext->defwrt_ptr.seg = current_seg;
}
}
#endif
if (is_global == 2) {
ext->commonsize = offset;
ext->commonelem = 1; /* default FAR */
} else
ext->commonsize = 0;
} else
return;
/*
* Now process the special text, if any, to find default-WRT
* specifications and common-variable element-size and near/far
* specifications.
*/
while (special && *special) {
used_special = true;
/*
* We might have a default-WRT specification.
*/
if (!nasm_strnicmp(special, "wrt", 3)) {
char *p;
int len;
special += 3;
special += strspn(special, " \t");
p = nasm_strndup(special, len = strcspn(special, ":"));
obj_ext_set_defwrt(ext, p);
special += len;
if (*special && *special != ':')
error(ERR_NONFATAL, "`:' expected in special symbol"
" text for `%s'", ext->name);
else if (*special == ':')
special++;
}
/*
* The NEAR or FAR keywords specify nearness or
* farness. FAR gives default element size 1.
*/
if (!nasm_strnicmp(special, "far", 3)) {
if (ext->commonsize)
ext->commonelem = 1;
else
error(ERR_NONFATAL,
"`%s': `far' keyword may only be applied"
" to common variables\n", ext->name);
special += 3;
special += strspn(special, " \t");
} else if (!nasm_strnicmp(special, "near", 4)) {
if (ext->commonsize)
ext->commonelem = 0;
else
error(ERR_NONFATAL,
"`%s': `far' keyword may only be applied"
" to common variables\n", ext->name);
special += 4;
special += strspn(special, " \t");
}
/*
* If it's a common, and anything else remains on the line
* before a further colon, evaluate it as an expression and
* use that as the element size. Forward references aren't
* allowed.
*/
if (*special == ':')
special++;
else if (*special) {
if (ext->commonsize) {
expr *e;
struct tokenval tokval;
stdscan_reset();
stdscan_bufptr = special;
tokval.t_type = TOKEN_INVALID;
e = evaluate(stdscan, NULL, &tokval, NULL, 1, error, NULL);
if (e) {
if (!is_simple(e))
error(ERR_NONFATAL, "cannot use relocatable"
" expression as common-variable element size");
else
ext->commonelem = reloc_value(e);
}
special = stdscan_bufptr;
} else {
error(ERR_NONFATAL,
"`%s': element-size specifications only"
" apply to common variables", ext->name);
while (*special && *special != ':')
special++;
if (*special == ':')
special++;
}
}
}
i = segment / 2;
eb = ebhead;
if (!eb) {
eb = *ebtail = nasm_malloc(sizeof(*eb));
eb->next = NULL;
ebtail = &eb->next;
}
while (i >= EXT_BLKSIZ) {
if (eb && eb->next)
eb = eb->next;
else {
eb = *ebtail = nasm_malloc(sizeof(*eb));
eb->next = NULL;
ebtail = &eb->next;
}
i -= EXT_BLKSIZ;
}
eb->exts[i] = ext;
ext->index = ++externals;
if (special && !used_special)
error(ERR_NONFATAL, "OBJ supports no special symbol features"
" for this symbol type");
}
/* forward declaration */
static void obj_write_fixup(ObjRecord * orp, int bytes,
int segrel, int32_t seg, int32_t wrt,
struct Segment *segto);
static void obj_out(int32_t segto, const void *data,
enum out_type type, uint64_t size,
int32_t segment, int32_t wrt)
{
const uint8_t *ucdata;
int32_t ldata;
struct Segment *seg;
ObjRecord *orp;
/*
* handle absolute-assembly (structure definitions)
*/
if (segto == NO_SEG) {
if (type != OUT_RESERVE)
error(ERR_NONFATAL, "attempt to assemble code in [ABSOLUTE]"
" space");
return;
}
/*
* If `any_segs' is still false, we must define a default
* segment.
*/
if (!any_segs) {
int tempint; /* ignored */
if (segto != obj_segment("__NASMDEFSEG", 2, &tempint))
error(ERR_PANIC, "strange segment conditions in OBJ driver");
}
/*
* Find the segment we are targetting.
*/
for (seg = seghead; seg; seg = seg->next)
if (seg->index == segto)
break;
if (!seg)
error(ERR_PANIC, "code directed to nonexistent segment?");
orp = seg->orp;
orp->parm[0] = seg->currentpos;
if (type == OUT_RAWDATA) {
ucdata = data;
while (size > 0) {
unsigned int len;
orp = obj_check(seg->orp, 1);
len = RECORD_MAX - orp->used;
if (len > size)
len = size;
memcpy(orp->buf + orp->used, ucdata, len);
orp->committed = orp->used += len;
orp->parm[0] = seg->currentpos += len;
ucdata += len;
size -= len;
}
} else if (type == OUT_ADDRESS || type == OUT_REL2ADR ||
type == OUT_REL4ADR) {
int rsize;
if (segment == NO_SEG && type != OUT_ADDRESS)
error(ERR_NONFATAL, "relative call to absolute address not"
" supported by OBJ format");
if (segment >= SEG_ABS)
error(ERR_NONFATAL, "far-absolute relocations not supported"
" by OBJ format");
ldata = *(int64_t *)data;
if (type == OUT_REL2ADR) {
ldata += (size - 2);
size = 2;
} else if (type == OUT_REL4ADR) {
ldata += (size - 4);
size = 4;
}
if (size == 2)
orp = obj_word(orp, ldata);
else
orp = obj_dword(orp, ldata);
rsize = size;
if (segment < SEG_ABS && (segment != NO_SEG && segment % 2) &&
size == 4) {
/*
* This is a 4-byte segment-base relocation such as
* `MOV EAX,SEG foo'. OBJ format can't actually handle
* these, but if the constant term has the 16 low bits
* zero, we can just apply a 2-byte segment-base
* relocation to the low word instead.
*/
rsize = 2;
if (ldata & 0xFFFF)
error(ERR_NONFATAL, "OBJ format cannot handle complex"
" dword-size segment base references");
}
if (segment != NO_SEG)
obj_write_fixup(orp, rsize,
(type == OUT_ADDRESS ? 0x4000 : 0),
segment, wrt, seg);
seg->currentpos += size;
} else if (type == OUT_RESERVE) {
if (orp->committed)
orp = obj_bump(orp);
seg->currentpos += size;
}
obj_commit(orp);
}
static void obj_write_fixup(ObjRecord * orp, int bytes,
int segrel, int32_t seg, int32_t wrt,
struct Segment *segto)
{
unsigned locat;
int method;
int base;
int32_t tidx, fidx;
struct Segment *s = NULL;
struct Group *g = NULL;
struct External *e = NULL;
ObjRecord *forp;
if (bytes == 1) {
error(ERR_NONFATAL, "`obj' output driver does not support"
" one-byte relocations");
return;
}
forp = orp->child;
if (forp == NULL) {
orp->child = forp = obj_new();
forp->up = &(orp->child);
/* We should choose between FIXUPP and FIXU32 record type */
/* If we're targeting a 32-bit segment, use a FIXU32 record */
if (segto->use32)
forp->type = FIXU32;
else
forp->type = FIXUPP;
}
if (seg % 2) {
base = true;
locat = FIX_16_SELECTOR;
seg--;
if (bytes != 2)
error(ERR_PANIC, "OBJ: 4-byte segment base fixup got"
" through sanity check");
} else {
base = false;
locat = (bytes == 2) ? FIX_16_OFFSET : FIX_32_OFFSET;
if (!segrel)
/*
* There is a bug in tlink that makes it process self relative
* fixups incorrectly if the x_size doesn't match the location
* size.
*/
forp = obj_force(forp, bytes << 3);
}
forp = obj_rword(forp, locat | segrel | (orp->parm[0] - orp->parm[2]));
tidx = fidx = -1, method = 0; /* placate optimisers */
/*
* See if we can find the segment ID in our segment list. If
* so, we have a T4 (LSEG) target.
*/
for (s = seghead; s; s = s->next)
if (s->index == seg)
break;
if (s)
method = 4, tidx = s->obj_index;
else {
for (g = grphead; g; g = g->next)
if (g->index == seg)
break;
if (g)
method = 5, tidx = g->obj_index;
else {
int32_t i = seg / 2;
struct ExtBack *eb = ebhead;
while (i >= EXT_BLKSIZ) {
if (eb)
eb = eb->next;
else
break;
i -= EXT_BLKSIZ;
}
if (eb)
method = 6, e = eb->exts[i], tidx = e->index;
else
error(ERR_PANIC,
"unrecognised segment value in obj_write_fixup");
}
}
/*
* If no WRT given, assume the natural default, which is method
* F5 unless:
*
* - we are doing an OFFSET fixup for a grouped segment, in
* which case we require F1 (group).
*
* - we are doing an OFFSET fixup for an external with a
* default WRT, in which case we must honour the default WRT.
*/
if (wrt == NO_SEG) {
if (!base && s && s->grp)
method |= 0x10, fidx = s->grp->obj_index;
else if (!base && e && e->defwrt_type != DEFWRT_NONE) {
if (e->defwrt_type == DEFWRT_SEGMENT)
method |= 0x00, fidx = e->defwrt_ptr.seg->obj_index;
else if (e->defwrt_type == DEFWRT_GROUP)
method |= 0x10, fidx = e->defwrt_ptr.grp->obj_index;
else {
error(ERR_NONFATAL, "default WRT specification for"
" external `%s' unresolved", e->name);
method |= 0x50, fidx = -1; /* got to do _something_ */
}
} else
method |= 0x50, fidx = -1;
} else {
/*
* See if we can find the WRT-segment ID in our segment
* list. If so, we have a F0 (LSEG) frame.
*/
for (s = seghead; s; s = s->next)
if (s->index == wrt - 1)
break;
if (s)
method |= 0x00, fidx = s->obj_index;
else {
for (g = grphead; g; g = g->next)
if (g->index == wrt - 1)
break;
if (g)
method |= 0x10, fidx = g->obj_index;
else {
int32_t i = wrt / 2;
struct ExtBack *eb = ebhead;
while (i >= EXT_BLKSIZ) {
if (eb)
eb = eb->next;
else
break;
i -= EXT_BLKSIZ;
}
if (eb)
method |= 0x20, fidx = eb->exts[i]->index;
else
error(ERR_PANIC,
"unrecognised WRT value in obj_write_fixup");
}
}
}
forp = obj_byte(forp, method);
if (fidx != -1)
forp = obj_index(forp, fidx);
forp = obj_index(forp, tidx);
obj_commit(forp);
}
static int32_t obj_segment(char *name, int pass, int *bits)
{
/*
* We call the label manager here to define a name for the new
* segment, and when our _own_ label-definition stub gets
* called in return, it should register the new segment name
* using the pointer it gets passed. That way we save memory,
* by sponging off the label manager.
*/
#if defined(DEBUG) && DEBUG>=3
fprintf(stderr, " obj_segment: < %s >, pass=%d, *bits=%d\n",
name, pass, *bits);
#endif
if (!name) {
*bits = 16;
current_seg = NULL;
return first_seg;
} else {
struct Segment *seg;
struct Group *grp;
struct External **extp;
int obj_idx, i, attrs;
bool rn_error;
char *p;
/*
* Look for segment attributes.
*/
attrs = 0;
while (*name == '.')
name++; /* hack, but a documented one */
p = name;
while (*p && !nasm_isspace(*p))
p++;
if (*p) {
*p++ = '\0';
while (*p && nasm_isspace(*p))
*p++ = '\0';
}
while (*p) {
while (*p && !nasm_isspace(*p))
p++;
if (*p) {
*p++ = '\0';
while (*p && nasm_isspace(*p))
*p++ = '\0';
}
attrs++;
}
obj_idx = 1;
for (seg = seghead; seg; seg = seg->next) {
obj_idx++;
if (!strcmp(seg->name, name)) {
if (attrs > 0 && pass == 1)
error(ERR_WARNING, "segment attributes specified on"
" redeclaration of segment: ignoring");
if (seg->use32)
*bits = 32;
else
*bits = 16;
current_seg = seg;
return seg->index;
}
}
*segtail = seg = nasm_malloc(sizeof(*seg));
seg->next = NULL;
segtail = &seg->next;
seg->index = (any_segs ? seg_alloc() : first_seg);
seg->obj_index = obj_idx;
seg->grp = NULL;
any_segs = true;
seg->name = NULL;
seg->currentpos = 0;
seg->align = 1; /* default */
seg->use32 = false; /* default */
seg->combine = CMB_PUBLIC; /* default */
seg->segclass = seg->overlay = NULL;
seg->pubhead = NULL;
seg->pubtail = &seg->pubhead;
seg->lochead = NULL;
seg->loctail = &seg->lochead;
seg->orp = obj_new();
seg->orp->up = &(seg->orp);
seg->orp->ori = ori_ledata;
seg->orp->type = LEDATA;
seg->orp->parm[1] = obj_idx;
/*
* Process the segment attributes.
*/
p = name;
while (attrs--) {
p += strlen(p);
while (!*p)
p++;
/*
* `p' contains a segment attribute.
*/
if (!nasm_stricmp(p, "private"))
seg->combine = CMB_PRIVATE;
else if (!nasm_stricmp(p, "public"))
seg->combine = CMB_PUBLIC;
else if (!nasm_stricmp(p, "common"))
seg->combine = CMB_COMMON;
else if (!nasm_stricmp(p, "stack"))
seg->combine = CMB_STACK;
else if (!nasm_stricmp(p, "use16"))
seg->use32 = false;
else if (!nasm_stricmp(p, "use32"))
seg->use32 = true;
else if (!nasm_stricmp(p, "flat")) {
/*
* This segment is an OS/2 FLAT segment. That means
* that its default group is group FLAT, even if
* the group FLAT does not explicitly _contain_ the
* segment.
*
* When we see this, we must create the group
* `FLAT', containing no segments, if it does not
* already exist; then we must set the default
* group of this segment to be the FLAT group.
*/
struct Group *grp;
for (grp = grphead; grp; grp = grp->next)
if (!strcmp(grp->name, "FLAT"))
break;
if (!grp) {
obj_directive("group", "FLAT", 1);
for (grp = grphead; grp; grp = grp->next)
if (!strcmp(grp->name, "FLAT"))
break;
if (!grp)
error(ERR_PANIC, "failure to define FLAT?!");
}
seg->grp = grp;
} else if (!nasm_strnicmp(p, "class=", 6))
seg->segclass = nasm_strdup(p + 6);
else if (!nasm_strnicmp(p, "overlay=", 8))
seg->overlay = nasm_strdup(p + 8);
else if (!nasm_strnicmp(p, "align=", 6)) {
seg->align = readnum(p + 6, &rn_error);
if (rn_error) {
seg->align = 1;
error(ERR_NONFATAL, "segment alignment should be"
" numeric");
}
switch ((int)seg->align) {
case 1: /* BYTE */
case 2: /* WORD */
case 4: /* DWORD */
case 16: /* PARA */
case 256: /* PAGE */
case 4096: /* PharLap extension */
break;
case 8:
error(ERR_WARNING,
"OBJ format does not support alignment"
" of 8: rounding up to 16");
seg->align = 16;
break;
case 32:
case 64:
case 128:
error(ERR_WARNING,
"OBJ format does not support alignment"
" of %d: rounding up to 256", seg->align);
seg->align = 256;
break;
case 512:
case 1024:
case 2048:
error(ERR_WARNING,
"OBJ format does not support alignment"
" of %d: rounding up to 4096", seg->align);
seg->align = 4096;
break;
default:
error(ERR_NONFATAL, "invalid alignment value %d",
seg->align);
seg->align = 1;
break;
}
} else if (!nasm_strnicmp(p, "absolute=", 9)) {
seg->align = SEG_ABS + readnum(p + 9, &rn_error);
if (rn_error)
error(ERR_NONFATAL, "argument to `absolute' segment"
" attribute should be numeric");
}
}
/* We need to know whenever we have at least one 32-bit segment */
obj_use32 |= seg->use32;
obj_seg_needs_update = seg;
if (seg->align >= SEG_ABS)
deflabel(name, NO_SEG, seg->align - SEG_ABS,
NULL, false, false, &of_obj, error);
else
deflabel(name, seg->index + 1, 0L,
NULL, false, false, &of_obj, error);
obj_seg_needs_update = NULL;
/*
* See if this segment is defined in any groups.
*/
for (grp = grphead; grp; grp = grp->next) {
for (i = grp->nindices; i < grp->nentries; i++) {
if (!strcmp(grp->segs[i].name, seg->name)) {
nasm_free(grp->segs[i].name);
grp->segs[i] = grp->segs[grp->nindices];
grp->segs[grp->nindices++].index = seg->obj_index;
if (seg->grp)
error(ERR_WARNING,
"segment `%s' is already part of"
" a group: first one takes precedence",
seg->name);
else
seg->grp = grp;
}
}
}
/*
* Walk through the list of externals with unresolved
* default-WRT clauses, and resolve any that point at this
* segment.
*/
extp = &dws;
while (*extp) {
if ((*extp)->defwrt_type == DEFWRT_STRING &&
!strcmp((*extp)->defwrt_ptr.string, seg->name)) {
nasm_free((*extp)->defwrt_ptr.string);
(*extp)->defwrt_type = DEFWRT_SEGMENT;
(*extp)->defwrt_ptr.seg = seg;
*extp = (*extp)->next_dws;
} else
extp = &(*extp)->next_dws;
}
if (seg->use32)
*bits = 32;
else
*bits = 16;
current_seg = seg;
return seg->index;
}
}
static int obj_directive(char *directive, char *value, int pass)
{
if (!strcmp(directive, "group")) {
char *p, *q, *v;
if (pass == 1) {
struct Group *grp;
struct Segment *seg;
struct External **extp;
int obj_idx;
q = value;
while (*q == '.')
q++; /* hack, but a documented one */
v = q;
while (*q && !nasm_isspace(*q))
q++;
if (nasm_isspace(*q)) {
*q++ = '\0';
while (*q && nasm_isspace(*q))
q++;
}
/*
* Here we used to sanity-check the group directive to
* ensure nobody tried to declare a group containing no
* segments. However, OS/2 does this as standard
* practice, so the sanity check has been removed.
*
* if (!*q) {
* error(ERR_NONFATAL,"GROUP directive contains no segments");
* return 1;
* }
*/
obj_idx = 1;
for (grp = grphead; grp; grp = grp->next) {
obj_idx++;
if (!strcmp(grp->name, v)) {
error(ERR_NONFATAL, "group `%s' defined twice", v);
return 1;
}
}
*grptail = grp = nasm_malloc(sizeof(*grp));
grp->next = NULL;
grptail = &grp->next;
grp->index = seg_alloc();
grp->obj_index = obj_idx;
grp->nindices = grp->nentries = 0;
grp->name = NULL;
obj_grp_needs_update = grp;
deflabel(v, grp->index + 1, 0L,
NULL, false, false, &of_obj, error);
obj_grp_needs_update = NULL;
while (*q) {
p = q;
while (*q && !nasm_isspace(*q))
q++;
if (nasm_isspace(*q)) {
*q++ = '\0';
while (*q && nasm_isspace(*q))
q++;
}
/*
* Now p contains a segment name. Find it.
*/
for (seg = seghead; seg; seg = seg->next)
if (!strcmp(seg->name, p))
break;
if (seg) {
/*
* We have a segment index. Shift a name entry
* to the end of the array to make room.
*/
grp->segs[grp->nentries++] = grp->segs[grp->nindices];
grp->segs[grp->nindices++].index = seg->obj_index;
if (seg->grp)
error(ERR_WARNING,
"segment `%s' is already part of"
" a group: first one takes precedence",
seg->name);
else
seg->grp = grp;
} else {
/*
* We have an as-yet undefined segment.
* Remember its name, for later.
*/
grp->segs[grp->nentries++].name = nasm_strdup(p);
}
}
/*
* Walk through the list of externals with unresolved
* default-WRT clauses, and resolve any that point at
* this group.
*/
extp = &dws;
while (*extp) {
if ((*extp)->defwrt_type == DEFWRT_STRING &&
!strcmp((*extp)->defwrt_ptr.string, grp->name)) {
nasm_free((*extp)->defwrt_ptr.string);
(*extp)->defwrt_type = DEFWRT_GROUP;
(*extp)->defwrt_ptr.grp = grp;
*extp = (*extp)->next_dws;
} else
extp = &(*extp)->next_dws;
}
}
return 1;
}
if (!strcmp(directive, "uppercase")) {
obj_uppercase = true;
return 1;
}
if (!strcmp(directive, "import")) {
char *q, *extname, *libname, *impname;
if (pass == 2)
return 1; /* ignore in pass two */
extname = q = value;
while (*q && !nasm_isspace(*q))
q++;
if (nasm_isspace(*q)) {
*q++ = '\0';
while (*q && nasm_isspace(*q))
q++;
}
libname = q;
while (*q && !nasm_isspace(*q))
q++;
if (nasm_isspace(*q)) {
*q++ = '\0';
while (*q && nasm_isspace(*q))
q++;
}
impname = q;
if (!*extname || !*libname)
error(ERR_NONFATAL, "`import' directive requires symbol name"
" and library name");
else {
struct ImpDef *imp;
bool err = false;
imp = *imptail = nasm_malloc(sizeof(struct ImpDef));
imptail = &imp->next;
imp->next = NULL;
imp->extname = nasm_strdup(extname);
imp->libname = nasm_strdup(libname);
imp->impindex = readnum(impname, &err);
if (!*impname || err)
imp->impname = nasm_strdup(impname);
else
imp->impname = NULL;
}
return 1;
}
if (!strcmp(directive, "export")) {
char *q, *extname, *intname, *v;
struct ExpDef *export;
int flags = 0;
unsigned int ordinal = 0;
if (pass == 2)
return 1; /* ignore in pass two */
intname = q = value;
while (*q && !nasm_isspace(*q))
q++;
if (nasm_isspace(*q)) {
*q++ = '\0';
while (*q && nasm_isspace(*q))
q++;
}
extname = q;
while (*q && !nasm_isspace(*q))
q++;
if (nasm_isspace(*q)) {
*q++ = '\0';
while (*q && nasm_isspace(*q))
q++;
}
if (!*intname) {
error(ERR_NONFATAL, "`export' directive requires export name");
return 1;
}
if (!*extname) {
extname = intname;
intname = "";
}
while (*q) {
v = q;
while (*q && !nasm_isspace(*q))
q++;
if (nasm_isspace(*q)) {
*q++ = '\0';
while (*q && nasm_isspace(*q))
q++;
}
if (!nasm_stricmp(v, "resident"))
flags |= EXPDEF_FLAG_RESIDENT;
else if (!nasm_stricmp(v, "nodata"))
flags |= EXPDEF_FLAG_NODATA;
else if (!nasm_strnicmp(v, "parm=", 5)) {
bool err = false;
flags |= EXPDEF_MASK_PARMCNT & readnum(v + 5, &err);
if (err) {
error(ERR_NONFATAL,
"value `%s' for `parm' is non-numeric", v + 5);
return 1;
}
} else {
bool err = false;
ordinal = readnum(v, &err);
if (err) {
error(ERR_NONFATAL,
"unrecognised export qualifier `%s'", v);
return 1;
}
flags |= EXPDEF_FLAG_ORDINAL;
}
}
export = *exptail = nasm_malloc(sizeof(struct ExpDef));
exptail = &export->next;
export->next = NULL;
export->extname = nasm_strdup(extname);
export->intname = nasm_strdup(intname);
export->ordinal = ordinal;
export->flags = flags;
return 1;
}
return 0;
}
static int32_t obj_segbase(int32_t segment)
{
struct Segment *seg;
/*
* Find the segment in our list.
*/
for (seg = seghead; seg; seg = seg->next)
if (seg->index == segment - 1)
break;
if (!seg) {
/*
* Might be an external with a default WRT.
*/
int32_t i = segment / 2;
struct ExtBack *eb = ebhead;
struct External *e;
while (i >= EXT_BLKSIZ) {
if (eb)
eb = eb->next;
else
break;
i -= EXT_BLKSIZ;
}
if (eb) {
e = eb->exts[i];
if (e->defwrt_type == DEFWRT_NONE)
return segment; /* fine */
else if (e->defwrt_type == DEFWRT_SEGMENT)
return e->defwrt_ptr.seg->index + 1;
else if (e->defwrt_type == DEFWRT_GROUP)
return e->defwrt_ptr.grp->index + 1;
else
return NO_SEG; /* can't tell what it is */
}
return segment; /* not one of ours - leave it alone */
}
if (seg->align >= SEG_ABS)
return seg->align; /* absolute segment */
if (seg->grp)
return seg->grp->index + 1; /* grouped segment */
return segment; /* no special treatment */
}
static void obj_filename(char *inname, char *outname, efunc lerror)
{
strcpy(obj_infile, inname);
standard_extension(inname, outname, ".obj", lerror);
}
static void obj_write_file(int debuginfo)
{
struct Segment *seg, *entry_seg_ptr = 0;
struct FileName *fn;
struct LineNumber *ln;
struct Group *grp;
struct Public *pub, *loc;
struct External *ext;
struct ImpDef *imp;
struct ExpDef *export;
int lname_idx;
ObjRecord *orp;
/*
* Write the THEADR module header.
*/
orp = obj_new();
orp->type = THEADR;
obj_name(orp, obj_infile);
obj_emit2(orp);
/*
* Write the NASM boast comment.
*/
orp->type = COMENT;
obj_rword(orp, 0); /* comment type zero */
obj_name(orp, nasm_comment);
obj_emit2(orp);
orp->type = COMENT;
/*
* Write the IMPDEF records, if any.
*/
for (imp = imphead; imp; imp = imp->next) {
obj_rword(orp, 0xA0); /* comment class A0 */
obj_byte(orp, 1); /* subfunction 1: IMPDEF */
if (imp->impname)
obj_byte(orp, 0); /* import by name */
else
obj_byte(orp, 1); /* import by ordinal */
obj_name(orp, imp->extname);
obj_name(orp, imp->libname);
if (imp->impname)
obj_name(orp, imp->impname);
else
obj_word(orp, imp->impindex);
obj_emit2(orp);
}
/*
* Write the EXPDEF records, if any.
*/
for (export = exphead; export; export = export->next) {
obj_rword(orp, 0xA0); /* comment class A0 */
obj_byte(orp, 2); /* subfunction 2: EXPDEF */
obj_byte(orp, export->flags);
obj_name(orp, export->extname);
obj_name(orp, export->intname);
if (export->flags & EXPDEF_FLAG_ORDINAL)
obj_word(orp, export->ordinal);
obj_emit2(orp);
}
/* we're using extended OMF if we put in debug info */
if (debuginfo) {
orp->type = COMENT;
obj_byte(orp, 0x40);
obj_byte(orp, dEXTENDED);
obj_emit2(orp);
}
/*
* Write the first LNAMES record, containing LNAME one, which
* is null. Also initialize the LNAME counter.
*/
orp->type = LNAMES;
obj_byte(orp, 0);
lname_idx = 1;
/*
* Write some LNAMES for the segment names
*/
for (seg = seghead; seg; seg = seg->next) {
orp = obj_name(orp, seg->name);
if (seg->segclass)
orp = obj_name(orp, seg->segclass);
if (seg->overlay)
orp = obj_name(orp, seg->overlay);
obj_commit(orp);
}
/*
* Write some LNAMES for the group names
*/
for (grp = grphead; grp; grp = grp->next) {
orp = obj_name(orp, grp->name);
obj_commit(orp);
}
obj_emit(orp);
/*
* Write the SEGDEF records.
*/
orp->type = SEGDEF;
for (seg = seghead; seg; seg = seg->next) {
int acbp;
uint32_t seglen = seg->currentpos;
acbp = (seg->combine << 2); /* C field */
if (seg->use32)
acbp |= 0x01; /* P bit is Use32 flag */
else if (seglen == 0x10000L) {
seglen = 0; /* This special case may be needed for old linkers */
acbp |= 0x02; /* B bit */
}
/* A field */
if (seg->align >= SEG_ABS)
/* acbp |= 0x00 */ ;
else if (seg->align >= 4096) {
if (seg->align > 4096)
error(ERR_NONFATAL, "segment `%s' requires more alignment"
" than OBJ format supports", seg->name);
acbp |= 0xC0; /* PharLap extension */
} else if (seg->align >= 256) {
acbp |= 0x80;
} else if (seg->align >= 16) {
acbp |= 0x60;
} else if (seg->align >= 4) {
acbp |= 0xA0;
} else if (seg->align >= 2) {
acbp |= 0x40;
} else
acbp |= 0x20;
obj_byte(orp, acbp);
if (seg->align & SEG_ABS) {
obj_x(orp, seg->align - SEG_ABS); /* Frame */
obj_byte(orp, 0); /* Offset */
}
obj_x(orp, seglen);
obj_index(orp, ++lname_idx);
obj_index(orp, seg->segclass ? ++lname_idx : 1);
obj_index(orp, seg->overlay ? ++lname_idx : 1);
obj_emit2(orp);
}
/*
* Write the GRPDEF records.
*/
orp->type = GRPDEF;
for (grp = grphead; grp; grp = grp->next) {
int i;
if (grp->nindices != grp->nentries) {
for (i = grp->nindices; i < grp->nentries; i++) {
error(ERR_NONFATAL, "group `%s' contains undefined segment"
" `%s'", grp->name, grp->segs[i].name);
nasm_free(grp->segs[i].name);
grp->segs[i].name = NULL;
}
}
obj_index(orp, ++lname_idx);
for (i = 0; i < grp->nindices; i++) {
obj_byte(orp, 0xFF);
obj_index(orp, grp->segs[i].index);
}
obj_emit2(orp);
}
/*
* Write the PUBDEF records: first the ones in the segments,
* then the far-absolutes.
*/
orp->type = PUBDEF;
orp->ori = ori_pubdef;
for (seg = seghead; seg; seg = seg->next) {
orp->parm[0] = seg->grp ? seg->grp->obj_index : 0;
orp->parm[1] = seg->obj_index;
for (pub = seg->pubhead; pub; pub = pub->next) {
orp = obj_name(orp, pub->name);
orp = obj_x(orp, pub->offset);
orp = obj_byte(orp, 0); /* type index */
obj_commit(orp);
}
obj_emit(orp);
}
orp->parm[0] = 0;
orp->parm[1] = 0;
for (pub = fpubhead; pub; pub = pub->next) { /* pub-crawl :-) */
if (orp->parm[2] != (uint32_t)pub->segment) {
obj_emit(orp);
orp->parm[2] = pub->segment;
}
orp = obj_name(orp, pub->name);
orp = obj_x(orp, pub->offset);
orp = obj_byte(orp, 0); /* type index */
obj_commit(orp);
}
obj_emit(orp);
/*
* Write the EXTDEF and COMDEF records, in order.
*/
orp->ori = ori_null;
for (ext = exthead; ext; ext = ext->next) {
if (ext->commonsize == 0) {
if (orp->type != EXTDEF) {
obj_emit(orp);
orp->type = EXTDEF;
}
orp = obj_name(orp, ext->name);
orp = obj_index(orp, 0);
} else {
if (orp->type != COMDEF) {
obj_emit(orp);
orp->type = COMDEF;
}
orp = obj_name(orp, ext->name);
orp = obj_index(orp, 0);
if (ext->commonelem) {
orp = obj_byte(orp, 0x61); /* far communal */
orp = obj_value(orp, (ext->commonsize / ext->commonelem));
orp = obj_value(orp, ext->commonelem);
} else {
orp = obj_byte(orp, 0x62); /* near communal */
orp = obj_value(orp, ext->commonsize);
}
}
obj_commit(orp);
}
obj_emit(orp);
/*
* Write a COMENT record stating that the linker's first pass
* may stop processing at this point. Exception is if our
* MODEND record specifies a start point, in which case,
* according to some variants of the documentation, this COMENT
* should be omitted. So we'll omit it just in case.
* But, TASM puts it in all the time so if we are using
* TASM debug stuff we are putting it in
*/
if (debuginfo || obj_entry_seg == NO_SEG) {
orp->type = COMENT;
obj_byte(orp, 0x40);
obj_byte(orp, dLINKPASS);
obj_byte(orp, 1);
obj_emit2(orp);
}
/*
* 1) put out the compiler type
* 2) Put out the type info. The only type we are using is near label #19
*/
if (debuginfo) {
int i;
struct Array *arrtmp = arrhead;
orp->type = COMENT;
obj_byte(orp, 0x40);
obj_byte(orp, dCOMPDEF);
obj_byte(orp, 4);
obj_byte(orp, 0);
obj_emit2(orp);
obj_byte(orp, 0x40);
obj_byte(orp, dTYPEDEF);
obj_word(orp, 0x18); /* type # for linking */
obj_word(orp, 6); /* size of type */
obj_byte(orp, 0x2a); /* absolute type for debugging */
obj_emit2(orp);
obj_byte(orp, 0x40);
obj_byte(orp, dTYPEDEF);
obj_word(orp, 0x19); /* type # for linking */
obj_word(orp, 0); /* size of type */
obj_byte(orp, 0x24); /* absolute type for debugging */
obj_byte(orp, 0); /* near/far specifier */
obj_emit2(orp);
obj_byte(orp, 0x40);
obj_byte(orp, dTYPEDEF);
obj_word(orp, 0x1A); /* type # for linking */
obj_word(orp, 0); /* size of type */
obj_byte(orp, 0x24); /* absolute type for debugging */
obj_byte(orp, 1); /* near/far specifier */
obj_emit2(orp);
obj_byte(orp, 0x40);
obj_byte(orp, dTYPEDEF);
obj_word(orp, 0x1b); /* type # for linking */
obj_word(orp, 0); /* size of type */
obj_byte(orp, 0x23); /* absolute type for debugging */
obj_byte(orp, 0);
obj_byte(orp, 0);
obj_byte(orp, 0);
obj_emit2(orp);
obj_byte(orp, 0x40);
obj_byte(orp, dTYPEDEF);
obj_word(orp, 0x1c); /* type # for linking */
obj_word(orp, 0); /* size of type */
obj_byte(orp, 0x23); /* absolute type for debugging */
obj_byte(orp, 0);
obj_byte(orp, 4);
obj_byte(orp, 0);
obj_emit2(orp);
obj_byte(orp, 0x40);
obj_byte(orp, dTYPEDEF);
obj_word(orp, 0x1d); /* type # for linking */
obj_word(orp, 0); /* size of type */
obj_byte(orp, 0x23); /* absolute type for debugging */
obj_byte(orp, 0);
obj_byte(orp, 1);
obj_byte(orp, 0);
obj_emit2(orp);
obj_byte(orp, 0x40);
obj_byte(orp, dTYPEDEF);
obj_word(orp, 0x1e); /* type # for linking */
obj_word(orp, 0); /* size of type */
obj_byte(orp, 0x23); /* absolute type for debugging */
obj_byte(orp, 0);
obj_byte(orp, 5);
obj_byte(orp, 0);
obj_emit2(orp);
/* put out the array types */
for (i = ARRAYBOT; i < arrindex; i++) {
obj_byte(orp, 0x40);
obj_byte(orp, dTYPEDEF);
obj_word(orp, i); /* type # for linking */
obj_word(orp, arrtmp->size); /* size of type */
obj_byte(orp, 0x1A); /* absolute type for debugging (array) */
obj_byte(orp, arrtmp->basetype); /* base type */
obj_emit2(orp);
arrtmp = arrtmp->next;
}
}
/*
* write out line number info with a LINNUM record
* switch records when we switch segments, and output the
* file in a pseudo-TASM fashion. The record switch is naive; that
* is that one file may have many records for the same segment
* if there are lots of segment switches
*/
if (fnhead && debuginfo) {
seg = fnhead->lnhead->segment;
for (fn = fnhead; fn; fn = fn->next) {
/* write out current file name */
orp->type = COMENT;
orp->ori = ori_null;
obj_byte(orp, 0x40);
obj_byte(orp, dFILNAME);
obj_byte(orp, 0);
obj_name(orp, fn->name);
obj_dword(orp, 0);
obj_emit2(orp);
/* write out line numbers this file */
orp->type = LINNUM;
orp->ori = ori_linnum;
for (ln = fn->lnhead; ln; ln = ln->next) {
if (seg != ln->segment) {
/* if we get here have to flush the buffer and start
* a new record for a new segment
*/
seg = ln->segment;
obj_emit(orp);
}
orp->parm[0] = seg->grp ? seg->grp->obj_index : 0;
orp->parm[1] = seg->obj_index;
orp = obj_word(orp, ln->lineno);
orp = obj_x(orp, ln->offset);
obj_commit(orp);
}
obj_emit(orp);
}
}
/*
* we are going to locate the entry point segment now
* rather than wait until the MODEND record, because,
* then we can output a special symbol to tell where the
* entry point is.
*
*/
if (obj_entry_seg != NO_SEG) {
for (seg = seghead; seg; seg = seg->next) {
if (seg->index == obj_entry_seg) {
entry_seg_ptr = seg;
break;
}
}
if (!seg)
error(ERR_NONFATAL, "entry point is not in this module");
}
/*
* get ready to put out symbol records
*/
orp->type = COMENT;
orp->ori = ori_local;
/*
* put out a symbol for the entry point
* no dots in this symbol, because, borland does
* not (officially) support dots in label names
* and I don't know what various versions of TLINK will do
*/
if (debuginfo && obj_entry_seg != NO_SEG) {
orp = obj_name(orp, "start_of_program");
orp = obj_word(orp, 0x19); /* type: near label */
orp = obj_index(orp, seg->grp ? seg->grp->obj_index : 0);
orp = obj_index(orp, seg->obj_index);
orp = obj_x(orp, obj_entry_ofs);
obj_commit(orp);
}
/*
* put out the local labels
*/
for (seg = seghead; seg && debuginfo; seg = seg->next) {
/* labels this seg */
for (loc = seg->lochead; loc; loc = loc->next) {
orp = obj_name(orp, loc->name);
orp = obj_word(orp, loc->type);
orp = obj_index(orp, seg->grp ? seg->grp->obj_index : 0);
orp = obj_index(orp, seg->obj_index);
orp = obj_x(orp, loc->offset);
obj_commit(orp);
}
}
if (orp->used)
obj_emit(orp);
/*
* Write the LEDATA/FIXUPP pairs.
*/
for (seg = seghead; seg; seg = seg->next) {
obj_emit(seg->orp);
nasm_free(seg->orp);
}
/*
* Write the MODEND module end marker.
*/
orp->type = obj_use32 ? MODE32 : MODEND;
orp->ori = ori_null;
if (entry_seg_ptr) {
orp->type = entry_seg_ptr->use32 ? MODE32 : MODEND;
obj_byte(orp, 0xC1);
seg = entry_seg_ptr;
if (seg->grp) {
obj_byte(orp, 0x10);
obj_index(orp, seg->grp->obj_index);
} else {
/*
* the below changed to prevent TLINK crashing.
* Previous more efficient version read:
*
* obj_byte (orp, 0x50);
*/
obj_byte(orp, 0x00);
obj_index(orp, seg->obj_index);
}
obj_index(orp, seg->obj_index);
obj_x(orp, obj_entry_ofs);
} else
obj_byte(orp, 0);
obj_emit2(orp);
nasm_free(orp);
}
static void obj_fwrite(ObjRecord * orp)
{
unsigned int cksum, len;
uint8_t *ptr;
cksum = orp->type;
if (orp->x_size == 32)
cksum |= 1;
fputc(cksum, ofp);
len = orp->committed + 1;
cksum += (len & 0xFF) + ((len >> 8) & 0xFF);
fwriteint16_t(len, ofp);
fwrite(orp->buf, 1, len - 1, ofp);
for (ptr = orp->buf; --len; ptr++)
cksum += *ptr;
fputc((-cksum) & 0xFF, ofp);
}
extern macros_t obj_stdmac[];
void dbgbi_init(struct ofmt *of, void *id, FILE * fp, efunc error)
{
(void)of;
(void)id;
(void)fp;
(void)error;
fnhead = NULL;
fntail = &fnhead;
arrindex = ARRAYBOT;
arrhead = NULL;
arrtail = &arrhead;
}
static void dbgbi_cleanup(void)
{
struct Segment *segtmp;
while (fnhead) {
struct FileName *fntemp = fnhead;
while (fnhead->lnhead) {
struct LineNumber *lntemp = fnhead->lnhead;
fnhead->lnhead = lntemp->next;
nasm_free(lntemp);
}
fnhead = fnhead->next;
nasm_free(fntemp->name);
nasm_free(fntemp);
}
for (segtmp = seghead; segtmp; segtmp = segtmp->next) {
while (segtmp->lochead) {
struct Public *loctmp = segtmp->lochead;
segtmp->lochead = loctmp->next;
nasm_free(loctmp->name);
nasm_free(loctmp);
}
}
while (arrhead) {
struct Array *arrtmp = arrhead;
arrhead = arrhead->next;
nasm_free(arrtmp);
}
}
static void dbgbi_linnum(const char *lnfname, int32_t lineno, int32_t segto)
{
struct FileName *fn;
struct LineNumber *ln;
struct Segment *seg;
if (segto == NO_SEG)
return;
/*
* If `any_segs' is still false, we must define a default
* segment.
*/
if (!any_segs) {
int tempint; /* ignored */
if (segto != obj_segment("__NASMDEFSEG", 2, &tempint))
error(ERR_PANIC, "strange segment conditions in OBJ driver");
}
/*
* Find the segment we are targetting.
*/
for (seg = seghead; seg; seg = seg->next)
if (seg->index == segto)
break;
if (!seg)
error(ERR_PANIC, "lineno directed to nonexistent segment?");
/* for (fn = fnhead; fn; fn = fnhead->next) */
for (fn = fnhead; fn; fn = fn->next) /* fbk - Austin Lunnen - John Fine */
if (!nasm_stricmp(lnfname, fn->name))
break;
if (!fn) {
fn = nasm_malloc(sizeof(*fn));
fn->name = nasm_malloc(strlen(lnfname) + 1);
strcpy(fn->name, lnfname);
fn->lnhead = NULL;
fn->lntail = &fn->lnhead;
fn->next = NULL;
*fntail = fn;
fntail = &fn->next;
}
ln = nasm_malloc(sizeof(*ln));
ln->segment = seg;
ln->offset = seg->currentpos;
ln->lineno = lineno;
ln->next = NULL;
*fn->lntail = ln;
fn->lntail = &ln->next;
}
static void dbgbi_deflabel(char *name, int32_t segment,
int64_t offset, int is_global, char *special)
{
struct Segment *seg;
(void)special;
/*
* If it's a special-retry from pass two, discard it.
*/
if (is_global == 3)
return;
/*
* First check for the double-period, signifying something
* unusual.
*/
if (name[0] == '.' && name[1] == '.' && name[2] != '@') {
return;
}
/*
* Case (i):
*/
if (obj_seg_needs_update) {
return;
} else if (obj_grp_needs_update) {
return;
}
if (segment < SEG_ABS && segment != NO_SEG && segment % 2)
return;
if (segment >= SEG_ABS || segment == NO_SEG) {
return;
}
/*
* If `any_segs' is still false, we might need to define a
* default segment, if they're trying to declare a label in
* `first_seg'. But the label should exist due to a prior
* call to obj_deflabel so we can skip that.
*/
for (seg = seghead; seg; seg = seg->next)
if (seg->index == segment) {
struct Public *loc = nasm_malloc(sizeof(*loc));
/*
* Case (ii). Maybe MODPUB someday?
*/
last_defined = *seg->loctail = loc;
seg->loctail = &loc->next;
loc->next = NULL;
loc->name = nasm_strdup(name);
loc->offset = offset;
}
}
static void dbgbi_typevalue(int32_t type)
{
int vsize;
int elem = TYM_ELEMENTS(type);
type = TYM_TYPE(type);
if (!last_defined)
return;
switch (type) {
case TY_BYTE:
last_defined->type = 8; /* uint8_t */
vsize = 1;
break;
case TY_WORD:
last_defined->type = 10; /* unsigned word */
vsize = 2;
break;
case TY_DWORD:
last_defined->type = 12; /* unsigned dword */
vsize = 4;
break;
case TY_FLOAT:
last_defined->type = 14; /* float */
vsize = 4;
break;
case TY_QWORD:
last_defined->type = 15; /* qword */
vsize = 8;
break;
case TY_TBYTE:
last_defined->type = 16; /* TBYTE */
vsize = 10;
break;
default:
last_defined->type = 0x19; /*label */
vsize = 0;
break;
}
if (elem > 1) {
struct Array *arrtmp = nasm_malloc(sizeof(*arrtmp));
int vtype = last_defined->type;
arrtmp->size = vsize * elem;
arrtmp->basetype = vtype;
arrtmp->next = NULL;
last_defined->type = arrindex++;
*arrtail = arrtmp;
arrtail = &(arrtmp->next);
}
last_defined = NULL;
}
static void dbgbi_output(int output_type, void *param)
{
(void)output_type;
(void)param;
}
static struct dfmt borland_debug_form = {
"Borland Debug Records",
"borland",
dbgbi_init,
dbgbi_linnum,
dbgbi_deflabel,
null_debug_routine,
dbgbi_typevalue,
dbgbi_output,
dbgbi_cleanup,
};
static struct dfmt *borland_debug_arr[3] = {
&borland_debug_form,
&null_debug_form,
NULL
};
struct ofmt of_obj = {
"MS-DOS 16-bit/32-bit OMF object files",
"obj",
NULL,
borland_debug_arr,
&borland_debug_form,
obj_stdmac,
obj_init,
obj_set_info,
obj_out,
obj_deflabel,
obj_segment,
obj_segbase,
obj_directive,
obj_filename,
obj_cleanup
};
#endif /* OF_OBJ */