From 86ad524ea29abe2d8dc3149b2e8d153fcc7a03e7 Mon Sep 17 00:00:00 2001 From: Joerg Wunsch Date: Thu, 2 Feb 2012 16:52:45 +0000 Subject: [PATCH] Implement ELF file reading (finally). Requires libelf(3) to be present on the host system. git-svn-id: svn://svn.savannah.nongnu.org/avrdude/trunk/avrdude@1056 81a1dc3b-b13d-400b-aceb-764788c761c2 --- ChangeLog | 13 ++ Makefile.am | 2 +- avrdude.1 | 13 +- configure.ac | 14 ++ doc/avrdude.texi | 4 + fileio.c | 370 +++++++++++++++++++++++++++++++++++++++++++++++ fileio.h | 3 +- update.c | 1 + 8 files changed, 417 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index d576dfcf..2e163277 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2012-02-02 Joerg Wunsch + + Implement ELF file reading (finally). Requires libelf(3) to be + present on the host system. + * configure.ac (HAVE_LIBELF): Add logic to detect presence of + libelf(3) + * Makefile.am (avrdude_LDADD): Add @LIBELF@ + * fileio.h (FILEFMT): add FMT_ELF + * fileio.c: Implement ELF file reader. + * update.c (parse_op): add 'e' format specifier + * avrdude.1: Document the ELF file reading capability + * doc/avrdude.texi: (Dito.) + 2012-02-01 Rene Liebscher bug #30559 Ft232 bit-bang support diff --git a/Makefile.am b/Makefile.am index 4e05bb44..e38e3c2b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -61,7 +61,7 @@ avrdude_CFLAGS = @ENABLE_WARNINGS@ libavrdude_a_CFLAGS = @ENABLE_WARNINGS@ -avrdude_LDADD = $(top_builddir)/$(noinst_LIBRARIES) @LIBUSB@ @LIBUSB_1_0@ @LIBFTDI@ @LIBHID@ -lm +avrdude_LDADD = $(top_builddir)/$(noinst_LIBRARIES) @LIBUSB@ @LIBUSB_1_0@ @LIBFTDI@ @LIBHID@ @LIBELF@ -lm bin_PROGRAMS = avrdude diff --git a/avrdude.1 b/avrdude.1 index e541a280..ae291166 100644 --- a/avrdude.1 +++ b/avrdude.1 @@ -19,7 +19,7 @@ .\" .\" $Id$ .\" -.Dd DATE August 30, 2011 +.Dd DATE February 2, 2012 .Os .Dt AVRDUDE 1 .Sh NAME @@ -173,6 +173,14 @@ as a standalone assembler, or .Xr avr-objcopy 1 for the final stage of the GNU toolchain for the AVR microcontroller. .Pp +Provided +.Xr libelf 3 +was present when compiling +.Nm avrdude , +the input file can also be the final ELF file as produced by the linker. +The appropriate ELF section(s) will be examined, according to the memory +area to write to. +.Pp .Nm Avrdude can program the EEPROM and flash ROM memory cells of supported AVR parts. Where supported by the serial instruction set, fuse bits and @@ -583,6 +591,8 @@ Intel Hex Motorola S-record .It Ar r raw binary; little-endian byte order, in the case of the flash ROM data +.It Ar e +ELF (Executable and Linkable Format) .It Ar m immediate; actual byte values specified on the command line, separated by commas or spaces. This is good for programming fuse bytes without @@ -984,6 +994,7 @@ normal ISP communication. .Sh SEE ALSO .Xr avr-objcopy 1 , .Xr ppi 4 , +.Xr libelf 3, .Xr readline 3 .Pp The AVR microcontroller product description can be found at diff --git a/configure.ac b/configure.ac index 70c39674..fa624ccb 100644 --- a/configure.ac +++ b/configure.ac @@ -74,6 +74,20 @@ AM_PROG_CC_C_O AC_CHECK_LIB([termcap], [tputs]) AC_CHECK_LIB([ncurses], [tputs]) AC_CHECK_LIB([readline], [readline]) +AH_TEMPLATE([HAVE_LIBELF], + [Define if ELF support is enabled via libelf]) +AC_CHECK_LIB([elf], [elf_begin], [have_libelf=yes]) +if test x$have_libelf = xyes; then + case $target in + *) + LIBELF="-lelf" + ;; + esac + AC_DEFINE([HAVE_LIBELF]) + AC_CHECK_HEADERS([libelf.h]) +fi +AC_SUBST(LIBELF, $LIBELF) + AC_SEARCH_LIBS([gethostent], [nsl]) AC_SEARCH_LIBS([setsockopt], [socket]) AH_TEMPLATE([HAVE_LIBUSB], diff --git a/doc/avrdude.texi b/doc/avrdude.texi index fb1883f1..3bfa9edb 100644 --- a/doc/avrdude.texi +++ b/doc/avrdude.texi @@ -773,6 +773,10 @@ Motorola S-record @itemx r raw binary; little-endian byte order, in the case of the flash ROM data +@itemx e +ELF (Executable and Linkable Format), the final output file from the +linker; currently only accepted as an input file + @itemx m immediate mode; actual byte values specified on the command line, separated by commas or spaces in place of the @var{filename} field of diff --git a/fileio.c b/fileio.c index fa07a8f3..622d920c 100644 --- a/fileio.c +++ b/fileio.c @@ -28,6 +28,11 @@ #include #include +#ifdef HAVE_LIBELF +#include +#define EM_AVR32 0x18ad /* inofficial */ +#endif + #include "avrdude.h" #include "avr.h" #include "fileio.h" @@ -74,6 +79,16 @@ static int fileio_ihex(struct fioparms * fio, static int fileio_srec(struct fioparms * fio, char * filename, FILE * f, AVRMEM * mem, int size); +#ifdef HAVE_LIBELF +static int elf2b(char * infile, FILE * inf, + AVRMEM * mem, struct avrpart * p, + int bufsize, unsigned int fileoffset); + +static int fileio_elf(struct fioparms * fio, + char * filename, FILE * f, AVRMEM * mem, + struct avrpart * p, int size); +#endif + static int fileio_num(struct fioparms * fio, char * filename, FILE * f, AVRMEM * mem, int size, FILEFMT fmt); @@ -89,6 +104,7 @@ char * fmtstr(FILEFMT format) case FMT_SREC : return "Motorola S-Record"; break; case FMT_IHEX : return "Intel Hex"; break; case FMT_RBIN : return "raw binary"; break; + case FMT_ELF : return "ELF"; break; default : return "invalid format"; break; }; } @@ -669,6 +685,307 @@ static int srec2b(char * infile, FILE * inf, return maxaddr; } +#ifdef HAVE_LIBELF +/* + * Return the ELF section descriptor that corresponds to program + * header `ph'. The program header is expected to be of p_type + * PT_LOAD, and to have a nonzero p_filesz. (PT_LOAD sections with a + * zero p_filesz are typically RAM sections that are not initialized + * by file data, e.g. ".bss".) + */ +static Elf_Scn *elf_get_scn(Elf *e, Elf32_Phdr *ph, Elf32_Shdr **shptr) +{ + Elf_Scn *s = NULL; + + while ((s = elf_nextscn(e, s)) != NULL) { + Elf32_Shdr *sh; + size_t ndx = elf_ndxscn(s); + if ((sh = elf32_getshdr(s)) == NULL) { + fprintf(stderr, + "%s: ERROR: Error reading section #%u header: %s\n", + progname, (unsigned int)ndx, elf_errmsg(-1)); + continue; + } + if ((sh->sh_flags & SHF_ALLOC) == 0 || + sh->sh_type != SHT_PROGBITS) + /* we are only interested in PROGBITS, ALLOC sections */ + continue; + if (ph->p_vaddr == sh->sh_addr && + ph->p_offset == sh->sh_offset) { + /* yeah, we found it */ + *shptr = sh; + return s; + } + } + + fprintf(stderr, + "%s: ERROR: Cannot find a matching section for " + "program header entry @p_vaddr 0x%x\n", + progname, ph->p_vaddr); + return NULL; +} + +static int elf_mem_limits(AVRMEM *mem, struct avrpart * p, + unsigned int *lowbound, + unsigned int *highbound, + unsigned int *fileoff) +{ + int rv = 0; + + if (p->flags & AVRPART_AVR32) { + if (strcmp(mem->desc, "flash") == 0) { + *lowbound = 0x80000000; + *highbound = 0xffffffff; + *fileoff = 0; + } else { + rv = -1; + } + } else { + if (strcmp(mem->desc, "flash") == 0) { + *lowbound = 0; + *highbound = 0x7ffff; /* max 8 MiB */ + *fileoff = 0; + } else if (strcmp(mem->desc, "eeprom") == 0) { + *lowbound = 0x810000; + *highbound = 0x81ffff; /* max 64 KiB */ + *fileoff = 0; + } else if (strcmp(mem->desc, "lfuse") == 0) { + *lowbound = 0x820000; + *highbound = 0x82ffff; + *fileoff = 0; + } else if (strcmp(mem->desc, "hfuse") == 0) { + *lowbound = 0x820000; + *highbound = 0x82ffff; + *fileoff = 1; + } else if (strcmp(mem->desc, "efuse") == 0) { + *lowbound = 0x820000; + *highbound = 0x82ffff; + *fileoff = 2; + } else if (strncmp(mem->desc, "fuse", 4) == 0 && + (mem->desc[4] >= '0' && mem->desc[4] <= '9')) { + /* Xmega fuseN */ + *lowbound = 0x820000; + *highbound = 0x82ffff; + *fileoff = mem->desc[4] - '0'; + } else if (strcmp(mem->desc, "lock") == 0) { + *lowbound = 0x830000; + *highbound = 0x83ffff; + *fileoff = 0; + } else { + rv = -1; + } + } + + return rv; +} + + +static int elf2b(char * infile, FILE * inf, + AVRMEM * mem, struct avrpart * p, + int bufsize, unsigned int fileoffset) +{ + Elf *e; + int rv = -1; + unsigned int low, high, foff; + + if (elf_mem_limits(mem, p, &low, &high, &foff) != 0) { + fprintf(stderr, + "%s: ERROR: Cannot handle \"%s\" memory region from ELF file\n", + progname, mem->desc); + return -1; + } + + if (elf_version(EV_CURRENT) == EV_NONE) { + fprintf(stderr, + "%s: ERROR: ELF library initialization failed: %s\n", + progname, elf_errmsg(-1)); + return -1; + } + if ((e = elf_begin(fileno(inf), ELF_C_READ, NULL)) == NULL) { + fprintf(stderr, + "%s: ERROR: Cannot open \"%s\" as an ELF file: %s\n", + progname, infile, elf_errmsg(-1)); + return -1; + } + if (elf_kind(e) != ELF_K_ELF) { + fprintf(stderr, + "%s: ERROR: Cannot use \"%s\" as an ELF input file\n", + progname, infile); + goto done; + } + + size_t i, isize; + const char *id = elf_getident(e, &isize); + + if (id == NULL) { + fprintf(stderr, + "%s: ERROR: Error reading ident area of \"%s\": %s\n", + progname, infile, elf_errmsg(-1)); + goto done; + } + + const char *endianname; + unsigned char endianess; + if (p->flags & AVRPART_AVR32) { + endianess = ELFDATA2MSB; + endianname = "little"; + } else { + endianess = ELFDATA2LSB; + endianname = "big"; + } + if (id[EI_CLASS] != ELFCLASS32 || + id[EI_DATA] != endianess) { + fprintf(stderr, + "%s: ERROR: ELF file \"%s\" is not a " + "32-bit, %s-endian file that was expected\n", + progname, infile, endianname); + goto done; + } + + Elf32_Ehdr *eh; + if ((eh = elf32_getehdr(e)) == NULL) { + fprintf(stderr, + "%s: ERROR: Error reading ehdr of \"%s\": %s\n", + progname, infile, elf_errmsg(-1)); + goto done; + } + + if (eh->e_type != ET_EXEC) { + fprintf(stderr, + "%s: ERROR: ELF file \"%s\" is not an executable file\n", + progname, infile); + goto done; + } + + const char *mname; + uint16_t machine; + if (p->flags & AVRPART_AVR32) { + machine = EM_AVR32; + mname = "AVR32"; + } else { + machine = EM_AVR; + mname = "AVR"; + } + if (eh->e_machine != machine) { + fprintf(stderr, + "%s: ERROR: ELF file \"%s\" is not for machine %s\n", + progname, infile, mname); + goto done; + } + if (eh->e_phnum == 0xffff /* PN_XNUM */) { + fprintf(stderr, + "%s: ERROR: ELF file \"%s\" uses extended " + "program header numbers which are not expected\n", + progname, infile); + goto done; + } + + Elf32_Phdr *ph; + if ((ph = elf32_getphdr(e)) == NULL) { + fprintf(stderr, + "%s: ERROR: Error reading program header table of \"%s\": %s\n", + progname, infile, elf_errmsg(-1)); + goto done; + } + + /* + * Walk the program header table, pick up entries that are of type + * PT_LOAD, and have a non-zero p_filesz. + */ + for (i = 0; i < eh->e_phnum; i++) { + if (ph[i].p_type != PT_LOAD || + ph[i].p_filesz == 0) + continue; + + if (verbose >= 2) { + fprintf(stderr, + "%s: Considering PT_LOAD program header entry #%d:\n" + " p_vaddr 0x%x, p_paddr 0x%x, p_filesz %d\n", + progname, i, ph[i].p_vaddr, ph[i].p_paddr, ph[i].p_filesz); + } + if (ph[i].p_paddr >= low && + ph[i].p_paddr < high) { + /* OK */ + } else { + if (verbose >= 2) { + fprintf(stderr, + " => skipping, inappropriate for \"%s\" memory region\n", + mem->desc); + } + continue; + } + /* + * 1-byte sized memory regions are special: they are used for fuse + * bits, where multiple regions (in the config file) map to a + * single, larger region in the ELF file (e.g. "lfuse", "hfuse", + * and "efuse" all map to ".fuse"). We silently accept a larger + * ELF file region for these, and extract the actual byte to write + * from it, using the "foff" offset obtained above. + */ + if (mem->size != 1 && + ph[i].p_paddr + ph[i].p_filesz > mem->size) { + fprintf(stderr, + "%s: ERROR: program header entry #%d does not fit into \"%s\" memory:\n" + " 0x%x + %u > %u\n", + progname, i, mem->desc, ph[i].p_paddr, ph[i].p_filesz, mem->size); + continue; + } + + Elf32_Shdr *sh; + Elf_Scn *s = elf_get_scn(e, ph + i, &sh); + if (s == NULL) + continue; + + if ((sh->sh_flags & SHF_ALLOC) && sh->sh_size) { + Elf_Data *d = NULL; + while ((d = elf_getdata(s, d)) != NULL) { + if (verbose >= 2) { + fprintf(stderr, + " Data block: d_buf 0x%x, d_off 0x%x, d_size %d\n", + (unsigned int)d->d_buf, (unsigned int)d->d_off, d->d_size); + } + if (mem->size == 1) { + if (d->d_off != 0) { + fprintf(stderr, + "%s: ERROR: unexpected data block at offset != 0\n", + progname); + } else if (foff >= d->d_size) { + fprintf(stderr, + "%s: ERROR: ELF file section does not contain byte at offset %d\n", + progname, foff); + } else { + if (verbose >= 2) { + fprintf(stderr, + " Extracting one byte from file offset %d\n", + foff); + } + mem->buf[0] = ((unsigned char *)d->d_buf)[foff]; + mem->tags[0] = TAG_ALLOCATED; + rv = 1; + } + } else { + unsigned int idx; + + idx = ph[i].p_paddr - low + d->d_off; + if ((int)(idx + d->d_size) > rv) + rv = idx + d->d_size; + if (verbose >= 3) { + fprintf(stderr, + " Writing %d bytes to mem offset 0x%x\n", + d->d_size, idx); + } + memcpy(mem->buf + idx, d->d_buf, d->d_size); + memset(mem->tags + idx, TAG_ALLOCATED, d->d_size); + } + } + } + } +done: + (void)elf_end(e); + return rv; +} +#endif /* HAVE_LIBELF */ + /* * Simple itoa() implementation. Caller needs to allocate enough * space in buf. Only positive integers are handled. @@ -851,6 +1168,36 @@ static int fileio_srec(struct fioparms * fio, } +#ifdef HAVE_LIBELF +static int fileio_elf(struct fioparms * fio, + char * filename, FILE * f, AVRMEM * mem, + struct avrpart * p, int size) +{ + int rc; + + switch (fio->op) { + case FIO_WRITE: + fprintf(stderr, "%s: ERROR: write operation not (yet) " + "supported for ELF\n", + progname); + return -1; + break; + + case FIO_READ: + rc = elf2b(filename, f, mem, p, size, fio->fileoffset); + return rc; + + default: + fprintf(stderr, "%s: ERROR: invalid Motorola S-Records file I/O " + "operation=%d\n", + progname, fio->op); + return -1; + break; + } +} + +#endif + static int fileio_num(struct fioparms * fio, char * filename, FILE * f, AVRMEM * mem, int size, FILEFMT fmt) @@ -975,6 +1322,7 @@ static int fmt_autodetect(char * fname) int i; int len; int found; + int first = 1; f = fopen(fname, "r"); if (f == NULL) { @@ -984,6 +1332,14 @@ static int fmt_autodetect(char * fname) } while (fgets((char *)buf, MAX_LINE_LEN, f)!=NULL) { + /* check for ELF file */ + if (first && + (buf[0] == 0177 && buf[1] == 'E' && + buf[2] == 'L' && buf[3] == 'F')) { + fclose(f); + return FMT_ELF; + } + buf[MAX_LINE_LEN-1] = 0; len = strlen((char *)buf); if (buf[len-1] == '\n') @@ -1031,6 +1387,8 @@ static int fmt_autodetect(char * fname) return FMT_SREC; } } + + first = 0; } fclose(f); @@ -1150,6 +1508,18 @@ int fileio(int op, char * filename, FILEFMT format, rc = fileio_rbin(&fio, fname, f, mem, size); break; + case FMT_ELF: +#ifdef HAVE_LIBELF + rc = fileio_elf(&fio, fname, f, mem, p, size); +#else + fprintf(stderr, + "%s: can't handle ELF file %s, " + "ELF file support was not compiled in\n", + progname, fname); + rc = -1; +#endif + break; + case FMT_IMM: rc = fileio_imm(&fio, fname, f, mem, size); break; diff --git a/fileio.h b/fileio.h index 711dca5a..8e81c27a 100644 --- a/fileio.h +++ b/fileio.h @@ -31,7 +31,8 @@ typedef enum { FMT_HEX, FMT_DEC, FMT_OCT, - FMT_BIN + FMT_BIN, + FMT_ELF } FILEFMT; struct fioparms { diff --git a/update.c b/update.c index 5a2d38bf..d49f80a9 100644 --- a/update.c +++ b/update.c @@ -138,6 +138,7 @@ UPDATE * parse_op(char * s) case 's': upd->format = FMT_SREC; break; case 'i': upd->format = FMT_IHEX; break; case 'r': upd->format = FMT_RBIN; break; + case 'e': upd->format = FMT_ELF; break; case 'm': upd->format = FMT_IMM; break; case 'b': upd->format = FMT_BIN; break; case 'd': upd->format = FMT_DEC; break;