1 // 2 // Copyright (c) 2010-2024 Antmicro 3 // Copyright (c) 2011-2015 Realtime Embedded 4 // 5 // This file is licensed under the MIT License. 6 // Full license text is available in 'licenses/MIT.txt'. 7 // 8 9 using System; 10 using System.Runtime.InteropServices; 11 using System.Text; 12 using System.Collections.Generic; 13 using Antmicro.Renode.Exceptions; 14 using Antmicro.Renode.Utilities; 15 using Antmicro.Renode.Logging; 16 17 namespace Antmicro.Renode.Peripherals.CPU.Disassembler 18 { 19 public class LLVMDisassembler : IDisassembler 20 { LLVMDisassembler(ICPU cpu)21 public LLVMDisassembler(ICPU cpu) 22 { 23 if(!LLVMArchitectureMapping.IsSupported(cpu)) 24 { 25 throw new ArgumentOutOfRangeException("cpu"); 26 } 27 28 this.cpu = cpu; 29 cache = new Dictionary<string, IDisassembler>(); 30 } 31 TryDisassembleInstruction(ulong pc, byte[] data, uint flags, out DisassemblyResult result, int memoryOffset = 0)32 public bool TryDisassembleInstruction(ulong pc, byte[] data, uint flags, out DisassemblyResult result, int memoryOffset = 0) 33 { 34 return GetDisassembler(flags).TryDisassembleInstruction(pc, data, flags, out result, memoryOffset); 35 } 36 TryDecodeInstruction(ulong pc, byte[] memory, uint flags, out byte[] opcode, int memoryOffset = 0)37 public bool TryDecodeInstruction(ulong pc, byte[] memory, uint flags, out byte[] opcode, int memoryOffset = 0) 38 { 39 return GetDisassembler(flags).TryDecodeInstruction(pc, memory, flags, out opcode, memoryOffset); 40 } 41 DisassembleBlock(ulong pc, byte[] memory, uint flags, out string text)42 public int DisassembleBlock(ulong pc, byte[] memory, uint flags, out string text) 43 { 44 var sofar = 0; 45 var strBldr = new StringBuilder(); 46 47 while(sofar < (int)memory.Length) 48 { 49 if(!TryDisassembleInstruction(pc, memory, flags, out var result, memoryOffset: sofar)) 50 { 51 strBldr.AppendLine("Disassembly error detected. The rest of the output will be truncated."); 52 break; 53 } 54 55 if(result.OpcodeSize == 0) 56 { 57 strBldr.AppendFormat("0x{0:x8}: ", pc).AppendLine("No valid instruction, disassembling stopped."); 58 break; 59 } 60 else 61 { 62 strBldr.AppendLine(result.ToString()); 63 } 64 65 sofar += result.OpcodeSize; 66 pc += (ulong)result.OpcodeSize; 67 } 68 69 text = strBldr.ToString(); 70 return sofar; 71 } 72 GetDisassembler(uint flags)73 private IDisassembler GetDisassembler(uint flags) 74 { 75 LLVMArchitectureMapping.GetTripleAndModelKey(cpu, flags, out var triple, out var model); 76 var key = $"{triple} {model} {flags}"; 77 if(!cache.ContainsKey(key)) 78 { 79 IDisassembler disas = new LLVMDisasWrapper(model, triple, flags); 80 if(cpu.Architecture == "arm-m") 81 { 82 disas = new CortexMDisassemblerWrapper(disas); 83 } 84 else if(cpu.Architecture == "riscv" || cpu.Architecture == "riscv64") 85 { 86 disas = new RiscVDisassemblerWrapper(disas); 87 } 88 89 cache.Add(key, disas); 90 } 91 92 return cache[key]; 93 } 94 95 private readonly Dictionary<string, IDisassembler> cache; 96 private readonly ICPU cpu; 97 98 private class LLVMDisasWrapper : IDisposable, IDisassembler 99 { LLVMDisasWrapper(string cpu, string triple, uint flags)100 public LLVMDisasWrapper(string cpu, string triple, uint flags) 101 { 102 try 103 { 104 context = llvm_create_disasm_cpu_with_flags(triple, cpu, flags); 105 } 106 catch(DllNotFoundException) 107 { 108 throw new RecoverableException("Could not find libllvm-disas. Please check in current output directory."); 109 } 110 catch(EntryPointNotFoundException) 111 { 112 context = llvm_create_disasm_cpu(triple, cpu); 113 Logger.Warning("Old version of libllvm-disas is in use, unable to specify disassembly flags"); 114 } 115 if(context == IntPtr.Zero) 116 { 117 throw new ArgumentOutOfRangeException("cpu", "CPU or triple name not detected by LLVM. Disassembling will not be possible."); 118 } 119 isThumb = triple.Contains("thumb"); 120 121 switch(triple) 122 { 123 case "ppc": 124 case "ppc64le": 125 case "sparc": 126 case "i386": 127 case "x86_64": 128 HexFormatter = FormatHexForx86; 129 break; 130 case "riscv64": 131 case "riscv32": 132 case "thumb": 133 case "arm": 134 case "armv7a": 135 case "arm64": 136 HexFormatter = FormatHexForARM; 137 break; 138 default: 139 throw new ArgumentOutOfRangeException("cpu", "CPU not supported."); 140 } 141 } 142 Dispose()143 public void Dispose() 144 { 145 if(context != IntPtr.Zero) 146 { 147 llvm_disasm_dispose(context); 148 } 149 } 150 TryDisassembleInstruction(ulong pc, byte[] data, uint flags, out DisassemblyResult result, int memoryOffset = 0)151 public bool TryDisassembleInstruction(ulong pc, byte[] data, uint flags, out DisassemblyResult result, int memoryOffset = 0) 152 { 153 var strBuf = Marshal.AllocHGlobal(1024); 154 var marshalledData = Marshal.AllocHGlobal(data.Length - memoryOffset); 155 156 Marshal.Copy(data, memoryOffset, marshalledData, data.Length - memoryOffset); 157 158 var bytes = llvm_disasm_instruction(context, marshalledData, (ulong)(data.Length - memoryOffset), strBuf, 1024); 159 if(bytes == 0) 160 { 161 result = default(DisassemblyResult); 162 return false; 163 } 164 165 var strBldr = new StringBuilder(); 166 if(!HexFormatter(strBldr, bytes, memoryOffset, data)) 167 { 168 result = default(DisassemblyResult); 169 return false; 170 } 171 172 result = new DisassemblyResult 173 { 174 PC = pc, 175 OpcodeSize = bytes, 176 OpcodeString = strBldr.ToString().Replace(" ", ""), 177 DisassemblyString = Marshal.PtrToStringAnsi(strBuf) 178 }; 179 180 Marshal.FreeHGlobal(strBuf); 181 Marshal.FreeHGlobal(marshalledData); 182 return true; 183 } 184 TryDecodeInstruction(ulong pc, byte[] memory, uint flags, out byte[] opcode, int memoryOffset = 0)185 public bool TryDecodeInstruction(ulong pc, byte[] memory, uint flags, out byte[] opcode, int memoryOffset = 0) 186 { 187 if(!TryDisassembleInstruction(pc, memory, flags, out var result, memoryOffset)) 188 { 189 opcode = new byte[0]; 190 return false; 191 } 192 193 opcode = Misc.HexStringToByteArray(result.OpcodeString.Trim(), true); 194 return true; 195 } 196 FormatHexForx86(StringBuilder strBldr, int bytes, int position, byte[] data)197 private bool FormatHexForx86(StringBuilder strBldr, int bytes, int position, byte[] data) 198 { 199 int i; 200 for(i = 0; i < bytes && position + i < data.Length; i++) 201 { 202 strBldr.AppendFormat("{0:x2} ", data[position + i]); 203 } 204 205 //This is a sane minimal length, based on some different binaries for quark. 206 //X86 instructions do not have the upper limit of lenght, so we have to approximate. 207 for(var j = i; j < 7; ++j) 208 { 209 strBldr.Append(" "); 210 } 211 212 return i == bytes; 213 } 214 FormatHexForARM(StringBuilder strBldr, int bytes, int position, byte[] data)215 private bool FormatHexForARM(StringBuilder strBldr, int bytes, int position, byte[] data) 216 { 217 if(isThumb) 218 { 219 if(bytes == 4 && position + 3 < data.Length) 220 { 221 strBldr.AppendFormat("{0:x2}{1:x2} {2:x2}{3:x2}", data[position + 1], data[position], data[position + 3], data[position + 2]); 222 } 223 else if(bytes == 2 && position + 1 < data.Length) 224 { 225 strBldr.AppendFormat("{0:x2}{1:x2} ", data[position + 1], data[position]); 226 } 227 else 228 { 229 return false; 230 } 231 } 232 else 233 { 234 for(int i = bytes - 1; i >= 0; i--) 235 { 236 if(position + i < data.Length) 237 { 238 strBldr.AppendFormat("{0:x2}", data[position + i]); 239 } 240 else 241 { 242 return false; 243 } 244 } 245 } 246 247 return true; 248 } 249 250 private readonly Func<StringBuilder, int, int, byte[], bool> HexFormatter; 251 252 private readonly bool isThumb; 253 254 [DllImport("libllvm-disas")] llvm_disasm_instruction(IntPtr dc, IntPtr bytes, UInt64 bytesSize, IntPtr outString, UInt32 outStringSize)255 private static extern int llvm_disasm_instruction(IntPtr dc, IntPtr bytes, UInt64 bytesSize, IntPtr outString, UInt32 outStringSize); 256 257 [DllImport("libllvm-disas")] llvm_create_disasm_cpu_with_flags(string tripleName, string cpu, uint flags)258 private static extern IntPtr llvm_create_disasm_cpu_with_flags(string tripleName, string cpu, uint flags); 259 260 // Fallback in case a new version of Renode is used with an old version of libllvm-disas 261 [DllImport("libllvm-disas")] llvm_create_disasm_cpu(string tripleName, string cpu)262 private static extern IntPtr llvm_create_disasm_cpu(string tripleName, string cpu); 263 264 [DllImport("libllvm-disas")] llvm_disasm_dispose(IntPtr disasm)265 private static extern void llvm_disasm_dispose(IntPtr disasm); 266 267 private readonly IntPtr context; 268 } 269 270 private class CortexMDisassemblerWrapper : IDisassembler 271 { CortexMDisassemblerWrapper(IDisassembler actualDisassembler)272 public CortexMDisassemblerWrapper(IDisassembler actualDisassembler) 273 { 274 underlyingDisassembler = actualDisassembler; 275 } 276 TryDisassembleInstruction(ulong pc, byte[] memory, uint flags, out DisassemblyResult result, int memoryOffset = 0)277 public bool TryDisassembleInstruction(ulong pc, byte[] memory, uint flags, out DisassemblyResult result, int memoryOffset = 0) 278 { 279 switch(pc) 280 { 281 case 0xFFFFFFF0: 282 case 0xFFFFFFF1: 283 // Return to Handler mode, exception return uses non-floating-point state from the MSP and execution uses MSP after return. 284 result = new DisassemblyResult 285 { 286 PC = pc, 287 OpcodeSize = 4, 288 OpcodeString = pc.ToString("X"), 289 DisassemblyString = "Handler mode: non-floating-point state, MSP/MSP" 290 }; 291 return true; 292 case 0xFFFFFFF8: 293 case 0xFFFFFFF9: 294 // Return to Thread mode, exception return uses non-floating-point state from the MSP and execution uses MSP after return. 295 result = new DisassemblyResult 296 { 297 PC = pc, 298 OpcodeSize = 4, 299 OpcodeString = pc.ToString("X"), 300 DisassemblyString = "Thread mode: non-floating-point state, MSP/MSP" 301 }; 302 return true; 303 case 0xFFFFFFFC: 304 case 0xFFFFFFFD: 305 // Return to Thread mode, exception return uses non-floating-point state from the PSP and execution uses PSP after return. 306 result = new DisassemblyResult 307 { 308 PC = pc, 309 OpcodeSize = 4, 310 OpcodeString = pc.ToString("X"), 311 DisassemblyString = "Thread mode: non-floating-point state, PSP/PSP" 312 }; 313 return true; 314 case 0xFFFFFFE0: 315 case 0xFFFFFFE1: 316 // Return to Handler mode, exception return uses floating-point state from the MSP and execution uses MSP after return. 317 result = new DisassemblyResult 318 { 319 PC = pc, 320 OpcodeSize = 4, 321 OpcodeString = pc.ToString("X"), 322 DisassemblyString = "Handler mode: floating-point state, MSP/MSP" 323 }; 324 return true; 325 case 0xFFFFFFE8: 326 case 0xFFFFFFE9: 327 // Return to Thread mode, exception return uses floating-point state from the MSP and execution uses MSP after return. 328 result = new DisassemblyResult 329 { 330 PC = pc, 331 OpcodeSize = 4, 332 OpcodeString = pc.ToString("X"), 333 DisassemblyString = "Thread mode: floating-point state, MSP/MSP" 334 }; 335 return true; 336 case 0xFFFFFFEC: 337 case 0xFFFFFFED: 338 // Return to Thread mode, exception return uses floating-point state from the PSP and execution uses PSP after return. 339 result = new DisassemblyResult 340 { 341 PC = pc, 342 OpcodeSize = 4, 343 OpcodeString = pc.ToString("X"), 344 DisassemblyString = "Thread mode: floating-point state, PSP/PSP" 345 }; 346 return true; 347 default: 348 return underlyingDisassembler.TryDisassembleInstruction(pc, memory, flags, out result, memoryOffset); 349 } 350 } 351 TryDecodeInstruction(ulong pc, byte[] memory, uint flags, out byte[] opcode, int memoryOffset = 0)352 public bool TryDecodeInstruction(ulong pc, byte[] memory, uint flags, out byte[] opcode, int memoryOffset = 0) 353 { 354 if(!TryDisassembleInstruction(pc, memory, flags, out var result, memoryOffset)) 355 { 356 opcode = new byte[0]; 357 return false; 358 } 359 360 opcode = Misc.HexStringToByteArray(result.OpcodeString, true); 361 return true; 362 } 363 364 private readonly IDisassembler underlyingDisassembler; 365 } 366 367 private class RiscVDisassemblerWrapper : IDisassembler 368 { RiscVDisassemblerWrapper(IDisassembler actualDisassembler)369 public RiscVDisassemblerWrapper(IDisassembler actualDisassembler) 370 { 371 underlyingDisassembler = actualDisassembler; 372 } 373 TryDecodeInstruction(ulong pc, byte[] memory, uint flags, out byte[] opcode, int memoryOffset = 0)374 public bool TryDecodeInstruction(ulong pc, byte[] memory, uint flags, out byte[] opcode, int memoryOffset = 0) 375 { 376 var opcodeLength = DecodeRiscVOpcodeLength(memory, memoryOffset); 377 if(opcodeLength == 0) 378 { 379 opcode = new byte[0]; 380 return false; 381 } 382 383 opcode = new byte[opcodeLength]; 384 Array.Copy(memory, memoryOffset, opcode, 0, opcodeLength); 385 return true; 386 } 387 TryDisassembleInstruction(ulong pc, byte[] data, uint flags, out DisassemblyResult result, int memoryOffset = 0)388 public bool TryDisassembleInstruction(ulong pc, byte[] data, uint flags, out DisassemblyResult result, int memoryOffset = 0) 389 { 390 return underlyingDisassembler.TryDisassembleInstruction(pc, data, flags, out result, memoryOffset); 391 } 392 DecodeRiscVOpcodeLength(byte[] memory, int memoryOffset)393 private int DecodeRiscVOpcodeLength(byte[] memory, int memoryOffset) 394 { 395 var lengthEncoder = memory[memoryOffset] & 0x7F; 396 if(lengthEncoder == 0x7F) 397 { 398 // opcodes longer than 64-bits - currently not supported 399 return 0; 400 } 401 402 lengthEncoder &= 0x3F; 403 if(lengthEncoder == 0x3F) 404 { 405 return 8; 406 } 407 else if(lengthEncoder == 0x1F) 408 { 409 return 3; 410 } 411 else if((lengthEncoder & 0x3) == 0x3) 412 { 413 return 4; 414 } 415 else 416 { 417 return 2; 418 } 419 } 420 421 private readonly IDisassembler underlyingDisassembler; 422 } 423 } 424 } 425