1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5#include "common.h"
6
7#include "disassembler.h"
8#include "dllimport.h"
9
10#if USE_DISASSEMBLER
11
12// TODO: Which contracts should be used where? Currently, everything is using LIMITED_METHOD_CONTRACT.
13
14#if USE_COREDISTOOLS_DISASSEMBLER
15HMODULE Disassembler::s_libraryHandle = nullptr;
16InitDisasm_t *Disassembler::External_InitDisasm = nullptr;
17FinishDisasm_t *Disassembler::External_FinishDisasm = nullptr;
18DisasmInstruction_t *Disassembler::External_DisasmInstruction = nullptr;
19#endif // USE_COREDISTOOLS_DISASSEMBLER
20
21Disassembler::ExternalDisassembler *Disassembler::s_availableExternalDisassembler = nullptr;
22
23#if defined(_TARGET_AMD64_) || defined(_TARGET_X86_)
24// static
25bool Disassembler::IsRexPrefix(UINT8 potentialRexByte)
26{
27 LIMITED_METHOD_CONTRACT;
28
29#ifdef _TARGET_AMD64_
30 return (potentialRexByte & 0xf0) == REX_PREFIX_BASE;
31#else // !_TARGET_AMD64_
32 return false;
33#endif // _TARGET_AMD64_
34}
35
36// static
37UINT8 Disassembler::DecodeModFromModRm(UINT8 modRm)
38{
39 LIMITED_METHOD_CONTRACT;
40 return modRm >> 6;
41}
42
43// static
44UINT8 Disassembler::DecodeRegOrOpCodeFromModRm(UINT8 modRm)
45{
46 LIMITED_METHOD_CONTRACT;
47 return (modRm >> 3) & 0x7;
48}
49
50// static
51UINT8 Disassembler::DecodeRmFromModRm(UINT8 modRm)
52{
53 LIMITED_METHOD_CONTRACT;
54 return modRm & 0x7;
55}
56#endif // defined(_TARGET_AMD64_) || defined(_TARGET_X86_)
57
58// static
59bool Disassembler::IsAvailable()
60{
61 LIMITED_METHOD_CONTRACT;
62
63#if USE_COREDISTOOLS_DISASSEMBLER
64 return s_libraryHandle != nullptr;
65#else // !USE_COREDISTOOLS_DISASSEMBLER
66 return true;
67#endif // USE_COREDISTOOLS_DISASSEMBLER
68}
69
70#if _DEBUG
71#define DISPLAYERROR(FMT, ...) wprintf(FMT, __VA_ARGS__)
72#else
73#define DISPLAYERROR(FMT, ...) (void)0
74#endif
75
76namespace
77{
78 HMODULE LoadCoreDisToolsModule(PathString &libPath)
79 {
80 LIMITED_METHOD_CONTRACT;
81
82 //
83 // Look for the coredistools module next to the hosting binary
84 //
85
86 DWORD result = WszGetModuleFileName(nullptr, libPath);
87 if (result == 0)
88 {
89 DISPLAYERROR(
90 W("GetModuleFileName failed, function 'DisasmInstruction': error %u\n"),
91 GetLastError());
92 return nullptr;
93 }
94
95 LPCWSTR libFileName = MAKEDLLNAME(W("coredistools"));
96 PathString::Iterator iter = libPath.End();
97 if (libPath.FindBack(iter, DIRECTORY_SEPARATOR_CHAR_W))
98 {
99 libPath.Truncate(++iter);
100 libPath.Append(libFileName);
101 }
102 else
103 {
104 _ASSERTE(false && "unreachable");
105 }
106
107 LPCWSTR libraryName = libPath.GetUnicode();
108 HMODULE libraryHandle = CLRLoadLibrary(libraryName);
109 if (libraryHandle != nullptr)
110 return libraryHandle;
111
112 DISPLAYERROR(W("LoadLibrary failed for '%s': error %u\n"), libraryName, GetLastError());
113
114 //
115 // Fallback to the CORE_ROOT path
116 //
117
118 DWORD pathLen = GetEnvironmentVariableW(W("CORE_ROOT"), nullptr, 0);
119 if (pathLen == 0) // not set
120 return nullptr;
121
122 pathLen += 1; // Add 1 for null
123 PathString coreRoot;
124 WCHAR *coreRootRaw = coreRoot.OpenUnicodeBuffer(pathLen);
125 GetEnvironmentVariableW(W("CORE_ROOT"), coreRootRaw, pathLen);
126
127 libPath.Clear();
128 libPath.AppendPrintf(W("%s%s%s"), coreRootRaw, DIRECTORY_SEPARATOR_STR_W, libFileName);
129
130 libraryName = libPath.GetUnicode();
131 libraryHandle = CLRLoadLibrary(libraryName);
132 if (libraryHandle != nullptr)
133 return libraryHandle;
134
135 DISPLAYERROR(W("LoadLibrary failed for '%s': error %u\n"), libraryName, GetLastError());
136 return nullptr;
137 }
138}
139
140void Disassembler::StaticInitialize()
141{
142 LIMITED_METHOD_CONTRACT;
143
144#if USE_COREDISTOOLS_DISASSEMBLER
145 _ASSERTE(!IsAvailable());
146
147 PathString libPath;
148 HMODULE libraryHandle = LoadCoreDisToolsModule(libPath);
149 if (libraryHandle == nullptr)
150 return;
151
152 External_InitDisasm =
153 reinterpret_cast<decltype(External_InitDisasm)>(GetProcAddress(libraryHandle, "InitDisasm"));
154 if (External_InitDisasm == nullptr)
155 {
156 DISPLAYERROR(
157 W("GetProcAddress failed for library '%s', function 'InitDisasm': error %u\n"),
158 libPath.GetUnicode(),
159 GetLastError());
160 return;
161 }
162
163 External_DisasmInstruction =
164 reinterpret_cast<decltype(External_DisasmInstruction)>(GetProcAddress(libraryHandle, "DisasmInstruction"));
165 if (External_DisasmInstruction == nullptr)
166 {
167 DISPLAYERROR(
168 W("GetProcAddress failed for library '%s', function 'DisasmInstruction': error %u\n"),
169 libPath.GetUnicode(),
170 GetLastError());
171 return;
172 }
173
174 External_FinishDisasm =
175 reinterpret_cast<decltype(External_FinishDisasm)>(GetProcAddress(libraryHandle, "FinishDisasm"));
176 if (External_FinishDisasm == nullptr)
177 {
178 DISPLAYERROR(
179 W("GetProcAddress failed for library '%s', function 'FinishDisasm': error %u\n"),
180 libPath.GetUnicode(),
181 GetLastError());
182 return;
183 }
184
185 // Set this last to indicate successful load of the library and all exports
186 s_libraryHandle = libraryHandle;
187 _ASSERTE(IsAvailable());
188#endif // USE_COREDISTOOLS_DISASSEMBLER
189}
190
191// static
192void Disassembler::StaticClose()
193{
194 LIMITED_METHOD_CONTRACT;
195
196 if (!IsAvailable())
197 {
198 return;
199 }
200
201#if USE_COREDISTOOLS_DISASSEMBLER
202 CLRFreeLibrary(s_libraryHandle);
203 s_libraryHandle = nullptr;
204#endif
205}
206
207Disassembler::Disassembler()
208{
209 LIMITED_METHOD_CONTRACT;
210 _ASSERTE(IsAvailable());
211
212 // TODO: Is it ok to save and reuse an instance of the LLVM-based disassembler? It may later be used from a different
213 // thread, and it may be deleted from a different thread than the one from which it was created.
214
215 // Try to get an external disassembler that is already available for use before creating one
216 ExternalDisassembler *externalDisassembler =
217 FastInterlockExchangePointer(&s_availableExternalDisassembler, static_cast<ExternalDisassembler *>(nullptr));
218 if (externalDisassembler == nullptr)
219 {
220 #if USE_COREDISTOOLS_DISASSEMBLER
221 // First parameter:
222 // - Empty string for the current architecture
223 // - A string of the form "x86_64-pc-win32"
224 externalDisassembler = External_InitDisasm(Target_Host);
225 #elif USE_MSVC_DISASSEMBLER
226 #ifdef _TARGET_X86_
227 externalDisassembler = ExternalDisassembler::PdisNew(ExternalDisassembler::distX86);
228 #elif defined(_TARGET_AMD64_)
229 externalDisassembler = ExternalDisassembler::PdisNew(ExternalDisassembler::distX8664);
230 #endif // defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
231 #endif // USE_COREDISTOOLS_DISASSEMBLER || USE_MSVC_DISASSEMBLER
232 }
233
234 _ASSERTE(externalDisassembler != nullptr);
235 m_externalDisassembler = externalDisassembler;
236}
237
238Disassembler::~Disassembler()
239{
240 LIMITED_METHOD_CONTRACT;
241 _ASSERTE(IsAvailable());
242
243 // Save the external disassembler for future use. We only save one instance, so delete a previously saved one.
244 ExternalDisassembler *externalDisassemblerToDelete =
245 FastInterlockExchangePointer(&s_availableExternalDisassembler, m_externalDisassembler);
246 if (externalDisassemblerToDelete == nullptr)
247 {
248 return;
249 }
250
251#if USE_COREDISTOOLS_DISASSEMBLER
252 External_FinishDisasm(externalDisassemblerToDelete);
253#elif USE_MSVC_DISASSEMBLER
254 delete externalDisassemblerToDelete;
255#endif // USE_COREDISTOOLS_DISASSEMBLER || USE_MSVC_DISASSEMBLER
256}
257
258SIZE_T Disassembler::DisassembleInstruction(const UINT8 *code, SIZE_T codeLength, InstructionType *instructionTypeRef) const
259{
260 LIMITED_METHOD_CONTRACT;
261 _ASSERTE(IsAvailable());
262
263#if USE_COREDISTOOLS_DISASSEMBLER
264 SIZE_T instructionLength = External_DisasmInstruction(m_externalDisassembler, code, code, codeLength);
265#elif USE_MSVC_DISASSEMBLER
266 SIZE_T instructionLength =
267 m_externalDisassembler->CbDisassemble(reinterpret_cast<ExternalDisassembler::ADDR>(code), code, codeLength);
268#endif // USE_COREDISTOOLS_DISASSEMBLER || USE_MSVC_DISASSEMBLER
269 _ASSERTE(instructionLength <= codeLength);
270
271 if (instructionTypeRef != nullptr)
272 {
273 if (instructionLength == 0)
274 {
275 *instructionTypeRef = InstructionType::Unknown;
276 }
277 else
278 {
279 #if USE_COREDISTOOLS_DISASSEMBLER
280 *instructionTypeRef = DetermineInstructionType(code, instructionLength);
281 #elif USE_MSVC_DISASSEMBLER
282 *instructionTypeRef = DetermineInstructionType(m_externalDisassembler->Trmt());
283 #endif // USE_COREDISTOOLS_DISASSEMBLER || USE_MSVC_DISASSEMBLER
284 }
285 }
286
287 return instructionLength;
288}
289
290// static
291InstructionType Disassembler::DetermineInstructionType(
292#if USE_COREDISTOOLS_DISASSEMBLER
293 const UINT8 *instructionCode, SIZE_T instructionCodeLength
294#elif USE_MSVC_DISASSEMBLER
295 ExternalDisassembler::TRMT terminationType
296#endif // USE_COREDISTOOLS_DISASSEMBLER || USE_MSVC_DISASSEMBLER
297 )
298{
299 LIMITED_METHOD_CONTRACT;
300
301#if USE_COREDISTOOLS_DISASSEMBLER
302 _ASSERTE(instructionCodeLength != 0);
303
304 SIZE_T i = 0;
305 if (Disassembler::IsRexPrefix(instructionCode[i]))
306 {
307 ++i;
308 }
309
310 switch (instructionCode[i])
311 {
312 case 0xe8: // call near rel
313 #ifdef _TARGET_X86_
314 case 0x9a: // call far ptr
315 #endif // _TARGET_X86_
316 return InstructionType::Call_DirectUnconditional;
317
318 case 0xff:
319 ++i;
320 if (i >= instructionCodeLength)
321 {
322 break;
323 }
324
325 switch (Disassembler::DecodeRegOrOpCodeFromModRm(instructionCode[i]))
326 {
327 case 2: // call near r/m
328 case 3: // call far m
329 return InstructionType::Call_IndirectUnconditional;
330
331 case 4: // jmp near r/m
332 case 5: // jmp far m
333 return InstructionType::Branch_IndirectUnconditional;
334 }
335 break;
336 }
337#elif USE_MSVC_DISASSEMBLER
338 switch (terminationType)
339 {
340 case ExternalDisassembler::trmtCall:
341 return InstructionType::Call_DirectUnconditional;
342
343 case ExternalDisassembler::trmtCallInd:
344 return InstructionType::Call_IndirectUnconditional;
345
346 case ExternalDisassembler::trmtBraInd:
347 return InstructionType::Branch_IndirectUnconditional;
348 }
349#endif // USE_COREDISTOOLS_DISASSEMBLER || USE_MSVC_DISASSEMBLER
350
351 return InstructionType::Unknown;
352}
353
354#endif // USE_DISASSEMBLER
355