summaryrefslogtreecommitdiff
path: root/elfcopy/ascii.c
diff options
context:
space:
mode:
Diffstat (limited to 'elfcopy/ascii.c')
-rw-r--r--elfcopy/ascii.c1078
1 files changed, 1078 insertions, 0 deletions
diff --git a/elfcopy/ascii.c b/elfcopy/ascii.c
new file mode 100644
index 0000000000000..968e5c667f631
--- /dev/null
+++ b/elfcopy/ascii.c
@@ -0,0 +1,1078 @@
+/*-
+ * Copyright (c) 2010,2011 Kai Wang
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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 AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <ctype.h>
+#include <err.h>
+#include <gelf.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "elfcopy.h"
+
+ELFTC_VCSID("$Id: ascii.c 2358 2011-12-19 18:22:32Z kaiwang27 $");
+
+static void append_data(struct section *s, const void *buf, size_t sz);
+static char hex_digit(uint8_t n);
+static int hex_value(int x);
+static void finalize_data_section(struct section *s);
+static int ishexdigit(int x);
+static int ihex_read(const char *line, char *type, uint64_t *addr,
+ uint64_t *num, uint8_t *data, size_t *sz);
+static void ihex_write(int ofd, int type, uint64_t addr, uint64_t num,
+ const void *buf, size_t sz);
+static void ihex_write_00(int ofd, uint64_t addr, const void *buf, size_t sz);
+static void ihex_write_01(int ofd);
+static void ihex_write_04(int ofd, uint16_t addr);
+static void ihex_write_05(int ofd, uint64_t e_entry);
+static struct section *new_data_section(struct elfcopy *ecp, int sec_index,
+ uint64_t off, uint64_t addr);
+static int read_num(const char *line, int *len, uint64_t *num, size_t sz,
+ int *checksum);
+static int srec_read(const char *line, char *type, uint64_t *addr,
+ uint8_t *data, size_t *sz);
+static void srec_write(int ofd, char type, uint64_t addr, const void *buf,
+ size_t sz);
+static void srec_write_symtab(int ofd, const char *ofn, Elf *e, Elf_Scn *scn,
+ GElf_Shdr *sh);
+static void srec_write_S0(int ofd, const char *ofn);
+static void srec_write_Sd(int ofd, char dr, uint64_t addr, const void *buf,
+ size_t sz, size_t rlen);
+static void srec_write_Se(int ofd, uint64_t e_entry, int forceS3);
+static void write_num(char *line, int *len, uint64_t num, size_t sz,
+ int *checksum);
+
+#define _LINE_BUFSZ 1024
+#define _DATA_BUFSZ 256
+
+/*
+ * Convert ELF object to S-Record.
+ */
+void
+create_srec(struct elfcopy *ecp, int ifd, int ofd, const char *ofn)
+{
+ Elf *e;
+ Elf_Scn *scn;
+ Elf_Data *d;
+ GElf_Ehdr eh;
+ GElf_Shdr sh;
+ uint64_t max_addr;
+ size_t rlen;
+ int elferr, addr_sz;
+ char dr;
+
+ if ((e = elf_begin(ifd, ELF_C_READ, NULL)) == NULL)
+ errx(EXIT_FAILURE, "elf_begin() failed: %s",
+ elf_errmsg(-1));
+
+ /* Output a symbol table for `symbolsrec' target. */
+ if (!strncmp(ecp->otgt, "symbolsrec", strlen("symbolsrec"))) {
+ scn = NULL;
+ while ((scn = elf_nextscn(e, scn)) != NULL) {
+ if (gelf_getshdr(scn, &sh) == NULL) {
+ warnx("gelf_getshdr failed: %s",
+ elf_errmsg(-1));
+ (void) elf_errno();
+ continue;
+ }
+ if (sh.sh_type != SHT_SYMTAB)
+ continue;
+ srec_write_symtab(ofd, ofn, e, scn, &sh);
+ break;
+ }
+ }
+
+ if (ecp->flags & SREC_FORCE_S3)
+ dr = '3';
+ else {
+ /*
+ * Find maximum address size in the first iteration.
+ */
+ max_addr = 0;
+ scn = NULL;
+ while ((scn = elf_nextscn(e, scn)) != NULL) {
+ if (gelf_getshdr(scn, &sh) == NULL) {
+ warnx("gelf_getshdr failed: %s",
+ elf_errmsg(-1));
+ (void) elf_errno();
+ continue;
+ }
+ if ((sh.sh_flags & SHF_ALLOC) == 0 ||
+ sh.sh_type == SHT_NOBITS ||
+ sh.sh_size == 0)
+ continue;
+ if ((uint64_t) sh.sh_addr > max_addr)
+ max_addr = sh.sh_addr;
+ }
+ elferr = elf_errno();
+ if (elferr != 0)
+ warnx("elf_nextscn failed: %s", elf_errmsg(elferr));
+
+ if (max_addr <= 0xFFFF)
+ dr = '1';
+ else if (max_addr <= 0xFFFFFF)
+ dr = '2';
+ else
+ dr = '3';
+ }
+
+ if (ecp->flags & SREC_FORCE_LEN) {
+ addr_sz = dr - '0' + 1;
+ if (ecp->srec_len < 1)
+ rlen = 1;
+ else if (ecp->srec_len + addr_sz + 1 > 255)
+ rlen = 255 - (addr_sz + 1);
+ else
+ rlen = ecp->srec_len;
+ } else
+ rlen = 16;
+
+ /* Generate S0 record which contains the output filename. */
+ srec_write_S0(ofd, ofn);
+
+ /* Generate S{1,2,3} data records for section data. */
+ scn = NULL;
+ while ((scn = elf_nextscn(e, scn)) != NULL) {
+ if (gelf_getshdr(scn, &sh) == NULL) {
+ warnx("gelf_getshdr failed: %s", elf_errmsg(-1));
+ (void) elf_errno();
+ continue;
+ }
+ if ((sh.sh_flags & SHF_ALLOC) == 0 ||
+ sh.sh_type == SHT_NOBITS ||
+ sh.sh_size == 0)
+ continue;
+ if (sh.sh_addr > 0xFFFFFFFF) {
+ warnx("address space too big for S-Record file");
+ continue;
+ }
+ (void) elf_errno();
+ if ((d = elf_getdata(scn, NULL)) == NULL) {
+ elferr = elf_errno();
+ if (elferr != 0)
+ warnx("elf_getdata failed: %s", elf_errmsg(-1));
+ continue;
+ }
+ if (d->d_buf == NULL || d->d_size == 0)
+ continue;
+ srec_write_Sd(ofd, dr, sh.sh_addr, d->d_buf, d->d_size, rlen);
+ }
+ elferr = elf_errno();
+ if (elferr != 0)
+ warnx("elf_nextscn failed: %s", elf_errmsg(elferr));
+
+ /* Generate S{7,8,9} end of block recrod. */
+ if (gelf_getehdr(e, &eh) == NULL)
+ errx(EXIT_FAILURE, "gelf_getehdr() failed: %s",
+ elf_errmsg(-1));
+ srec_write_Se(ofd, eh.e_entry, ecp->flags & SREC_FORCE_S3);
+}
+
+void
+create_elf_from_srec(struct elfcopy *ecp, int ifd)
+{
+ char line[_LINE_BUFSZ], name[_LINE_BUFSZ];
+ uint8_t data[_DATA_BUFSZ];
+ GElf_Ehdr oeh;
+ struct section *s, *shtab;
+ FILE *ifp;
+ uint64_t addr, entry, off, sec_addr;
+ uintmax_t st_value;
+ size_t sz;
+ int _ifd, first, sec_index, in_symtab, symtab_created;
+ char *rlt;
+ char type;
+
+ if ((_ifd = dup(ifd)) < 0)
+ err(EXIT_FAILURE, "dup failed");
+ if ((ifp = fdopen(_ifd, "r")) == NULL)
+ err(EXIT_FAILURE, "fdopen failed");
+
+ /* Create EHDR for output .o file. */
+ if (gelf_newehdr(ecp->eout, ecp->oec) == NULL)
+ errx(EXIT_FAILURE, "gelf_newehdr failed: %s",
+ elf_errmsg(-1));
+ if (gelf_getehdr(ecp->eout, &oeh) == NULL)
+ errx(EXIT_FAILURE, "gelf_getehdr() failed: %s",
+ elf_errmsg(-1));
+
+ /* Initialise e_ident fields. */
+ oeh.e_ident[EI_CLASS] = ecp->oec;
+ oeh.e_ident[EI_DATA] = ecp->oed;
+ /*
+ * TODO: Set OSABI according to the OS platform where elfcopy(1)
+ * was build. (probably)
+ */
+ oeh.e_ident[EI_OSABI] = ELFOSABI_NONE;
+ oeh.e_machine = ecp->oem;
+ oeh.e_type = ET_REL;
+ oeh.e_entry = 0;
+
+ ecp->flags |= RELOCATABLE;
+
+ /* Create .shstrtab section */
+ init_shstrtab(ecp);
+ ecp->shstrtab->off = 0;
+
+ /* Data sections are inserted after EHDR. */
+ off = gelf_fsize(ecp->eout, ELF_T_EHDR, 1, EV_CURRENT);
+ if (off == 0)
+ errx(EXIT_FAILURE, "gelf_fsize() failed: %s", elf_errmsg(-1));
+
+ /* Create data sections. */
+ s = NULL;
+ first = 1;
+ sec_index = 1;
+ sec_addr = entry = 0;
+ while (fgets(line, _LINE_BUFSZ, ifp) != NULL) {
+ if (line[0] == '\r' || line[0] == '\n')
+ continue;
+ if (line[0] == '$' && line[1] == '$') {
+ ecp->flags |= SYMTAB_EXIST;
+ while ((rlt = fgets(line, _LINE_BUFSZ, ifp)) != NULL) {
+ if (line[0] == '$' && line[1] == '$')
+ break;
+ }
+ if (rlt == NULL)
+ break;
+ continue;
+ }
+ if (line[0] != 'S' || line[1] < '0' || line[1] > '9') {
+ warnx("Invalid srec record");
+ continue;
+ }
+ if (srec_read(line, &type, &addr, data, &sz) < 0) {
+ warnx("Invalid srec record or mismatched checksum");
+ continue;
+ }
+ switch (type) {
+ case '1':
+ case '2':
+ case '3':
+ if (sz == 0)
+ break;
+ if (first || sec_addr != addr) {
+ if (s != NULL)
+ finalize_data_section(s);
+ s = new_data_section(ecp, sec_index, off,
+ addr);
+ if (s == NULL) {
+ warnx("new_data_section failed");
+ break;
+ }
+ sec_index++;
+ sec_addr = addr;
+ first = 0;
+ }
+ append_data(s, data, sz);
+ off += sz;
+ sec_addr += sz;
+ break;
+ case '7':
+ case '8':
+ case '9':
+ entry = addr;
+ break;
+ default:
+ break;
+ }
+ }
+ if (s != NULL)
+ finalize_data_section(s);
+ if (ferror(ifp))
+ warn("fgets failed");
+
+ /* Insert .shstrtab after data sections. */
+ if ((ecp->shstrtab->os = elf_newscn(ecp->eout)) == NULL)
+ errx(EXIT_FAILURE, "elf_newscn failed: %s",
+ elf_errmsg(-1));
+ insert_to_sec_list(ecp, ecp->shstrtab, 1);
+
+ /* Insert section header table here. */
+ shtab = insert_shtab(ecp, 1);
+
+ /*
+ * Rescan and create symbol table if we found '$$' section in
+ * the first scan.
+ */
+ symtab_created = 0;
+ in_symtab = 0;
+ if (ecp->flags & SYMTAB_EXIST) {
+ if (fseek(ifp, 0, SEEK_SET) < 0) {
+ warn("fseek failed");
+ ecp->flags &= ~SYMTAB_EXIST;
+ goto done;
+ }
+ while (fgets(line, _LINE_BUFSZ, ifp) != NULL) {
+ if (in_symtab) {
+ if (line[0] == '$' && line[1] == '$') {
+ in_symtab = 0;
+ continue;
+ }
+ if (sscanf(line, "%s $%jx", name,
+ &st_value) != 2) {
+ warnx("Invalid symbolsrec record");
+ continue;
+ }
+ if (!symtab_created) {
+ create_external_symtab(ecp);
+ symtab_created = 1;
+ }
+ add_to_symtab(ecp, name, st_value, 0, SHN_ABS,
+ ELF32_ST_INFO(STB_GLOBAL, STT_NOTYPE), 0, 1);
+ }
+ if (line[0] == '$' && line[1] == '$') {
+ in_symtab = 1;
+ continue;
+ }
+ }
+ }
+ if (ferror(ifp))
+ warn("fgets failed");
+ if (symtab_created) {
+ finalize_external_symtab(ecp);
+ create_symtab_data(ecp);
+ /* Count in .symtab and .strtab section headers. */
+ shtab->sz += gelf_fsize(ecp->eout, ELF_T_SHDR, 2, EV_CURRENT);
+ } else
+ ecp->flags &= ~SYMTAB_EXIST;
+
+done:
+ fclose(ifp);
+
+ /* Set entry point. */
+ oeh.e_entry = entry;
+
+ /*
+ * Write the underlying ehdr. Note that it should be called
+ * before elf_setshstrndx() since it will overwrite e->e_shstrndx.
+ */
+ if (gelf_update_ehdr(ecp->eout, &oeh) == 0)
+ errx(EXIT_FAILURE, "gelf_update_ehdr() failed: %s",
+ elf_errmsg(-1));
+
+ /* Generate section name string table (.shstrtab). */
+ set_shstrtab(ecp);
+
+ /* Update sh_name pointer for each section header entry. */
+ update_shdr(ecp, 0);
+
+ /* Renew oeh to get the updated e_shstrndx. */
+ if (gelf_getehdr(ecp->eout, &oeh) == NULL)
+ errx(EXIT_FAILURE, "gelf_getehdr() failed: %s",
+ elf_errmsg(-1));
+
+ /* Resync section offsets. */
+ resync_sections(ecp);
+
+ /* Store SHDR offset in EHDR. */
+ oeh.e_shoff = shtab->off;
+
+ /* Update ehdr since we modified e_shoff. */
+ if (gelf_update_ehdr(ecp->eout, &oeh) == 0)
+ errx(EXIT_FAILURE, "gelf_update_ehdr() failed: %s",
+ elf_errmsg(-1));
+
+ /* Write out the output elf object. */
+ if (elf_update(ecp->eout, ELF_C_WRITE) < 0)
+ errx(EXIT_FAILURE, "elf_update() failed: %s",
+ elf_errmsg(-1));
+
+ /* Release allocated resource. */
+ free_elf(ecp);
+}
+
+void
+create_ihex(int ifd, int ofd)
+{
+ Elf *e;
+ Elf_Scn *scn;
+ Elf_Data *d;
+ GElf_Ehdr eh;
+ GElf_Shdr sh;
+ int elferr;
+ uint16_t addr_hi, old_addr_hi;
+
+ if ((e = elf_begin(ifd, ELF_C_READ, NULL)) == NULL)
+ errx(EXIT_FAILURE, "elf_begin() failed: %s",
+ elf_errmsg(-1));
+
+ old_addr_hi = 0;
+ scn = NULL;
+ while ((scn = elf_nextscn(e, scn)) != NULL) {
+ if (gelf_getshdr(scn, &sh) == NULL) {
+ warnx("gelf_getshdr failed: %s", elf_errmsg(-1));
+ (void) elf_errno();
+ continue;
+ }
+ if ((sh.sh_flags & SHF_ALLOC) == 0 ||
+ sh.sh_type == SHT_NOBITS ||
+ sh.sh_size == 0)
+ continue;
+ if (sh.sh_addr > 0xFFFFFFFF) {
+ warnx("address space too big for Intel Hex file");
+ continue;
+ }
+ (void) elf_errno();
+ if ((d = elf_getdata(scn, NULL)) == NULL) {
+ elferr = elf_errno();
+ if (elferr != 0)
+ warnx("elf_getdata failed: %s", elf_errmsg(-1));
+ continue;
+ }
+ if (d->d_buf == NULL || d->d_size == 0)
+ continue;
+ addr_hi = (sh.sh_addr >> 16) & 0xFFFF;
+ if (addr_hi > 0 && addr_hi != old_addr_hi) {
+ /* Write 04 record if addr_hi is new. */
+ old_addr_hi = addr_hi;
+ ihex_write_04(ofd, addr_hi);
+ }
+ ihex_write_00(ofd, sh.sh_addr, d->d_buf, d->d_size);
+ }
+ elferr = elf_errno();
+ if (elferr != 0)
+ warnx("elf_nextscn failed: %s", elf_errmsg(elferr));
+
+ if (gelf_getehdr(e, &eh) == NULL)
+ errx(EXIT_FAILURE, "gelf_getehdr() failed: %s",
+ elf_errmsg(-1));
+ ihex_write_05(ofd, eh.e_entry);
+ ihex_write_01(ofd);
+}
+
+void
+create_elf_from_ihex(struct elfcopy *ecp, int ifd)
+{
+ char line[_LINE_BUFSZ];
+ uint8_t data[_DATA_BUFSZ];
+ GElf_Ehdr oeh;
+ struct section *s, *shtab;
+ FILE *ifp;
+ uint64_t addr, addr_base, entry, num, off, rec_addr, sec_addr;
+ size_t sz;
+ int _ifd, first, sec_index;
+ char type;
+
+ if ((_ifd = dup(ifd)) < 0)
+ err(EXIT_FAILURE, "dup failed");
+ if ((ifp = fdopen(_ifd, "r")) == NULL)
+ err(EXIT_FAILURE, "fdopen failed");
+
+ /* Create EHDR for output .o file. */
+ if (gelf_newehdr(ecp->eout, ecp->oec) == NULL)
+ errx(EXIT_FAILURE, "gelf_newehdr failed: %s",
+ elf_errmsg(-1));
+ if (gelf_getehdr(ecp->eout, &oeh) == NULL)
+ errx(EXIT_FAILURE, "gelf_getehdr() failed: %s",
+ elf_errmsg(-1));
+
+ /* Initialise e_ident fields. */
+ oeh.e_ident[EI_CLASS] = ecp->oec;
+ oeh.e_ident[EI_DATA] = ecp->oed;
+ /*
+ * TODO: Set OSABI according to the OS platform where elfcopy(1)
+ * was build. (probably)
+ */
+ oeh.e_ident[EI_OSABI] = ELFOSABI_NONE;
+ oeh.e_machine = ecp->oem;
+ oeh.e_type = ET_REL;
+ oeh.e_entry = 0;
+
+ ecp->flags |= RELOCATABLE;
+
+ /* Create .shstrtab section */
+ init_shstrtab(ecp);
+ ecp->shstrtab->off = 0;
+
+ /* Data sections are inserted after EHDR. */
+ off = gelf_fsize(ecp->eout, ELF_T_EHDR, 1, EV_CURRENT);
+ if (off == 0)
+ errx(EXIT_FAILURE, "gelf_fsize() failed: %s", elf_errmsg(-1));
+
+ /* Create data sections. */
+ s = NULL;
+ first = 1;
+ sec_index = 1;
+ addr_base = rec_addr = sec_addr = entry = 0;
+ while (fgets(line, _LINE_BUFSZ, ifp) != NULL) {
+ if (line[0] == '\r' || line[0] == '\n')
+ continue;
+ if (line[0] != ':') {
+ warnx("Invalid ihex record");
+ continue;
+ }
+ if (ihex_read(line, &type, &addr, &num, data, &sz) < 0) {
+ warnx("Invalid ihex record or mismatched checksum");
+ continue;
+ }
+ switch (type) {
+ case '0':
+ /* Data record. */
+ if (sz == 0)
+ break;
+ rec_addr = addr_base + addr;
+ if (first || sec_addr != rec_addr) {
+ if (s != NULL)
+ finalize_data_section(s);
+ s = new_data_section(ecp, sec_index, off,
+ rec_addr);
+ if (s == NULL) {
+ warnx("new_data_section failed");
+ break;
+ }
+ sec_index++;
+ sec_addr = rec_addr;
+ first = 0;
+ }
+ append_data(s, data, sz);
+ off += sz;
+ sec_addr += sz;
+ break;
+ case '1':
+ /* End of file record. */
+ goto done;
+ case '2':
+ /* Extended segment address record. */
+ addr_base = addr << 4;
+ break;
+ case '3':
+ /* Start segment address record (CS:IP). Ignored. */
+ break;
+ case '4':
+ /* Extended linear address record. */
+ addr_base = num << 16;
+ break;
+ case '5':
+ /* Start linear address record. */
+ entry = num;
+ break;
+ default:
+ break;
+ }
+ }
+done:
+ if (s != NULL)
+ finalize_data_section(s);
+ if (ferror(ifp))
+ warn("fgets failed");
+ fclose(ifp);
+
+ /* Insert .shstrtab after data sections. */
+ if ((ecp->shstrtab->os = elf_newscn(ecp->eout)) == NULL)
+ errx(EXIT_FAILURE, "elf_newscn failed: %s",
+ elf_errmsg(-1));
+ insert_to_sec_list(ecp, ecp->shstrtab, 1);
+
+ /* Insert section header table here. */
+ shtab = insert_shtab(ecp, 1);
+
+ /* Set entry point. */
+ oeh.e_entry = entry;
+
+ /*
+ * Write the underlying ehdr. Note that it should be called
+ * before elf_setshstrndx() since it will overwrite e->e_shstrndx.
+ */
+ if (gelf_update_ehdr(ecp->eout, &oeh) == 0)
+ errx(EXIT_FAILURE, "gelf_update_ehdr() failed: %s",
+ elf_errmsg(-1));
+
+ /* Generate section name string table (.shstrtab). */
+ set_shstrtab(ecp);
+
+ /* Update sh_name pointer for each section header entry. */
+ update_shdr(ecp, 0);
+
+ /* Renew oeh to get the updated e_shstrndx. */
+ if (gelf_getehdr(ecp->eout, &oeh) == NULL)
+ errx(EXIT_FAILURE, "gelf_getehdr() failed: %s",
+ elf_errmsg(-1));
+
+ /* Resync section offsets. */
+ resync_sections(ecp);
+
+ /* Store SHDR offset in EHDR. */
+ oeh.e_shoff = shtab->off;
+
+ /* Update ehdr since we modified e_shoff. */
+ if (gelf_update_ehdr(ecp->eout, &oeh) == 0)
+ errx(EXIT_FAILURE, "gelf_update_ehdr() failed: %s",
+ elf_errmsg(-1));
+
+ /* Write out the output elf object. */
+ if (elf_update(ecp->eout, ELF_C_WRITE) < 0)
+ errx(EXIT_FAILURE, "elf_update() failed: %s",
+ elf_errmsg(-1));
+
+ /* Release allocated resource. */
+ free_elf(ecp);
+}
+
+#define _SEC_NAMESZ 64
+#define _SEC_INIT_CAP 1024
+
+static struct section *
+new_data_section(struct elfcopy *ecp, int sec_index, uint64_t off,
+ uint64_t addr)
+{
+ char *name;
+
+ if ((name = malloc(_SEC_NAMESZ)) == NULL)
+ errx(EXIT_FAILURE, "malloc failed");
+ snprintf(name, _SEC_NAMESZ, ".sec%d", sec_index);
+
+ return (create_external_section(ecp, name, name, NULL, 0, off,
+ SHT_PROGBITS, ELF_T_BYTE, SHF_ALLOC | SHF_WRITE, 1, addr, 0));
+}
+
+static void
+finalize_data_section(struct section *s)
+{
+ Elf_Data *od;
+
+ if ((od = elf_newdata(s->os)) == NULL)
+ errx(EXIT_FAILURE, "elf_newdata() failed: %s",
+ elf_errmsg(-1));
+ od->d_align = s->align;
+ od->d_off = 0;
+ od->d_buf = s->buf;
+ od->d_size = s->sz;
+ od->d_version = EV_CURRENT;
+}
+
+static void
+append_data(struct section *s, const void *buf, size_t sz)
+{
+ uint8_t *p;
+
+ if (s->buf == NULL) {
+ s->sz = 0;
+ s->cap = _SEC_INIT_CAP;
+ if ((s->buf = malloc(s->cap)) == NULL)
+ err(EXIT_FAILURE, "malloc failed");
+ }
+
+ while (sz + s->sz > s->cap) {
+ s->cap *= 2;
+ if ((s->buf = realloc(s->buf, s->cap)) == NULL)
+ err(EXIT_FAILURE, "realloc failed");
+ }
+
+ p = s->buf;
+ memcpy(&p[s->sz], buf, sz);
+ s->sz += sz;
+}
+
+static int
+srec_read(const char *line, char *type, uint64_t *addr, uint8_t *data,
+ size_t *sz)
+{
+ uint64_t count, _checksum, num;
+ size_t addr_sz;
+ int checksum, i, len;
+
+ checksum = 0;
+ len = 2;
+ if (read_num(line, &len, &count, 1, &checksum) < 0)
+ return (-1);
+ *type = line[1];
+ switch (*type) {
+ case '0':
+ case '1':
+ case '5':
+ case '9':
+ addr_sz = 2;
+ break;
+ case '2':
+ case '8':
+ addr_sz = 3;
+ break;
+ case '3':
+ case '7':
+ addr_sz = 4;
+ break;
+ default:
+ return (-1);
+ }
+
+ if (read_num(line, &len, addr, addr_sz, &checksum) < 0)
+ return (-1);
+
+ count -= addr_sz + 1;
+ if (*type >= '0' && *type <= '3') {
+ for (i = 0; (uint64_t) i < count; i++) {
+ if (read_num(line, &len, &num, 1, &checksum) < 0)
+ return -1;
+ data[i] = (uint8_t) num;
+ }
+ *sz = count;
+ } else
+ *sz = 0;
+
+ if (read_num(line, &len, &_checksum, 1, NULL) < 0)
+ return (-1);
+
+ if ((int) _checksum != (~checksum & 0xFF))
+ return (-1);
+
+ return (0);
+}
+
+static void
+srec_write_symtab(int ofd, const char *ofn, Elf *e, Elf_Scn *scn, GElf_Shdr *sh)
+{
+ char line[_LINE_BUFSZ];
+ GElf_Sym sym;
+ Elf_Data *d;
+ const char *name;
+ size_t sc;
+ int elferr, i;
+
+#define _WRITE_LINE do { \
+ if (write(ofd, line, strlen(line)) != (ssize_t) strlen(line)) \
+ errx(EXIT_FAILURE, "write failed"); \
+ } while (0)
+
+
+ (void) elf_errno();
+ if ((d = elf_getdata(scn, NULL)) == NULL) {
+ elferr = elf_errno();
+ if (elferr != 0)
+ warnx("elf_getdata failed: %s",
+ elf_errmsg(-1));
+ return;
+ }
+ if (d->d_buf == NULL || d->d_size == 0)
+ return;
+
+ snprintf(line, sizeof(line), "$$ %s\r\n", ofn);
+ _WRITE_LINE;
+ sc = d->d_size / sh->sh_entsize;
+ for (i = 1; (size_t) i < sc; i++) {
+ if (gelf_getsym(d, i, &sym) != &sym) {
+ warnx("gelf_getsym failed: %s", elf_errmsg(-1));
+ continue;
+ }
+ if (GELF_ST_TYPE(sym.st_info) == STT_SECTION ||
+ GELF_ST_TYPE(sym.st_info) == STT_FILE)
+ continue;
+ if ((name = elf_strptr(e, sh->sh_link, sym.st_name)) == NULL) {
+ warnx("elf_strptr failed: %s", elf_errmsg(-1));
+ continue;
+ }
+ snprintf(line, sizeof(line), " %s $%jx\r\n", name,
+ (uintmax_t) sym.st_value);
+ _WRITE_LINE;
+ }
+ snprintf(line, sizeof(line), "$$ \r\n");
+ _WRITE_LINE;
+
+#undef _WRITE_LINE
+}
+
+static void
+srec_write_S0(int ofd, const char *ofn)
+{
+
+ srec_write(ofd, '0', 0, ofn, strlen(ofn));
+}
+
+static void
+srec_write_Sd(int ofd, char dr, uint64_t addr, const void *buf, size_t sz,
+ size_t rlen)
+{
+ const uint8_t *p, *pe;
+
+ p = buf;
+ pe = p + sz;
+ while (pe - p >= (int) rlen) {
+ srec_write(ofd, dr, addr, p, rlen);
+ addr += rlen;
+ p += rlen;
+ }
+ if (pe - p > 0)
+ srec_write(ofd, dr, addr, p, pe - p);
+}
+
+static void
+srec_write_Se(int ofd, uint64_t e_entry, int forceS3)
+{
+ char er;
+
+ if (e_entry > 0xFFFFFFFF) {
+ warnx("address space too big for S-Record file");
+ return;
+ }
+
+ if (forceS3)
+ er = '7';
+ else {
+ if (e_entry <= 0xFFFF)
+ er = '9';
+ else if (e_entry <= 0xFFFFFF)
+ er = '8';
+ else
+ er = '7';
+ }
+
+ srec_write(ofd, er, e_entry, NULL, 0);
+}
+
+static void
+srec_write(int ofd, char type, uint64_t addr, const void *buf, size_t sz)
+{
+ char line[_LINE_BUFSZ];
+ const uint8_t *p, *pe;
+ int len, addr_sz, checksum;
+
+ if (type == '0' || type == '1' || type == '5' || type == '9')
+ addr_sz = 2;
+ else if (type == '2' || type == '8')
+ addr_sz = 3;
+ else
+ addr_sz = 4;
+
+ checksum = 0;
+ line[0] = 'S';
+ line[1] = type;
+ len = 2;
+ write_num(line, &len, addr_sz + sz + 1, 1, &checksum);
+ write_num(line, &len, addr, addr_sz, &checksum);
+ for (p = buf, pe = p + sz; p < pe; p++)
+ write_num(line, &len, *p, 1, &checksum);
+ write_num(line, &len, ~checksum & 0xFF, 1, NULL);
+ line[len++] = '\r';
+ line[len++] = '\n';
+ if (write(ofd, line, len) != (ssize_t) len)
+ err(EXIT_FAILURE, "write failed");
+}
+
+static void
+ihex_write_00(int ofd, uint64_t addr, const void *buf, size_t sz)
+{
+ uint16_t addr_hi, old_addr_hi;
+ const uint8_t *p, *pe;
+
+ old_addr_hi = (addr >> 16) & 0xFFFF;
+ p = buf;
+ pe = p + sz;
+ while (pe - p >= 16) {
+ ihex_write(ofd, 0, addr, 0, p, 16);
+ addr += 16;
+ p += 16;
+ addr_hi = (addr >> 16) & 0xFFFF;
+ if (addr_hi != old_addr_hi) {
+ old_addr_hi = addr_hi;
+ ihex_write_04(ofd, addr_hi);
+ }
+ }
+ if (pe - p > 0)
+ ihex_write(ofd, 0, addr, 0, p, pe - p);
+}
+
+static int
+ihex_read(const char *line, char *type, uint64_t *addr, uint64_t *num,
+ uint8_t *data, size_t *sz)
+{
+ uint64_t count, _checksum;
+ int checksum, i, len;
+
+ *sz = 0;
+ checksum = 0;
+ len = 1;
+ if (read_num(line, &len, &count, 1, &checksum) < 0)
+ return (-1);
+ if (read_num(line, &len, addr, 2, &checksum) < 0)
+ return (-1);
+ if (line[len++] != '0')
+ return (-1);
+ *type = line[len++];
+ checksum += *type - '0';
+ switch (*type) {
+ case '0':
+ for (i = 0; (uint64_t) i < count; i++) {
+ if (read_num(line, &len, num, 1, &checksum) < 0)
+ return (-1);
+ data[i] = (uint8_t) *num;
+ }
+ *sz = count;
+ break;
+ case '1':
+ if (count != 0)
+ return (-1);
+ break;
+ case '2':
+ case '4':
+ if (count != 2)
+ return (-1);
+ if (read_num(line, &len, num, 2, &checksum) < 0)
+ return (-1);
+ break;
+ case '3':
+ case '5':
+ if (count != 4)
+ return (-1);
+ if (read_num(line, &len, num, 4, &checksum) < 0)
+ return (-1);
+ break;
+ default:
+ return (-1);
+ }
+
+ if (read_num(line, &len, &_checksum, 1, &checksum) < 0)
+ return (-1);
+
+ if ((checksum & 0xFF) != 0) {
+ return (-1);
+ }
+
+ return (0);
+}
+
+static void
+ihex_write_01(int ofd)
+{
+
+ ihex_write(ofd, 1, 0, 0, NULL, 0);
+}
+
+static void
+ihex_write_04(int ofd, uint16_t addr)
+{
+
+ ihex_write(ofd, 4, 0, addr, NULL, 2);
+}
+
+static void
+ihex_write_05(int ofd, uint64_t e_entry)
+{
+
+ if (e_entry > 0xFFFFFFFF) {
+ warnx("address space too big for Intel Hex file");
+ return;
+ }
+
+ ihex_write(ofd, 5, 0, e_entry, NULL, 4);
+}
+
+static void
+ihex_write(int ofd, int type, uint64_t addr, uint64_t num, const void *buf,
+ size_t sz)
+{
+ char line[_LINE_BUFSZ];
+ const uint8_t *p, *pe;
+ int len, checksum;
+
+ if (sz > 16)
+ errx(EXIT_FAILURE, "Internal: ihex_write() sz too big");
+ checksum = 0;
+ line[0] = ':';
+ len = 1;
+ write_num(line, &len, sz, 1, &checksum);
+ write_num(line, &len, addr, 2, &checksum);
+ write_num(line, &len, type, 1, &checksum);
+ if (sz > 0) {
+ if (buf != NULL) {
+ for (p = buf, pe = p + sz; p < pe; p++)
+ write_num(line, &len, *p, 1, &checksum);
+ } else
+ write_num(line, &len, num, sz, &checksum);
+ }
+ write_num(line, &len, (~checksum + 1) & 0xFF, 1, NULL);
+ line[len++] = '\r';
+ line[len++] = '\n';
+ if (write(ofd, line, len) != (ssize_t) len)
+ err(EXIT_FAILURE, "write failed");
+}
+
+static int
+read_num(const char *line, int *len, uint64_t *num, size_t sz, int *checksum)
+{
+ uint8_t b;
+
+ *num = 0;
+ for (; sz > 0; sz--) {
+ if (!ishexdigit(line[*len]) || !ishexdigit(line[*len + 1]))
+ return (-1);
+ b = (hex_value(line[*len]) << 4) | hex_value(line[*len + 1]);
+ *num = (*num << 8) | b;
+ *len += 2;
+ if (checksum != NULL)
+ *checksum = (*checksum + b) & 0xFF;
+ }
+
+ return (0);
+}
+
+static void
+write_num(char *line, int *len, uint64_t num, size_t sz, int *checksum)
+{
+ uint8_t b;
+
+ for (; sz > 0; sz--) {
+ b = (num >> ((sz - 1) * 8)) & 0xFF;
+ line[*len] = hex_digit((b >> 4) & 0xF);
+ line[*len + 1] = hex_digit(b & 0xF);
+ *len += 2;
+ if (checksum != NULL)
+ *checksum = (*checksum + b) & 0xFF;
+ }
+}
+
+static char
+hex_digit(uint8_t n)
+{
+
+ return ((n < 10) ? '0' + n : 'A' + (n - 10));
+}
+
+static int
+hex_value(int x)
+{
+
+ if (isdigit(x))
+ return (x - '0');
+ else if (x >= 'a' && x <= 'f')
+ return (x - 'a' + 10);
+ else
+ return (x - 'A' + 10);
+}
+
+static int
+ishexdigit(int x)
+{
+
+ if (isdigit(x))
+ return (1);
+ if ((x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F'))
+ return (1);
+
+ return (0);
+}