public Proc(Instruction instruction) { if (instruction.proc != null) throw new InvalidOperationException("Instruction already associated with another procedure"); firstInstruction = instruction; firstInstruction.proc = this; }
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; }
public static Instruction Disassemble(byte[] buffer, ushort offsetInBuffer, ushort addr) { Func<byte> readByte = () => { addr++; return (byte)(offsetInBuffer<buffer.Length ? buffer[offsetInBuffer++] : 0); }; var i = new Instruction(); i.addr = addr; var start_addr = addr; byte opc = readByte(); byte disp_u = 0; ushort jump_addr = 0; bool have_jump_addr = false; OpCode dasm = null; bool have_disp = false; switch(opc) { case 0xDD: case 0xFD: byte next = readByte(); if((next | 0x20) == 0xFD || next == 0xED) { i.Asm = "NOP*"; i.t_states=4; dasm=null; } else if(next == 0xCB) { disp_u = readByte(); next = readByte(); dasm = (opc==0xDD)? OpCodes.dasm_ddcb[next]: OpCodes.dasm_fdcb[next]; have_disp=true; } else { dasm = (opc==0xDD)? OpCodes.dasm_dd[next]: OpCodes.dasm_fd[next]; if(dasm.mnemonic == null) //mirrored instructions { dasm = OpCodes.dasm_base[next]; i.t_states=4; i.t_states2=4; } } break; case 0xED: next = readByte(); dasm = OpCodes.dasm_ed[next]; if(dasm.mnemonic == null) { i.Asm = "NOP*"; i.t_states=8; dasm=null; } break; case 0xCB: next = readByte(); dasm = OpCodes.dasm_cb[next]; break; default: dasm = OpCodes.dasm_base[opc]; break; } if (dasm != null) { i.opCode = dasm; var sb = new StringBuilder(); foreach (var ch in LowerCase ? dasm.mnemonic.ToLower() : dasm.mnemonic) { switch (ch) { case '@': { var lo = readByte(); var hi = readByte(); ushort val = (ushort)(lo + hi * 0x100); if ((dasm.flags & (OpCodeFlags.RefAddr | OpCodeFlags.Jumps)) != 0) sb.Append(FormatAddr(val)); else sb.Append(FormatWord(val)); i.word_val = val; jump_addr = val; have_jump_addr = true; break; } case '$': case '%': { if (!have_disp) disp_u = readByte(); var disp = (disp_u & 0x80) != 0 ? -(((~disp_u) & 0x7f) + 1) : disp_u; if (ShowRelativeOffsets) { if (disp > 0) { i.Comment = string.Format("+{0}", disp); } else { i.Comment = string.Format("{0}", disp); } } if (ch == '$') sb.Append(FormatByte((byte)disp)); else { jump_addr = (ushort)(addr + disp); have_jump_addr = true; sb.Append(FormatAddr(jump_addr)); } break; } case '#': { var lo = readByte(); sb.Append(FormatByte(lo)); if (lo>=0x20 && lo<=0x7f) i.Comment = string.Format("'{0}'", (char)lo); i.byte_val = lo; break; } default: sb.Append(ch); break; } } i.Asm = sb.ToString(); i.t_states += dasm.t_states; i.t_states2 += dasm.t_states2; // Return continue address if ((dasm.flags & OpCodeFlags.Continues) != 0) i.next_addr_1 = addr; // Return jump target address (if have it) if ((dasm.flags & OpCodeFlags.Jumps) != 0 && have_jump_addr) { i.next_addr_2 = jump_addr; } } else { i.next_addr_1 = addr; } if (i.t_states == i.t_states2) i.t_states2 = 0; i.bytes = (ushort)(addr - start_addr); return i; }