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 |
15 | HMODULE Disassembler::s_libraryHandle = nullptr; |
16 | InitDisasm_t *Disassembler::External_InitDisasm = nullptr; |
17 | FinishDisasm_t *Disassembler::External_FinishDisasm = nullptr; |
18 | DisasmInstruction_t *Disassembler::External_DisasmInstruction = nullptr; |
19 | #endif // USE_COREDISTOOLS_DISASSEMBLER |
20 | |
21 | Disassembler::ExternalDisassembler *Disassembler::s_availableExternalDisassembler = nullptr; |
22 | |
23 | #if defined(_TARGET_AMD64_) || defined(_TARGET_X86_) |
24 | // static |
25 | bool 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 |
37 | UINT8 Disassembler::DecodeModFromModRm(UINT8 modRm) |
38 | { |
39 | LIMITED_METHOD_CONTRACT; |
40 | return modRm >> 6; |
41 | } |
42 | |
43 | // static |
44 | UINT8 Disassembler::DecodeRegOrOpCodeFromModRm(UINT8 modRm) |
45 | { |
46 | LIMITED_METHOD_CONTRACT; |
47 | return (modRm >> 3) & 0x7; |
48 | } |
49 | |
50 | // static |
51 | UINT8 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 |
59 | bool 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 | |
76 | namespace |
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 | |
140 | void 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 |
192 | void 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 | |
207 | Disassembler::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 | |
238 | Disassembler::~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 | |
258 | SIZE_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 |
291 | InstructionType 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 | |