Properly keep track of the base of relative relocations

For expressions like [foo - $] or [bar - $$] our relocation base is
not the same as the end of the instruction.  Make that explicit.

Signed-off-by: H. Peter Anvin <hpa@zytor.com>
This commit is contained in:
H. Peter Anvin 2017-02-21 11:30:22 -08:00
parent ed71316e2b
commit 8930a8fc15
3 changed files with 68 additions and 36 deletions

View file

@ -307,6 +307,29 @@ static void warn_overflow_opd(const struct operand *o, int size)
}
}
static void warn_overflow_out(int64_t data, int size, enum out_sign sign)
{
bool err;
switch (sign) {
case OUT_WRAP:
err = overflow_general(data, size);
break;
case OUT_SIGNED:
err = overflow_signed(data, size);
break;
case OUT_UNSIGNED:
err = overflow_unsigned(data, size);
break;
default:
panic();
break;
}
if (err)
warn_overflow(ERR_PASS2, size);
}
/*
* This routine wrappers the real output format's output routine,
* in order to pass a copy of the data off to the listing file
@ -324,6 +347,7 @@ static void out(struct out_data *data)
uint64_t q;
} xdata;
uint64_t size = data->size;
int64_t addrval;
if (!data->size)
return; /* Nothing to do */
@ -334,31 +358,22 @@ static void out(struct out_data *data)
*/
switch (data->type) {
case OUT_ADDRESS:
addrval = data->toffset;
goto address;
case OUT_RELADDR:
addrval = data->toffset - data->relbase;
goto address;
address:
asize = data->size;
nasm_assert(asize <= 8);
if (data->tsegment == NO_SEG && data->twrt == NO_SEG) {
/* XXX: check for overflow */
uint8_t *q = xdata.b;
WRITEADDR(q, data->toffset, asize);
data->data = xdata.b;
data->type = OUT_RAWDATA;
asize = 0; /* No longer an address */
}
break;
warn_overflow_out(addrval, asize, data->sign);
case OUT_RELADDR:
asize = data->size;
nasm_assert(asize <= 8);
if (data->tsegment == data->segment && data->twrt == NO_SEG) {
uint8_t *q = xdata.b;
int64_t delta = data->toffset - data->offset
- (data->inslen - data->insoffs);
if (overflow_signed(delta, asize))
warn_overflow(ERR_PASS2, asize);
WRITEADDR(q, delta, asize);
WRITEADDR(q, addrval, asize);
data->data = xdata.b;
data->type = OUT_RAWDATA;
asize = 0; /* No longer an address */
@ -442,6 +457,14 @@ static inline void out_imm(struct out_data *data, const struct operand *opx,
data->toffset = opx->offset;
data->tsegment = opx->segment;
data->twrt = opx->wrt;
/*
* XXX: improve this if at some point in the future we can
* distinguish the subtrahend in expressions like [foo - bar]
* where bar is a symbol in the current segment. However, at the
* current point, if OPFLAG_RELATIVE is set that subtraction has
* already occurred.
*/
data->relbase = 0;
out(data);
}
@ -457,6 +480,7 @@ static void out_reladdr(struct out_data *data, const struct operand *opx,
data->toffset = opx->offset;
data->tsegment = opx->segment;
data->twrt = opx->wrt;
data->relbase = data->offset + (data->inslen - data->insoffs);
out(data);
}
@ -523,6 +547,7 @@ int64_t assemble(int32_t segment, int64_t start, int bits, iflag_t cp,
cpu = cp;
nasm_zero(&data);
data.offset = start;
data.segment = segment;
data.itemp = NULL;
@ -553,6 +578,7 @@ int64_t assemble(int32_t segment, int64_t start, int bits, iflag_t cp,
data.toffset = e->offset;
data.tsegment = e->segment;
data.twrt = e->wrt;
data.relbase = 0;
out(&data);
}
} else if (e->type == EOT_DB_STRING ||

View file

@ -84,29 +84,20 @@ struct ofmt;
/*
* Values for the `type' parameter to an output function.
*
* Exceptions are OUT_RELxADR, which denote an x-byte relocation
* which will be a relative jump. For this we need to know the
* distance in bytes from the start of the relocated record until
* the end of the containing instruction. _This_ is what is stored
* in the size part of the parameter, in this case.
*
* Also OUT_RESERVE denotes reservation of N bytes of BSS space,
* and the contents of the "data" parameter is irrelevant.
*
* The "data" parameter for the output function points to a "int32_t",
* containing the address in question, unless the type is
* OUT_RAWDATA, in which case it points to an "uint8_t"
* array.
*/
enum out_type {
OUT_RAWDATA, /* Plain bytes */
OUT_RESERVE, /* Reserved bytes (RESB et al) */
OUT_ADDRESS, /* An address (symbol value) */
OUT_RELADDR, /* A relative address (relative to instruction end) */
OUT_RELADDR, /* A relative address */
OUT_SEGMENT, /* A segment number */
/* These are temporary until the backend change */
/*
* These values are used by the legacy backend interface only;
* see output/legacy.c for more information. These should never
* be used otherwise. Once all backends have been migrated to the
* new interface they should be removed.
*/
OUT_REL1ADR,
OUT_REL2ADR,
OUT_REL4ADR,
@ -138,6 +129,7 @@ struct out_data {
uint64_t toffset; /* Target address offset for relocation */
int32_t tsegment; /* Target segment for relocation */
int32_t twrt; /* Relocation with respect to */
int64_t relbase; /* Relative base for OUT_RELADDR */
};
/*

View file

@ -36,6 +36,20 @@
*
* Mangle a struct out_data to match the rather bizarre legacy
* backend interface.
*
* The "data" parameter for the output function points to a "int64_t",
* containing the address of the target in question, unless the type is
* OUT_RAWDATA, in which case it points to an "uint8_t"
* array.
*
* Exceptions are OUT_RELxADR, which denote an x-byte relocation
* which will be a relative jump. For this we need to know the
* distance in bytes from the start of the relocated record until
* the end of the containing instruction. _This_ is what is stored
* in the size part of the parameter, in this case.
*
* Also OUT_RESERVE denotes reservation of N bytes of BSS space,
* and the contents of the "data" parameter is irrelevant.
*/
#include "nasm.h"
@ -70,7 +84,7 @@ void nasm_do_legacy_output(const struct out_data *data)
}
dptr = &data->toffset;
size = data->inslen - data->insoffs;
size = data->relbase - data->offset;
break;
case OUT_SEGMENT: