/* * odmdump - read AIX ODM object class files directly, without libodm. * * usage: odmdump [-s] [-r] [-d] [-x] classfile * -s print the class definition (like odmshow) instead of objects * -r raw: also print _id and raw vchar offsets * -d also print deleted objects (odmdelete only sets _id to -1) * -x slack scan: non-zero record slots past the last object, and * .vc strings that no record (live or deleted) points at * * On-disk layout (worked out by hand on AIX 4.3.3, see NOTES): * 0x00 struct ClassHdr { magic=ODMI_MAGIC, ndata, version } * 0x0c struct Class pointers stored as file offsets * ClassElem[nelem] (40 bytes each), pointers as file offsets * string table (class name, element names, link names) * data ndata records of structsize bytes; first 12 bytes are * _id, _reserved, _scratch * Companion ".vc" holds vchar strings, magic VCHAR_MAGIC. * * Both kinds of .vc string are stored in the record as a 4-byte offset * relative to the StringClxn data pointer (file offset 0x18 in .vc): * vchar (ODM_VCHAR): a char * in memory too, so the element table * already gives it 4 bytes; its "size" is only a maximum length. * nchar (ODM_CHAR with ClassElem.reserved == 1): an inline char[n] in * memory, but only 4 bytes on disk, so every later field moves * down by n - 4. Offsets need not be aligned. */ #include #include #include #include #include #define ODMI_MAGIC 0xdcfac #define VCHAR_MAGIC 0xcaa1c /* ODM field types, from odmi.h */ #define ODM_CHAR 0 #define ODM_LONGCHAR 1 #define ODM_BINARY 2 #define ODM_SHORT 3 #define ODM_LONG 4 #define ODM_LINK 5 #define ODM_METHOD 6 #define ODM_VCHAR 7 #define ODM_DOUBLE 8 #define ODM_ULONG 9 #define ODM_LONG_LONG 10 #define ODM_ULONG_LONG 11 #define CLASS_OFF 0x0c /* struct Class follows ClassHdr */ #define CLASS_SIZE 316 /* sizeof(struct Class) on 32-bit AIX */ #define ELEM_SIZE 40 /* sizeof(struct ClassElem) */ struct buf { unsigned char *p; unsigned long len; }; static int rawflag, delflag, slackflag; struct field { char *name; unsigned long type; unsigned long size; /* in-memory size, from ClassElem */ unsigned long doff; /* on-disk offset within a record */ int invc; /* value lives in the .vc file */ char *linkclass, *linkcol; }; static unsigned long be32(struct buf *b, unsigned long off) { unsigned char *q; if (off + 4 > b->len) { fprintf(stderr, "odmdump: read past end at 0x%lx\n", off); exit(2); } q = b->p + off; return ((unsigned long)q[0] << 24) | ((unsigned long)q[1] << 16) | ((unsigned long)q[2] << 8) | (unsigned long)q[3]; } static char * str(struct buf *b, unsigned long off) { if (off == 0 || off >= b->len) return ""; return (char *)b->p + off; } static int slurp(char *path, struct buf *b) { FILE *f; struct stat st; b->p = NULL; b->len = 0; if ((f = fopen(path, "r")) == NULL) return -1; if (fstat(fileno(f), &st) < 0) { fclose(f); return -1; } b->len = st.st_size; if ((b->p = malloc(b->len + 1)) == NULL || fread(b->p, 1, b->len, f) != b->len) { fclose(f); return -1; } b->p[b->len] = '\0'; fclose(f); return 0; } static char *typenames[] = { "char", "longchar", "binary", "short", "long", "link", "method", "vchar", "double", "ulong", "long long", "ulong long" }; /* * Read the element table and work out where each field sits on disk. * Returns the on-disk record size. */ static unsigned long fields(struct buf *cf, unsigned long elemoff, unsigned long nelem, unsigned long structsize, struct field *fl) { unsigned long j, e, shrink = 0; for (j = 0; j < nelem; j++) { e = elemoff + j * ELEM_SIZE; fl[j].name = str(cf, be32(cf, e)); fl[j].type = be32(cf, e + 4); fl[j].doff = be32(cf, e + 8) - shrink; fl[j].size = be32(cf, e + 12); fl[j].linkclass = str(cf, be32(cf, e + 16)); fl[j].linkcol = str(cf, be32(cf, e + 20)); fl[j].invc = fl[j].type == ODM_VCHAR || be32(cf, e + 36) == 1; if (fl[j].type == ODM_CHAR && fl[j].invc) shrink += fl[j].size - 4; } return structsize - shrink; } /* * Print a string the way odmget does: in double quotes, with '"' and * '\\' backslashed and newlines written as \n plus a backslash-newline * continuation. Every other byte, control characters included, goes * out raw. */ static void putq(unsigned char *s, unsigned long max) { unsigned long i; putchar('"'); for (i = 0; i < max && s[i]; i++) { if (s[i] == '"' || s[i] == '\\') printf("\\%c", s[i]); else if (s[i] == '\n') printf("\\n\\\n"); else putchar(s[i]); } putchar('"'); } static void printrec(struct buf *cf, struct buf *vc, unsigned long vcbase, char *classname, struct field *fl, unsigned long nelem, unsigned long rec) { unsigned long j, v; printf("\n%s:\n", classname); if (rawflag) printf("\t_id = %ld\n", (long)be32(cf, rec)); for (j = 0; j < nelem; j++) { unsigned char *f = cf->p + rec + fl[j].doff; printf("\t%s = ", fl[j].name); if (fl[j].invc) { v = be32(cf, rec + fl[j].doff); if (rawflag) printf("@0x%lx ", v); if (v != 0 && vcbase + v < vc->len) putq(vc->p + vcbase + v, vc->len - vcbase - v); else printf("\"\""); putchar('\n'); continue; } switch (fl[j].type) { case ODM_CHAR: case ODM_LONGCHAR: case ODM_METHOD: putq(f, fl[j].size); break; case ODM_LINK: putq(f + 8, fl[j].size); /* 8-byte link header first */ break; case ODM_SHORT: printf("%d", (short)((f[0] << 8) | f[1])); break; case ODM_LONG: printf("%ld", (long)be32(cf, rec + fl[j].doff)); break; case ODM_ULONG: printf("%lu", be32(cf, rec + fl[j].doff)); break; case ODM_DOUBLE: { /* host is big-endian IEEE too; copy for alignment */ double d; memcpy(&d, f, sizeof d); printf("%g", d); } break; case ODM_BINARY: printf("\"0x"); for (v = 0; v < fl[j].size; v++) printf("%02X", f[v]); putchar('"'); break; case ODM_LONG_LONG: printf("%lld", ((long long)be32(cf, rec + fl[j].doff) << 32) | be32(cf, rec + fl[j].doff + 4)); break; case ODM_ULONG_LONG: printf("%llu", ((unsigned long long)be32(cf, rec + fl[j].doff) << 32) | be32(cf, rec + fl[j].doff + 4)); break; default: printf("", fl[j].type); break; } putchar('\n'); } } /* * Report anything past the live data: non-zero record slots after the * last object, then .vc strings that no record slot refers to. */ static void slack(struct buf *cf, struct buf *vc, unsigned long vcbase, char *classname, struct field *fl, unsigned long nelem, unsigned long dataoff, unsigned long recsize, unsigned long ndata) { unsigned long i, j, k, rec, nslots, v, vcend; char *used; nslots = recsize ? (cf->len - dataoff) / recsize : 0; for (i = ndata; i < nslots; i++) { rec = dataoff + i * recsize; for (k = 0; k < recsize && cf->p[rec + k] == 0; k++) ; if (k == recsize) continue; printf("\n# slack slot %lu (file offset 0x%lx)\n", i, rec); printrec(cf, vc, vcbase, classname, fl, nelem, rec); } if (vc->len == 0 || vcbase >= vc->len) return; if ((used = calloc(vc->len, 1)) == NULL) return; for (i = 0; i < nslots; i++) { rec = dataoff + i * recsize; for (j = 0; j < nelem; j++) { if (!fl[j].invc) continue; v = be32(cf, rec + fl[j].doff); if (v != 0 && vcbase + v < vc->len) used[vcbase + v] = 1; } } vcend = vcbase + be32(vc, 4); if (vcend > vc->len) vcend = vc->len; for (k = vcbase; k < vc->len; k++) { if (vc->p[k] == 0) continue; if (!used[k]) printf("# orphan .vc string @0x%lx%s: ", k - vcbase, k >= vcend ? " (past end mark)" : ""); j = k; while (k < vc->len && vc->p[k] != 0) k++; if (!used[j]) { putq(vc->p + j, k - j); putchar('\n'); } } free(used); } int main(int argc, char **argv) { struct buf cf, vc; unsigned long ndata, structsize, nelem, elemoff, dataoff; unsigned long i, j, rec, v, recsize, vcbase; struct field *fl; char *classname, *path, vcpath[1024]; int showflag = 0, c; extern int optind; while ((c = getopt(argc, argv, "srdx")) != EOF) { switch (c) { case 's': showflag = 1; break; case 'r': rawflag = 1; break; case 'd': delflag = 1; break; case 'x': slackflag = 1; break; default: fprintf(stderr, "usage: odmdump [-s] [-r] [-d] [-x] classfile\n"); return 1; } } if (optind != argc - 1) { fprintf(stderr, "usage: odmdump [-s] [-r] [-d] [-x] classfile\n"); return 1; } path = argv[optind]; if (slurp(path, &cf) < 0) { perror(path); return 1; } if (be32(&cf, 0) != ODMI_MAGIC || be32(&cf, CLASS_OFF) != ODMI_MAGIC) { fprintf(stderr, "%s: not an ODM class file\n", path); return 1; } ndata = be32(&cf, 4); classname = str(&cf, be32(&cf, CLASS_OFF + 4)); structsize = be32(&cf, CLASS_OFF + 8); nelem = be32(&cf, CLASS_OFF + 12); elemoff = be32(&cf, CLASS_OFF + 16); dataoff = be32(&cf, CLASS_OFF + 32); sprintf(vcpath, "%.1000s.vc", path); vc.p = NULL; vc.len = 0; vcbase = 0; if (slurp(vcpath, &vc) == 0) { if (be32(&vc, 0) != VCHAR_MAGIC) { fprintf(stderr, "%s: bad vchar magic\n", vcpath); vc.len = 0; } else vcbase = be32(&vc, 0x18); } if ((fl = malloc((nelem ? nelem : 1) * sizeof *fl)) == NULL) { perror("malloc"); return 1; } recsize = fields(&cf, elemoff, nelem, structsize, fl); if (showflag) { printf("class %s { /* ndata=%lu version=%lu structsize=%lu " "disk recsize=%lu data@0x%lx */\n", classname, ndata, be32(&cf, 8), structsize, recsize, dataoff); for (j = 0; j < nelem; j++) { char *tn = fl[j].type <= ODM_ULONG_LONG ? typenames[fl[j].type] : "?"; if (fl[j].invc && fl[j].type == ODM_CHAR) tn = "nchar"; printf("\t%-10s %-16s; /* size %lu, disk off %lu", tn, fl[j].name, fl[j].size, fl[j].doff); if (fl[j].type == ODM_LINK) printf(" -> %s.%s", fl[j].linkclass, fl[j].linkcol); printf(" */\n"); } printf("};\n"); return 0; } for (i = 0; i < ndata; i++) { rec = dataoff + i * recsize; if (rec + recsize > cf.len) { fprintf(stderr, "odmdump: record %lu runs past EOF\n", i); return 2; } if ((long)be32(&cf, rec) == -1) { if (!delflag) continue; printf("\n# deleted\n"); } printrec(&cf, &vc, vcbase, classname, fl, nelem, rec); } if (slackflag) slack(&cf, &vc, vcbase, classname, fl, nelem, dataoff, recsize, ndata); return 0; }