/* outformat_common.c -- Common functions for ELF/PE output Copyright © 2023-2024 Samuel Lidén Borell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #undef NDEBUG /* to be safe. uncomment at your own risk! */ #include #include #include "outformat_common.h" void binwriter_init(struct BinWriter *bw, const struct ArchInfo *arch, unsigned char *buffer, size_t max_size) { bw->buffer = buffer; bw->size = max_size; bw->remaining = max_size; bw->section_number = 1; bw->little_endian = !arch->big_endian; bw->addr_size = arch->wordbits; bw->off_size = arch->wordbits; } size_t binwriter_get_position(const struct BinWriter *bw) { return bw->size - bw->remaining; } void binwriter_reset(struct BinWriter *bw) { bw->buffer -= binwriter_get_position(bw); bw->remaining = bw->size; } void binwriter_seekto(struct BinWriter *bw, size_t offs) { binwriter_reset(bw); binwriter_skip(bw, offs); } void binwriter_skip(struct BinWriter *bw, size_t amount) { bw->buffer += amount; bw->remaining -= amount; } void outbytes(struct BinWriter *bw, const unsigned char *bytes, size_t len) { assert(bytes != NULL); assert(len > 0); if (bw->buffer) { assert(len <= bw->remaining); memcpy(bw->buffer, bytes, len); bw->buffer += len; } bw->remaining -= len; } void outnulls(struct BinWriter *bw, unsigned len) { if (bw->buffer) { assert(len <= bw->remaining); memset(bw->buffer, 0, len); bw->buffer += len; } bw->remaining -= len; } void out8(struct BinWriter *bw, uint8 value) { outbytes(bw, &value, 1); } void out16(struct BinWriter *bw, uint16 value) { unsigned char bytes[2]; if (bw->little_endian) { bytes[0] = value & 0xFF; bytes[1] = value >> 8; } else { bytes[0] = value >> 8; bytes[1] = value & 0xFF; } outbytes(bw, bytes, sizeof(bytes)); } void out32(struct BinWriter *bw, uint32 value) { unsigned char bytes[4]; if (bw->little_endian) { bytes[0] = value & 0xFF; bytes[1] = (value >> 8) & 0xFF; bytes[2] = (value >> 16) & 0xFF; bytes[3] = value >> 24; } else { bytes[0] = value >> 24; bytes[1] = (value >> 16) & 0xFF; bytes[2] = (value >> 8) & 0xFF; bytes[3] = value & 0xFF; } outbytes(bw, bytes, sizeof(bytes)); } void out32_or(struct BinWriter *bw, uint32 value) { if (bw->buffer) { unsigned char *bytes = bw->buffer; assert(bw->remaining >= 4); if (bw->little_endian) { bytes[0] |= value & 0xFF; bytes[1] |= (value >> 8) & 0xFF; bytes[2] |= (value >> 16) & 0xFF; bytes[3] |= value >> 24; } else { bytes[0] |= value >> 24; bytes[1] |= (value >> 16) & 0xFF; bytes[2] |= (value >> 8) & 0xFF; bytes[3] |= value & 0xFF; } bw->buffer += 4; } bw->remaining -= 4; } void out64(struct BinWriter *bw, uint64 value) { unsigned char bytes[8]; if (bw->little_endian) { bytes[0] = value & 0xFF; bytes[1] = (value >> 8) & 0xFF; bytes[2] = (value >> 16) & 0xFF; bytes[3] = (value >> 24) & 0xFF; bytes[4] = (value >> 32) & 0xFF; bytes[5] = (value >> 40) & 0xFF; bytes[6] = (value >> 48) & 0xFF; bytes[7] = value >> 56; } else { bytes[0] = value >> 56; bytes[1] = (value >> 48) & 0xFF; bytes[2] = (value >> 40) & 0xFF; bytes[3] = (value >> 32) & 0xFF; bytes[4] = (value >> 24) & 0xFF; bytes[5] = (value >> 16) & 0xFF; bytes[6] = (value >> 8) & 0xFF; bytes[7] = value & 0xFF; } outbytes(bw, bytes, sizeof(bytes)); } void outaddr(struct BinWriter *bw, uint64 value) { if (bw->addr_size == 32) out32(bw, value); else out64(bw, value); } void outoff(struct BinWriter *bw, uint64 value) { if (bw->off_size == 32) out32(bw, value); else out64(bw, value); } void outalign(struct BinWriter *bw, unsigned alignment) { size_t offset; unsigned len; assert(alignment >= 1); assert(alignment <= 16384); /* if not, it's probably an overflow */ offset = bw->size - bw->remaining; len = UINT_ALIGN(offset, alignment) - offset; outnulls(bw, len); } void out_nullstring(struct BinWriter *bw, size_t *offs_out, const char *s, size_t len) { *offs_out = binwriter_get_position(bw); outbytes(bw, (const unsigned char *)s, len); out8(bw, 0); } void out_machinecode(struct BinWriter *bw, const struct CgCode *code) { struct CgCodeChunk *chunk; for (chunk = code->chunks; chunk; chunk = chunk->next) { outbytes(bw, chunk->code, chunk->size); } } struct DatadefOutState { unsigned num_valueslots; const struct DatadefValueSlot *valueslots; }; static void out_scalar(struct BinWriter *bw, CsbeUint64 x, unsigned size) { if (size == 1) out8(bw, x); else if (size == 2) out16(bw, x); else if (size == 4) out32(bw, x); else if (size == 8) out64(bw, x); else assert(0); } static void out_datadef_by_type(struct BinWriter *bw, const struct CgCtx *cg, const struct CsbeType *type, struct DatadefOutState *ddstate) { int ok; unsigned size, align; size_t startpos; /* XXX For nested structs, this will compute the size/alignment for each field once for every nesting level. That could be slow */ ok = cg_get_type_size(cg, type, &size, &align); assert(ok); outalign(bw, align); startpos = binwriter_get_position(bw); if (type->is_struct) { unsigned arraylen = type->is_array ? type->array_length : 1; int is_union = (type->packmode == CSBEPM_UNION); while (arraylen--) { const struct CsbeType *fieldtype = type->elemtypes; unsigned remaining = type->num_fields; while (remaining--) { out_datadef_by_type(bw, cg, fieldtype, ddstate); if (is_union) break; fieldtype++; } outalign(bw, align); } } else { const struct DatadefValueSlot *slot; assert(ddstate->num_valueslots >= 1); ddstate->num_valueslots--; slot = ddstate->valueslots++; if (type->is_array) { enum CsbeTypeKind tk = type->simple_kind; unsigned len = slot->array_length; if (tk == CSBET_BOOL || tk == CSBET_I8 || tk == CSBET_U8) { assert(slot->slot_type == CSBEST_BYTES); if (len > 0) { outbytes(bw, slot->u.bytes, len); } } else { const CsbeUint64 *arr = slot->u.uint64_array; unsigned elemsize = cg->arch->types_sizes[tk]; assert(tk < CSBET_DPTR); assert(slot->slot_type == CSBEST_UINT64_ARRAY); for (; len--; arr++) { out_scalar(bw, *arr, elemsize); } } } else { assert(slot->slot_type == CSBEST_UINT64); out_scalar(bw, slot->u.uint64_value, size); } } assert(binwriter_get_position(bw) - startpos == size); } /** Outputs a single data definition to the data section */ static void out_datadef(struct BinWriter *bw, const struct SymInfo *sym, const struct CgCtx *cg) { const struct CsbeDatadef *datadef = sym->datadef; struct DatadefOutState ddstate; if (datadef->valueslots != NULL) { assert(datadef->num_valueslots >= 1); ddstate.num_valueslots = datadef->num_valueslots; ddstate.valueslots = datadef->valueslots; out_datadef_by_type(bw, cg, &datadef->type, &ddstate); } else { outalign(bw, sym->align); outnulls(bw, sym->size); } /* Ensure that all datadefs get a unique address */ if (sym->size == 0) { outnulls(bw, 1); } } /* FIXME Currently everything is placed in .rodata That works for SLUL, but will not work for other languages */ void out_datasection(const struct CgCtx *cg, struct BinWriter *bw) { const struct SymInfo *sym; if (cg->arch->is_textual) return; for (sym = cg->syminfos; sym; sym = sym->next) { if (!sym->datadef || sym->mode == SYMMODE_IMPORT) continue; out_datadef(bw, sym, cg); } } void section_start(struct Section *section, struct BinWriter *bw, unsigned align) { outalign(bw, align); section->start = binwriter_get_position(bw); section->number = bw->section_number++; } void section_end(struct Section *section, struct BinWriter *bw) { section->len = binwriter_get_position(bw) - section->start; if (section->len != 0) { section->present = 1; } else if (!section->present) { bw->section_number--; } } struct SymInfo *new_sym(struct CgCtx *cg) { struct SymInfo *sym = allocp(cg->csbe, sizeof(struct SymInfo)); if (UNLIKELY(!sym)) return NULL; sym->offset = 0; sym->size = 0; sym->strtab_offs = 0; sym->next = NULL; *cg->syminfo_nextptr = sym; cg->syminfo_nextptr = &sym->next; return sym; }