unit debug;
interface
procedure outputError(addr : pointer);
implementation
uses
    Windows,
    Math,
    SysUtils,
    Classes,
    strip,
    memcheck,
    mudsystem;
type
    TSymbol = class
      section : cardinal;
      startAddress : cardinal;
      name : string;
    end;
    TLine = class
      section, address : cardinal;
      linenr : cardinal;
      filename : string;
    end;
var
   lines, symbols : TList;
function IMAGE_FIRST_SECTION(ntheader : PImageNtHeaders) : PImageSectionHeader;
begin
  Result := pointer(integer(ntheader) + (sizeof(ntheader^.Signature) + sizeof(ntheader^.FileHeader)) + ntheader^.FileHeader.SizeofOptionalHeader);
end;
function GetLogicalAddress(addr : pointer;  szModule : pchar; len : cardinal; var section, offset : cardinal) : boolean;
var
   hMod : HMODULE;
   mbi : MEMORY_BASIC_INFORMATION;
   pDosHdr : PImageDosHeader;
   pNtHdr : PImageNtHeaders;
   pSection : PImageSectionHeader;
   i, rva : cardinal;
   sectionStart, sectionEnd : cardinal;
begin
  if (VirtualQuery(addr, mbi, sizeof(mbi)) = 0) then
    begin
    Result := false;
    exit;
    end;
  hMod := HMODULE(mbi.AllocationBase);
  if (GetModuleFileName(hMod, szModule, len) = 0) then
    begin
    Result := false;
    exit;
    end;
  // Point to the DOS header in memory
  pDosHdr := PImageDosHeader(hMod);
  // From the DOS header, find the NT (PE) header
  pNtHdr := PImageNtHeaders(hMod + pDosHdr^._lfanew);
  pSection := IMAGE_FIRST_SECTION(pNtHdr);
  rva := cardinal(addr) - cardinal(hMod); // RVA is offset from module load address
  // Iterate through the section table, looking for the one that encompasses
  // the linear address.
  for i := 0 to pNtHdr^.FileHeader.NumberOfSections - 1 do
    begin
    sectionStart := pSection^.VirtualAddress;
    sectionEnd := sectionStart + Max(pSection^.SizeOfRawData, pSection^.Misc.VirtualSize);
    if (rva >= sectionStart) or (rva <= sectionEnd) then
      begin
      section := i + 1;
      offset := rva - sectionStart;
      Result := true;
      exit;
      end;
    inc(pSection);
    end;
  Result := false;
end;
function findSymbol(section, addr : cardinal) : TSymbol;
var
   a : integer;
   res, symbol : TSymbol;
begin
  res := nil;
  for a := 0 to symbols.count - 1 do
    begin
    symbol := symbols[a];
    if (symbol.section = section) and (addr >= symbol.startAddress) then
      begin
      if (res <> nil) and (res.startAddress > symbol.startAddress) then
        continue;
      res := symbol;
      end;
    end;
  Result := res;
end;
function findLine(section, offset : cardinal) : TLine;
var
   a : integer;
   res, line : TLine;
begin
  res := nil;
  for a := 0 to lines.count - 1 do
    begin
    line := lines[a];
    if (offset >= line.address) and (line.section = section) then
      begin
      if (res <> nil) and (res.address > line.address) then
        continue;
      res := line;
      end;
    end;
  Result := res;
end;
function hexRead(s : string) : cardinal;
var
   d : integer;
   x : cardinal;
begin
  d := 1;
  x := 0;
  while (d <= length(s)) do
    begin
    inc(x, strtoint('$' + s[d] + s[d+1]) shl ((7 - d) * 4));
    inc(d, 2);
    end;
  Result := x;
end;
procedure readMapfile;
var
   f : textfile;
   s, g : string;
   symbol : TSymbol;
   line : TLine;
   changed : boolean;
   a : integer;
   temp : string;
begin
  assignfile(f, 'grendel.map');
  {$I-}
  reset(f);
  {$I+}
  if (IOResult <> 0) then
    begin
    write_console('Could not load mapfile, symbol info disabled.');
    exit;
    end;
  repeat
    readln(f, s);
  until (pos('Address', s) > 0) and (pos('Publics by Name', s) > 0);
  repeat
    readln(f, s);
  until (trim(s) <> '');
  repeat
    g := trim(s);
    if (g <> '') then
      begin
      symbol := TSymbol.Create;
      symbol.section := strtointdef('$' + stripl(g, ':'), 0);
      g := striprbeg(g, ':');
      symbol.startAddress := hexRead(stripl(g, ' '));
      symbol.name := trim(striprbeg(g, ' '));
      symbols.add(symbol);
      end;
    readln(f, s);
  until (s = '');
  while (true) do
    begin
    repeat
      readln(f, s);
    until (pos('Line numbers for', s) > 0) or (eof(f));
    if (eof(f)) then
      break;
    temp := stripl(striprbeg(s, '('), ')');
    repeat
      readln(f, s);
    until (trim(s) <> '');
    repeat
      g := trim(s);
      while (g <> '') do
        begin
        line := TLine.Create;
        line.filename := temp;
        line.linenr := strtointdef(stripl(g, ' '), 0);
        g := striprbeg(g, ' ');
        line.section := strtointdef('$' + stripl(g, ':'), 0);
        g := striprbeg(g, ':');
        line.address := strtointdef('$' + stripl(g, ' '), 0);
        lines.add(line);
        if (pos(' ', g) = 0) then
          break;
        g := trim(striprbeg(g, ' '));
        end;
      readln(f, s);
    until (s = '');
    end;
  closefile(f);
end;
procedure showAddress(addr : pointer);
var
   section, offset : cardinal;
   modu : array[0..1023] of char;
   symbol : TSymbol;
   line : TLine;
   symboln, linen : string;
begin
  GetLogicalAddress(addr, modu, 1024, section, offset);
  symbol := findSymbol(section, offset);
  line := findLine(section, offset);
  if (symbol <> nil) then
    symboln := symbol.name
  else
    symboln := 'no symbol';
  if (line <> nil) then
    linen := IntToStr(line.linenr) + ' (' + line.filename + ')'
  else
    linen := 'no line';
  write_console(symboln + ':' + linen + ' (' + ExtractFileName(modu) + '@' + IntToHex(offset, 8) + ')');
end;
procedure outputError(addr : pointer);
var
   st : TCallStack;
   a : integer;
begin
  write_console('Win32 exception detected, call stack follows:');
  showAddress(addr);
  FillCallStack(st, false);
  for a := 0 to 1 do
    begin
    if (st[a] = nil) then
      continue;
    showAddress(st[a]);
    end;
end;
begin
  symbols := TList.Create;
  lines := TList.Create;
  readMapfile;
end.