Source and documentation for disasm/crash dialog support code.

This commit is contained in:
Glenn Maynard
2002-08-27 22:00:41 +00:00
parent c2379b6a52
commit 484fc403e6
4 changed files with 2740 additions and 0 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+401
View File
@@ -0,0 +1,401 @@
// mapconv - symbolic debugging info generator for VirtualDub
// Copyright (C) 2002 Avery Lee, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#include <vector>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_CNAMBUF (131072)
#define MAX_FNAMBUF (131072)
#define MAX_SEGMENTS (64)
#define MAX_GROUPS (64)
struct RVAEnt {
long rva;
char *line;
};
std::vector<RVAEnt> rvabuf;
char fnambuf[MAX_FNAMBUF];
char *fnamptr = fnambuf;
char cnambuf[MAX_CNAMBUF];
char *cnamptr = cnambuf;
long segbuf[MAX_SEGMENTS][2];
int segcnt=0;
int seggrp[MAX_SEGMENTS];
long grpstart[MAX_GROUPS];
char line[8192];
long codeseg_flags = 0;
FILE *f, *fo;
bool readline() {
if (!fgets(line, sizeof line, f))
return false;
int l = strlen(line);
if (l>0 && line[l-1]=='\n')
line[l-1]=0;
return true;
}
bool findline(const char *searchstr) {
while(readline()) {
if (strstr(line, searchstr))
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////
void parsename(long rva, char *buf) {
char *func_name = NULL;
char *class_name = NULL;
char c;
int special_func = 0;
if (*buf++ != '?') {
func_name = buf;
} else {
if (*buf == '?') {
// ??0
// ??1
// ??_G
// ??_E
++buf;
c=*buf++;
special_func = 31;
if (c=='0')
special_func = 1; // constructor
else if (c=='1')
special_func = 2; // destructor
else if (c=='_') {
c = *buf++;
if (c == 'G')
special_func = 3; // scalar deleting destructor
else if (c == 'E')
special_func = 4; // vector deleting destructor?
}
} else {
func_name = buf;
while(*buf != '@') {
if (!*buf)
throw "bad decorated name";
++buf;
}
*buf++ = 0;
}
// Look for a class name.
if (*buf != '@') {
if (!*buf)
throw "bad decorated name";
class_name = buf;
while(*buf != '@') {
if (!*buf)
throw "bad decorated name (class)";
++buf;
}
*buf++ = 0;
}
}
// write out to buffers
if (class_name) {
char *csptr = cnambuf;
int idx = 0;
while(csptr < cnamptr) {
if (!strcmp(csptr, class_name)) {
break;
}
while(*csptr++);
++idx;
}
if (csptr >= cnamptr) {
strcpy(cnamptr, class_name);
while(*cnamptr++);
}
*fnamptr++ = 1 + (idx / 128);
*fnamptr++ = 1 + (idx % 128);
if (special_func)
*fnamptr++ = special_func;
}
if (func_name) {
strcpy(fnamptr, func_name);
while(*fnamptr++);
} else
*fnamptr++ = 0;
}
struct RVASorter {
bool operator()(const RVAEnt& e1, const RVAEnt& e2) {
return e1.rva < e2.rva;
}
};
int main(int argc, char **argv) {
int ver=0;
int i;
long load_addr;
if (argc<4) {
printf("mapconv <listing-file> <output-name> <disassembler module>\n");
return 0;
}
if (f=fopen("version.bin", "rb")) {
fread(&ver,4,1,f);
fclose(f);
} else {
printf("can't read version file\n");
return 20;
}
if (!(f=fopen(argv[1], "r"))) {
printf("can't open listing file \"%s\"\n", argv[1]);
return 20;
}
if (!(fo=fopen(argv[2], "wb"))) {
printf("can't open output file \"%s\"\n", argv[2]);
return 20;
}
int disasm_size = 0;
{
FILE *fd;
if (!(fd=fopen(argv[3], "rb"))) {
printf("can't open disassembler module \"%s\"\n", argv[3]);
return 20;
}
void *buf = malloc(32768);
int act;
while((act = fread(buf, 1, 32768, fd)) > 0) {
disasm_size += act;
fwrite(buf, act, 1, fo);
}
free(buf);
fclose(fd);
}
// Begin parsing file
try {
line[0] = 0;
// printf("Looking for segment list.\n");
if (!findline("Start Length"))
throw "can't find segment list";
// printf("Reading in segment list.\n");
while(readline()) {
long grp, start, len;
if (3!=sscanf(line, "%lx:%lx %lx", &grp, &start, &len))
break;
if (strstr(line+49, "CODE")) {
// printf("%04x:%08lx %08lx type code\n", grp, start, len);
codeseg_flags |= 1<<grp;
segbuf[segcnt][0] = start;
segbuf[segcnt][1] = len;
seggrp[segcnt] = grp;
++segcnt;
}
}
// printf("Looking for public symbol list.\n");
if (!findline("Publics by Value"))
throw "Can't find public symbol list.";
readline();
// printf("Found public symbol list.\n");
while(readline()) {
long grp, start, rva;
char symname[2048];
int i;
if (4!=sscanf(line, "%lx:%lx %s %lx", &grp, &start, symname, &rva))
break;
if (!(codeseg_flags & (1<<grp)))
continue;
RVAEnt entry = { rva, strdup(line) };
rvabuf.push_back(entry);
// parsename(rva,symname);
}
// printf("Looking for static symbol list.\n");
if (!findline("Static symbols"))
printf("WARNING: No static symbols found!\n");
else {
readline();
while(readline()) {
long grp, start, rva;
char symname[4096];
if (4!=sscanf(line, "%lx:%lx %s %lx", &grp, &start, symname, &rva))
break;
if (!(codeseg_flags & (1<<grp)))
continue;
RVAEnt entry = { rva, strdup(line) };
rvabuf.push_back(entry);
// parsename(rva,symname);
}
}
// printf("Sorting RVA entries...\n");
std::sort(rvabuf.begin(), rvabuf.end(), RVASorter());
// printf("Processing RVA entries...\n");
for(i=0; i<rvabuf.size(); i++) {
long grp, start, rva;
char symname[4096];
sscanf(rvabuf[i].line, "%lx:%lx %s %lx", &grp, &start, symname, &rva);
grpstart[grp] = rva - start;
parsename(rva, symname);
}
// printf("Processing segment entries...\n");
for(i=0; i<segcnt; i++) {
segbuf[i][0] += grpstart[seggrp[i]];
// printf("\t#%-2d %08lx-%08lx\n", i+1, segbuf[i][0], segbuf[i][0]+segbuf[i][1]-1);
}
/*
printf("Raw statistics:\n");
printf("\tDisassembler: %ld bytes\n", disasm_size);
printf("\tRVA bytes: %ld\n", rvabuf.size()*4);
printf("\tClass name bytes: %ld\n", cnamptr - cnambuf);
printf("\tFunc name bytes: %ld\n", fnamptr - fnambuf);
printf("\nPacking RVA data..."); fflush(stdout);
*/
std::vector<RVAEnt>::iterator itRVA = rvabuf.begin(), itRVAEnd = rvabuf.end();
std::vector<char> rvaout;
long firstrva = (*itRVA++).rva;
long lastrva = firstrva;
for(; itRVA != itRVAEnd; ++itRVA) {
long rvadiff = (*itRVA).rva - lastrva;
lastrva += rvadiff;
if (rvadiff & 0xF0000000) rvaout.push_back((char)(0x80 | ((rvadiff>>28) & 0x7F)));
if (rvadiff & 0xFFE00000) rvaout.push_back((char)(0x80 | ((rvadiff>>21) & 0x7F)));
if (rvadiff & 0xFFFFC000) rvaout.push_back((char)(0x80 | ((rvadiff>>14) & 0x7F)));
if (rvadiff & 0xFFFFFF80) rvaout.push_back((char)(0x80 | ((rvadiff>> 7) & 0x7F)));
rvaout.push_back((char)(rvadiff & 0x7F));
}
// printf("%ld bytes\n", rvaout.size());
// dump data
long t;
static const char header[64]="[01|01] VirtualDub symbolic debug information\r\n\x1A";
fwrite(header, 64, 1, fo);
t = ver;
fwrite(&t, 4, 1, fo);
t = rvaout.size() + 4;
fwrite(&t, 4, 1, fo);
t = cnamptr - cnambuf;
fwrite(&t, 4, 1, fo);
t = fnamptr - fnambuf;
fwrite(&t, 4, 1, fo);
t = segcnt;
fwrite(&t, 4, 1, fo);
fwrite(&firstrva, 4, 1, fo);
fwrite(&rvaout[0], rvaout.size(), 1, fo);
fwrite(cnambuf, cnamptr - cnambuf, 1, fo);
fwrite(fnambuf, fnamptr - fnambuf, 1, fo);
fwrite(segbuf, segcnt*8, 1, fo);
// really all done
if (fclose(fo))
throw "output file close failed";
} catch(const char *s) {
fprintf(stderr, "%s: %s\n", argv[1], s);
}
fclose(f);
return 0;
}
+112
View File
@@ -0,0 +1,112 @@
This stuff is all built manually; just do "nmake foo.exe" for each binary.
It's not worth making a project for each since that'll just triple the number
of files, and they probably won't be updated much, and since they're prereqs
for building the main project, it's not worth the hassle of VC dependencies.
(I'll check in binaries for the actual utils, since they're tiny.) I've modified
verinc to output C++ instead of assembly, so we don't introduce asm deps, and
removed most output, since it's too noisy.
- glenn
mapconv - symbolic debugging info generator for VirtualDub
Copyright (C) 1998-2002 Avery Lee, All Rights Reserved
disasm - Disassembly module compiler for VirtualDub
Copyright (C) 2002 Avery Lee, All Rights Reserved
These programs are free software; you can redistribute them and/or modify
then under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
These programs are distributed in the hope that they will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
verinc is public domain since it's too short and simple to bother -- but
you still get no warranty with it.
=============================================================================
Verinc
=============================================================================
Verinc takes version.bin, which holds a single four-byte binary build number,
increments it, generates an .asm file with the current build number and date,
and then exits. That's it.
=============================================================================
Mapconv
=============================================================================
When VirtualDub crashes, it takes a snapshot of the current stack and code
around EIP, and produces a crash dump that I can use to diagnose the cause
of the failure. Part of this process is producing a disassembly. IA-32
(x86) is non-trivial to parse, and the current version of VirtualDub
(1.4.10) requires both a table to drive the disassembler and a list of
symbols to guide the disassembly and call stack generation.
Mapconv produces the symbolic debugging tables. It accepts a map file from
the Visual C++ linker and produces a .vdi file with a segment list and
a table for translating relative virtual addresses (RVAs) to symbols. It
also prepends a disassembler table to the beginning of the file (see below).
At runtime, VirtualDub uses the segment list to determine which entries on
the stack are likely valid return addresses, and then consults the symbol
table to translate the addresses to symbols.
Run mapconv as follows:
mapconv virtualdub.map virtualdub.vdi ia32.bin
=============================================================================
Disasm
=============================================================================
Disasm produces the table that drives the 1.4.10+ disassembler.
The table itself is a list of rules, which are sequentially applied against
the code bytes. When one matches, the rule's result string is expanded to
produce the disassembly for that instruction. Rules may embed other sets
of rules as part of their pattern; this functionality avoids having to
replicate rules for the mod-reg-mem (modrm) and scale-index-base (sib) bytes.
Full documentation for the pattern matching and result languages is included
in the ia32.txt file.
There are two ways you can run disasm:
disasm
disasm <source.txt> <dst.bin>
In the first case, the source is assumed to be 'ia32.txt' and the destination
'ia32.bin'. Disasm will read the source file and attempt to compile it. If
the compilation fails, an error will be printed. Otherwise, disasm will write
out the compiled module, and then attempt to test the module against some of
its own code. If you have trouble compiling disasm, it may be due to
P4/Athlon instructions in test1(); you can safely delete the offending
instructions from this function as it is never executed.
Once the disassembler module (ia32.bin) is produced, you can do two things
with it. The usual use is to feed it as input to the 1.1 version of mapconv
to fold it into the VirtualDub.vdi file, but you can also copy the ia32.bin
file to the VirtualDub directory as ia32.vdi. VirtualDub looks for this
file on a crash and will use the disassembler table in it before using the
table in VirtualDub.vdi; this allows the table to be updated without having
to recompile the debug module. You can also rename the .vdi file from a
newer version of VirtualDub to ia32.vdi to use it on an older version,
provided that the table format in the newer file is not incompatible.
VirtualDub has a special command-line option to test the disassembler:
virtualdub /fsck
This will execute a rather strange instruction and manually trigger the
crash dialog. Note that the disassembler in disasm.exe is slightly older
than the module in 1.4.10 -- the 1.4.10 disassembler fixes a couple of
cosmetic bugs (most notably symbol lookup syntax). The table formats are
the same, however.
If you have any questions or comments, I'd like to hear them. Let me know
if any instructions are missing or decoded incorrectly.
-- Avery Lee <phaeron@virtualdub.org>
April 10, 2002