BizHawk/BizHawk.Emulation/CPUs/ARM/ARM.Disassembly.cs

506 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
namespace BizHawk.Emulation.CPUs.ARM
{
partial class ARM
{
public class DisassemblyOptions
{
//TODO - refactor these to NumberOptions or somesuch and have several of those
//0 = unpadded ; 1,2,4 = numbytes: -1 = no hex
public int SVC_hex;
public bool SVC_hexPrefix, SVC_literalPrefix, SVC_caps;
public bool SVC_showName = true;
public void SetNstyle()
{
SVC_hex = 0;
SVC_hexPrefix = false;
SVC_literalPrefix = false;
SVC_caps = true;
hideZeroOffset = true;
//showExplicitAccumulateRegisters = false;
}
public bool showoptadd = false;
//fixes retarded debugger label format by adding #0x in front of it
public bool fix_nstyle_label = true;
//TODO - subsume nstyle
//show all registers when accumulating values, i.e. add r0, r0, #4 (instead of add r0, #4)
//TODO - obseleted by rd~rx approach
public bool showExplicitAccumulateRegisters = true;
//hide offsets of #0 (set to true for more terseness with no real loss of information)
public bool hideZeroOffset = false;
//compute PC relative labels for certain operations (like LDR)
//public bool computePCLabel = true;
//consider:
//in RVCT many (all?) thumb instructions dont show {S} suffix in thumb mode, because it is sort of redundant (since they always apply in many thumb instructions)
//but this is gay. well, consider making it an option
}
public class ProcessorOptions
{
public void SetNStyle()
{
Thumb_LSL_immediate_T1_0_is_MOV_register_T2 = false;
}
public bool Thumb_LSL_immediate_T1_0_is_MOV_register_T2 = true;
}
public DisassemblyOptions disopt = new DisassemblyOptions();
public ProcessorOptions procopt = new ProcessorOptions();
bool DIS_Check(object[] args, int argnum, string str, string find)
{
if (args.Length <= argnum) return false;
int index = str.IndexOf(find);
if (index == -1) return false;
return true;
}
string disformat_num(object item, bool caps, bool literalprefix, bool hexprefix, int hex)
{
string hexformat;
switch (hex)
{
case -1: hexformat = "{0}"; break;
case 0: hexformat = "{0:x}"; break;
case 1: hexformat = "{0:x2}"; break;
case 2: hexformat = "{0:x4}"; break;
case 4: hexformat = "{0:x8}"; break;
default: throw new ArgumentException();
}
string value = string.Format(hexformat, item);
if (caps) value = value.ToUpper();
string ret = string.Format("{0}{1}{2}", literalprefix ? "#" : "", hexprefix ? "0x" : "", value);
return ret;
}
uint DIS(string opcode, string format, params object[] args)
{
//options:
//capital reg names
//capital mnemonic
//tab between mnemonic and arguments (always? sometimes)
//0 front-pad hex arguments
const bool capMnemonic = true;
if (DIS_Check(args, 0, opcode, "/s0/")) opcode = opcode.Replace("/s0/", (bool)args[0] ? "s" : "");
if (_CurrentCond() == 14)
opcode = opcode.Replace("/c/", "");
else opcode = opcode.Replace("/c/", CC_strings[_CurrentCond()]);
if (capMnemonic) opcode = opcode.ToUpper();
string ret = string.Format(opcode + " " + format, args);
if (DIS_Check(args, 0, ret, "/a0/")) ret = ret.Replace("/a0/", string.Format("{0:x}", args[0])); //address format
if (DIS_Check(args, 1, ret, "/a1/")) ret = ret.Replace("/a1/", string.Format("{0:x}", args[1]));
if (DIS_Check(args, 0, ret, "/r0/")) ret = ret.Replace("/r0/", Reg_names[(uint)args[0]].ToLower());
if (DIS_Check(args, 1, ret, "/r1/")) ret = ret.Replace("/r1/", Reg_names[(uint)args[1]].ToLower());
if (DIS_Check(args, 2, ret, "/r2/")) ret = ret.Replace("/r2/", Reg_names[(uint)args[2]].ToLower());
if (DIS_Check(args, 0, ret, "/imm8_0/")) ret = ret.Replace("/imm8_0/", string.Format("#{0}", (uint)args[0]));
if (DIS_Check(args, 1, ret, "/imm8_1/")) ret = ret.Replace("/imm8_1/", string.Format("#{0}", (uint)args[1]));
if (DIS_Check(args, 1, ret, "/uimm32h_1/")) ret = ret.Replace("/uimm32h_1/", string.Format("#0x{0:x}", (uint)args[1]));
if (DIS_Check(args, 0, ret, "/label0/")) ret = ret.Replace("/label0/", string.Format("{0:x}", args[0]));
if (DIS_Check(args, 1, ret, "/label1/")) ret = ret.Replace("/label1/", string.Format("{0:x}", args[1]));
if (DIS_Check(args, 0, ret, "/svc0/")) ret = ret.Replace("/svc0/", disformat_num(args[0], disopt.SVC_caps, disopt.SVC_literalPrefix, disopt.SVC_hexPrefix, disopt.SVC_hex));
if (DIS_Check(args, 0, ret, "/const0/")) ret = ret.Replace("/const0/", "#0x" + string.Format("{0:x}", args[0]).ToUpper());
if (DIS_Check(args, 1, ret, "/const1/")) ret = ret.Replace("/const1/", "#0x" + string.Format("{0:x}", args[1]).ToUpper());
if (DIS_Check(args, 2, ret, "/const2/")) ret = ret.Replace("/const2/", "#0x" + string.Format("{0:x}", args[2]).ToUpper());
//TODO - run constN through the disformat scheme
ret = DIS_Reglist(ret, args);
disassembly = ret;
return 0;
}
string DISNEW_optaddsub(object value)
{
return (bool)value ? (disopt.showoptadd ? "+" : "") : "-";
}
uint DISNEW(string opcode, string format, params object[] args)
{
bool hasComment = false;
const bool capMnemonic = true;
int index = 0;
int tagindex = -1;
int argindex = 0;
uint regReport = 0;
//-----
//work on opcode
if (opcode.IndexOf("<s?>") != -1)
{
bool s = (bool)args[argindex++];
opcode = opcode.Replace("<s?>", s ? "s" : "");
}
//TODO - look for cmaybe and get rid of them.
//alternatively, do a good job placing them and just always choose to put them there.
//well, we can do that as a separate polish pass later
bool cmaybe = (opcode.IndexOf("<c?>") != -1);
bool usec = true;
if (cmaybe)
usec = (bool)args[argindex++];
string crepl = cmaybe ? "<c?>" : "<c>";
if (usec)
{
if (_CurrentCond() == 14)
opcode = opcode.Replace(crepl, "");
else opcode = opcode.Replace(crepl, CC_strings[_CurrentCond()]);
}
else opcode = opcode.Replace(crepl, "");
if(opcode.Contains("{.fpsize}"))
opcode = opcode.Replace("<{.fpsize}>",".F" + args[argindex++].ToString());
if (opcode.Contains(".fpsize"))
opcode = opcode.Replace("<.fpsize>", ".F" + args[argindex++].ToString());
//---------
string cpcomment = null;
while (index < format.Length)
{
if (tagindex != -1)
{
if (format[index] == '>')
{
int len = index - tagindex - 1;
string item = format.Substring(tagindex + 1, len).ToLower();
switch (item)
{
case "rt":
case "rm":
case "rn":
case "rd":
{
item = Reg_names[(uint)args[argindex++]].ToLower();
break;
}
case "rt!":
case "rm!":
case "rn!":
case "rd!":
case "sp!":
{
uint reg;
if (item == "sp!") reg = 13;
else reg = (uint)args[argindex++];
item = Reg_names[reg].ToLower();
regReport |= (uint)(1 << (int)reg);
break;
}
case "{rd!~rm, }":
case "{rd!~rn, }":
{
uint rd = (uint)args[argindex++];
uint rx = (uint)args[argindex++];
if (disopt.showExplicitAccumulateRegisters || rd != rx)
item = string.Format("{0}, ", Reg_names[rd].ToLower());
else item = "";
break;
}
case "sdd":
case "sdn":
case "sdm":
if ((bool)args[argindex++])
item = "s" + args[argindex++].ToString();
else
item = "d" + args[argindex++].ToString();
break;
case "{sdd~sdn, }":
{
bool s = (bool)args[argindex++];
uint rd = (uint)args[argindex++];
uint rx = (uint)args[argindex++];
if (disopt.showExplicitAccumulateRegisters || rd != rx)
item = string.Format("{0}{1}, ", s ? "s" : "d", rd);
else item = "";
break;
}
case "fpscr": item = nstyle ? "fpscr" : "FPSCR"; break;
case "const": item = string.Format("0x{0:x}", args[argindex++]); break;
case "imm": item = string.Format("0x{0:x}", args[argindex++]); break;
case "imm5": item = string.Format("0x{0:x}", args[argindex++]); break;
case "{ ,#imm}":
{
uint temp = (uint)args[argindex++];
if (temp == 0) item = "";
else item = string.Format(",#0x{0:x}", temp);
break;
}
case "{, #+/-imm}":
{
uint temp = (uint)args[argindex + 1];
if (temp == 0 && disopt.hideZeroOffset) item = "";
else item = string.Format(",#{0}0x{1:x}", DISNEW_optaddsub(args[argindex]), temp);
argindex++;
break;
}
//case "labelaccess": //same as label but with [] around it
case "label":
if (disopt.fix_nstyle_label)
item = string.Format("#0x{0:x}", args[argindex++]);
else
item = string.Format("{0:x}", args[argindex++]);
break;
case "%08x": item = string.Format("0x{0:x8}", args[argindex++]); break;
case "optaddsub":
case "+/-": item = DISNEW_optaddsub((bool)args[argindex++]); break;
case "{, shift}":
{
//TODO - consider whether it is necessary to pass in arguments (can they always be pulled from shift_n and shift_t? i think so
SRType sr = (SRType)args[argindex++];
int shift_n = (int)args[argindex++];
switch (sr)
{
case SRType.LSL:
if (shift_n == 0)
{
//special case for non-rotated things. theyre usually encoded this way
item = "";
break;
}
item = ", LSL " + shift_n.ToString(); break;
case SRType.LSR: item = ", LSR " + shift_n.ToString(); break;
case SRType.ASR: item = ", ASR " + shift_n.ToString(); break;
case SRType.ROR: item = ", ROR " + shift_n.ToString(); break;
case SRType.RRX: item = ", RRX"; break;
}
break;
}
case "{, rotation}":
{
uint rotation = (uint)args[argindex++] * 8;
if (rotation == 0) item = "";
else item = "ROR #" + rotation;
break;
}
case "registers":
item = DISNEW_Reglist((uint)args[argindex++]);
break;
case "svc":
{
uint svc = (uint)args[argindex++];
if (nstyle) opcode = opcode.Replace("XXX", "SWI");
else opcode = opcode.Replace("XXX", "SVC");
item = disformat_num(svc, disopt.SVC_caps, disopt.SVC_literalPrefix, disopt.SVC_hexPrefix, disopt.SVC_hex);
if (disopt.SVC_showName) { item += " ; " + sys.svc_name(svc); hasComment = true; }
break;
}
case "{wback!}":
if ((bool)args[argindex++])
item = "!";
else item = "";
break;
case "coproc":
item = "cp" + args[argindex++].ToString();
break;
case "opc1":
case "opc2":
item = "#" + args[argindex++].ToString();
break;
case "coproc_rt!":
{
uint reg = (uint)args[argindex++];
if (reg == 15)
{
item = "APSR_nzcv";
}
else
regReport |= (uint)(1 << (int)reg);
item = Reg_names[reg].ToLower();
break;
}
case "crn":
case "crm":
item = "c" + args[argindex++].ToString();
break;
case "{ ;cp_comment}":
{
item = "";
cpcomment = args[argindex++] as string;
break;
}
case "list":
{
bool single = (bool)args[argindex++];
uint d = (uint)args[argindex++];
uint regs = (uint)args[argindex++];
item = "{";
string name = single ? "s" : "d";
bool first=true;
for (uint r = 0; r <= regs - 1; r++)
{
if (!first) item += ",";
first = false;
item += name + (r + d).ToString();
}
item += "}";
break;
}
default: item = "<" + item + ">"; break;
}
format = format.Substring(0, tagindex) + item + format.Substring(tagindex + len + 2);
index = tagindex + item.Length - 1;
tagindex = -1;
}
}
else
if (format[index] == '<')
tagindex = index;
index++;
}
if (capMnemonic) opcode = opcode.ToUpper();
disassembly = opcode + " " + format;
if (unpredictable)
{
disassembly = disassembly + " ; UNPREDICTABLE";
hasComment = true;
}
//report any relevant registers
bool hasRegReport = false;
for (int i = 0; i < 16; i++)
{
uint doit = regReport & 1;
regReport >>= 1;
if (doit == 1)
{
if (!hasComment) disassembly += " ; ";
if (hasRegReport) disassembly += ", ";
hasComment = true;
hasRegReport = true;
disassembly += string.Format("{0}={1:x8}", Reg_names[i].ToLower(), r[i]);
}
}
if (!string.IsNullOrEmpty(cpcomment))
{
disassembly += " ;" + cpcomment;
}
return 0;
}
string DIS_Reglist(string str, object[] args)
{
int index = str.IndexOf("/reglist/");
if (index == -1) return str;
uint regs = (uint)args[0];
StringBuilder sb = new StringBuilder(32);
sb.Append("{");
//TODO - coalesce these to the range style!!
bool first = true;
for (int i = 0; i <= 15; i++)
{
if (_.BITN(i, regs) == 1)
{
if (!first) sb.Append(",");
sb.Append(Reg_names[i].ToLower());
first = false;
}
}
sb.Append("}");
return str.Replace("/reglist/", sb.ToString());
}
//TODO - unit test this
unsafe string DISNEW_Reglist(uint regs)
{
StringBuilder sb = new StringBuilder(32);
//TODO - coalesce these to the range style!!
const int firstname = 13;
bool first = true;
int range = -1;
int lastrange = -1;
int* ra = stackalloc int[8];
int* rb = stackalloc int[8];
for (int i = 0; i <= 15; i++)
{
bool bit = _.BITN(i, regs) != 0;
if (bit && i < firstname)
{
if (lastrange == -1)
{
lastrange = i;
}
else
{
}
}
if (!bit || i >= firstname)
{
if (bit && i >= firstname)
{
range++;
ra[range] = i;
rb[range] = i;
}
if (lastrange != -1)
{
range++;
ra[range] = lastrange;
rb[range] = i - 1;
lastrange = -1;
}
}
}
sb.Append("{");
for (int i = 0; i <= range; i++)
{
int a = ra[i];
int b = rb[i];
if (!first) sb.Append(",");
if (a == b) sb.Append(Reg_names[a].ToLower());
else sb.AppendFormat("{0}-{1}", Reg_names[a].ToLower(), Reg_names[b].ToLower());
first = false;
}
sb.Append("}");
//sb.Append("{");
//for (int i = 0; i <= 15; i++)
//{
// if (_.BITN(i, regs) == 1)
// {
// if (!first) sb.Append(",");
// sb.Append(Reg_names[i].ToLower());
// first = false;
// }
//}
//sb.Append("}");
return sb.ToString();
}
}
}