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