nasm/output/outcoff.c
H. Peter Anvin (Intel) 720a32672c Refactor the handling of primary input/output files
Replace the direct setting of variables for input and output variables
with accessors. This allows for properly tracking the lifetimes of the
data and allows for things like checking of the overwrite of the
primary input file to be centralized.

It isn't possible *in the general case* to check for overwrite of
*any* of the input files, although in the particularly important case
of the assembler proper it ought to be possible to do a bit better:

it should be able to guard for overwrites of non-primary input files
except for the error file or the list file if and only if -Lp is used.

That is, however, a latter project.

Signed-off-by: H. Peter Anvin (Intel) <hpa@zytor.com>
2026-06-29 18:22:26 -07:00

1506 lines
46 KiB
C

/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright 1996-2020 The NASM Authors - All Rights Reserved */
/*
* outcoff.c output routines for the Netwide Assembler to produce
* COFF object files (for DJGPP and Win32)
*/
#include "compiler.h"
#include "nctype.h"
#include <time.h>
#include "ver.h"
#include "nasm.h"
#include "nasmlib.h"
#include "ilog2.h"
#include "files.h"
#include "error.h"
#include "saa.h"
#include "raa.h"
#include "eval.h"
#include "outform.h"
#include "outlib.h"
#include "pecoff.h"
#if defined(OF_COFF) || defined(OF_WIN32) || defined(OF_WIN64)
/*
* Notes on COFF:
*
* (0) When I say `standard COFF' below, I mean `COFF as output and
* used by DJGPP'. I assume DJGPP gets it right.
*
* (1) Win32 appears to interpret the term `relative relocation'
* differently from standard COFF. Standard COFF understands a
* relative relocation to mean that during relocation you add the
* address of the symbol you're referencing, and subtract the base
* address of the section you're in. Win32 COFF, by contrast, seems
* to add the address of the symbol and then subtract the address
* of THE BYTE AFTER THE RELOCATED DWORD. Hence the two formats are
* subtly incompatible.
*
* (2) Win32 doesn't bother putting any flags in the header flags
* field (at offset 0x12 into the file).
*
* (3) Win32/64 uses some extra flags into the section header table:
* it defines flags 0x80000000 (writable), 0x40000000 (readable)
* and 0x20000000 (executable), and uses them in the expected
* combinations. It also defines 0x00100000 through 0x00f00000 for
* section alignments of 1 through 8192 bytes.
*
* (4) Both standard COFF and Win32 COFF seem to use the DWORD
* field directly after the section name in the section header
* table for something strange: they store what the address of the
* section start point _would_ be, if you laid all the sections end
* to end starting at zero. Dunno why. Microsoft's documentation
* lists this field as "Virtual Size of Section", which doesn't
* seem to fit at all. In fact, Win32 even includes non-linked
* sections such as .drectve in this calculation.
*
* Newer versions of MASM seem to have changed this to be zero, and
* that apparently matches the COFF spec, so go with that.
*
* (5) Standard COFF does something very strange to common
* variables: the relocation point for a common variable is as far
* _before_ the variable as its size stretches out _after_ it. So
* we must fix up common variable references. Win32 seems to be
* sensible on this one.
*/
/* Flag which version of COFF we are currently outputting. */
bool win32, win64;
static int32_t imagebase_sect;
#define WRT_IMAGEBASE "..imagebase"
static int32_t symtab_sect;
#define WRT_SYMTAB "..symtab"
/*
* Some common section flags by default
*/
#define TEXT_FLAGS_WIN \
(IMAGE_SCN_CNT_CODE | \
IMAGE_SCN_ALIGN_16BYTES | \
IMAGE_SCN_MEM_EXECUTE | \
IMAGE_SCN_MEM_READ)
#define TEXT_FLAGS_DOS \
(IMAGE_SCN_CNT_CODE)
#define DATA_FLAGS_WIN \
(IMAGE_SCN_CNT_INITIALIZED_DATA | \
IMAGE_SCN_ALIGN_4BYTES | \
IMAGE_SCN_MEM_READ | \
IMAGE_SCN_MEM_WRITE)
#define DATA_FLAGS_DOS \
(IMAGE_SCN_CNT_INITIALIZED_DATA)
#define BSS_FLAGS_WIN \
(IMAGE_SCN_CNT_UNINITIALIZED_DATA | \
IMAGE_SCN_ALIGN_4BYTES | \
IMAGE_SCN_MEM_READ | \
IMAGE_SCN_MEM_WRITE)
#define BSS_FLAGS_DOS \
(IMAGE_SCN_CNT_UNINITIALIZED_DATA)
#define RDATA_FLAGS_WIN \
(IMAGE_SCN_CNT_INITIALIZED_DATA | \
IMAGE_SCN_ALIGN_8BYTES | \
IMAGE_SCN_MEM_READ)
#define RDATA_FLAGS_DOS \
(IMAGE_SCN_CNT_INITIALIZED_DATA)
#define PDATA_FLAGS \
(IMAGE_SCN_CNT_INITIALIZED_DATA | \
IMAGE_SCN_ALIGN_4BYTES | \
IMAGE_SCN_MEM_READ)
#define XDATA_FLAGS \
(IMAGE_SCN_CNT_INITIALIZED_DATA | \
IMAGE_SCN_ALIGN_8BYTES | \
IMAGE_SCN_MEM_READ)
#define INFO_FLAGS \
(IMAGE_SCN_ALIGN_1BYTES | \
IMAGE_SCN_LNK_INFO | \
IMAGE_SCN_LNK_REMOVE)
#define TEXT_FLAGS ((win32 | win64) ? TEXT_FLAGS_WIN : TEXT_FLAGS_DOS)
#define DATA_FLAGS ((win32 | win64) ? DATA_FLAGS_WIN : DATA_FLAGS_DOS)
#define BSS_FLAGS ((win32 | win64) ? BSS_FLAGS_WIN : BSS_FLAGS_DOS)
#define RDATA_FLAGS ((win32 | win64) ? RDATA_FLAGS_WIN : RDATA_FLAGS_DOS)
#define COFF_MAX_ALIGNMENT 8192
#define SECT_DELTA 32
struct coff_Section **coff_sects;
static int sectlen;
int coff_nsects;
struct SAA *coff_syms;
uint32_t coff_nsyms;
static int32_t def_seg;
static int initsym;
static struct RAA *bsym, *symval;
struct SAA *coff_strs;
static uint32_t strslen;
static void coff_gen_init(void);
static void coff_sect_write(struct coff_Section *, const uint8_t *, uint32_t);
static void coff_write(void);
static void coff_section_header(char *, int32_t, int32_t, int32_t, int32_t, int32_t, int, int32_t);
static void coff_write_relocs(struct coff_Section *);
static void coff_write_symbols(void);
static void coff_defcomdatname(char *name, int32_t segment);
#define COMDAT_PLACEHOLDER_NAME ".tmpcmdt"
static void coff_win32_init(void)
{
win32 = true;
win64 = false;
coff_gen_init();
}
static void coff_win64_init(void)
{
win32 = false;
win64 = true;
coff_gen_init();
imagebase_sect = seg_alloc()+1;
backend_label(WRT_IMAGEBASE, imagebase_sect, 0);
}
static void coff_std_init(void)
{
win32 = win64 = false;
coff_gen_init();
}
static void coff_gen_init(void)
{
coff_sects = NULL;
coff_nsects = sectlen = 0;
coff_syms = saa_init(sizeof(struct coff_Symbol));
coff_nsyms = 0;
bsym = raa_init();
symval = raa_init();
coff_strs = saa_init(1);
strslen = 0;
def_seg = seg_alloc();
symtab_sect = seg_alloc()+1;
backend_label(WRT_SYMTAB, symtab_sect, 0);
}
static void coff_cleanup(void)
{
struct coff_Reloc *r;
int i;
dfmt->cleanup();
coff_write();
for (i = 0; i < coff_nsects; i++) {
if (coff_sects[i]->data)
saa_free(coff_sects[i]->data);
while (coff_sects[i]->head) {
r = coff_sects[i]->head;
coff_sects[i]->head = coff_sects[i]->head->next;
nasm_free(r);
}
while (coff_sects[i]->symidx_reloc_head) {
struct coff_SymIdxReloc * const ir = coff_sects[i]->symidx_reloc_head;
coff_sects[i]->symidx_reloc_head = ir->next;
nasm_free(ir);
}
nasm_free(coff_sects[i]->name);
nasm_free(coff_sects[i]->comdat_name);
nasm_free(coff_sects[i]);
}
nasm_free(coff_sects);
saa_free(coff_syms);
raa_free(bsym);
raa_free(symval);
saa_free(coff_strs);
}
int coff_make_section(char *name, uint32_t flags)
{
struct coff_Section *s;
size_t namelen;
s = nasm_zalloc(sizeof(*s));
if (flags != BSS_FLAGS)
s->data = saa_init(1);
s->tail = &s->head;
if (!strcmp(name, ".text"))
s->index = def_seg;
else
s->index = seg_alloc();
s->namepos = -1;
namelen = strlen(name);
if (namelen > 8) {
if (win32 || win64) {
s->namepos = strslen + 4;
saa_wbytes(coff_strs, name, namelen + 1);
strslen += namelen + 1;
} else {
namelen = 8;
}
}
s->name = nasm_malloc(namelen + 1);
strncpy(s->name, name, namelen);
s->name[namelen] = '\0';
s->flags = flags;
if (coff_nsects >= sectlen) {
sectlen += SECT_DELTA;
coff_sects = nasm_realloc(coff_sects, sectlen * sizeof(*coff_sects));
}
coff_sects[coff_nsects++] = s;
return coff_nsects - 1;
}
/*
* Update the name and flags of an existing section
*/
static void coff_update_section(int section, char *name, uint32_t flags)
{
struct coff_Section *s = coff_sects[section];
size_t namelen = strlen(name);
if (namelen > 8) {
if (win32 || win64) {
s->namepos = strslen + 4;
saa_wbytes(coff_strs, name, namelen + 1);
strslen += namelen + 1;
} else {
namelen = 8;
}
}
nasm_free(s->name);
s->name = nasm_malloc(namelen + 1);
strncpy(s->name, name, namelen);
s->name[namelen] = '\0';
s->flags = flags;
}
/*
* Convert an alignment value to the corresponding flags.
* An alignment value of 0 means no flags should be set.
*/
static inline uint32_t coff_sectalign_flags(unsigned int align)
{
return (alignlog2_32(align) + 1) << 20;
}
/*
* Get the default section flags (based on section name)
*/
static uint32_t coff_section_flags(char *name, uint32_t flags)
{
if (!flags) {
flags = TEXT_FLAGS;
if (!strcmp(name, ".data")) {
flags = DATA_FLAGS;
} else if (!strcmp(name, ".rdata")) {
flags = RDATA_FLAGS;
} else if (!strcmp(name, ".bss")) {
flags = BSS_FLAGS;
} else if (win64) {
if (!strcmp(name, ".pdata"))
flags = PDATA_FLAGS;
else if (!strcmp(name, ".xdata"))
flags = XDATA_FLAGS;
}
}
return flags;
}
static int32_t coff_section_names(char *name, int *bits)
{
char *p, *comdat_name;
uint32_t flags, align_flags;
int i, j;
int8_t comdat_selection;
int32_t comdat_associated;
/*
* Set default bits.
*/
if (!name) {
if(win64)
*bits = 64;
else
*bits = 32;
return def_seg;
}
p = name;
while (*p && !nasm_isspace(*p))
p++;
if (*p)
*p++ = '\0';
if (strlen(name) > 8) {
if (!win32 && !win64) {
nasm_warn(WARN_OTHER, "COFF section names limited to 8 characters: truncating");
name[8] = '\0';
}
}
flags = align_flags = comdat_selection = comdat_associated = 0;
comdat_name = NULL;
while (*p && nasm_isspace(*p))
p++;
while (*p) {
char *q = p;
while (*p && !nasm_isspace(*p))
p++;
if (*p)
*p++ = '\0';
while (*p && nasm_isspace(*p))
p++;
if (!nasm_stricmp(q, "code") || !nasm_stricmp(q, "text")) {
flags = TEXT_FLAGS;
} else if (!nasm_stricmp(q, "data")) {
flags = DATA_FLAGS;
} else if (!nasm_stricmp(q, "rdata")) {
if (win32 | win64)
flags = RDATA_FLAGS;
else {
flags = DATA_FLAGS; /* gotta do something */
nasm_nonfatal("standard COFF does not support"
" read-only data sections");
}
} else if (!nasm_stricmp(q, "bss")) {
flags = BSS_FLAGS;
} else if (!nasm_stricmp(q, "info")) {
if (win32 | win64)
flags = INFO_FLAGS;
else {
flags = DATA_FLAGS; /* gotta do something */
nasm_nonfatal("standard COFF does not support"
" informational sections");
}
} else if (!nasm_strnicmp(q, "align=", 6)) {
if (q[6 + strspn(q + 6, "0123456789")])
nasm_nonfatal("argument to `align' is not numeric");
else {
unsigned int align = atoi(q + 6);
/* Allow align=0 meaning use default */
if (!align) {
align_flags = 0;
} else if (!is_power2(align)) {
nasm_nonfatal("argument to `align' is not a"
" power of two");
} else if (align > COFF_MAX_ALIGNMENT) {
nasm_nonfatal("maximum alignment in COFF is %d bytes",
COFF_MAX_ALIGNMENT);
} else {
align_flags = coff_sectalign_flags(align);
}
}
} else if (!nasm_strnicmp(q, "comdat=", 7)) {
/*
* Expected format: comdat=num:name]
* where
* num is a number: one of the IMAGE_COMDAT_SELECT_* constants
* name is a string: the "COMDAT name"
*/
comdat_selection = strtoul(q + 7, &q, 10);
if (!comdat_selection)
nasm_nonfatal("invalid argument to `comdat'");
else if (*q != ':' || q[1] == '\0')
nasm_nonfatal("missing name in `comdat'");
else {
comdat_name = q + 1;
}
}
}
for (i = 0; i < coff_nsects; i++)
if (!strcmp(name, coff_sects[i]->name)) {
if (!comdat_name && !coff_sects[i]->comdat_name)
break;
else if (comdat_name && coff_sects[i]->comdat_name &&
!strcmp(comdat_name, coff_sects[i]->comdat_name)) {
/*
* For COMDAT, it makes sense to have multiple sections with
* the same name (different comdat name though)
*/
if ((coff_sects[i]->comdat_selection == IMAGE_COMDAT_SELECT_ASSOCIATIVE &&
comdat_selection == IMAGE_COMDAT_SELECT_ASSOCIATIVE) ||
(coff_sects[i]->comdat_selection != IMAGE_COMDAT_SELECT_ASSOCIATIVE &&
comdat_selection != IMAGE_COMDAT_SELECT_ASSOCIATIVE)) {
/*
* Let's also allow an associative/other pair with the same name
*/
break;
}
}
}
else if (comdat_name && coff_sects[i]->comdat_name &&
!coff_sects[i]->comdat_selection &&
!strcmp(comdat_name, coff_sects[i]->comdat_name) &&
comdat_selection != IMAGE_COMDAT_SELECT_ASSOCIATIVE) {
/*
* This seems to be a "placeholder section" we've created before
* to be the associate of a previous comdat section.
* We'll just update the name and flags with the real ones now.
*/
flags = coff_section_flags(name, flags);
coff_update_section(i, name, flags | IMAGE_SCN_LNK_COMDAT);
coff_sects[i]->comdat_selection = comdat_selection;
break;
}
if (i == coff_nsects) {
flags = coff_section_flags(name, flags);
if (comdat_name) {
flags |= IMAGE_SCN_LNK_COMDAT;
if (comdat_selection == IMAGE_COMDAT_SELECT_ASSOCIATIVE) {
/*
* Find an existing section with given comdat name
*/
for (j = 0; j < coff_nsects; j++)
if (coff_sects[j]->comdat_name &&
!strcmp(coff_sects[j]->comdat_name, comdat_name))
break;
if (j == coff_nsects) {
/*
* The associated section doesn't exist (yet)
* Even though the specs don't enforce a particular order,
* VS (2019) linker doesn't accept .obj files where the
* target section is a later one (than the one with sel==5)
*
* So let's insert another section now (a placeholder),
* hoping it will be turned into the target section later.
*/
j = coff_make_section(COMDAT_PLACEHOLDER_NAME, TEXT_FLAGS);
coff_sects[j]->comdat_name = nasm_strdup(comdat_name);
}
comdat_associated = j + 1;
}
}
i = coff_make_section(name, flags);
coff_sects[i]->align_flags = align_flags;
if (comdat_name) {
coff_sects[i]->comdat_selection = comdat_selection;
coff_sects[i]->comdat_associated = comdat_associated;
coff_sects[i]->comdat_name = nasm_strdup(comdat_name);
}
} else {
if (flags) {
if (comdat_name)
flags |= IMAGE_SCN_LNK_COMDAT;
/* Warn if non-alignment flags differ */
if (((flags ^ coff_sects[i]->flags) & ~IMAGE_SCN_ALIGN_MASK) &&
coff_sects[i]->pass_last_seen == pass_count()) {
nasm_warn(WARN_OTHER, "section attributes changed on"
" redeclaration of section `%s'", name);
}
}
/*
* Alignment can be increased, but never decreased. However,
* specifying a narrower alignment is permitted and ignored.
*/
if (align_flags > coff_sects[i]->align_flags) {
coff_sects[i]->align_flags = align_flags;
}
if (comdat_name) {
if ((coff_sects[i]->comdat_selection != comdat_selection) &&
coff_sects[i]->pass_last_seen == pass_count()) {
nasm_warn(WARN_OTHER, "comdat selection changed on"
" redeclaration of name `%s'", comdat_name);
}
}
}
coff_sects[i]->pass_last_seen = pass_count();
return coff_sects[i]->index;
}
static void coff_deflabel(char *name, int32_t segment, int64_t offset,
int is_global, char *special)
{
int pos, section;
struct coff_Symbol *sym;
nasm_debug(2, " coff_deflabel: %s, seg=%"PRIx32", off=%"PRIx64", is_global=%d, %s, coff_nsyms=%"PRIu32"\n",
name, segment, offset, is_global, special, coff_nsyms);
if (name[0] == '.' && name[1] == '.' && name[2] != '@') {
if (strcmp(name,WRT_IMAGEBASE) && strcmp(name, WRT_SYMTAB))
nasm_nonfatal("unrecognized special symbol `%s'", name);
return;
}
if (is_global == 3) /* discard special-retry from pass two. */
return;
if (segment == NO_SEG)
section = -1; /* absolute symbol */
else {
int i;
section = 0;
for (i = 0; i < coff_nsects; i++)
if (segment == coff_sects[i]->index) {
section = i + 1;
if (coff_sects[i]->comdat_name && !coff_sects[i]->comdat_symbol) {
/*
* The "comdat symbol" must be the first one in symbol table
* So we'll insert/define it - before defining the other one
*/
coff_sects[i]->comdat_symbol = 1;
if (coff_sects[i]->comdat_selection != IMAGE_COMDAT_SELECT_ASSOCIATIVE &&
0 != strcmp(coff_sects[i]->comdat_name, name)) {
coff_defcomdatname(coff_sects[i]->comdat_name, segment);
}
}
break;
}
}
pos = strslen + 4;
if (strlen(name) > 8) {
size_t nlen = strlen(name)+1;
saa_wbytes(coff_strs, name, nlen);
strslen += nlen;
} else
pos = -1;
sym = saa_wstruct(coff_syms);
sym->strpos = pos;
sym->namlen = strlen(name);
if (pos == -1)
strcpy(sym->name, name);
sym->is_global = !!is_global;
sym->type = 0; /* Default to T_NULL (no type) */
sym->section = section;
if (!sym->section)
sym->is_global = true;
if (is_global == 2)
sym->value = offset;
else
sym->value = (sym->section == 0 ? 0 : offset);
if (special) {
special = nasm_skip_spaces(special);
while (*special) {
const char *wend = nasm_skip_word(special);
size_t wlen = wend - special;
if (wlen == 8 && !nasm_strnicmp(special, "function", 8))
sym->type = 0x20; /* DT_FCN */
else
nasm_nonfatal("unrecognised symbol type `%*.*s' on `%s'",
(int)wlen, (int)wlen, special, name);
special = nasm_skip_spaces(wend);
}
}
/*
* define the references from external-symbol segment numbers
* to these symbol records.
*/
if (sym->section == 0)
bsym = raa_write(bsym, segment, coff_nsyms);
if (segment != NO_SEG)
symval = raa_write(symval, segment, sym->section ? 0 : sym->value);
coff_nsyms++;
}
static int32_t coff_add_reloc(struct coff_Section *sect, int32_t segment,
int16_t type)
{
struct coff_Reloc *r;
r = *sect->tail = nasm_malloc(sizeof(struct coff_Reloc));
sect->tail = &r->next;
r->next = NULL;
r->address = sect->len;
if (segment == NO_SEG) {
r->symbol = 0, r->symbase = ABS_SYMBOL;
} else {
int i;
r->symbase = REAL_SYMBOLS;
for (i = 0; i < coff_nsects; i++) {
if (segment == coff_sects[i]->index) {
r->symbol = i * 2;
r->symbase = SECT_SYMBOLS;
break;
}
}
if (r->symbase == REAL_SYMBOLS)
r->symbol = raa_read(bsym, segment);
}
r->type = type;
sect->nrelocs++;
/*
* Return the fixup for standard COFF common variables.
*/
if (r->symbase == REAL_SYMBOLS && !(win32 | win64))
return raa_read(symval, segment);
return 0;
}
/* Helper for coff_out handling wrt ..symtab addressing. */
static uint32_t coff_out_get_symbol_idx(int32_t tsegment, int64_t offset)
{
/* No better way of doing this? */
int section;
if (tsegment == NO_SEG) {
section = -1;
} else {
for (section = 0; section < coff_nsects; section++)
if (tsegment == coff_sects[section]->index)
break;
section++;
}
/* Do symbol table lookup (unless it is external). */
if (section <= coff_nsects) {
uint32_t n, symidx = UINT32_MAX;
saa_rewind(coff_syms);
for (n = 0; n < coff_nsyms; n++) {
struct coff_Symbol *sym = saa_rstruct(coff_syms);
nasm_try_static_assert(sizeof(sym->value) == sizeof(int32_t));
if (sym->section == section && sym->value == (int32_t)offset) {
if (sym->type & 0x30) /* N_TMASK */
return n; /* Prefer symbol with non-NULL type. */
if (symidx == UINT32_MAX)
symidx = n;
}
}
if (symidx == UINT32_MAX)
nasm_nonfatal("wrt ..symtab: Unable to find symbol for %s:%#" PRIx64,
section > 0 ? coff_sects[section - 1]->name : "ABS", offset);
return symidx;
}
/* External symbols. */
if (tsegment != NO_SEG)
return raa_read(bsym, tsegment);
nasm_nonfatal("wrt ..symtab: Unable to find symbol for %#"PRIx32":%#" PRIx64,
tsegment, offset);
return UINT32_MAX;
}
static void coff_out(const struct out_data *out)
{
OUT_LEGACY(out,segto,data,type,size,segment,wrt);
struct coff_Section *s;
uint8_t mydata[8], *p;
int i;
if (wrt != NO_SEG && wrt != symtab_sect && !win64) {
wrt = NO_SEG; /* continue to do _something_ */
nasm_nonfatal("WRT not supported by COFF output formats");
}
s = NULL;
for (i = 0; i < coff_nsects; i++) {
if (segto == coff_sects[i]->index) {
s = coff_sects[i];
break;
}
}
if (!s) {
int tempint; /* ignored */
if (segto != coff_section_names(".text", &tempint))
nasm_panic("strange segment conditions in COFF driver");
else
s = coff_sects[coff_nsects - 1];
}
/* magically default to 'wrt ..imagebase' in .pdata and .xdata */
if (win64 && wrt == NO_SEG) {
if (!strcmp(s->name,".pdata") || !strcmp(s->name,".xdata"))
wrt = imagebase_sect;
}
if (!s->data && type != OUT_RESERVE) {
nasm_warn(WARN_OTHER, "attempt to initialize memory in"
" BSS section `%s': ignored", s->name);
s->len += realsize(type, size);
return;
}
memset(mydata, 0, sizeof(mydata));
if (dfmt && dfmt->debug_output) {
struct coff_DebugInfo dinfo;
dinfo.segto = segto;
dinfo.seg = segment;
dinfo.section = s;
if (type == OUT_ADDRESS)
dinfo.size = abs((int)size);
else
dinfo.size = realsize(type, size);
dfmt->debug_output(type, &dinfo);
}
if (type == OUT_RESERVE) {
if (s->data) {
nasm_warn(WARN_ZEROING, "uninitialised space declared in"
" non-BSS section `%s': zeroing", s->name);
coff_sect_write(s, NULL, size);
} else
s->len += size;
} else if (type == OUT_RAWDATA) {
coff_sect_write(s, data, size);
} else if (type == OUT_ADDRESS) {
int asize = abs((int)size);
if (wrt == symtab_sect) {
/* Emit internal fixup since the table starts with sections and
stuff that can be added to after this statement. */
struct coff_SymIdxReloc *ir;
nasm_new(ir);
ir->offset = s->len;
ir->size = (uint8_t)asize;
ir->symbol = coff_out_get_symbol_idx(segment, out->toffset);
ir->next = s->symidx_reloc_head;
s->symidx_reloc_head = ir;
nasm_assert((unsigned)asize <= sizeof(mydata));
coff_sect_write(s, mydata, asize);
if (asize > 4)
nasm_warn(WARN_OTHER, "zero extending 'wrt "WRT_SYMTAB
"' (32-bit) to %u-bit", asize * 8);
else if (asize < 4)
nasm_warn(WARN_OTHER, "truncating 'wrt "WRT_SYMTAB
"' (32-bit) to %u-bit", asize * 8);
} else if (!win64) {
if (asize != 4 && (segment != NO_SEG || wrt != NO_SEG)) {
nasm_nonfatal("COFF format does not support non-32-bit"
" relocations");
} else {
int32_t fix = 0;
if (segment != NO_SEG || wrt != NO_SEG) {
if (wrt != NO_SEG) {
nasm_nonfatal("COFF format does not support WRT types");
} else if (segment % 2) {
nasm_nonfatal("COFF format does not support"
" segment base references");
} else
fix = coff_add_reloc(s, segment, IMAGE_REL_I386_DIR32);
}
p = mydata;
WRITELONG(p, *(int64_t *)data + fix);
coff_sect_write(s, mydata, asize);
}
} else {
int32_t fix = 0;
p = mydata;
if (asize == 8) {
if (wrt == imagebase_sect) {
nasm_nonfatal("operand size mismatch: 'wrt "
WRT_IMAGEBASE "' is a 32-bit operand");
}
fix = coff_add_reloc(s, segment, IMAGE_REL_AMD64_ADDR64);
WRITEDLONG(p, *(int64_t *)data + fix);
coff_sect_write(s, mydata, asize);
} else {
fix = coff_add_reloc(s, segment,
wrt == imagebase_sect ? IMAGE_REL_AMD64_ADDR32NB:
IMAGE_REL_AMD64_ADDR32);
WRITELONG(p, *(int64_t *)data + fix);
coff_sect_write(s, mydata, asize);
}
}
} else if (type == OUT_REL2ADR) {
nasm_nonfatal("COFF format does not support 16-bit relocations");
} else if (type == OUT_REL4ADR) {
if (segment == segto && !(win64)) /* Acceptable for RIP-relative */
nasm_panic("intra-segment OUT_REL4ADR");
else if (segment == NO_SEG && win32)
nasm_nonfatal("Win32 COFF does not correctly support"
" relative references to absolute addresses");
else {
int32_t fix = 0;
if (segment != NO_SEG && segment % 2) {
nasm_nonfatal("COFF format does not support"
" segment base references");
} else
fix = coff_add_reloc(s, segment,
win64 ? IMAGE_REL_AMD64_REL32 : IMAGE_REL_I386_REL32);
p = mydata;
if (win32 | win64) {
WRITELONG(p, *(int64_t *)data + 4 - size + fix);
} else {
WRITELONG(p, *(int64_t *)data - (size + s->len) + fix);
}
coff_sect_write(s, mydata, 4L);
}
}
}
static void coff_sect_write(struct coff_Section *sect,
const uint8_t *data, uint32_t len)
{
saa_wbytes(sect->data, data, len);
sect->len += len;
}
typedef struct tagString {
struct tagString *next;
int len;
char *String;
} STRING;
#define EXPORT_SECTION_NAME ".drectve"
#define EXPORT_SECTION_FLAGS INFO_FLAGS
/*
* #define EXPORT_SECTION_NAME ".text"
* #define EXPORT_SECTION_FLAGS TEXT_FLAGS
*/
static STRING *Exports = NULL;
static struct coff_Section *directive_sec;
static void AddExport(char *name)
{
STRING *rvp = Exports, *newS;
newS = (STRING *) nasm_malloc(sizeof(STRING));
newS->len = strlen(name);
newS->next = NULL;
newS->String = (char *)nasm_malloc(newS->len + 1);
strcpy(newS->String, name);
if (rvp == NULL) {
int i;
for (i = 0; i < coff_nsects; i++) {
if (!strcmp(EXPORT_SECTION_NAME, coff_sects[i]->name))
break;
}
if (i == coff_nsects)
i = coff_make_section(EXPORT_SECTION_NAME, EXPORT_SECTION_FLAGS);
directive_sec = coff_sects[i];
Exports = newS;
} else {
while (rvp->next) {
if (!strcmp(rvp->String, name))
return;
rvp = rvp->next;
}
rvp->next = newS;
}
}
static void BuildExportTable(STRING **rvp)
{
STRING *p, *t;
if (!rvp || !*rvp)
return;
list_for_each_safe(p, t, *rvp) {
coff_sect_write(directive_sec, (uint8_t *)"-export:", 8);
coff_sect_write(directive_sec, (uint8_t *)p->String, p->len);
coff_sect_write(directive_sec, (uint8_t *)" ", 1);
nasm_free(p->String);
nasm_free(p);
}
*rvp = NULL;
}
static void coff_defcomdatname(char *name, int32_t segment)
{
coff_deflabel(name, segment, 0, 1, NULL);
}
static enum directive_result
coff_directives(enum directive directive, char *value)
{
switch (directive) {
case D_EXPORT:
{
char *q, *name;
/*
* XXX: pass_first() is really wrong here, but AddExport()
* needs to be modified to handle duplicate calls for the
* same value in order to change that. The right thing to do
* is probably to mark a label as an export in the label
* structure, in case the label doesn't actually exist.
*/
if (!value || !pass_first())
return DIRR_OK; /* ignore in pass two */
name = q = value;
while (*q && !nasm_isspace(*q))
q++;
if (nasm_isspace(*q)) {
*q++ = '\0';
while (*q && nasm_isspace(*q))
q++;
}
if (!*name) {
nasm_nonfatal("`export' directive requires export name");
return DIRR_ERROR;
}
if (*q) {
nasm_nonfatal("unrecognized export qualifier `%s'", q);
return DIRR_ERROR;
}
AddExport(name);
return DIRR_OK;
}
case D_SAFESEH:
{
static int sxseg=-1;
int i;
int32_t ignseg;
int64_t ignoff;
if (!win32) /* Only applicable for -f win32 */
return DIRR_UNKNOWN;
if (!value)
return DIRR_OK;
lookup_label(value, &ignseg, &ignoff); /* prevent extern elimination */
if (sxseg == -1) {
for (i = 0; i < coff_nsects; i++)
if (!strcmp(".sxdata",coff_sects[i]->name))
break;
if (i == coff_nsects)
sxseg = coff_make_section(".sxdata", IMAGE_SCN_LNK_INFO);
else
sxseg = i;
}
/*
* pass_final() is the only time when the full set of symbols are
* guaranteed to be present as it is the final output pass.
*/
if (pass_final()) {
uint32_t n;
saa_rewind(coff_syms);
for (n = 0; n < coff_nsyms; n++) {
struct coff_Symbol *sym = saa_rstruct(coff_syms);
bool equals;
/*
* sym->strpos is biased by 4, because symbol
* table is prefixed with table length
*/
if (sym->strpos >=4) {
char *name = nasm_malloc(sym->namlen+1);
saa_fread(coff_strs, sym->strpos-4, name, sym->namlen);
name[sym->namlen] = '\0';
equals = !strcmp(value,name);
nasm_free(name);
} else {
equals = !strcmp(value,sym->name);
}
if (equals) {
/*
* this value arithmetic effectively reflects
* initsym in coff_write(): 2 for file, 1 for
* .absolute and two per each section
*/
unsigned char value[4],*p=value;
WRITELONG(p,n + 2 + 1 + coff_nsects*2);
coff_sect_write(coff_sects[sxseg],value,4);
sym->type = 0x20;
break;
}
}
if (n == coff_nsyms) {
nasm_nonfatal("`safeseh' directive requires valid symbol");
return DIRR_ERROR;
}
}
return DIRR_OK;
}
default:
return DIRR_UNKNOWN;
}
}
/* handle relocations storm, valid for win32/64 only */
static inline void coff_adjust_relocs(struct coff_Section *s)
{
if (s->nrelocs < IMAGE_SCN_MAX_RELOC)
return;
#ifdef OF_COFF
else
{
if (ofmt == &of_coff)
nasm_fatal("Too many relocations (%d) for section `%s'",
s->nrelocs, s->name);
}
#endif
s->flags |= IMAGE_SCN_LNK_NRELOC_OVFL;
s->nrelocs++;
}
/*
* Make sure we satisfy all section alignment requirements and put the
* resulting alignment flags into the flags value in the header. If
* no user-specified alignment is given, use the default for the
* section type; then either way round up to alignment specified by
* sectalign directives.
*/
static inline void coff_adjust_alignment(struct coff_Section *s)
{
uint32_t align_flags = s->align_flags;
if (!align_flags) {
/* No user-specified alignment, use default for partition type */
align_flags = s->flags & IMAGE_SCN_ALIGN_MASK;
}
if (align_flags < s->sectalign_flags)
align_flags = s->sectalign_flags;
s->flags = (s->flags & ~IMAGE_SCN_ALIGN_MASK) | align_flags;
}
static void coff_write(void)
{
int32_t pos, sympos, vsize;
int i;
/* fill in the .drectve section with -export's */
BuildExportTable(&Exports);
if (win32) {
/* add default value for @feat.00, this allows to 'link /safeseh' */
uint32_t n;
saa_rewind(coff_syms);
for (n = 0; n < coff_nsyms; n++) {
struct coff_Symbol *sym = saa_rstruct(coff_syms);
if (sym->strpos == -1 && !strcmp("@feat.00",sym->name))
break;
}
if (n == coff_nsyms)
coff_deflabel("@feat.00", NO_SEG, 1, 0, NULL);
}
/*
* Check all comdat sections
*/
for (i = 0; i < coff_nsects; i++)
if (coff_sects[i]->comdat_name) {
if (!coff_sects[i]->comdat_symbol &&
coff_sects[i]->comdat_selection != IMAGE_COMDAT_SELECT_ASSOCIATIVE) {
/*
* This section doesn't have its comdat symbol defined; do it
*/
coff_defcomdatname(coff_sects[i]->comdat_name, coff_sects[i]->index);
}
if (!coff_sects[i]->comdat_selection) {
/*
* This is a placeholder section that wasn't properly defined
*/
nasm_nonfatal("`comdat' associate with symbol `%s` wasn't defined",
coff_sects[i]->comdat_name);
}
}
/*
* Work out how big the file will get.
* Calculate the start of the `real' symbols at the same time.
* Check for massive relocations.
*/
pos = 0x14 + 0x28 * coff_nsects;
initsym = 3; /* two for the file, one absolute */
for (i = 0; i < coff_nsects; i++) {
coff_adjust_alignment(coff_sects[i]);
if (coff_sects[i]->data) {
coff_adjust_relocs(coff_sects[i]);
coff_sects[i]->pos = pos;
pos += coff_sects[i]->len;
coff_sects[i]->relpos = pos;
pos += 10 * coff_sects[i]->nrelocs;
} else
coff_sects[i]->pos = coff_sects[i]->relpos = 0L;
initsym += 2; /* two for each section */
}
sympos = pos;
/*
* Output the COFF header.
*/
if (win64)
i = IMAGE_FILE_MACHINE_AMD64;
else
i = IMAGE_FILE_MACHINE_I386;
fwriteint16_t(i, ofile); /* machine type */
fwriteint16_t(coff_nsects, ofile); /* number of sections */
fwriteint32_t(posix_timestamp(), ofile); /* timestamp */
fwriteint32_t(sympos, ofile);
fwriteint32_t(coff_nsyms + initsym, ofile);
fwriteint16_t(0, ofile); /* no optional header */
/* Flags: 32-bit, no line numbers. Win32 doesn't even bother with them. */
fwriteint16_t((win32 | win64) ? 0 : 0x104, ofile);
/*
* Output the section headers.
*/
vsize = 0L;
for (i = 0; i < coff_nsects; i++) {
coff_section_header(coff_sects[i]->name, coff_sects[i]->namepos, vsize, coff_sects[i]->len,
coff_sects[i]->pos, coff_sects[i]->relpos,
coff_sects[i]->nrelocs, coff_sects[i]->flags);
vsize += coff_sects[i]->len;
}
/*
* Output the sections and their relocations.
*/
for (i = 0; i < coff_nsects; i++)
if (coff_sects[i]->data) {
/* Apply symbol table index fixups before writing */
struct coff_SymIdxReloc *ir;
for (ir = coff_sects[i]->symidx_reloc_head; ir; ir = ir->next) {
uint8_t mydata[4];
setu32(mydata, ir->symbol + initsym);
saa_fwrite(coff_sects[i]->data, ir->offset,
mydata, ir->size >= 4 ? 4 : ir->size);
}
saa_fpwrite(coff_sects[i]->data, ofile);
coff_write_relocs(coff_sects[i]);
if (coff_sects[i]->flags & IMAGE_SCN_LNK_COMDAT) {
/*
* Checksum the section data
*/
uint32_t checksum = 0;
const char *data;
size_t len;
saa_rewind(coff_sects[i]->data);
while (len = coff_sects[i]->data->datalen,
(data = saa_rbytes(coff_sects[i]->data, &len)) != NULL)
checksum = crc32b(checksum, data, len);
coff_sects[i]->checksum = checksum;
}
}
/*
* Output the symbol and string tables.
*/
coff_write_symbols();
fwriteint32_t(strslen + 4, ofile); /* length includes length count */
saa_fpwrite(coff_strs, ofile);
}
static void coff_section_header(char *name, int32_t namepos, int32_t vsize,
int32_t datalen, int32_t datapos,
int32_t relpos, int nrelocs, int32_t flags)
{
char padname[8];
(void)vsize;
if (namepos == -1) {
strncpy(padname, name, 8);
nasm_write(padname, 8, ofile);
} else {
/*
* If name is longer than 8 bytes, write '/' followed
* by offset into the strings table represented as
* decimal number.
*/
namepos = namepos % 100000000;
padname[0] = '/';
padname[1] = '0' + (namepos / 1000000);
namepos = namepos % 1000000;
padname[2] = '0' + (namepos / 100000);
namepos = namepos % 100000;
padname[3] = '0' + (namepos / 10000);
namepos = namepos % 10000;
padname[4] = '0' + (namepos / 1000);
namepos = namepos % 1000;
padname[5] = '0' + (namepos / 100);
namepos = namepos % 100;
padname[6] = '0' + (namepos / 10);
namepos = namepos % 10;
padname[7] = '0' + (namepos);
nasm_write(padname, 8, ofile);
}
fwriteint32_t(0, ofile); /* Virtual size field - set to 0 or vsize */
fwriteint32_t(0L, ofile); /* RVA/offset - we ignore */
fwriteint32_t(datalen, ofile);
fwriteint32_t(datapos, ofile);
fwriteint32_t(relpos, ofile);
fwriteint32_t(0L, ofile); /* no line numbers - we don't do 'em */
/*
* a special case -- if there are too many relocs
* we have to put IMAGE_SCN_MAX_RELOC here and write
* the real relocs number into VirtualAddress of first
* relocation
*/
if (flags & IMAGE_SCN_LNK_NRELOC_OVFL)
fwriteint16_t(IMAGE_SCN_MAX_RELOC, ofile);
else
fwriteint16_t(nrelocs, ofile);
fwriteint16_t(0, ofile); /* again, no line numbers */
fwriteint32_t(flags, ofile);
}
static void coff_write_relocs(struct coff_Section *s)
{
struct coff_Reloc *r;
/* a real number of relocations if needed */
if (s->flags & IMAGE_SCN_LNK_NRELOC_OVFL) {
fwriteint32_t(s->nrelocs, ofile);
fwriteint32_t(0, ofile);
fwriteint16_t(0, ofile);
}
for (r = s->head; r; r = r->next) {
fwriteint32_t(r->address, ofile);
fwriteint32_t(r->symbol + (r->symbase == REAL_SYMBOLS ? initsym :
r->symbase == ABS_SYMBOL ? initsym - 1 :
r->symbase == SECT_SYMBOLS ? 2 : 0),
ofile);
fwriteint16_t(r->type, ofile);
}
}
static void coff_symbol(char *name, int32_t strpos, int32_t value,
int section, int type, int storageclass, int aux)
{
char padname[8];
if (name) {
strncpy(padname, name, 8);
nasm_write(padname, 8, ofile);
} else {
fwriteint32_t(0, ofile);
fwriteint32_t(strpos, ofile);
}
fwriteint32_t(value, ofile);
fwriteint16_t(section, ofile);
fwriteint16_t(type, ofile);
fputc(storageclass, ofile);
fputc(aux, ofile);
}
static void coff_write_symbols(void)
{
char filename[18];
uint32_t i;
/*
* The `.file' record, and the file name auxiliary record.
*/
coff_symbol(".file", 0L, 0L, -2, 0, 0x67, 1);
if (reproducible)
memset(filename, 0, 18);
else
strncpy(filename, get_filename(FN_INFILE), 18);
nasm_write(filename, 18, ofile);
/*
* The section records, with their auxiliaries.
*/
memset(filename, 0, 18); /* useful zeroed buffer */
for (i = 0; i < (uint32_t) coff_nsects; i++) {
coff_symbol(coff_sects[i]->name, 0L, 0L, i + 1, 0, 3, 1);
fwriteint32_t(coff_sects[i]->len, ofile);
fwriteint16_t(coff_sects[i]->nrelocs,ofile);
if (coff_sects[i]->flags & IMAGE_SCN_LNK_COMDAT) {
fwriteint16_t(0, ofile);
fwriteint32_t(coff_sects[i]->checksum, ofile);
fwriteint16_t(coff_sects[i]->comdat_associated, ofile);
fputc(coff_sects[i]->comdat_selection, ofile);
nasm_write(filename, 3, ofile);
}
else
nasm_write(filename, 12, ofile);
}
/*
* The absolute symbol, for relative-to-absolute relocations.
*/
coff_symbol(".absolut", 0L, 0L, -1, 0, 3, 0);
/*
* The real symbols.
*/
saa_rewind(coff_syms);
for (i = 0; i < coff_nsyms; i++) {
struct coff_Symbol *sym = saa_rstruct(coff_syms);
coff_symbol(sym->strpos == -1 ? sym->name : NULL,
sym->strpos, sym->value, sym->section,
sym->type, sym->is_global ? 2 : 3, 0);
}
}
static void coff_sectalign(int32_t seg, unsigned int value)
{
struct coff_Section *s = NULL;
uint32_t flags;
int i;
for (i = 0; i < coff_nsects; i++) {
if (coff_sects[i]->index == seg) {
s = coff_sects[i];
break;
}
}
if (!s || !is_power2(value))
return;
if (value > COFF_MAX_ALIGNMENT)
value = COFF_MAX_ALIGNMENT; /* Do our best... */
flags = coff_sectalign_flags(value);
if (flags > s->sectalign_flags)
s->sectalign_flags = flags;
}
extern macros_t coff_stdmac[];
#endif /* defined(OF_COFF) || defined(OF_WIN32) */
#ifdef OF_COFF
static const struct pragma_facility coff_pragma_list[] = {
{ "coff", NULL },
{ NULL, NULL }
};
const struct ofmt of_coff = {
"COFF (i386) (DJGPP, some Unix variants)",
"coff",
".o",
0,
32,
null_debug_arr,
&null_debug_form,
coff_stdmac,
coff_std_init,
null_reset,
coff_out,
coff_deflabel,
coff_section_names,
NULL,
coff_sectalign,
null_segbase,
coff_directives,
coff_cleanup,
coff_pragma_list
};
#endif
#ifdef OF_WIN32
static const struct pragma_facility coff_win_pragma_list[] = {
{ "win", NULL },
{ "coff", NULL },
{ NULL, NULL }
};
extern const struct dfmt df_cv8;
static const struct dfmt * const win32_debug_arr[2] = { &df_cv8, NULL };
const struct ofmt of_win32 = {
"Microsoft extended COFF for Win32 (i386)",
"win32",
".obj",
0,
32,
win32_debug_arr,
&df_cv8,
coff_stdmac,
coff_win32_init,
null_reset,
coff_out,
coff_deflabel,
coff_section_names,
NULL,
coff_sectalign,
null_segbase,
coff_directives,
coff_cleanup,
coff_win_pragma_list
};
#endif
#ifdef OF_WIN64
static const struct dfmt * const win64_debug_arr[2] = { &df_cv8, NULL };
const struct ofmt of_win64 = {
"Microsoft extended COFF for Win64 (x86-64)",
"win64",
".obj",
0,
64,
win64_debug_arr,
&df_cv8,
coff_stdmac,
coff_win64_init,
null_reset,
coff_out,
coff_deflabel,
coff_section_names,
NULL,
coff_sectalign,
null_segbase,
coff_directives,
coff_cleanup,
coff_win_pragma_list
};
#endif