/* 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 <u.h>
#include <libc.h>
#include <bio.h>
#include <mach.h>
#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
*
*/
|