/* srec - convert Plan 9 executables to SREC format, aka s-record or S19 format * * http://en.wikipedia.org/wiki/SREC_(file_format) * http://www.linux-mips.org/wiki/SREC */ #include #include #include #include #define U32INT_MAX (0xFFFFFFFFULL) int verbose = 0; void mumble(char *fmt, ...) { if (verbose) { va_list arg; va_start(arg, fmt); vfprint(2, fmt, arg); va_end(arg); } } #pragma varargck argpos mumble 1 void xBprint(Biobufhdr *bp, char *fmt, ...) { va_list arg; va_start(arg, fmt); if (Bvprint(bp, fmt, arg) == Beof) sysfatal("Bvprint: %r"); va_end(arg); } #pragma varargck argpos xBprint 2 uchar cksum(uchar *bytes, int n) { uchar sum; for (sum = 0; n > 0; --n) sum += *bytes++; return(~sum); } /* Each "S record" looks like: * * 'S' * one decimal digit of type * two hex digits of a single-byte count of bytes in the rest of the record * some "address" bytes (variable by type), each byte as two hex digits, big-endian * some data bytes, each byte as two hex digits * one byte of checksum, as two hex digits * * Checksum isn't additive for multiple blocks, so we assemble entire record * for one checksum operation, then sum it, then print it. Sorry about the * extra copy. */ int sent; void record(int type, u32int addr, uchar *bytes, int nbytes, Biobufhdr *obp) { int addrlen = 0, i; uchar count, sum; uchar line[80], *bp; assert(nbytes <= 64); switch (type) { case 0: sent = 0; /* S0 = start of transmission */ addrlen = 2; break; case 1: case 5: case 9: addrlen = 2; break; case 2: case 8: addrlen = 3; break; case 3: case 7: addrlen = 4; break; default: sysfatal("Unimplemented S-record %d", type); } count = addrlen + nbytes + sizeof(sum); bp = line; /* count */ *bp++ = count; /* addr, big-endian */ while (addrlen > 0) { *bp++ = (addr >> ((addrlen - 1) * 8)) & 0xFF; --addrlen; } /* data */ for (i = 0; i < nbytes; i++) *bp++ = bytes[i]; /* checksum */ *bp++ = cksum(line, bp - line); /* emit */ xBprint(obp, "S%1d", type); for (i = 0; i < bp - line; i++) xBprint(obp, "%02uX", line[i]); xBprint(obp, "\n"); ++sent; } void header(char *fname, Biobufhdr *obp) { char h[12]; snprint(h, 10+1, "%-10s", fname); /* +1: snprint() may temporarily place '\0' in h[10] */ mumble("Encoding name as \"%s\"\n", h); h[10] = 9; /* version */ h[11] = 9; /* revision */ record(0, 0, (uchar *) h, sizeof(h), obp); } void emit(Map *m, uvlong addr, long n, Biobufhdr *obp) { uchar buf[16]; /* 64 max by "standard"; objcopy uses 16 */ assert(addr <= U32INT_MAX); mumble("Emit %ld bytes from %ullx\n", n, addr); while (n > 0) { int get = n; if (get > sizeof(buf)) get = sizeof(buf); if (get1(m, addr, buf, get) != get) sysfatal("Can't get1(%d bytes) from executable at %0llx: %r", get, addr); record(3, (u32int) addr, buf, get, obp); n -= get; addr += get; } } void recordcount(Biobufhdr *obp) { record(5, (u32int) sent, nil, 0, obp); } /* * SREC is inherently 32-bit. Plan 9 a.out is, too (entry is "long"). * However, libmach's entry is 64-bit ("uvlong"); when long is * promoted to uvlong it gets sign-extended if, say, KZERO is * 0xFxxxxxxx, which these days it is. So we'll take addresses * which aren't really 32-bit as long as they are off by only sign * extension. */ void entry(uvlong e, Biobufhdr *obp) { if (e & (1<<31)) if ((e & (U32INT_MAX<<32)) == (U32INT_MAX<<32)) e &= ~(U32INT_MAX<<32); assert(e <= U32INT_MAX); mumble("Entry %ullx\n", e); record(7, (u32int) e, nil, 0, obp); } /* * Sigh: loadmap() returns a Map with EXACTLY two slots, and * setmap() won't grow a Map. */ Map *loadmapbss(Map *map, int fd, Fhdr *fp) { int zero; map = newmap(map, 10); if (map == 0) return 0; if ((zero = open("/dev/zero", OREAD)) < 0) { free(map); return 0; } setmap(map, fd, fp->txtaddr, fp->txtaddr+fp->txtsz, fp->txtoff, "text"); setmap(map, fd, fp->dataddr, fp->dataddr+fp->datsz, fp->datoff, "data"); setmap(map, zero, fp->dataddr+fp->datsz, fp->dataddr+fp->datsz+fp->bsssz, 0, "bss"); return map; } void emitfile(char *fname, int ifd, Biobufhdr *obp) { int seg; Fhdr f; Map *m; if(!crackhdr(ifd, &f)) { fprint(2, "%s: %s not an executable?\n", argv0, fname); exits("bad format"); } machbytype(f.type); mumble("%s executable\n", mach->name); mumble("%ldt + %ldd + %ldb = %ld\t%s\n", f.txtsz, f.datsz, f.bsssz, f.txtsz+f.datsz+f.bsssz, fname); if(!(m = loadmapbss(nil, ifd, &f))) { fprint(2, "Cannot loadmapbss()\n"); exits("bad format"); } for(seg=0; seg < m->nsegs; ++seg) { if (m->seg[seg].inuse) { mumble("segment %d is named %s\n", seg, m->seg[seg].name); mumble("\tbase: 0x%llux, end: 0x%llux\n", m->seg[seg].b, m->seg[seg].e); m->seg[seg].cache = 1; } } header(fname, obp); emit(m, f.txtaddr, f.txtsz, obp); emit(m, f.dataddr, f.datsz, obp); emit(m, f.dataddr+f.datsz, f.bsssz, obp); recordcount(obp); entry(f.entry, obp); } void usage(void) { fprint(2, "usage: %s [-v] [executable-file [output-file]]\n", argv0); exits("usage"); } void main(int argc, char *argv[]) { int ifd; Biobuf obuf, *obp; char *iname, *oname; ifd = -1; obp = nil; iname = oname = nil; ARGBEGIN { case 'v': ++verbose; break; default: usage(); } ARGEND; switch (argc) { case 0: /* stdin to stdout */ iname = "(stdin)"; ifd = 0; oname = "(stdout)"; Binit(&obuf, 1, OWRITE); obp = &obuf; break; case 1: /* named file to stdout */ iname = argv[0]; oname = "(stdout)"; Binit(&obuf, 1, OWRITE); obp = &obuf; break; case 2: /* named input and output */ iname = argv[0]; oname = argv[1]; break; default: usage(); } /* check input availability before truncating old output file */ if (ifd < 0) { if((ifd = open(iname, OREAD)) < 0){ fprint(2, "%s: ", argv0); perror(iname); exits("open()"); } } if (obp == nil) { if ((obp = Bopen(oname, OWRITE)) == nil) { fprint(2, "%s: ", argv0); perror(oname); exits("open()"); } } emitfile(iname, ifd, obp); close(ifd); if (Bterm(obp) < 0) sysfatal("Bterm: %r"); exits(nil); } /* "Test suite" * * echo 'moo' | srec * srec -v srec /dev/vgactl * echo still here > foo ; srec /no/such/file foo ; cat foo * */