Inheritance: System.IO.TextWriter
Exemplo n.º 1
0
        public int Run(string[] args)
        {
            // Process command line
            if (!ProcessArgs(args))
            {
                return(0);
            }

            if (_inputFile == null)
            {
                ShowLogo();
                ShowHelp();
                return(7);
            }

            // Open input file
            var code = System.IO.File.ReadAllBytes(_inputFile);

            // Work out the available address space
            _addrSpaceStart = _baseAddr;
            _addrSpaceEnd   = _baseAddr + (code.Length - _header);

            // Work out auto length
            if (_len == 0)
            {
                _len = (ushort)(code.Length - _header);
            }

            // Check specified address range
            CheckAddress(_start);
            CheckAddress(_start + _len - 1);
            foreach (var addr in _entryPoints)
            {
                CheckAddress(addr);
            }

            // Setup disassembler parameters
            Disassembler.LabelledRangeLow    = (ushort)_start;
            Disassembler.LabelledRangeHigh   = (ushort)(_start + _len);
            Disassembler.LowerCase           = _lowerCase;
            Disassembler.HtmlMode            = _htmlMode;
            Disassembler.ShowRelativeOffsets = _reloffs;

            // Disassemble
            var instructions = new Dictionary <int, Instruction>();

            if (_entryPoints.Count > 0)
            {
                var pendingCodePaths = new List <int>();
                pendingCodePaths.AddRange(_entryPoints);

                while (pendingCodePaths.Count > 0)
                {
                    // Get a new address that needs disassembling
                    int addr = pendingCodePaths[0];
                    pendingCodePaths.RemoveAt(0);

                    // Disassemble
                    while (!instructions.ContainsKey(addr) && addr >= _start && addr < _start + _len)
                    {
                        // Disassemble this instruction
                        var i = Disassembler.Disassemble(code, (ushort)(addr - _baseAddr + _header), (ushort)addr);

                        // Possible address reference?
                        if (_markWordRefs && i.word_val.HasValue && (i.opCode.flags & (OpCodeFlags.Jumps | OpCodeFlags.RefAddr)) == 0)
                        {
                            i.Comment = "address or value?";
                        }

                        // Add it
                        instructions.Add(addr, i);

                        // If have a jump address, dump it
                        if (i.next_addr_2.HasValue)
                        {
                            pendingCodePaths.Add(i.next_addr_2.Value);
                        }

                        // Continue
                        if (i.next_addr_1.HasValue)
                        {
                            addr = i.next_addr_1.Value;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
            else
            {
                // Linear disassembly
                for (int addr = _start; addr < _start + _len;)
                {
                    // Disassemble this instruction
                    var i = Disassembler.Disassemble(code, (ushort)(addr - _baseAddr + _header), (ushort)addr);

                    // Add it
                    instructions.Add(addr, i);

                    // Update address
                    addr += i.bytes;
                }
            }

            // Sort all instructions
            var sorted = instructions.Values.OrderBy(x => x.addr).ToList();

            // Helper for generating DB directives
            Func <int, int, int, int> FillData = delegate(int from, int to, int index)
            {
                for (int j = from; j < to; j++)
                {
                    var data = code[j - _baseAddr + _header];

                    // Get the byte
                    var instruction = new Instruction();
                    if (data >= 0x20 && data < 0x7f)
                    {
                        instruction.Comment = string.Format("'{0}'", (char)data);

                        if ((char)data != '\"')
                        {
                            instruction.AsciiChar = (char)data;
                        }
                    }
                    instruction.addr        = (ushort)j;
                    instruction.Asm         = string.Format("DB {0}", Disassembler.FormatByte(data));
                    instruction.next_addr_1 = (ushort)(j + 1);
                    instruction.bytes       = 1;

                    // Add to instruction map
                    instructions.Add(instruction.addr, instruction);

                    // Add to sorted list
                    sorted.Insert(index, instruction);
                    index++;
                }
                return(index);
            };

            // Fill in all unpopulated areas with DBs
            int expectedNextAddress = _start;

            for (int i = 0; i < sorted.Count; i++)
            {
                var inst = sorted[i];
                if (inst.addr != expectedNextAddress)
                {
                    i = FillData(expectedNextAddress, inst.addr, i);
                }

                expectedNextAddress = sorted[i].addr + sorted[i].bytes;
            }
            FillData(expectedNextAddress, _start + _len, instructions.Count);

            // Mark all entry points
            foreach (var e in _entryPoints)
            {
                instructions[e].entryPoint = true;
            }

            // Resolve references
            foreach (var i in instructions)
            {
                ushort?ref_addr = i.Value.next_addr_2;
                if (!ref_addr.HasValue)
                {
                    if (i.Value.word_val.HasValue && (i.Value.opCode.flags & OpCodeFlags.RefAddr) != 0)
                    {
                        ref_addr = i.Value.word_val;
                    }
                }


                if (ref_addr.HasValue)
                {
                    for (int stepback = 0; stepback < 6; stepback++)
                    {
                        Instruction target;
                        if (instructions.TryGetValue(ref_addr.Value - stepback, out target))
                        {
                            if (target.referencedFrom == null)
                            {
                                target.referencedFrom = new List <Instruction>();
                            }
                            target.referencedFrom.Add(i.Value);

                            if (stepback != 0)
                            {
                                // patch the original instruction
                                i.Value.Asm     = i.Value.Asm.Replace(Disassembler.FormatAddr(ref_addr.Value), Disassembler.FormatAddr(target.addr) + "+" + stepback.ToString());
                                i.Value.Comment = "reference not aligned to instruction";
                            }

                            break;
                        }
                    }
                }
            }

            // Coalesc Ascii strings
            if (_coalescStrings)
            {
                int coalescFrom = 0;
                for (int i = 0; i < sorted.Count; i++)
                {
                    var inst = sorted[i];

                    // Can we continue coalescing?
                    if (inst.AsciiChar == 0 || i - coalescFrom > 128 || inst.entryPoint || inst.referencedFrom != null) // || (i > 0 && sorted[i-1].next_addr_1.HasValue))
                    {
                        // Any thing to coalesc?
                        var bytesToCoalesc = i - coalescFrom;
                        if (bytesToCoalesc > 4)
                        {
                            // Get the first instruction
                            var fromInstruction = sorted[coalescFrom];

                            // Build the DB "string" string
                            var sb = new StringBuilder();
                            sb.Append("DB\t\"");
                            for (int j = 0; j < bytesToCoalesc; j++)
                            {
                                sb.Append((char)code[fromInstruction.addr - _baseAddr + _header + j]);
                            }
                            sb.Append("\"");

                            // Create the new instruction
                            var instruction = new Instruction();
                            instruction.addr           = fromInstruction.addr;
                            instruction.next_addr_1    = inst.addr;
                            instruction.bytes          = (ushort)bytesToCoalesc;
                            instruction.Asm            = sb.ToString();
                            instruction.referencedFrom = fromInstruction.referencedFrom;

                            // Remove the old instructions
                            for (int j = 0; j < bytesToCoalesc; j++)
                            {
                                instructions.Remove(fromInstruction.addr + j);
                                sorted.RemoveAt(coalescFrom);
                            }

                            // Insert the new instruction
                            instructions.Add(fromInstruction.addr, instruction);
                            sorted.Insert(coalescFrom, instruction);

                            // Rewind index
                            i = coalescFrom;
                        }

                        // Start next coalesc from the next instruction
                        coalescFrom = i + 1;
                    }
                }
            }

            TextWriter targetWriter = Console.Out;

            if (_outputFile != null)
            {
                targetWriter = new StreamWriter(_outputFile);
            }

            TextWriter w;

            if (_htmlMode)
            {
                w = targetWriter;
            }
            else
            {
                w = new TabbedTextWriter(targetWriter);
                if (_lst)
                {
                    ((TabbedTextWriter)w).TabStops = new int[] { 32, 40, 48, 56, 64 };
                }
                else
                {
                    ((TabbedTextWriter)w).TabStops = new int[] { 8, 16, 32 };
                }
            }

            if (_htmlMode)
            {
                w.WriteLine("<html>");
                w.WriteLine("<head>");
                w.WriteLine("</head>");
                w.WriteLine("<body>");
                w.WriteLine("<pre><code>");
            }

            // Analyse call graph
            var cg = new CallGraphAnalyzer(instructions, sorted);
            Dictionary <int, Proc> procs = cg.Analyse();


            // Write out the "ORG" directive
            if (sorted.Count > 0 && !_lst)
            {
                w.WriteLine("\n\tORG\t{0}\n", Disassembler.FormatWord(sorted[0].addr));
            }

            // List it
            Instruction prev = null;

            foreach (var i in sorted)
            {
                // Blank line after data blocks
                if (prev != null && prev.opCode == null && i.opCode != null)
                {
                    w.WriteLine();
                }

                if (_htmlMode)
                {
                    w.Write("<a name=\"L{0:X4}\"></a>", i.addr);
                }

                // Include cross references?
                if (_xref && i.referencedFrom != null)
                {
                    // Ensure a blank line before reference comments
                    if (prev != null && prev.next_addr_1.HasValue)
                    {
                        w.WriteLine();
                    }

                    if (_lst)
                    {
                        w.Write("{0}\t", new string(' ', 23));
                    }

                    w.WriteLine("\t; Referenced from {0}",
                                string.Join(", ", i.referencedFrom.Select(x => Disassembler.FormatAddr(x.addr, true, false)).ToList()));
                }

                if (i.entryPoint)
                {
                    if (_lst)
                    {
                        w.Write("{0}\t", new string(' ', 23));
                    }
                    w.WriteLine("\t; Entry Point");
                }

                if (procs.ContainsKey(i.addr))
                {
                    if (_lst)
                    {
                        w.Write("{0}\t", new string(' ', 23));
                    }
                    w.WriteLine("\t; --- START PROC {0} ---", Disassembler.FormatAddr(i.addr, false, true));
                }

                if (_lst)
                {
                    w.Write("{0:X4}:", i.addr);
                    for (int j = 0; j < Math.Min((int)i.bytes, 8); j++)
                    {
                        var data = code[i.addr + j - _baseAddr + _header];
                        w.Write(" {0:X2}", data);
                    }

                    int spaces = 3 * (6 - i.bytes);

                    if (spaces > 0)
                    {
                        w.Write(new string(' ', spaces));
                    }
                    w.Write("\t ");
                }

                // Work out label
                string label = "";
                if (i.entryPoint || i.referencedFrom != null || (prev != null && !prev.next_addr_1.HasValue))
                {
                    label  = Disassembler.FormatAddr(i.addr, false);
                    label += ":";
                }

                // Write the disassembled instruction
                var asm = i.Asm;
                if (!asm.StartsWith("DB\t\""))
                {
                    asm = asm.Replace(" ", "\t");
                }

                w.Write("{0}\t{1}", label, asm);

                // Write out an optional comment
                if (i.Comment != null)
                {
                    w.Write("\t; {0}", i.Comment);
                }

                if (_lst)
                {
                    if (i.bytes > 8)
                    {
                        for (int j = 8; j < i.bytes; j++)
                        {
                            if ((j % 8) == 0)
                            {
                                w.Write("\n{0:X4}:", i.addr + j);
                            }

                            var data = code[i.addr + j - _baseAddr + _header];
                            w.Write(" {0:X2}", data);
                        }
                    }
                }

                if (_htmlMode)
                {
                    w.WriteLine("</a>");
                }
                else
                {
                    w.WriteLine();
                }


                // If this instruction doesn't continue on, insert a blank line
                if (!i.next_addr_1.HasValue)
                {
                    w.WriteLine();
                }

                // Remember the previous instruction
                prev = i;
            }

            if (_lst)
            {
                // Build a list of all possible address references
                Dictionary <int, AddressInfo> addressInfos = new Dictionary <int, AddressInfo>();
                Dictionary <int, PortInfo>    portInfos    = new Dictionary <int, PortInfo>();
                foreach (var i in sorted)
                {
                    // Does this instruction reference a word value?
                    if (i.word_val.HasValue)
                    {
                        AddressInfo ai;
                        if (!addressInfos.TryGetValue(i.word_val.Value, out ai))
                        {
                            ai = new AddressInfo(i.word_val.Value);
                            addressInfos.Add(ai.addr, ai);
                        }

                        if ((i.opCode.flags & OpCodeFlags.RefAddr) != 0)
                        {
                            // Known referenced data address
                            ai.DataReferences.Add(i);
                        }

                        if ((i.opCode.flags & OpCodeFlags.Jumps) != 0)
                        {
                            // Known referenced code address
                            ai.CodeReferences.Add(i);
                        }

                        if ((i.opCode.flags & (OpCodeFlags.Jumps | OpCodeFlags.RefAddr)) == 0)
                        {
                            // Potential address
                            ai.PotentialReferences.Add(i);
                        }
                    }

                    // Is it a port reference?
                    if (i.opCode != null && (i.opCode.flags & OpCodeFlags.PortRef) != 0)
                    {
                        // Which port (-1, referenced through a register)
                        int port = -1;
                        if (i.byte_val.HasValue)
                        {
                            port = i.byte_val.Value;
                        }

                        // Get the port info
                        PortInfo pi;
                        if (!portInfos.TryGetValue(port, out pi))
                        {
                            pi = new PortInfo(port);
                            portInfos.Add(port, pi);
                        }

                        pi.References.Add(i);
                    }
                }

                if (w is TabbedTextWriter)
                {
                    ((TabbedTextWriter)w).TabStops = new int[] { 8, 16, 24, 32 }
                }
                ;

                // Build a list of all external references
                var extRefs = addressInfos.Values.Where(x => x.addr < _start || x.addr >= _start + _len).OrderBy(x => x.addr);
                foreach (var r in extRefs)
                {
                    if (r.DataReferences.Count > 0 || r.CodeReferences.Count > 0)
                    {
                        w.WriteLine("\nreferences to external address {0}:", Disassembler.FormatAddr((ushort)r.addr, true, false));

                        foreach (var i in (r.DataReferences.Concat(r.CodeReferences).Concat(r.PotentialReferences)).OrderBy(x => x.addr))
                        {
                            w.WriteLine("\t{0} {1}", Disassembler.FormatAddr(i.addr, true, false), i.Asm);
                        }
                    }
                }

                foreach (var r in addressInfos.Values.Where(x => (x.addr >= _start && x.addr < _start + _len) && x.PotentialReferences.Count > 0).OrderBy(x => x.addr))
                {
                    w.WriteLine("\npossible references to internal address {0}:", Disassembler.FormatAddr((ushort)r.addr, true, false));
                    ListPotentialAddresses(w, r);
                }

                foreach (var r in addressInfos.Values.Where(x => (x.addr < _start || x.addr >= _start + _len) && x.PotentialReferences.Count > 0).OrderBy(x => x.addr))
                {
                    w.WriteLine("\npossible references to external address {0}:", Disassembler.FormatAddr((ushort)r.addr, true, false));
                    ListPotentialAddresses(w, r);
                }

                foreach (var r in portInfos.Values.OrderBy(x => x.port))
                {
                    if (r.port == -1)
                    {
                        w.WriteLine("\nport references through a register:");
                    }
                    else
                    {
                        w.WriteLine("\nreferences to port {0}", Disassembler.FormatByte((byte)r.port));
                    }

                    foreach (var i in r.References.OrderBy(x => x.opCode.mnemonic[0]).ThenBy(x => x.addr))
                    {
                        w.WriteLine("\t{0} {1}", Disassembler.FormatAddr(i.addr, true, false), i.Asm);
                    }
                }

                // Dump all procs
                w.WriteLine("\nProcedures ({0}):", procs.Count);
                w.WriteLine("  Proc  Length  References Dependants");
                foreach (var p in procs.Values.OrderBy(x => x.firstInstruction.addr))
                {
                    w.WriteLine("  {0}  {1:X4}   {2,10} {3,10}", Disassembler.FormatAddr(p.firstInstruction.addr, true, true), p.lengthInBytes, p.firstInstruction.referencedFrom == null ? 0 : p.firstInstruction.referencedFrom.Count, p.dependants.Count);
                }

                // Dump call graph
                w.WriteLine("\nCall Graph:");

                List <int>   CallStack     = new List <int>();
                Action <int> DumpCallGraph = null;
                DumpCallGraph = delegate(int addr)
                {
                    // Display the proc address, indented
                    w.Write("{0}{1}", new String(' ', CallStack.Count * 2), Disassembler.FormatAddr((ushort)addr, true, true));

                    if (CallStack.Count == 0)
                    {
                        w.Write(" - Entry Point");
                    }

                    // Is it a recursive call?
                    if (CallStack.Contains(addr))
                    {
                        w.WriteLine(" - Recursive");
                        return;
                    }

                    // Is it external?
                    Proc p;
                    if (!procs.TryGetValue(addr, out p))
                    {
                        w.WriteLine(" - External");
                        return;
                    }

                    // Dump dependants
                    w.WriteLine();
                    CallStack.Add(addr);
                    foreach (var d in p.dependants)
                    {
                        DumpCallGraph(d);
                    }
                    CallStack.RemoveAt(CallStack.Count - 1);
                };

                foreach (var ep in _entryPoints.OrderBy(x => x))
                {
                    // Get the proc's entry point
                    DumpCallGraph(ep);
                }
            }

            if (_htmlMode)
            {
                w.WriteLine("</code></pre>");
                w.WriteLine("</body>");
                w.WriteLine("</head>");
            }


            // Close file
            w.Close();
            if (_outputFile != null)
            {
                targetWriter.Close();
            }

            // Open the file for browsing?
            if (_autoOpen && _outputFile != null)
            {
                Process.Start(_outputFile);
            }


            return(0);
        }
Exemplo n.º 2
0
        public int Run(string[] args)
        {
            // Process command line
            if (!ProcessArgs(args))
                return 0;

            if (_inputFile == null)
            {
                ShowLogo();
                ShowHelp();
                return 7;
            }

            // Open input file
            var code = System.IO.File.ReadAllBytes(_inputFile);

            // Work out the available address space
            _addrSpaceStart = _baseAddr;
            _addrSpaceEnd = (ushort)(_baseAddr + (code.Length - _header));

            // Work out auto length
            if (_len == 0)
                _len = (ushort)(code.Length - _header);

            // Check specified address range
            CheckAddress(_start);
            CheckAddress(_start + _len);
            foreach (var addr in _entryPoints)
                CheckAddress(addr);

            // Setup disassembler parameters
            Disassembler.LabelledRangeLow = (ushort)_start;
            Disassembler.LabelledRangeHigh = (ushort)(_start + _len);
            Disassembler.LowerCase = _lowerCase;
            Disassembler.HtmlMode = _htmlMode;
            Disassembler.ShowRelativeOffsets = _reloffs;

            // Disassemble
            var instructions = new Dictionary<int, Instruction>();
            if (_entryPoints.Count > 0)
            {
                var pendingCodePaths = new List<int>();
                pendingCodePaths.AddRange(_entryPoints);

                while (pendingCodePaths.Count > 0)
                {
                    // Get a new address that needs disassembling
                    int addr = pendingCodePaths[0];
                    pendingCodePaths.RemoveAt(0);

                    // Disassemble
                    while (!instructions.ContainsKey(addr) && addr >= _start && addr < _start + _len)
                    {
                        // Disassemble this instruction
                        var i = Disassembler.Disassemble(code, (ushort)(addr - _baseAddr + _header), (ushort)addr);

                        // Possible address reference?
                        if (_markWordRefs && i.word_val.HasValue && (i.opCode.flags & (OpCodeFlags.Jumps | OpCodeFlags.RefAddr)) == 0)
                        {
                            i.Comment = "address or value?";
                        }

                        // Add it
                        instructions.Add(addr, i);

                        // If have a jump address, dump it
                        if (i.next_addr_2.HasValue)
                        {
                            pendingCodePaths.Add(i.next_addr_2.Value);
                        }

                        // Continue
                        if (i.next_addr_1.HasValue)
                            addr = i.next_addr_1.Value;
                        else
                            break;

                    }
                }
            }
            else
            {
                // Linear disassembly
                for (int addr = _start; addr < _start + _len; )
                {
                    // Disassemble this instruction
                    var i = Disassembler.Disassemble(code, (ushort)(addr - _baseAddr + _header), (ushort)addr);

                    // Add it
                    instructions.Add(addr, i);

                    // Update address
                    addr += i.bytes;
                }
            }

            // Sort all instructions
            var sorted = instructions.Values.OrderBy(x => x.addr).ToList();

            // Helper for generating DB directives
            Func<int, int, int, int> FillData = delegate(int from, int to, int index)
            {
                for (int j = from; j < to; j++)
                {
                    var data = code[j - _baseAddr + _header];

                    // Get the byte
                    var instruction = new Instruction();
                    if (data >= 0x20 && data <= 0x7f)
                        instruction.Comment = string.Format("'{0}'", (char)data);
                    instruction.addr = (ushort)j;
                    instruction.Asm = string.Format("DB {0}", Disassembler.FormatByte(data));
                    instruction.next_addr_1 = (ushort)(j + 1);
                    instruction.bytes = 1;

                    // Add to instruction map
                    instructions.Add(instruction.addr, instruction);

                    // Add to sorted list
                    sorted.Insert(index, instruction);
                    index++;
                }
                return index;
            };

            // Fill in all unpopulated areas with DBs
            int expectedNextAddress = _start;
            for (int i = 0; i < sorted.Count; i++)
            {
                var inst = sorted[i];
                if (inst.addr != expectedNextAddress)
                {
                    i = FillData(expectedNextAddress, inst.addr, i);
                }

                expectedNextAddress = sorted[i].addr + sorted[i].bytes;
            }
            FillData(expectedNextAddress, _start + _len, instructions.Count);

            // Mark all entry points
            foreach (var e in _entryPoints)
            {
                instructions[e].entryPoint = true;
            }

            // Resolve references
            foreach (var i in instructions)
            {
                ushort? ref_addr = i.Value.next_addr_2;
                if (!ref_addr.HasValue)
                {
                    if (i.Value.word_val.HasValue && (i.Value.opCode.flags & OpCodeFlags.RefAddr) != 0)
                        ref_addr = i.Value.word_val;
                }

                if (ref_addr.HasValue)
                {
                    for (int stepback = 0; stepback < 6; stepback++)
                    {
                        Instruction target;
                        if (instructions.TryGetValue(ref_addr.Value-stepback, out target))
                        {
                            if (target.referencedFrom == null)
                                target.referencedFrom = new List<Instruction>();
                            target.referencedFrom.Add(i.Value);

                            if (stepback != 0)
                            {
                                // patch the original instruction
                                i.Value.Asm = i.Value.Asm.Replace(Disassembler.FormatAddr(ref_addr.Value), Disassembler.FormatAddr(target.addr) + "+" + stepback.ToString());
                                i.Value.Comment = "reference not aligned to instruction";
                            }

                            break;
                        }
                    }
                }
            }

            TextWriter targetWriter = Console.Out;
            if (_outputFile != null)
            {
                targetWriter = new StreamWriter(_outputFile);
            }

            TextWriter w;

            if (_htmlMode)
            {
                w = targetWriter;
            }
            else
            {
                w = new TabbedTextWriter(targetWriter);
                if (_lst)
                {
                    ((TabbedTextWriter)w).TabStops = new int[] { 32, 40, 48, 56, 64 };
                }
                else
                {
                    ((TabbedTextWriter)w).TabStops = new int[] { 8, 16, 32 };
                }
            }

            if (_htmlMode)
            {
                w.WriteLine("<html>");
                w.WriteLine("<head>");
                w.WriteLine("</head>");
                w.WriteLine("<body>");
                w.WriteLine("<pre><code>");
            }

            // Analyse call graph
            var cg = new CallGraphAnalyzer(instructions, sorted);
            Dictionary<int, Proc> procs = cg.Analyse();

            // Write out the "ORG" directive
            if (sorted.Count > 0 && !_lst)
            {
                w.WriteLine("\n\tORG\t{0}\n", Disassembler.FormatWord(sorted[0].addr));
            }

            // List it
            Instruction prev = null;
            foreach (var i in sorted)
            {
                // Blank line after data blocks
                if (prev != null && prev.opCode == null && i.opCode != null)
                {
                    w.WriteLine();
                }

                if (_htmlMode)
                    w.Write("<a name=\"L{0:X4}\"></a>", i.addr);

                // Include cross references?
                if (_xref && i.referencedFrom != null)
                {
                    // Ensure a blank line before reference comments
                    if (prev != null && prev.next_addr_1.HasValue)
                    {
                        w.WriteLine();
                    }

                    if (_lst)
                        w.Write("{0}\t", new string(' ', 23));

                    w.WriteLine("\t; Referenced from {0}",
                            string.Join(", ", i.referencedFrom.Select(x => Disassembler.FormatAddr(x.addr, true, false)).ToList()));
                }

                if (i.entryPoint)
                {
                    if (_lst)
                        w.Write("{0}\t", new string(' ', 23));
                    w.WriteLine("\t; Entry Point");
                }

                if (procs.ContainsKey(i.addr))
                {
                    if (_lst)
                        w.Write("{0}\t", new string(' ', 23));
                    w.WriteLine("\t; --- START PROC {0} ---", Disassembler.FormatAddr(i.addr, false, true));
                }

                if (_lst)
                {
                    w.Write("{0:X4}:", i.addr);
                    for (int j = 0; j < i.bytes; j++)
                    {
                        var data = code[i.addr + j - _baseAddr + _header];
                        w.Write(" {0:X2}", data);
                    }

                    w.Write(new string(' ', 3 * (6 - i.bytes)));
                    w.Write("\t ");
                }

                // Work out label
                string label = "";
                if (i.entryPoint || i.referencedFrom != null || (prev != null && !prev.next_addr_1.HasValue))
                {
                    label = Disassembler.FormatAddr(i.addr, false);
                    label += ":";
                }

                // Write the disassembled instruction
                w.Write("{0}\t{1}", label, i.Asm.Replace(" ", "\t"));

                // Write out an optional comment
                if (i.Comment != null)
                    w.Write("\t; {0}", i.Comment);

                if (_htmlMode)
                    w.WriteLine("</a>");
                else
                    w.WriteLine();

                // If this instruction doesn't continue on, insert a blank line
                if (!i.next_addr_1.HasValue)
                {
                    w.WriteLine();
                }

                // Remember the previous instruction
                prev = i;
            }

            if (_lst)
            {
                // Build a list of all possible address references
                Dictionary<int, AddressInfo> addressInfos = new Dictionary<int, AddressInfo>();
                Dictionary<int, PortInfo> portInfos = new Dictionary<int, PortInfo>();
                foreach (var i in sorted)
                {
                    // Does this instruction reference a word value?
                    if (i.word_val.HasValue)
                    {
                        AddressInfo ai;
                        if (!addressInfos.TryGetValue(i.word_val.Value, out ai))
                        {
                            ai = new AddressInfo(i.word_val.Value);
                            addressInfos.Add(ai.addr, ai);
                        }

                        if ((i.opCode.flags & OpCodeFlags.RefAddr) != 0)
                        {
                            // Known referenced data address
                            ai.DataReferences.Add(i);
                        }

                        if ((i.opCode.flags & OpCodeFlags.Jumps) != 0)
                        {
                            // Known referenced code address
                            ai.CodeReferences.Add(i);
                        }

                        if ((i.opCode.flags & (OpCodeFlags.Jumps | OpCodeFlags.RefAddr)) == 0)
                        {
                            // Potential address
                            ai.PotentialReferences.Add(i);
                        }
                    }

                    // Is it a port reference?
                    if (i.opCode != null && (i.opCode.flags & OpCodeFlags.PortRef) != 0)
                    {
                        // Which port (-1, referenced through a register)
                        int port = -1;
                        if (i.byte_val.HasValue)
                            port = i.byte_val.Value;

                        // Get the port info
                        PortInfo pi;
                        if (!portInfos.TryGetValue(port, out pi))
                        {
                            pi = new PortInfo(port);
                            portInfos.Add(port, pi);
                        }

                        pi.References.Add(i);
                    }
                }

                if (w is TabbedTextWriter)
                    ((TabbedTextWriter)w).TabStops = new int[] { 8, 16, 24, 32 };

                // Build a list of all external references
                var extRefs = addressInfos.Values.Where(x => x.addr < _start || x.addr >= _start + _len).OrderBy(x => x.addr);
                foreach (var r in extRefs)
                {
                    if (r.DataReferences.Count > 0 || r.CodeReferences.Count > 0)
                    {
                        w.WriteLine("\nreferences to external address {0}:", Disassembler.FormatAddr((ushort)r.addr, true, false));

                        foreach (var i in (r.DataReferences.Concat(r.CodeReferences).Concat(r.PotentialReferences)).OrderBy(x => x.addr))
                        {
                            w.WriteLine("\t{0} {1}", Disassembler.FormatAddr(i.addr, true, false), i.Asm);
                        }

                    }
                }

                foreach (var r in addressInfos.Values.Where(x => (x.addr >= _start && x.addr < _start + _len) && x.PotentialReferences.Count > 0).OrderBy(x => x.addr))
                {
                    w.WriteLine("\npossible references to internal address {0}:", Disassembler.FormatAddr((ushort)r.addr, true, false));
                    ListPotentialAddresses(w, r);
                }

                foreach (var r in addressInfos.Values.Where(x => (x.addr < _start || x.addr >= _start + _len) && x.PotentialReferences.Count > 0).OrderBy(x => x.addr))
                {
                    w.WriteLine("\npossible references to external address {0}:", Disassembler.FormatAddr((ushort)r.addr, true, false));
                    ListPotentialAddresses(w, r);
                }

                foreach (var r in portInfos.Values.OrderBy(x => x.port))
                {
                    if (r.port == -1)
                    {
                        w.WriteLine("\nport references through a register:");
                    }
                    else
                    {
                        w.WriteLine("\nreferences to port {0}", Disassembler.FormatByte((byte)r.port));
                    }

                    foreach (var i in r.References.OrderBy(x => x.opCode.mnemonic[0]).ThenBy(x => x.addr))
                    {
                        w.WriteLine("\t{0} {1}", Disassembler.FormatAddr(i.addr, true, false), i.Asm);
                    }

                }

                // Dump all procs
                w.WriteLine("\nProcedures ({0}):", procs.Count);
                w.WriteLine("  Proc  Length  References Dependants");
                foreach (var p in procs.Values.OrderBy(x=>x.firstInstruction.addr))
                {
                    w.WriteLine("  {0}  {1:X4}   {2,10} {3,10}", Disassembler.FormatAddr(p.firstInstruction.addr, true, true), p.lengthInBytes, p.firstInstruction.referencedFrom == null ? 0 : p.firstInstruction.referencedFrom.Count, p.dependants.Count);
                }

                // Dump call graph
                w.WriteLine("\nCall Graph:");

                List<int> CallStack = new List<int>();
                Action<int> DumpCallGraph = null;
                DumpCallGraph = delegate(int addr)
                {
                    // Display the proc address, indented
                    w.Write("{0}{1}", new String(' ', CallStack.Count * 2), Disassembler.FormatAddr((ushort)addr, true, true));

                    if (CallStack.Count == 0)
                    {
                        w.Write(" - Entry Point");
                    }

                    // Is it a recursive call?
                    if (CallStack.Contains(addr))
                    {
                        w.WriteLine(" - Recursive");
                        return;
                    }

                    // Is it external?
                    Proc p;
                    if (!procs.TryGetValue(addr, out p))
                    {
                        w.WriteLine(" - External");
                        return;
                    }

                    // Dump dependants
                    w.WriteLine();
                    CallStack.Add(addr);
                    foreach (var d in p.dependants)
                    {
                        DumpCallGraph(d);
                    }
                    CallStack.RemoveAt(CallStack.Count - 1);
                };

                foreach (var ep in _entryPoints.OrderBy(x => x))
                {
                    // Get the proc's entry point
                    DumpCallGraph(ep);
                }

            }

            if (_htmlMode)
            {
                w.WriteLine("</code></pre>");
                w.WriteLine("</body>");
                w.WriteLine("</head>");
            }

            // Close file
            w.Close();
            if (_outputFile != null)
            {
                targetWriter.Close();
            }

            // Open the file for browsing?
            if (_autoOpen && _outputFile!=null)
            {
                Process.Start(_outputFile);
            }

            return 0;
        }