mirror of
https://github.com/netwide-assembler/nasm.git
synced 2024-12-21 09:19:31 +08:00
0623e7dcf5
Signed-off-by: Cyrill Gorcunov <gorcunov@gmail.com>
2696 lines
80 KiB
C
2696 lines
80 KiB
C
/* ----------------------------------------------------------------------- *
|
|
*
|
|
* Copyright 1996-2017 The NASM Authors - All Rights Reserved
|
|
* See the file AUTHORS included with the NASM distribution for
|
|
* the specific copyright holders.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following
|
|
* conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer in the documentation and/or other materials provided
|
|
* with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* ----------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* outobj.c output routines for the Netwide Assembler to produce
|
|
* .OBJ object files
|
|
*/
|
|
|
|
#include "compiler.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
|
|
#include "nasm.h"
|
|
#include "nasmlib.h"
|
|
#include "error.h"
|
|
#include "stdscan.h"
|
|
#include "eval.h"
|
|
#include "ver.h"
|
|
|
|
#include "outform.h"
|
|
#include "outlib.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 */
|
|
dTRANSL = 0x0000, /* translator comment */
|
|
dOMFEXT = 0xC0A0, /* "OMF extension" */
|
|
dEXTENDED = 0xC0A1, /* translator-specific extensions */
|
|
dLINKPASS = 0x40A2, /* link pass 2 marker */
|
|
dTYPEDEF = 0xC0E3, /* define a type */
|
|
dSYM = 0xC0E6, /* symbol debug record */
|
|
dFILNAME = 0xC0E8, /* file name record */
|
|
dDEPFILE = 0xC0E9, /* dependency file */
|
|
dCOMPDEF = 0xC0EA /* 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 */
|
|
static bool obj_nodepend; /* Flag: don't emit file dependencies */
|
|
|
|
/*
|
|
* 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_rword(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 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;
|
|
char *name;
|
|
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 */
|
|
struct Public *pubhead, **pubtail, *lochead, **loctail;
|
|
char *segclass, *overlay; /* `class' is a C++ keyword :-) */
|
|
ObjRecord *orp;
|
|
enum {
|
|
CMB_PRIVATE = 0,
|
|
CMB_PUBLIC = 2,
|
|
CMB_STACK = 5,
|
|
CMB_COMMON = 6
|
|
} combine;
|
|
bool use32; /* is this segment 32-bit? */
|
|
} *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;
|
|
|
|
const struct ofmt of_obj;
|
|
static const struct dfmt borland_debug_form;
|
|
|
|
/* The current segment */
|
|
static struct Segment *current_seg;
|
|
|
|
static int32_t obj_segment(char *, int, int *);
|
|
static void obj_write_file(void);
|
|
static enum directive_result obj_directive(enum directive, char *, int);
|
|
|
|
static void obj_init(void)
|
|
{
|
|
strlcpy(obj_infile, inname, sizeof(obj_infile));
|
|
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;
|
|
}
|
|
|
|
static void obj_cleanup(void)
|
|
{
|
|
obj_write_file();
|
|
dfmt->cleanup();
|
|
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
|
|
nasm_debug(" obj_deflabel: %s, seg=%"PRIx32", off=%"PRIx64", 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;
|
|
}
|
|
nasm_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)
|
|
nasm_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))
|
|
nasm_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)
|
|
nasm_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 != ':')
|
|
nasm_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
|
|
nasm_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
|
|
nasm_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_set(special);
|
|
tokval.t_type = TOKEN_INVALID;
|
|
e = evaluate(stdscan, NULL, &tokval, NULL, 1, NULL);
|
|
if (e) {
|
|
if (!is_simple(e))
|
|
nasm_nonfatal("cannot use relocatable"
|
|
" expression as common-variable element size");
|
|
else
|
|
ext->commonelem = reloc_value(e);
|
|
}
|
|
special = stdscan_get();
|
|
} else {
|
|
nasm_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_zalloc(sizeof(*eb));
|
|
eb->next = NULL;
|
|
ebtail = &eb->next;
|
|
}
|
|
while (i >= EXT_BLKSIZ) {
|
|
if (eb && eb->next)
|
|
eb = eb->next;
|
|
else {
|
|
eb = *ebtail = nasm_zalloc(sizeof(*eb));
|
|
eb->next = NULL;
|
|
ebtail = &eb->next;
|
|
}
|
|
i -= EXT_BLKSIZ;
|
|
}
|
|
eb->exts[i] = ext;
|
|
ext->index = ++externals;
|
|
|
|
if (special && !used_special)
|
|
nasm_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;
|
|
|
|
/*
|
|
* 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))
|
|
nasm_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)
|
|
nasm_panic("code directed to nonexistent segment?");
|
|
|
|
orp = seg->orp;
|
|
orp->parm[0] = seg->currentpos;
|
|
|
|
switch (type) {
|
|
case 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;
|
|
}
|
|
break;
|
|
|
|
case OUT_ADDRESS:
|
|
case OUT_REL1ADR:
|
|
case OUT_REL2ADR:
|
|
case OUT_REL4ADR:
|
|
case OUT_REL8ADR:
|
|
{
|
|
int rsize;
|
|
|
|
if (type == OUT_ADDRESS)
|
|
size = abs((int)size);
|
|
|
|
if (segment == NO_SEG && type != OUT_ADDRESS)
|
|
nasm_nonfatal("relative call to absolute address not"
|
|
" supported by OBJ format");
|
|
if (segment >= SEG_ABS)
|
|
nasm_nonfatal("far-absolute relocations not supported"
|
|
" by OBJ format");
|
|
|
|
ldata = *(int64_t *)data;
|
|
if (type != OUT_ADDRESS) {
|
|
/*
|
|
* For 16-bit and 32-bit x86 code, the size and realsize() always
|
|
* matches as only jumps, calls and loops uses PC relative
|
|
* addressing and the address isn't followed by any other opcode
|
|
* bytes. In 64-bit mode there is RIP relative addressing which
|
|
* means the fixup location can be followed by an immediate value,
|
|
* meaning that size > realsize().
|
|
*
|
|
* When the CPU is calculating the effective address, it takes the
|
|
* RIP at the end of the instruction and adds the fixed up relative
|
|
* address value to it.
|
|
*
|
|
* The linker's point of reference is the end of the fixup location
|
|
* (which is the end of the instruction for Jcc, CALL, LOOP[cc]).
|
|
* It is calculating distance between the target symbol and the end
|
|
* of the fixup location, and add this to the displacement value we
|
|
* are calculating here and storing at the fixup location.
|
|
*
|
|
* To get the right effect, we need to _reduce_ the displacement
|
|
* value by the number of bytes following the fixup.
|
|
*
|
|
* Example:
|
|
* data at address 0x100; REL4ADR at 0x050, 4 byte immediate,
|
|
* end of fixup at 0x054, end of instruction at 0x058.
|
|
* => size = 8.
|
|
* => realsize() -> 4
|
|
* => CPU needs a value of: 0x100 - 0x058 = 0x0a8
|
|
* => linker/loader will add: 0x100 - 0x054 = 0x0ac
|
|
* => We must add an addend of -4.
|
|
* => realsize() - size = -4.
|
|
*
|
|
* The code used to do size - realsize() at least since v0.90,
|
|
* probably because it wasn't needed...
|
|
*/
|
|
ldata -= size;
|
|
size = realsize(type, size);
|
|
ldata += size;
|
|
}
|
|
|
|
switch (size) {
|
|
default:
|
|
nasm_nonfatal("OBJ format can only handle 16- or "
|
|
"32-byte relocations");
|
|
segment = NO_SEG; /* Don't actually generate a relocation */
|
|
break;
|
|
case 2:
|
|
orp = obj_word(orp, ldata);
|
|
break;
|
|
case 4:
|
|
orp = obj_dword(orp, ldata);
|
|
break;
|
|
}
|
|
|
|
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)
|
|
nasm_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;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
nasm_nonfatal("Relocation type not supported by output format");
|
|
/* fall through */
|
|
|
|
case OUT_RESERVE:
|
|
if (orp->committed)
|
|
orp = obj_bump(orp);
|
|
seg->currentpos += size;
|
|
break;
|
|
}
|
|
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 != 2 && bytes != 4) {
|
|
nasm_nonfatal("`obj' output driver does not support"
|
|
" %d-bit relocations", bytes << 3);
|
|
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)
|
|
nasm_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
|
|
nasm_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 {
|
|
nasm_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
|
|
nasm_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
|
|
nasm_debug(" 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++;
|
|
}
|
|
|
|
for (seg = seghead, obj_idx = 1; ; seg = seg->next, obj_idx++) {
|
|
if (!seg)
|
|
break;
|
|
|
|
if (!strcmp(seg->name, name)) {
|
|
if (attrs > 0 && pass == 1)
|
|
nasm_warn("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 = nasm_strdup(name);
|
|
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(D_GROUP, "FLAT", 1);
|
|
for (grp = grphead; grp; grp = grp->next)
|
|
if (!strcmp(grp->name, "FLAT"))
|
|
break;
|
|
if (!grp)
|
|
nasm_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;
|
|
nasm_nonfatal("segment alignment should be numeric");
|
|
}
|
|
switch (seg->align) {
|
|
case 1: /* BYTE */
|
|
case 2: /* WORD */
|
|
case 4: /* DWORD */
|
|
case 16: /* PARA */
|
|
case 256: /* PAGE */
|
|
case 4096: /* PharLap extension */
|
|
break;
|
|
case 8:
|
|
nasm_warn("OBJ format does not support alignment"
|
|
" of 8: rounding up to 16");
|
|
seg->align = 16;
|
|
break;
|
|
case 32:
|
|
case 64:
|
|
case 128:
|
|
nasm_warn("OBJ format does not support alignment"
|
|
" of %d: rounding up to 256", seg->align);
|
|
seg->align = 256;
|
|
break;
|
|
case 512:
|
|
case 1024:
|
|
case 2048:
|
|
nasm_warn("OBJ format does not support alignment"
|
|
" of %d: rounding up to 4096", seg->align);
|
|
seg->align = 4096;
|
|
break;
|
|
default:
|
|
nasm_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)
|
|
nasm_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)
|
|
define_label(name, NO_SEG, seg->align - SEG_ABS, false);
|
|
else
|
|
define_label(name, seg->index + 1, 0L, false);
|
|
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)
|
|
nasm_warn("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 enum directive_result
|
|
obj_directive(enum directive directive, char *value, int pass)
|
|
{
|
|
switch (directive) {
|
|
case D_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) {
|
|
* nasm_error(ERR_NONFATAL,"GROUP directive contains no segments");
|
|
* return DIRR_ERROR;
|
|
* }
|
|
*/
|
|
|
|
obj_idx = 1;
|
|
for (grp = grphead; grp; grp = grp->next) {
|
|
obj_idx++;
|
|
if (!strcmp(grp->name, v)) {
|
|
nasm_nonfatal("group `%s' defined twice", v);
|
|
return DIRR_ERROR;
|
|
}
|
|
}
|
|
|
|
*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;
|
|
backend_label(v, grp->index + 1, 0L);
|
|
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)
|
|
nasm_warn("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 DIRR_OK;
|
|
}
|
|
case D_UPPERCASE:
|
|
obj_uppercase = true;
|
|
return DIRR_OK;
|
|
|
|
case D_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)
|
|
nasm_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 DIRR_OK;
|
|
}
|
|
case D_EXPORT:
|
|
{
|
|
char *q, *extname, *intname, *v;
|
|
struct ExpDef *export;
|
|
int flags = 0;
|
|
unsigned int ordinal = 0;
|
|
|
|
if (pass == 2)
|
|
return DIRR_OK; /* 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) {
|
|
nasm_nonfatal("`export' directive requires export name");
|
|
return DIRR_OK;
|
|
}
|
|
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) {
|
|
nasm_nonfatal("value `%s' for `parm' is non-numeric", v + 5);
|
|
return DIRR_ERROR;
|
|
}
|
|
} else {
|
|
bool err = false;
|
|
ordinal = readnum(v, &err);
|
|
if (err) {
|
|
nasm_nonfatal("unrecognised export qualifier `%s'", v);
|
|
return DIRR_ERROR;
|
|
}
|
|
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 DIRR_OK;
|
|
}
|
|
default:
|
|
return DIRR_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
static void obj_sectalign(int32_t seg, unsigned int value)
|
|
{
|
|
struct Segment *s;
|
|
|
|
list_for_each(s, seghead) {
|
|
if (s->index == seg)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* it should not be too big value
|
|
* and applied on non-absolute sections
|
|
*/
|
|
if (!s || !is_power2(value) ||
|
|
value > 4096 || s->align >= SEG_ABS)
|
|
return;
|
|
|
|
/*
|
|
* FIXME: No code duplication please
|
|
* consider making helper for this
|
|
* mapping since section handler has
|
|
* to do the same
|
|
*/
|
|
switch (value) {
|
|
case 8:
|
|
value = 16;
|
|
break;
|
|
case 32:
|
|
case 64:
|
|
case 128:
|
|
value = 256;
|
|
break;
|
|
case 512:
|
|
case 1024:
|
|
case 2048:
|
|
value = 4096;
|
|
break;
|
|
}
|
|
|
|
if (s->align < (int)value)
|
|
s->align = value;
|
|
}
|
|
|
|
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) {
|
|
/* Not available yet, probably a forward reference */
|
|
nasm_assert(pass0 < 2); /* Convergence failure */
|
|
return NO_SEG;
|
|
}
|
|
|
|
switch (e->defwrt_type) {
|
|
case DEFWRT_NONE:
|
|
return segment; /* fine */
|
|
case DEFWRT_SEGMENT:
|
|
return e->defwrt_ptr.seg->index + 1;
|
|
case DEFWRT_GROUP:
|
|
return e->defwrt_ptr.grp->index + 1;
|
|
default:
|
|
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 */
|
|
}
|
|
|
|
/* Get a file timestamp in MS-DOS format */
|
|
static uint32_t obj_file_timestamp(const char *pathname)
|
|
{
|
|
time_t t;
|
|
const struct tm *lt;
|
|
|
|
if (!nasm_file_time(&t, pathname))
|
|
return 0;
|
|
|
|
lt = localtime(&t);
|
|
if (!lt)
|
|
return 0;
|
|
|
|
if (lt->tm_year < 80 || lt->tm_year > 207)
|
|
return 0; /* Only years 1980-2107 representable */
|
|
|
|
return
|
|
((uint32_t)lt->tm_sec >> 1) +
|
|
((uint32_t)lt->tm_min << 5) +
|
|
((uint32_t)lt->tm_hour << 11) +
|
|
((uint32_t)lt->tm_mday << 16) +
|
|
(((uint32_t)lt->tm_mon + 1) << 21) +
|
|
(((uint32_t)lt->tm_year - 80) << 25);
|
|
}
|
|
|
|
static void obj_write_file(void)
|
|
{
|
|
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;
|
|
const struct strlist_entry *depfile;
|
|
const bool debuginfo = (dfmt == &borland_debug_form);
|
|
|
|
/*
|
|
* 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, dTRANSL);
|
|
obj_name(orp, nasm_comment());
|
|
obj_emit2(orp);
|
|
|
|
/*
|
|
* Output file dependency information
|
|
*/
|
|
if (!obj_nodepend && depend_list) {
|
|
list_for_each(depfile, depend_list->head) {
|
|
uint32_t ts;
|
|
|
|
ts = obj_file_timestamp(depfile->str);
|
|
if (ts) {
|
|
orp->type = COMENT;
|
|
obj_rword(orp, dDEPFILE);
|
|
obj_dword(orp, ts);
|
|
obj_name(orp, depfile->str);
|
|
obj_emit2(orp);
|
|
}
|
|
}
|
|
}
|
|
|
|
orp->type = COMENT;
|
|
/*
|
|
* Write the IMPDEF records, if any.
|
|
*/
|
|
for (imp = imphead; imp; imp = imp->next) {
|
|
obj_rword(orp, dOMFEXT);
|
|
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, dOMFEXT);
|
|
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_rword(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)
|
|
nasm_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++) {
|
|
nasm_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_rword(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_rword(orp, dCOMPDEF);
|
|
obj_byte(orp, 4);
|
|
obj_byte(orp, 0);
|
|
obj_emit2(orp);
|
|
|
|
obj_rword(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_rword(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_rword(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_rword(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_rword(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_rword(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_rword(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_rword(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_rword(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)
|
|
nasm_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, ofile);
|
|
len = orp->committed + 1;
|
|
cksum += (len & 0xFF) + ((len >> 8) & 0xFF);
|
|
fwriteint16_t(len, ofile);
|
|
nasm_write(orp->buf, len-1, ofile);
|
|
for (ptr = orp->buf; --len; ptr++)
|
|
cksum += *ptr;
|
|
fputc((-cksum) & 0xFF, ofile);
|
|
}
|
|
|
|
static enum directive_result
|
|
obj_pragma(const struct pragma *pragma)
|
|
{
|
|
switch (pragma->opcode) {
|
|
case D_NODEPEND:
|
|
obj_nodepend = true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return DIRR_OK;
|
|
}
|
|
|
|
extern macros_t obj_stdmac[];
|
|
|
|
static void dbgbi_init(void)
|
|
{
|
|
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))
|
|
nasm_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)
|
|
nasm_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;
|
|
|
|
/*
|
|
* Note: ..[^@] special symbols are filtered in labels.c
|
|
*/
|
|
|
|
/*
|
|
* If it's a special-retry from pass two, discard it.
|
|
*/
|
|
if (is_global == 3)
|
|
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 const struct dfmt borland_debug_form = {
|
|
"Borland Debug Records",
|
|
"borland",
|
|
dbgbi_init,
|
|
dbgbi_linnum,
|
|
dbgbi_deflabel,
|
|
null_debug_directive,
|
|
dbgbi_typevalue,
|
|
dbgbi_output,
|
|
dbgbi_cleanup,
|
|
NULL /* pragma list */
|
|
};
|
|
|
|
static const struct dfmt * const borland_debug_arr[3] = {
|
|
&borland_debug_form,
|
|
&null_debug_form,
|
|
NULL
|
|
};
|
|
|
|
static const struct pragma_facility obj_pragma_list[] = {
|
|
{ NULL, obj_pragma }
|
|
};
|
|
|
|
const struct ofmt of_obj = {
|
|
"MS-DOS 16-bit/32-bit OMF object files",
|
|
"obj",
|
|
".obj",
|
|
0,
|
|
32,
|
|
borland_debug_arr,
|
|
&borland_debug_form,
|
|
obj_stdmac,
|
|
obj_init,
|
|
null_reset,
|
|
nasm_do_legacy_output,
|
|
obj_out,
|
|
obj_deflabel,
|
|
obj_segment,
|
|
NULL,
|
|
obj_sectalign,
|
|
obj_segbase,
|
|
obj_directive,
|
|
obj_cleanup,
|
|
obj_pragma_list
|
|
};
|
|
#endif /* OF_OBJ */
|