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 | * |
6 | * File: dis.cpp |
7 | * |
8 | |
9 | * |
10 | * File Comments: |
11 | * |
12 | * This file handles disassembly. It is adapted from the MS linker. |
13 | * |
14 | ***********************************************************************/ |
15 | |
16 | #include "jitpch.h" |
17 | #ifdef _MSC_VER |
18 | #pragma hdrstop |
19 | #endif |
20 | |
21 | /*****************************************************************************/ |
22 | #ifdef LATE_DISASM |
23 | /*****************************************************************************/ |
24 | |
25 | // Define DISASM_DEBUG to get verbose output of late disassembler inner workings. |
26 | //#define DISASM_DEBUG |
27 | #ifdef DISASM_DEBUG |
28 | #ifdef DEBUG |
29 | #define DISASM_DUMP(...) \ |
30 | if (VERBOSE) \ |
31 | printf(__VA_ARGS__) |
32 | #else // !DEBUG |
33 | #define DISASM_DUMP(...) printf(__VA_ARGS__) |
34 | #endif // !DEBUG |
35 | #else // !DISASM_DEBUG |
36 | #define DISASM_DUMP(...) |
37 | #endif // !DISASM_DEBUG |
38 | |
39 | /*****************************************************************************/ |
40 | |
41 | #define MAX_CLASSNAME_LENGTH 1024 |
42 | |
43 | #if defined(_AMD64_) |
44 | |
45 | #pragma comment(linker, \ |
46 | "/ALTERNATENAME:__imp_?CchFormatAddr@DIS@@QEBA_K_KPEAG0@Z=__imp_?CchFormatAddr@DIS@@QEBA_K_KPEA_W0@Z") |
47 | #pragma comment(linker, \ |
48 | "/ALTERNATENAME:__imp_?CchFormatInstr@DIS@@QEBA_KPEAG_K@Z=__imp_?CchFormatInstr@DIS@@QEBA_KPEA_W_K@Z") |
49 | #pragma comment( \ |
50 | linker, \ |
51 | "/ALTERNATENAME:__imp_?PfncchaddrSet@DIS@@QEAAP6A_KPEBV1@_KPEAG1PEA_K@ZP6A_K01213@Z@Z=__imp_?PfncchaddrSet@DIS@@QEAAP6A_KPEBV1@_KPEA_W1PEA_K@ZP6A_K01213@Z@Z") |
52 | #pragma comment( \ |
53 | linker, \ |
54 | "/ALTERNATENAME:__imp_?PfncchregSet@DIS@@QEAAP6A_KPEBV1@W4REGA@1@PEAG_K@ZP6A_K0123@Z@Z=__imp_?PfncchregSet@DIS@@QEAAP6A_KPEBV1@W4REGA@1@PEA_W_K@ZP6A_K0123@Z@Z") |
55 | #pragma comment( \ |
56 | linker, \ |
57 | "/ALTERNATENAME:__imp_?PfncchregrelSet@DIS@@QEAAP6A_KPEBV1@W4REGA@1@KPEAG_KPEAK@ZP6A_K01K234@Z@Z=__imp_?PfncchregrelSet@DIS@@QEAAP6A_KPEBV1@W4REGA@1@KPEA_W_KPEAK@ZP6A_K01K234@Z@Z") |
58 | #pragma comment( \ |
59 | linker, \ |
60 | "/ALTERNATENAME:__imp_?PfncchfixupSet@DIS@@QEAAP6A_KPEBV1@_K1PEAG1PEA_K@ZP6A_K011213@Z@Z=__imp_?PfncchfixupSet@DIS@@QEAAP6A_KPEBV1@_K1PEA_W1PEA_K@ZP6A_K011213@Z@Z") |
61 | |
62 | #elif defined(_X86_) |
63 | |
64 | #pragma comment(linker, "/ALTERNATENAME:__imp_?CchFormatAddr@DIS@@QBEI_KPAGI@Z=__imp_?CchFormatAddr@DIS@@QBEI_KPA_WI@Z") |
65 | #pragma comment(linker, "/ALTERNATENAME:__imp_?CchFormatInstr@DIS@@QBEIPAGI@Z=__imp_?CchFormatInstr@DIS@@QBEIPA_WI@Z") |
66 | #pragma comment( \ |
67 | linker, \ |
68 | "/ALTERNATENAME:__imp_?PfncchaddrSet@DIS@@QAEP6GIPBV1@_KPAGIPA_K@ZP6GI012I3@Z@Z=__imp_?PfncchaddrSet@DIS@@QAEP6GIPBV1@_KPA_WIPA_K@ZP6GI012I3@Z@Z") |
69 | #pragma comment( \ |
70 | linker, \ |
71 | "/ALTERNATENAME:__imp_?PfncchregSet@DIS@@QAEP6GIPBV1@W4REGA@1@PAGI@ZP6GI012I@Z@Z=__imp_?PfncchregSet@DIS@@QAEP6GIPBV1@W4REGA@1@PA_WI@ZP6GI012I@Z@Z") |
72 | #pragma comment( \ |
73 | linker, \ |
74 | "/ALTERNATENAME:__imp_?PfncchregrelSet@DIS@@QAEP6GIPBV1@W4REGA@1@KPAGIPAK@ZP6GI01K2I3@Z@Z=__imp_?PfncchregrelSet@DIS@@QAEP6GIPBV1@W4REGA@1@KPA_WIPAK@ZP6GI01K2I3@Z@Z") |
75 | #pragma comment( \ |
76 | linker, \ |
77 | "/ALTERNATENAME:__imp_?PfncchfixupSet@DIS@@QAEP6GIPBV1@_KIPAGIPA_K@ZP6GI01I2I3@Z@Z=__imp_?PfncchfixupSet@DIS@@QAEP6GIPBV1@_KIPA_WIPA_K@ZP6GI01I2I3@Z@Z") |
78 | |
79 | #endif |
80 | |
81 | /***************************************************************************** |
82 | * Given an absolute address from the beginning of the code |
83 | * find the corresponding emitter block and the relative offset |
84 | * of the current address in that block |
85 | * Was used to get to the fixup list of each block. The new emitter has |
86 | * no such fixups. Something needs to be added for this. |
87 | */ |
88 | |
89 | // These structs were defined in emit.h. Fake them here so DisAsm.cpp can compile |
90 | |
91 | typedef struct codeFix |
92 | { |
93 | codeFix* cfNext; |
94 | unsigned cfFixup; |
95 | } * codeFixPtr; |
96 | |
97 | typedef struct codeBlk |
98 | { |
99 | codeFix* cbFixupLst; |
100 | } * codeBlkPtr; |
101 | |
102 | /***************************************************************************** |
103 | * The following is the callback for jump label and direct function calls fixups. |
104 | * "addr" represents the address of jump that has to be |
105 | * replaced with a label or function name. |
106 | * |
107 | * Return 1 if a name was written representing the address, 0 otherwise. |
108 | */ |
109 | |
110 | /* static */ |
111 | size_t __stdcall DisAssembler::disCchAddr( |
112 | const DIS* pdis, DIS::ADDR addr, __in_ecount(cchMax) wchar_t* wz, size_t cchMax, DWORDLONG* pdwDisp) |
113 | { |
114 | DisAssembler* pDisAsm = (DisAssembler*)pdis->PvClient(); |
115 | assert(pDisAsm); |
116 | return pDisAsm->disCchAddrMember(pdis, addr, wz, cchMax, pdwDisp); |
117 | } |
118 | |
119 | size_t DisAssembler::disCchAddrMember( |
120 | const DIS* pdis, DIS::ADDR addr, __in_ecount(cchMax) wchar_t* wz, size_t cchMax, DWORDLONG* pdwDisp) |
121 | { |
122 | /* First check the termination type of the instruction |
123 | * because this might be a helper or static function call |
124 | * check to see if we have a fixup for the current address */ |
125 | |
126 | size_t retval = 0; // assume we don't know |
127 | |
128 | #if defined(_TARGET_XARCH_) |
129 | |
130 | DISX86::TRMTA terminationType = DISX86::TRMTA(pdis->Trmta()); |
131 | |
132 | DISASM_DUMP("AddrMember %p (%p), termType %u\n" , addr, disGetLinearAddr((size_t)addr), terminationType); |
133 | |
134 | switch (terminationType) |
135 | { |
136 | // int disCallSize; |
137 | |
138 | case DISX86::trmtaJmpShort: |
139 | case DISX86::trmtaJmpCcShort: |
140 | |
141 | /* We have a short jump in the current code block - generate the label to which we jump */ |
142 | |
143 | assert(0 <= disTarget && disTarget < disTotalCodeSize); |
144 | swprintf_s(wz, cchMax, W("short L_%02u" ), disLabels[disTarget]); |
145 | retval = 1; |
146 | break; |
147 | |
148 | case DISX86::trmtaJmpNear: |
149 | case DISX86::trmtaJmpCcNear: |
150 | |
151 | /* We have a near jump. Check if is in the current code block. |
152 | * Otherwise we have no target for it. */ |
153 | |
154 | if (0 <= disTarget && disTarget < disTotalCodeSize) |
155 | { |
156 | swprintf_s(wz, cchMax, W("L_%02u" ), disLabels[disTarget]); |
157 | retval = 1; |
158 | } |
159 | break; |
160 | |
161 | case DISX86::trmtaCallNear16: |
162 | case DISX86::trmtaCallNear32: |
163 | |
164 | /* check for local calls (i.e. CALL label) */ |
165 | |
166 | if (0 <= disTarget && disTarget < disTotalCodeSize) |
167 | { |
168 | /* not a "call ds:[0000]" - go ahead */ |
169 | /* disTarget within block boundary -> local call */ |
170 | |
171 | swprintf_s(wz, cchMax, W("short L_%02u" ), disLabels[disTarget]); |
172 | retval = 1; |
173 | break; |
174 | } |
175 | |
176 | /* this is a near call - in our case usually VM helper functions */ |
177 | |
178 | /* find the emitter block and the offset of the call fixup */ |
179 | /* for the fixup offset we have to add the opcode size for the call - in the case of a near call is 1 */ |
180 | |
181 | // disCallSize = 1; |
182 | |
183 | { |
184 | size_t absoluteTarget = (size_t)disGetLinearAddr(disTarget); |
185 | const char* name = disGetMethodFullName(absoluteTarget); |
186 | if (name != nullptr) |
187 | { |
188 | swprintf_s(wz, cchMax, W("%zx %S" ), dspAddr(absoluteTarget), name); |
189 | retval = 1; |
190 | break; |
191 | } |
192 | } |
193 | |
194 | break; |
195 | |
196 | #ifdef _TARGET_AMD64_ |
197 | |
198 | case DISX86::trmtaFallThrough: |
199 | |
200 | /* memory indirect case. Could be for an LEA for the base address of a switch table, which is an arbitrary |
201 | * address, currently of the first block after the prolog. */ |
202 | |
203 | /* find the emitter block and the offset for the fixup |
204 | * "addr" is the address of the immediate */ |
205 | |
206 | break; |
207 | |
208 | #endif // _TARGET_AMD64_ |
209 | |
210 | default: |
211 | |
212 | printf("Termination type is %d\n" , (int)terminationType); |
213 | assert(!"treat this case\n" ); |
214 | break; |
215 | } |
216 | |
217 | #elif defined(_TARGET_ARM64_) |
218 | |
219 | DISARM64::TRMTA terminationType = DISARM64::TRMTA(pdis->Trmta()); |
220 | |
221 | DISASM_DUMP("AddrMember %p (%p), termType %u\n" , addr, disGetLinearAddr((size_t)addr), terminationType); |
222 | |
223 | switch (terminationType) |
224 | { |
225 | // int disCallSize; |
226 | |
227 | case DISARM64::TRMTA::trmtaBra: |
228 | case DISARM64::TRMTA::trmtaBraCase: |
229 | case DISARM64::TRMTA::trmtaBraCc: |
230 | case DISARM64::TRMTA::trmtaBraCcCase: |
231 | case DISARM64::TRMTA::trmtaBraCcInd: |
232 | case DISARM64::TRMTA::trmtaBraInd: |
233 | |
234 | /* We have a jump. Check if is in the current code block. |
235 | * Otherwise we have no target for it. */ |
236 | |
237 | if (0 <= disTarget && disTarget < disTotalCodeSize) |
238 | { |
239 | swprintf_s(wz, cchMax, W("L_%02u" ), disLabels[disTarget]); |
240 | retval = 1; |
241 | } |
242 | break; |
243 | |
244 | case DISARM64::trmtaCall: |
245 | case DISARM64::trmtaCallCc: |
246 | case DISARM64::trmtaCallCcInd: |
247 | case DISARM64::trmtaCallInd: |
248 | |
249 | /* check for local calls (i.e. CALL label) */ |
250 | |
251 | if (0 <= disTarget && disTarget < disTotalCodeSize) |
252 | { |
253 | /* not a "call [0000]" - go ahead */ |
254 | /* disTarget within block boundary -> local call */ |
255 | |
256 | swprintf_s(wz, cchMax, W("L_%02u" ), disLabels[disTarget]); |
257 | retval = 1; |
258 | break; |
259 | } |
260 | |
261 | /* this is a near call - in our case usually VM helper functions */ |
262 | |
263 | /* find the emitter block and the offset of the call fixup */ |
264 | /* for the fixup offset we have to add the opcode size for the call - in the case of a near call is 1 */ |
265 | |
266 | // disCallSize = 1; |
267 | |
268 | { |
269 | size_t absoluteTarget = (size_t)disGetLinearAddr(disTarget); |
270 | const char* name = disGetMethodFullName(absoluteTarget); |
271 | if (name != nullptr) |
272 | { |
273 | swprintf_s(wz, cchMax, W("%zx %S" ), dspAddr(absoluteTarget), name); |
274 | retval = 1; |
275 | break; |
276 | } |
277 | } |
278 | |
279 | break; |
280 | |
281 | case DISARM64::trmtaFallThrough: |
282 | |
283 | /* memory indirect case. Could be for an LEA for the base address of a switch table, which is an arbitrary |
284 | * address, currently of the first block after the prolog. */ |
285 | |
286 | /* find the emitter block and the offset for the fixup |
287 | * "addr" is the address of the immediate */ |
288 | |
289 | { |
290 | DIS::INSTRUCTION instr; |
291 | DIS::OPERAND ops[DISARM64::coperandMax]; |
292 | bool ok = pdis->FDecode(&instr, ops, ArrLen(ops)); |
293 | if (ok) |
294 | { |
295 | bool isAddress = false; |
296 | switch ((DISARM64::OPA)instr.opa) |
297 | { |
298 | case DISARM64::opaAdr: |
299 | case DISARM64::opaAdrp: |
300 | isAddress = true; |
301 | break; |
302 | default: |
303 | break; |
304 | } |
305 | |
306 | if (isAddress && 0 <= addr && addr < disTotalCodeSize) |
307 | { |
308 | swprintf_s(wz, cchMax, W("L_%02u" ), disLabels[addr]); |
309 | retval = 1; |
310 | } |
311 | } |
312 | } |
313 | break; |
314 | |
315 | default: |
316 | |
317 | printf("Termination type is %d\n" , (int)terminationType); |
318 | assert(!"treat this case\n" ); |
319 | break; |
320 | } |
321 | |
322 | #else // _TARGET_* |
323 | #error Unsupported or unset target architecture |
324 | #endif // _TARGET_* |
325 | |
326 | if (retval == 0) |
327 | { |
328 | if (disDiffable) |
329 | { |
330 | swprintf_s(wz, cchMax, W("%p" ), dspAddr((void*)1)); |
331 | } |
332 | } |
333 | else |
334 | { |
335 | /* no displacement */ |
336 | |
337 | *pdwDisp = 0x0; |
338 | } |
339 | |
340 | return retval; |
341 | } |
342 | |
343 | /***************************************************************************** |
344 | * We annotate some instructions to get info needed to display the symbols |
345 | * for that instruction. |
346 | * |
347 | * Return 1 if a name was written representing the address, 0 otherwise. |
348 | */ |
349 | |
350 | /* static */ |
351 | size_t __stdcall DisAssembler::disCchFixup( |
352 | const DIS* pdis, DIS::ADDR addr, size_t size, __in_ecount(cchMax) wchar_t* wz, size_t cchMax, DWORDLONG* pdwDisp) |
353 | { |
354 | DisAssembler* pDisAsm = (DisAssembler*)pdis->PvClient(); |
355 | assert(pDisAsm); |
356 | |
357 | return pDisAsm->disCchFixupMember(pdis, addr, size, wz, cchMax, pdwDisp); |
358 | } |
359 | |
360 | size_t DisAssembler::disCchFixupMember( |
361 | const DIS* pdis, DIS::ADDR addr, size_t size, __in_ecount(cchMax) wchar_t* wz, size_t cchMax, DWORDLONG* pdwDisp) |
362 | { |
363 | #if defined(_TARGET_XARCH_) |
364 | |
365 | DISX86::TRMTA terminationType = DISX86::TRMTA(pdis->Trmta()); |
366 | // DIS::ADDR disIndAddr; |
367 | |
368 | DISASM_DUMP("FixupMember %016I64X (%08IX), size %d, termType %u\n" , addr, disGetLinearAddr((size_t)addr), size, |
369 | terminationType); |
370 | |
371 | // Is there a relocation registered for the address? |
372 | |
373 | size_t absoluteAddr = (size_t)disGetLinearAddr((size_t)addr); |
374 | size_t targetAddr; |
375 | bool anyReloc = GetRelocationMap()->Lookup(absoluteAddr, &targetAddr); |
376 | |
377 | switch (terminationType) |
378 | { |
379 | DIS::ADDR disCallSize; |
380 | |
381 | case DISX86::trmtaFallThrough: |
382 | |
383 | /* memory indirect case */ |
384 | |
385 | assert(addr > pdis->Addr()); |
386 | |
387 | /* find the emitter block and the offset for the fixup |
388 | * "addr" is the address of the immediate */ |
389 | |
390 | if (anyReloc) |
391 | { |
392 | // Make instructions like "mov rcx, 7FE8247A638h" diffable. |
393 | swprintf_s(wz, cchMax, W("%IXh" ), dspAddr(targetAddr)); |
394 | break; |
395 | } |
396 | |
397 | return 0; |
398 | |
399 | case DISX86::trmtaJmpInd: |
400 | |
401 | /* pretty rare case - something like "jmp [eax*4]" |
402 | * not a function call or anything worth annotating */ |
403 | |
404 | return 0; |
405 | |
406 | case DISX86::trmtaTrap: |
407 | case DISX86::trmtaTrapCc: |
408 | |
409 | /* some instructions like division have a TRAP termination type - ignore it */ |
410 | |
411 | return 0; |
412 | |
413 | case DISX86::trmtaJmpShort: |
414 | case DISX86::trmtaJmpCcShort: |
415 | |
416 | case DISX86::trmtaJmpNear: |
417 | case DISX86::trmtaJmpCcNear: |
418 | |
419 | /* these are treated by the CchAddr callback - skip them */ |
420 | |
421 | return 0; |
422 | |
423 | case DISX86::trmtaCallNear16: |
424 | case DISX86::trmtaCallNear32: |
425 | |
426 | if (anyReloc) |
427 | { |
428 | const char* name = disGetMethodFullName(targetAddr); |
429 | if (name != nullptr) |
430 | { |
431 | swprintf_s(wz, cchMax, W("%zx %S" ), dspAddr(targetAddr), name); |
432 | break; |
433 | } |
434 | } |
435 | |
436 | /* these are treated by the CchAddr callback - skip them */ |
437 | |
438 | return 0; |
439 | |
440 | case DISX86::trmtaCallInd: |
441 | |
442 | /* here we have an indirect call - find the indirect address */ |
443 | |
444 | // BYTE * code = disGetLinearAddr((size_t)addr); |
445 | // disIndAddr = (DIS::ADDR) (code+0); |
446 | |
447 | /* find the size of the call opcode - less the immediate */ |
448 | /* for the fixup offset we have to add the opcode size for the call */ |
449 | /* addr is the address of the immediate, pdis->Addr() returns the address of the disassembled instruction */ |
450 | |
451 | assert(addr > pdis->Addr()); |
452 | disCallSize = addr - pdis->Addr(); |
453 | |
454 | /* find the emitter block and the offset of the call fixup */ |
455 | |
456 | return 0; |
457 | |
458 | default: |
459 | |
460 | printf("Termination type is %d\n" , (int)terminationType); |
461 | assert(!"treat this case\n" ); |
462 | break; |
463 | } |
464 | |
465 | #elif defined(_TARGET_ARM64_) |
466 | |
467 | DISARM64::TRMTA terminationType = DISARM64::TRMTA(pdis->Trmta()); |
468 | // DIS::ADDR disIndAddr; |
469 | |
470 | DISASM_DUMP("FixupMember %016I64X (%08IX), size %d, termType %u\n" , addr, disGetLinearAddr((size_t)addr), size, |
471 | terminationType); |
472 | |
473 | // Is there a relocation registered for the address? |
474 | |
475 | size_t absoluteAddr = (size_t)disGetLinearAddr((size_t)addr); |
476 | size_t targetAddr; |
477 | bool anyReloc = GetRelocationMap()->Lookup(absoluteAddr, &targetAddr); |
478 | |
479 | switch (terminationType) |
480 | { |
481 | DIS::ADDR disCallSize; |
482 | |
483 | case DISARM64::TRMTA::trmtaUnknown: |
484 | return 0; |
485 | |
486 | case DISARM64::TRMTA::trmtaFallThrough: |
487 | |
488 | if (anyReloc) |
489 | { |
490 | /* memory indirect case */ |
491 | |
492 | assert(addr > pdis->Addr()); |
493 | |
494 | /* find the emitter block and the offset for the fixup |
495 | * "addr" is the address of the immediate */ |
496 | |
497 | // Make instructions like "mov rcx, 7FE8247A638h" diffable. |
498 | swprintf_s(wz, cchMax, W("%IXh" ), dspAddr(targetAddr)); |
499 | break; |
500 | } |
501 | |
502 | return 0; |
503 | |
504 | case DISARM64::TRMTA::trmtaBraInd: |
505 | case DISARM64::TRMTA::trmtaBraCcInd: |
506 | |
507 | /* pretty rare case - something like "jmp [eax*4]" |
508 | * not a function call or anything worth annotating */ |
509 | |
510 | return 0; |
511 | |
512 | case DISARM64::TRMTA::trmtaTrap: |
513 | case DISARM64::TRMTA::trmtaTrapCc: |
514 | |
515 | /* some instructions like division have a TRAP termination type - ignore it */ |
516 | |
517 | return 0; |
518 | |
519 | case DISARM64::TRMTA::trmtaBra: |
520 | case DISARM64::TRMTA::trmtaBraCase: |
521 | case DISARM64::TRMTA::trmtaBraCc: |
522 | case DISARM64::TRMTA::trmtaBraCcCase: |
523 | |
524 | /* these are treated by the CchAddr callback - skip them */ |
525 | |
526 | return 0; |
527 | |
528 | case DISARM64::TRMTA::trmtaCall: |
529 | case DISARM64::TRMTA::trmtaCallCc: |
530 | |
531 | if (anyReloc) |
532 | { |
533 | const char* name = disGetMethodFullName(targetAddr); |
534 | if (name != nullptr) |
535 | { |
536 | swprintf_s(wz, cchMax, W("%zx %S" ), dspAddr(targetAddr), name); |
537 | break; |
538 | } |
539 | } |
540 | |
541 | /* these are treated by the CchAddr callback - skip them */ |
542 | |
543 | return 0; |
544 | |
545 | case DISARM64::TRMTA::trmtaCallInd: |
546 | case DISARM64::TRMTA::trmtaCallCcInd: |
547 | |
548 | /* here we have an indirect call - find the indirect address */ |
549 | |
550 | // BYTE * code = disGetLinearAddr((size_t)addr); |
551 | // disIndAddr = (DIS::ADDR) (code+0); |
552 | |
553 | /* find the size of the call opcode - less the immediate */ |
554 | /* for the fixup offset we have to add the opcode size for the call */ |
555 | /* addr is the address of the immediate, pdis->Addr() returns the address of the disassembled instruction */ |
556 | |
557 | assert(addr > pdis->Addr()); |
558 | disCallSize = addr - pdis->Addr(); |
559 | |
560 | /* find the emitter block and the offset of the call fixup */ |
561 | |
562 | return 0; |
563 | |
564 | default: |
565 | |
566 | printf("Termination type is %d\n" , (int)terminationType); |
567 | assert(!"treat this case\n" ); |
568 | break; |
569 | } |
570 | |
571 | #else // _TARGET_* |
572 | #error Unsupported or unset target architecture |
573 | #endif // _TARGET_* |
574 | |
575 | /* no displacement */ |
576 | |
577 | *pdwDisp = 0x0; |
578 | |
579 | return 1; |
580 | } |
581 | |
582 | /***************************************************************************** |
583 | * This the callback for register-relative operands in an instruction. |
584 | * If the register is ESP or EBP, the operand may be a local variable |
585 | * or a parameter, else the operand may be an instance variable |
586 | * |
587 | * Return 1 if a name was written representing the register-relative operand, 0 otherwise. |
588 | */ |
589 | |
590 | /* static */ |
591 | size_t __stdcall DisAssembler::disCchRegRel( |
592 | const DIS* pdis, DIS::REGA reg, DWORD disp, __in_ecount(cchMax) wchar_t* wz, size_t cchMax, DWORD* pdwDisp) |
593 | { |
594 | DisAssembler* pDisAsm = (DisAssembler*)pdis->PvClient(); |
595 | assert(pDisAsm); |
596 | |
597 | return pDisAsm->disCchRegRelMember(pdis, reg, disp, wz, cchMax, pdwDisp); |
598 | } |
599 | |
600 | size_t DisAssembler::disCchRegRelMember( |
601 | const DIS* pdis, DIS::REGA reg, DWORD disp, __in_ecount(cchMax) wchar_t* wz, size_t cchMax, DWORD* pdwDisp) |
602 | { |
603 | #if defined(_TARGET_XARCH_) |
604 | |
605 | DISX86::TRMTA terminationType = DISX86::TRMTA(pdis->Trmta()); |
606 | // DIS::ADDR disIndAddr; |
607 | |
608 | DISASM_DUMP("RegRelMember reg %u, disp %u, termType %u\n" , reg, disp, terminationType); |
609 | |
610 | switch (terminationType) |
611 | { |
612 | int disOpcodeSize; |
613 | const char* var; |
614 | |
615 | case DISX86::trmtaFallThrough: |
616 | |
617 | /* some instructions like division have a TRAP termination type - ignore it */ |
618 | |
619 | case DISX86::trmtaTrap: |
620 | case DISX86::trmtaTrapCc: |
621 | |
622 | var = disComp->codeGen->siStackVarName((size_t)(pdis->Addr() - disStartAddr), pdis->Cb(), reg, disp); |
623 | if (var) |
624 | { |
625 | swprintf_s(wz, cchMax, W("%hs+%Xh '%hs'" ), getRegName(reg), disp, var); |
626 | *pdwDisp = 0; |
627 | |
628 | return 1; |
629 | } |
630 | |
631 | /* This case consists of non-static members */ |
632 | |
633 | /* find the emitter block and the offset for the fixup |
634 | * fixup is emited after the coding of the instruction - size = word (2 bytes) |
635 | * GRRRR!!! - for the 16 bit case we have to check for the address size prefix = 0x66 |
636 | */ |
637 | |
638 | if (*disGetLinearAddr(disCurOffset) == 0x66) |
639 | { |
640 | disOpcodeSize = 3; |
641 | } |
642 | else |
643 | { |
644 | disOpcodeSize = 2; |
645 | } |
646 | |
647 | return 0; |
648 | |
649 | case DISX86::trmtaCallNear16: |
650 | case DISX86::trmtaCallNear32: |
651 | case DISX86::trmtaJmpInd: |
652 | |
653 | break; |
654 | |
655 | case DISX86::trmtaCallInd: |
656 | |
657 | /* check if this is a one byte displacement */ |
658 | |
659 | if ((signed char)disp == (int)disp) |
660 | { |
661 | /* we have a one byte displacement -> there were no previous callbacks */ |
662 | |
663 | /* find the size of the call opcode - less the immediate */ |
664 | /* this is a call R/M indirect -> opcode size is 2 */ |
665 | |
666 | disOpcodeSize = 2; |
667 | |
668 | /* find the emitter block and the offset of the call fixup */ |
669 | |
670 | return 0; |
671 | } |
672 | else |
673 | { |
674 | /* check if we already have a symbol name as replacement */ |
675 | |
676 | if (disHasName) |
677 | { |
678 | /* CchFixup has been called before - we have a symbol name saved in global var disFuncTempBuf */ |
679 | |
680 | swprintf_s(wz, cchMax, W("%hs+%u '%hs'" ), getRegName(reg), disp, disFuncTempBuf); |
681 | *pdwDisp = 0; |
682 | disHasName = false; |
683 | return 1; |
684 | } |
685 | else |
686 | { |
687 | return 0; |
688 | } |
689 | } |
690 | |
691 | default: |
692 | |
693 | printf("Termination type is %d\n" , (int)terminationType); |
694 | assert(!"treat this case\n" ); |
695 | |
696 | break; |
697 | } |
698 | |
699 | #elif defined(_TARGET_ARM64_) |
700 | |
701 | DISARM64::TRMTA terminationType = DISARM64::TRMTA(pdis->Trmta()); |
702 | |
703 | DISASM_DUMP("RegRelMember reg %u, disp %u, termType %u\n" , reg, disp, terminationType); |
704 | |
705 | switch (terminationType) |
706 | { |
707 | int disOpcodeSize; |
708 | const char* var; |
709 | |
710 | case DISARM64::TRMTA::trmtaFallThrough: |
711 | |
712 | /* some instructions like division have a TRAP termination type - ignore it */ |
713 | |
714 | case DISARM64::TRMTA::trmtaTrap: |
715 | case DISARM64::TRMTA::trmtaTrapCc: |
716 | |
717 | var = disComp->codeGen->siStackVarName((size_t)(pdis->Addr() - disStartAddr), pdis->Cb(), reg, disp); |
718 | if (var) |
719 | { |
720 | swprintf_s(wz, cchMax, W("%hs+%Xh '%hs'" ), getRegName(reg), disp, var); |
721 | *pdwDisp = 0; |
722 | |
723 | return 1; |
724 | } |
725 | |
726 | /* This case consists of non-static members */ |
727 | |
728 | // TODO-ARM64-Bug?: Is this correct? |
729 | disOpcodeSize = 2; |
730 | return 0; |
731 | |
732 | case DISARM64::TRMTA::trmtaCall: |
733 | case DISARM64::TRMTA::trmtaCallCc: |
734 | case DISARM64::TRMTA::trmtaBraInd: |
735 | case DISARM64::TRMTA::trmtaBraCcInd: |
736 | break; |
737 | |
738 | case DISARM64::TRMTA::trmtaCallInd: |
739 | case DISARM64::TRMTA::trmtaCallCcInd: |
740 | |
741 | /* check if this is a one byte displacement */ |
742 | |
743 | if ((signed char)disp == (int)disp) |
744 | { |
745 | /* we have a one byte displacement -> there were no previous callbacks */ |
746 | |
747 | /* find the size of the call opcode - less the immediate */ |
748 | /* this is a call R/M indirect -> opcode size is 2 */ |
749 | |
750 | // TODO-ARM64-Bug?: Is this correct? |
751 | disOpcodeSize = 2; |
752 | |
753 | /* find the emitter block and the offset of the call fixup */ |
754 | |
755 | return 0; |
756 | } |
757 | else |
758 | { |
759 | /* check if we already have a symbol name as replacement */ |
760 | |
761 | if (disHasName) |
762 | { |
763 | /* CchFixup has been called before - we have a symbol name saved in global var disFuncTempBuf */ |
764 | |
765 | swprintf_s(wz, cchMax, W("%hs+%u '%hs'" ), getRegName(reg), disp, disFuncTempBuf); |
766 | *pdwDisp = 0; |
767 | disHasName = false; |
768 | return 1; |
769 | } |
770 | else |
771 | { |
772 | return 0; |
773 | } |
774 | } |
775 | |
776 | default: |
777 | |
778 | printf("Termination type is %d\n" , (int)terminationType); |
779 | assert(!"treat this case\n" ); |
780 | |
781 | break; |
782 | } |
783 | |
784 | #else // _TARGET_* |
785 | #error Unsupported or unset target architecture |
786 | #endif // _TARGET_* |
787 | |
788 | /* save displacement */ |
789 | |
790 | *pdwDisp = disp; |
791 | |
792 | return 1; |
793 | } |
794 | |
795 | /***************************************************************************** |
796 | * |
797 | * Callback for register operands. Most probably, this is a local variable or |
798 | * a parameter |
799 | * |
800 | * Return 1 if a name was written representing the register, 0 otherwise. |
801 | */ |
802 | |
803 | /* static */ |
804 | size_t __stdcall DisAssembler::disCchReg(const DIS* pdis, DIS::REGA reg, __in_ecount(cchMax) wchar_t* wz, size_t cchMax) |
805 | { |
806 | DisAssembler* pDisAsm = (DisAssembler*)pdis->PvClient(); |
807 | assert(pDisAsm); |
808 | |
809 | return pDisAsm->disCchRegMember(pdis, reg, wz, cchMax); |
810 | } |
811 | |
812 | size_t DisAssembler::disCchRegMember(const DIS* pdis, DIS::REGA reg, __in_ecount(cchMax) wchar_t* wz, size_t cchMax) |
813 | { |
814 | // TODO-Review: DIS::REGA does not directly map to our regNumber! E.g., look at DISARM64::REGA -- |
815 | // the Wt registers come first (and do map to our regNumber), but the Xt registers follow. |
816 | // Until this is fixed, don't use this function! |
817 | disHasName = false; |
818 | return 0; |
819 | |
820 | #if 0 |
821 | const char * var = disComp->codeGen->siRegVarName( |
822 | (size_t)(pdis->Addr() - disStartAddr), |
823 | pdis->Cb(), |
824 | reg); |
825 | |
826 | if (var) |
827 | { |
828 | if (disHasName) |
829 | { |
830 | /* CchRegRel has been called before - we have a symbol name saved in global var disFuncTempBuf */ |
831 | |
832 | swprintf_s(wz, cchMax, W("%hs'%hs.%hs'" ), getRegName(reg), var, disFuncTempBuf); |
833 | disHasName = false; |
834 | return 1; |
835 | } |
836 | else |
837 | { |
838 | swprintf_s(wz, cchMax, W("%hs'%hs'" ), getRegName(reg), var); |
839 | return 1; |
840 | } |
841 | } |
842 | else |
843 | { |
844 | if (disHasName) |
845 | { |
846 | /* this is the ugly case when a variable is incorrectly presumed dead */ |
847 | |
848 | swprintf_s(wz, cchMax, W("%hs'%hs.%hs'" ), getRegName(reg), "<InstVar>" , disFuncTempBuf); |
849 | disHasName = false; |
850 | return 1; |
851 | } |
852 | |
853 | /* just to make sure we didn't bungle if var returns NULL */ |
854 | disHasName = false; |
855 | return 0; |
856 | } |
857 | #endif // 0 |
858 | } |
859 | |
860 | /***************************************************************************** |
861 | * Helper function to lazily create a map from code address to CORINFO_METHOD_HANDLE. |
862 | */ |
863 | AddrToMethodHandleMap* DisAssembler::GetAddrToMethodHandleMap() |
864 | { |
865 | if (disAddrToMethodHandleMap == nullptr) |
866 | { |
867 | disAddrToMethodHandleMap = new (disComp->getAllocator()) AddrToMethodHandleMap(disComp->getAllocator()); |
868 | } |
869 | return disAddrToMethodHandleMap; |
870 | } |
871 | |
872 | /***************************************************************************** |
873 | * Helper function to lazily create a map from code address to CORINFO_METHOD_HANDLE. |
874 | */ |
875 | AddrToMethodHandleMap* DisAssembler::GetHelperAddrToMethodHandleMap() |
876 | { |
877 | if (disHelperAddrToMethodHandleMap == nullptr) |
878 | { |
879 | disHelperAddrToMethodHandleMap = new (disComp->getAllocator()) AddrToMethodHandleMap(disComp->getAllocator()); |
880 | } |
881 | return disHelperAddrToMethodHandleMap; |
882 | } |
883 | |
884 | /***************************************************************************** |
885 | * Helper function to lazily create a map from relocation address to relocation target address. |
886 | */ |
887 | AddrToAddrMap* DisAssembler::GetRelocationMap() |
888 | { |
889 | if (disRelocationMap == nullptr) |
890 | { |
891 | disRelocationMap = new (disComp->getAllocator()) AddrToAddrMap(disComp->getAllocator()); |
892 | } |
893 | return disRelocationMap; |
894 | } |
895 | |
896 | /***************************************************************************** |
897 | * Return the count of bytes disassembled. |
898 | */ |
899 | |
900 | size_t DisAssembler::CbDisassemble(DIS* pdis, |
901 | size_t offs, |
902 | DIS::ADDR addr, |
903 | const BYTE* pb, |
904 | size_t cbMax, |
905 | FILE* pfile, |
906 | bool findLabels, |
907 | bool printit /* = false */, |
908 | bool dispOffs /* = false */, |
909 | bool dispCodeBytes /* = false */) |
910 | { |
911 | assert(pdis); |
912 | |
913 | size_t cb = pdis->CbDisassemble(addr, pb, cbMax); |
914 | |
915 | if (cb == 0) |
916 | { |
917 | DISASM_DUMP("CbDisassemble offs %Iu addr %I64u\n" , offs, addr); |
918 | // assert(!"can't disassemble instruction!!!"); |
919 | fprintf(pfile, "MSVCDIS can't disassemble instruction @ offset %Iu (0x%02x)!!!\n" , offs, offs); |
920 | #if defined(_TARGET_ARM64_) |
921 | fprintf(pfile, "%08Xh\n" , *(unsigned int*)pb); |
922 | return 4; |
923 | #else |
924 | fprintf(pfile, "%02Xh\n" , *pb); |
925 | return 1; |
926 | #endif |
927 | } |
928 | |
929 | #if defined(_TARGET_ARM64_) |
930 | assert(cb == 4); // all instructions are 4 bytes! |
931 | #endif // _TARGET_ARM64_ |
932 | |
933 | /* remember current offset and instruction size */ |
934 | |
935 | disCurOffset = (size_t)addr; |
936 | disInstSize = cb; |
937 | |
938 | /* Set the disTarget address */ |
939 | |
940 | disTarget = (size_t)pdis->AddrTarget(); |
941 | |
942 | if (findLabels) |
943 | { |
944 | #if defined(_TARGET_XARCH_) |
945 | DISX86::TRMTA terminationType = DISX86::TRMTA(pdis->Trmta()); |
946 | |
947 | /* check the termination type of the instruction */ |
948 | |
949 | switch (terminationType) |
950 | { |
951 | case DISX86::trmtaCallNear16: |
952 | case DISX86::trmtaCallNear32: |
953 | case DISX86::trmtaCallFar: |
954 | |
955 | { |
956 | // Don't count addresses in the relocation table |
957 | size_t targetAddr; |
958 | size_t absoluteAddr = |
959 | (size_t)disGetLinearAddr((size_t)pdis->AddrAddress(1)); // Get the address in the instruction of the |
960 | // call target address (the address the |
961 | // reloc is applied to). |
962 | if (GetRelocationMap()->Lookup(absoluteAddr, &targetAddr)) |
963 | { |
964 | break; |
965 | } |
966 | } |
967 | |
968 | __fallthrough; |
969 | |
970 | case DISX86::trmtaJmpShort: |
971 | case DISX86::trmtaJmpNear: |
972 | case DISX86::trmtaJmpFar: |
973 | case DISX86::trmtaJmpCcShort: |
974 | case DISX86::trmtaJmpCcNear: |
975 | |
976 | /* a CALL is local iff the disTarget is within the block boundary */ |
977 | |
978 | /* mark the jump label in the disTarget vector and return */ |
979 | |
980 | if (disTarget != DIS::addrNil) // There seems to be an assumption that you can't branch to the first |
981 | // address of the function (prolog). |
982 | { |
983 | if (0 <= disTarget && disTarget < disTotalCodeSize) |
984 | { |
985 | /* we're OK, disTarget within block boundary */ |
986 | |
987 | disLabels[disTarget] = 1; |
988 | } |
989 | } |
990 | break; |
991 | |
992 | case DISX86::trmtaFallThrough: |
993 | // We'd like to be able to get a label for code like "lea rcx, [4]" that we use for jump tables, but I |
994 | // can't figure out how. |
995 | break; |
996 | |
997 | default: |
998 | |
999 | /* jump is not in the current code block */ |
1000 | break; |
1001 | |
1002 | } // end switch |
1003 | #elif defined(_TARGET_ARM64_) |
1004 | DISARM64::TRMTA terminationType = DISARM64::TRMTA(pdis->Trmta()); |
1005 | |
1006 | /* check the termination type of the instruction */ |
1007 | |
1008 | switch (terminationType) |
1009 | { |
1010 | case DISARM64::TRMTA::trmtaCall: |
1011 | case DISARM64::TRMTA::trmtaCallCc: |
1012 | |
1013 | { |
1014 | // Don't count addresses in the relocation table |
1015 | size_t targetAddr; |
1016 | size_t absoluteAddr = |
1017 | (size_t)disGetLinearAddr((size_t)pdis->AddrAddress(1)); // Get the address in the instruction of the |
1018 | // call target address (the address the |
1019 | // reloc is applied to). |
1020 | if (GetRelocationMap()->Lookup(absoluteAddr, &targetAddr)) |
1021 | { |
1022 | break; |
1023 | } |
1024 | } |
1025 | |
1026 | __fallthrough; |
1027 | |
1028 | case DISARM64::TRMTA::trmtaBra: |
1029 | case DISARM64::TRMTA::trmtaBraCase: |
1030 | case DISARM64::TRMTA::trmtaBraCc: |
1031 | case DISARM64::TRMTA::trmtaBraCcCase: |
1032 | |
1033 | /* a CALL is local iff the disTarget is within the block boundary */ |
1034 | |
1035 | /* mark the jump label in the disTarget vector and return */ |
1036 | |
1037 | if (disTarget != DIS::addrNil) // There seems to be an assumption that you can't branch to the first |
1038 | // address of the function (prolog). |
1039 | { |
1040 | if (0 <= disTarget && disTarget < disTotalCodeSize) |
1041 | { |
1042 | /* we're OK, disTarget within block boundary */ |
1043 | |
1044 | disLabels[disTarget] = 1; |
1045 | } |
1046 | } |
1047 | break; |
1048 | |
1049 | case DISARM64::TRMTA::trmtaFallThrough: |
1050 | { |
1051 | DIS::INSTRUCTION instr; |
1052 | DIS::OPERAND ops[DISARM64::coperandMax]; |
1053 | bool ok = pdis->FDecode(&instr, ops, ArrLen(ops)); |
1054 | if (ok) |
1055 | { |
1056 | switch ((DISARM64::OPA)instr.opa) |
1057 | { |
1058 | case DISARM64::opaAdr: |
1059 | case DISARM64::opaAdrp: |
1060 | // operand 1 is an address |
1061 | assert(instr.coperand >= 2); |
1062 | assert(ops[1].opcls == DIS::opclsImmediate); |
1063 | assert(ops[1].imcls == DIS::imclsAddress); |
1064 | disTarget = ops[1].dwl; |
1065 | break; |
1066 | default: |
1067 | break; |
1068 | } |
1069 | |
1070 | if (0 <= disTarget && disTarget < disTotalCodeSize) |
1071 | { |
1072 | /* we're OK, disTarget within block boundary */ |
1073 | |
1074 | disLabels[disTarget] = 1; |
1075 | } |
1076 | } |
1077 | } |
1078 | break; |
1079 | |
1080 | default: |
1081 | |
1082 | /* jump is not in the current code block */ |
1083 | break; |
1084 | |
1085 | } // end switch |
1086 | #else // _TARGET_* |
1087 | #error Unsupported or unset target architecture |
1088 | #endif // _TARGET_* |
1089 | |
1090 | return cb; |
1091 | } // end if |
1092 | |
1093 | /* check if we have a label here */ |
1094 | |
1095 | if (printit) |
1096 | { |
1097 | if (disLabels[addr]) |
1098 | { |
1099 | /* print the label and the offset */ |
1100 | |
1101 | fprintf(pfile, "L_%02u:\n" , disLabels[addr]); |
1102 | } |
1103 | } |
1104 | |
1105 | wchar_t wz[MAX_CLASSNAME_LENGTH]; |
1106 | pdis->CchFormatInstr(wz, _countof(wz)); |
1107 | |
1108 | if (printit) |
1109 | { |
1110 | if (dispOffs) |
1111 | { |
1112 | fprintf(pfile, "%03X" , offs); |
1113 | } |
1114 | |
1115 | #ifdef _TARGET_ARM64_ |
1116 | #define CCH_INDENT 8 // fixed sized instructions, always 8 characters |
1117 | #elif defined(_TARGET_AMD64_) |
1118 | #define CCH_INDENT 30 // large constants sometimes |
1119 | #else |
1120 | #define CCH_INDENT 24 |
1121 | #endif |
1122 | |
1123 | size_t cchIndent = CCH_INDENT; |
1124 | |
1125 | if (dispCodeBytes) |
1126 | { |
1127 | static size_t cchBytesMax = -1; |
1128 | |
1129 | if (cchBytesMax == -1) |
1130 | { |
1131 | cchBytesMax = pdis->CchFormatBytesMax(); |
1132 | } |
1133 | |
1134 | wchar_t wzBytes[MAX_CLASSNAME_LENGTH]; |
1135 | assert(cchBytesMax < MAX_CLASSNAME_LENGTH); |
1136 | |
1137 | size_t cchBytes = pdis->CchFormatBytes(wzBytes, _countof(wzBytes)); |
1138 | |
1139 | if (cchBytes > CCH_INDENT) |
1140 | { |
1141 | // Truncate the bytes if they are too long |
1142 | |
1143 | static const wchar_t* elipses = W("...\0" ); |
1144 | const size_t cchElipses = 4; |
1145 | |
1146 | memcpy(&wzBytes[CCH_INDENT - cchElipses], elipses, cchElipses * sizeof(wchar_t)); |
1147 | |
1148 | cchBytes = CCH_INDENT; |
1149 | } |
1150 | |
1151 | fprintf(pfile, " %ls" , wzBytes); |
1152 | cchIndent = CCH_INDENT - cchBytes; |
1153 | } |
1154 | |
1155 | // print the dis-assembled instruction |
1156 | |
1157 | fprintf(pfile, "%*c %ls\n" , cchIndent, ' ', wz); |
1158 | } |
1159 | |
1160 | return cb; |
1161 | } |
1162 | |
1163 | // TODO-Cleanup: this is currently unused, unreferenced. |
1164 | size_t CbDisassembleWithBytes(DIS* pdis, DIS::ADDR addr, const BYTE* pb, size_t cbMax, FILE* pfile) |
1165 | { |
1166 | assert(pdis); |
1167 | DisAssembler* pDisAsm = (DisAssembler*)pdis->PvClient(); |
1168 | assert(pDisAsm); |
1169 | |
1170 | wchar_t wz[MAX_CLASSNAME_LENGTH]; |
1171 | |
1172 | pdis->CchFormatAddr(addr, wz, _countof(wz)); |
1173 | |
1174 | size_t cchIndent = (size_t)fprintf(pfile, " %ls: " , wz); |
1175 | |
1176 | size_t cb = pdis->CbDisassemble(addr, pb, cbMax); |
1177 | |
1178 | if (cb == 0) |
1179 | { |
1180 | fprintf(pfile, "%02Xh\n" , *pb); |
1181 | return (1); |
1182 | } |
1183 | |
1184 | size_t cchBytesMax = pdis->CchFormatBytesMax(); |
1185 | |
1186 | if (cchBytesMax > 18) |
1187 | { |
1188 | // Limit bytes coded to 18 characters |
1189 | |
1190 | cchBytesMax = 18; |
1191 | } |
1192 | |
1193 | wchar_t wzBytes[64]; |
1194 | size_t cchBytes = pdis->CchFormatBytes(wzBytes, _countof(wzBytes)); |
1195 | |
1196 | wchar_t* pwzBytes; |
1197 | wchar_t* pwzNext; |
1198 | |
1199 | for (pwzBytes = wzBytes; pwzBytes != NULL; pwzBytes = pwzNext) |
1200 | { |
1201 | BOOL fFirst = (pwzBytes == wzBytes); |
1202 | |
1203 | cchBytes = wcslen(pwzBytes); |
1204 | |
1205 | if (cchBytes <= cchBytesMax) |
1206 | { |
1207 | pwzNext = NULL; |
1208 | } |
1209 | |
1210 | else |
1211 | { |
1212 | wchar_t ch = pwzBytes[cchBytesMax]; |
1213 | pwzBytes[cchBytesMax] = '\0'; |
1214 | |
1215 | if (ch == W(' ')) |
1216 | { |
1217 | pwzNext = pwzBytes + cchBytesMax + 1; |
1218 | } |
1219 | |
1220 | else |
1221 | { |
1222 | pwzNext = wcsrchr(pwzBytes, W(' ')); |
1223 | assert(pwzNext); |
1224 | |
1225 | pwzBytes[cchBytesMax] = ch; |
1226 | *pwzNext++ = '\0'; |
1227 | } |
1228 | } |
1229 | |
1230 | if (fFirst) |
1231 | { |
1232 | pdis->CchFormatInstr(wz, _countof(wz)); |
1233 | fprintf(pfile, "%-*ls %ls\n" , cchBytesMax, pwzBytes, wz); |
1234 | } |
1235 | |
1236 | else |
1237 | { |
1238 | fprintf(pfile, "%*c%ls\n" , cchIndent, ' ', pwzBytes); |
1239 | } |
1240 | } |
1241 | |
1242 | return (cb); |
1243 | } |
1244 | |
1245 | void DisAssembler::DisasmBuffer(FILE* pfile, bool printit) |
1246 | { |
1247 | DIS* pdis = NULL; |
1248 | |
1249 | #ifdef _TARGET_X86_ |
1250 | pdis = DIS::PdisNew(DIS::distX86); |
1251 | #elif defined(_TARGET_AMD64_) |
1252 | pdis = DIS::PdisNew(DIS::distX8664); |
1253 | #elif defined(_TARGET_ARM64_) |
1254 | pdis = DIS::PdisNew(DIS::distArm64); |
1255 | #else // _TARGET_* |
1256 | #error Unsupported or unset target architecture |
1257 | #endif |
1258 | |
1259 | if (pdis == NULL) |
1260 | { |
1261 | assert(!"out of memory in disassembler?" ); |
1262 | return; |
1263 | } |
1264 | |
1265 | #ifdef _TARGET_64BIT_ |
1266 | pdis->SetAddr64(true); |
1267 | #endif |
1268 | |
1269 | // Store a pointer to the DisAssembler so that the callback functions |
1270 | // can get to it. |
1271 | |
1272 | pdis->PvClientSet((void*)this); |
1273 | |
1274 | /* Calculate addresses */ |
1275 | |
1276 | size_t ibCur = 0; |
1277 | DIS::ADDR addr = 0; // Always emit code with respect to a "0" base address. |
1278 | |
1279 | /* First walk the code to find all jump targets */ |
1280 | |
1281 | while (ibCur < disTotalCodeSize) |
1282 | { |
1283 | size_t cb; |
1284 | |
1285 | cb = CbDisassemble(pdis, ibCur, addr + ibCur, disGetLinearAddr(ibCur), disGetBufferSize(ibCur), pfile, |
1286 | true); // find labels |
1287 | |
1288 | // CbDisassemble returning > MAX_INT... give me a break. |
1289 | ibCur += cb; |
1290 | } |
1291 | |
1292 | /* reset the label counter and start assigning consecutive number labels to the label locations */ |
1293 | |
1294 | BYTE label = 0; |
1295 | for (unsigned i = 0; i < disTotalCodeSize; i++) |
1296 | { |
1297 | if (disLabels[i] != 0) |
1298 | { |
1299 | disLabels[i] = ++label; |
1300 | } |
1301 | } |
1302 | |
1303 | /* Re-initialize addresses for disassemble phase */ |
1304 | |
1305 | ibCur = 0; |
1306 | addr = 0; |
1307 | |
1308 | // Set callbacks only if we are displaying it. Else, the scheduler has called it |
1309 | |
1310 | if (printit) |
1311 | { |
1312 | /* Set the callback functions for symbol lookup */ |
1313 | |
1314 | pdis->PfncchaddrSet(disCchAddr); |
1315 | pdis->PfncchfixupSet(disCchFixup); |
1316 | pdis->PfncchregrelSet(disCchRegRel); |
1317 | pdis->PfncchregSet(disCchReg); |
1318 | } |
1319 | |
1320 | while (ibCur < disTotalCodeSize) |
1321 | { |
1322 | size_t cb; |
1323 | |
1324 | cb = CbDisassemble(pdis, ibCur, addr + ibCur, disGetLinearAddr(ibCur), disGetBufferSize(ibCur), pfile, |
1325 | false, // find labels |
1326 | printit, |
1327 | !disDiffable, // display relative offset |
1328 | #ifdef DEBUG |
1329 | !disDiffable // Display code bytes? |
1330 | #else |
1331 | false // Display code bytes? |
1332 | #endif |
1333 | ); |
1334 | |
1335 | ibCur += (unsigned)cb; |
1336 | } |
1337 | |
1338 | delete pdis; |
1339 | } |
1340 | |
1341 | /***************************************************************************** |
1342 | * Given a linear offset into the code, find a pointer to the actual code (either in the hot or cold section) |
1343 | * |
1344 | * Arguments: |
1345 | * offset - The linear offset into the code. It must point within the code. |
1346 | */ |
1347 | |
1348 | const BYTE* DisAssembler::disGetLinearAddr(size_t offset) |
1349 | { |
1350 | if (offset < disHotCodeSize) |
1351 | { |
1352 | return (const BYTE*)disHotCodeBlock + offset; |
1353 | } |
1354 | else |
1355 | { |
1356 | return (const BYTE*)disColdCodeBlock + offset - disHotCodeSize; |
1357 | } |
1358 | } |
1359 | |
1360 | /***************************************************************************** |
1361 | * Given a linear offset into the code, determine how many bytes are remaining in the buffer. |
1362 | * This will only return the number of bytes left in either the hot or cold buffer. This is used |
1363 | * to avoid walking off the end of the buffer. |
1364 | * |
1365 | * Arguments: |
1366 | * offset - The linear offset into the code. It must point within the code. |
1367 | */ |
1368 | |
1369 | size_t DisAssembler::disGetBufferSize(size_t offset) |
1370 | { |
1371 | if (offset < disHotCodeSize) |
1372 | { |
1373 | return disHotCodeSize - offset; |
1374 | } |
1375 | else |
1376 | { |
1377 | return disHotCodeSize + disColdCodeSize - offset; |
1378 | } |
1379 | } |
1380 | |
1381 | /***************************************************************************** |
1382 | * Get the function name for a given absolute address. |
1383 | */ |
1384 | |
1385 | const char* DisAssembler::disGetMethodFullName(size_t addr) |
1386 | { |
1387 | CORINFO_METHOD_HANDLE res; |
1388 | |
1389 | // First check the JIT helper table: they're very common. |
1390 | if (GetHelperAddrToMethodHandleMap()->Lookup(addr, &res)) |
1391 | { |
1392 | return disComp->eeGetMethodFullName(res); |
1393 | } |
1394 | |
1395 | // Next check the "normal" registered call targets |
1396 | if (GetAddrToMethodHandleMap()->Lookup(addr, &res)) |
1397 | { |
1398 | return disComp->eeGetMethodFullName(res); |
1399 | } |
1400 | |
1401 | return nullptr; |
1402 | } |
1403 | |
1404 | /***************************************************************************** |
1405 | * Register a called function address as associated with a CORINFO_METHOD_HANDLE. |
1406 | * |
1407 | * Arguments: |
1408 | * addr - The absolute address of the target function. |
1409 | * methHnd - The method handle associated with 'addr'. |
1410 | */ |
1411 | |
1412 | void DisAssembler::disSetMethod(size_t addr, CORINFO_METHOD_HANDLE methHnd) |
1413 | { |
1414 | if (!disComp->opts.doLateDisasm) |
1415 | { |
1416 | return; |
1417 | } |
1418 | |
1419 | if (disComp->eeGetHelperNum(methHnd)) |
1420 | { |
1421 | DISASM_DUMP("Helper function: %p => %p\n" , addr, methHnd); |
1422 | GetHelperAddrToMethodHandleMap()->Set(addr, methHnd); |
1423 | } |
1424 | else |
1425 | { |
1426 | DISASM_DUMP("Function: %p => %p\n" , addr, methHnd); |
1427 | GetAddrToMethodHandleMap()->Set(addr, methHnd); |
1428 | } |
1429 | } |
1430 | |
1431 | /***************************************************************************** |
1432 | * Register a relocation. |
1433 | * |
1434 | * Arguments: |
1435 | * relocAddr - The absolute address the relocation applies to. |
1436 | * targetAddr - The absolute address the relocation points to. |
1437 | */ |
1438 | |
1439 | void DisAssembler::disRecordRelocation(size_t relocAddr, size_t targetAddr) |
1440 | { |
1441 | if (!disComp->opts.doLateDisasm) |
1442 | { |
1443 | return; |
1444 | } |
1445 | |
1446 | DISASM_DUMP("Relocation %p => %p\n" , relocAddr, targetAddr); |
1447 | GetRelocationMap()->Set(relocAddr, targetAddr); |
1448 | } |
1449 | |
1450 | /***************************************************************************** |
1451 | * |
1452 | * Disassemble the code which has been generated |
1453 | */ |
1454 | |
1455 | void DisAssembler::disAsmCode(BYTE* hotCodePtr, size_t hotCodeSize, BYTE* coldCodePtr, size_t coldCodeSize) |
1456 | { |
1457 | if (!disComp->opts.doLateDisasm) |
1458 | { |
1459 | return; |
1460 | } |
1461 | |
1462 | #ifdef DEBUG |
1463 | // Should we make it diffable? |
1464 | disDiffable = disComp->opts.dspDiffable; |
1465 | #else // !DEBUG |
1466 | // NOTE: non-debug builds are always diffable! |
1467 | disDiffable = true; |
1468 | #endif // !DEBUG |
1469 | |
1470 | #ifdef DEBUG |
1471 | const wchar_t* fileName = JitConfig.JitLateDisasmTo(); |
1472 | if (fileName != nullptr) |
1473 | { |
1474 | errno_t ec = _wfopen_s(&disAsmFile, fileName, W("a+" )); |
1475 | if (ec != 0) |
1476 | { |
1477 | disAsmFile = nullptr; |
1478 | } |
1479 | } |
1480 | #else // !DEBUG |
1481 | // NOTE: non-DEBUG builds always use jitstdout currently! |
1482 | disAsmFile = jitstdout; |
1483 | #endif // !DEBUG |
1484 | |
1485 | if (disAsmFile == nullptr) |
1486 | { |
1487 | disAsmFile = jitstdout; |
1488 | } |
1489 | |
1490 | // As this writes to a common file, this is not reentrant. |
1491 | |
1492 | assert(hotCodeSize > 0); |
1493 | if (coldCodeSize == 0) |
1494 | { |
1495 | fprintf(disAsmFile, "************************** %hs:%hs size 0x%04IX **************************\n\n" , |
1496 | disCurClassName, disCurMethodName, hotCodeSize); |
1497 | |
1498 | fprintf(disAsmFile, "Base address : %ph\n" , dspAddr(hotCodePtr)); |
1499 | } |
1500 | else |
1501 | { |
1502 | fprintf(disAsmFile, |
1503 | "************************** %hs:%hs hot size 0x%04IX cold size 0x%04IX **************************\n\n" , |
1504 | disCurClassName, disCurMethodName, hotCodeSize, coldCodeSize); |
1505 | |
1506 | fprintf(disAsmFile, "Hot address : %ph\n" , dspAddr(hotCodePtr)); |
1507 | fprintf(disAsmFile, "Cold address : %ph\n" , dspAddr(coldCodePtr)); |
1508 | } |
1509 | |
1510 | disStartAddr = 0; |
1511 | disHotCodeBlock = (size_t)hotCodePtr; |
1512 | disHotCodeSize = hotCodeSize; |
1513 | disColdCodeBlock = (size_t)coldCodePtr; |
1514 | disColdCodeSize = coldCodeSize; |
1515 | |
1516 | disTotalCodeSize = disHotCodeSize + disColdCodeSize; |
1517 | |
1518 | disLabels = new (disComp, CMK_DebugOnly) BYTE[disTotalCodeSize](); |
1519 | |
1520 | DisasmBuffer(disAsmFile, /* printIt */ true); |
1521 | fprintf(disAsmFile, "\n" ); |
1522 | |
1523 | if (disAsmFile != jitstdout) |
1524 | { |
1525 | fclose(disAsmFile); |
1526 | } |
1527 | else |
1528 | { |
1529 | fflush(disAsmFile); |
1530 | } |
1531 | } |
1532 | |
1533 | /*****************************************************************************/ |
1534 | // This function is called for every method. Checks if we are supposed to disassemble |
1535 | // the method, and where to send the disassembly output. |
1536 | |
1537 | void DisAssembler::disOpenForLateDisAsm(const char* curMethodName, const char* curClassName, PCCOR_SIGNATURE sig) |
1538 | { |
1539 | if (!disComp->opts.doLateDisasm) |
1540 | { |
1541 | return; |
1542 | } |
1543 | |
1544 | disCurMethodName = curMethodName; |
1545 | disCurClassName = curClassName; |
1546 | } |
1547 | |
1548 | /*****************************************************************************/ |
1549 | |
1550 | void DisAssembler::disInit(Compiler* pComp) |
1551 | { |
1552 | assert(pComp); |
1553 | disComp = pComp; |
1554 | disHasName = false; |
1555 | disLabels = nullptr; |
1556 | disAddrToMethodHandleMap = nullptr; |
1557 | disHelperAddrToMethodHandleMap = nullptr; |
1558 | disRelocationMap = nullptr; |
1559 | disDiffable = false; |
1560 | disAsmFile = nullptr; |
1561 | } |
1562 | |
1563 | /*****************************************************************************/ |
1564 | #endif // LATE_DISASM |
1565 | /*****************************************************************************/ |
1566 | |