-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Expand file tree
/
Copy pathmodule.cpp
More file actions
5150 lines (4402 loc) · 177 KB
/
module.cpp
File metadata and controls
5150 lines (4402 loc) · 177 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//*****************************************************************************
// File: module.cpp
//
//
//*****************************************************************************
#include "stdafx.h"
#include "winbase.h"
#include "winbase.h"
#include "corpriv.h"
#include "corsym.h"
#include "pedecoder.h"
#include "memorystreams.h"
//---------------------------------------------------------------------------------------
// Initialize a new CordbModule around a Module in the target.
//
// Arguments:
// pProcess - process that this module lives in
// vmAssembly - CLR cookie for module.
CordbModule::CordbModule(
CordbProcess * pProcess,
VMPTR_Module vmModule,
VMPTR_Assembly vmAssembly)
: CordbBase(pProcess, VmPtrToCookie(vmModule), enumCordbModule),
m_pAssembly(0),
m_pAppDomain(0),
m_classes(11),
m_functions(101),
m_vmAssembly(vmAssembly),
m_vmModule(vmModule),
m_EnCCount(0),
m_fForceMetaDataSerialize(FALSE),
m_nativeCodeTable(101)
{
_ASSERTE(pProcess->GetProcessLock()->HasLock());
_ASSERTE(!vmModule.IsNull());
m_nLoadEventContinueCounter = 0;
#ifdef _DEBUG
m_classes.DebugSetRSLock(pProcess->GetProcessLock());
m_functions.DebugSetRSLock(pProcess->GetProcessLock());
#endif
// Fill out properties via DAC.
ModuleInfo modInfo;
IfFailThrow(pProcess->GetDAC()->GetModuleData(vmModule, &modInfo)); // throws
m_PEBuffer.Init(modInfo.pPEBaseAddress, modInfo.nPESize);
m_fDynamic = modInfo.fIsDynamic;
m_fInMemory = modInfo.fInMemory;
m_vmPEFile = modInfo.vmPEAssembly;
m_pAppDomain = pProcess->GetAppDomain();
if (!vmAssembly.IsNull())
{
m_pAssembly = m_pAppDomain->LookupOrCreateAssembly(vmAssembly);
}
else
{
m_pAssembly = m_pAppDomain->LookupOrCreateAssembly(modInfo.vmAssembly);
}
#ifdef _DEBUG
m_nativeCodeTable.DebugSetRSLock(GetProcess()->GetProcessLock());
#endif
// MetaData is initialized lazily (via code:CordbModule::GetMetaDataImporter).
// Getting the metadata may be very expensive (especially if we go through the metadata locator, which
// invokes back to the data-target), so don't do it until asked.
// m_pIMImport, m_pInternalMetaDataImport are smart pointers that already initialize to NULL.
}
#ifdef _DEBUG
//---------------------------------------------------------------------------------------
// Callback helper for code:CordbModule::DbgAssertModuleDeleted
//
// Arguments
// vmAssembly - assembly in the enumeration
// pUserData - pointer to the CordbModule that we just got an exit event for.
//
void DbgAssertModuleDeletedCallback(VMPTR_Assembly vmAssembly, void * pUserData)
{
CordbModule * pThis = reinterpret_cast<CordbModule *>(pUserData);
INTERNAL_DAC_CALLBACK(pThis->GetProcess());
if (!pThis->m_vmAssembly.IsNull())
{
VMPTR_Assembly vmAssemblyDeleted = pThis->m_vmAssembly;
CONSISTENCY_CHECK_MSGF((vmAssemblyDeleted != vmAssembly),
("A Module Unload event was sent for a module, but it still shows up in the enumeration.\n vmAssemblyDeleted=%p\n",
VmPtrToCookie(vmAssemblyDeleted)));
}
}
//---------------------------------------------------------------------------------------
// Assert that a module is no longer discoverable via enumeration.
//
// Notes:
// See code:IDacDbiInterface#Enumeration for rules that we're asserting.
// This is a debug only method.
//
void CordbModule::DbgAssertModuleDeleted()
{
IfFailThrow(GetProcess()->GetDAC()->EnumerateAssembliesInAppDomain(
m_pAppDomain->GetADToken(),
DbgAssertModuleDeletedCallback,
this));
}
#endif // _DEBUG
CordbModule::~CordbModule()
{
// We should have been explicitly neutered before our internal ref went to 0.
_ASSERTE(IsNeutered());
_ASSERTE(m_pIMImport == NULL);
}
// Neutered by CordbAppDomain
void CordbModule::Neuter()
{
// m_pAppDomain, m_pAssembly assigned w/o AddRef()
m_classes.NeuterAndClear(GetProcess()->GetProcessLock());
m_functions.NeuterAndClear(GetProcess()->GetProcessLock());
m_nativeCodeTable.NeuterAndClear(GetProcess()->GetProcessLock());
m_pClass.Clear();
// This is very important because it also releases the metadata's potential file locks.
m_pInternalMetaDataImport.Clear();
m_pIMImport.Clear();
CordbBase::Neuter();
}
//
// Creates an IStream based off the memory described by the TargetBuffer.
//
// Arguments:
// pProcess - process that buffer is valid in.
// buffer - memory range in target
// ppStream - out parameter to receive the new stream. *ppStream == NULL on input.
// caller owns the new object and must call Release.
//
// Returns:
// Throws on error.
// Common errors include if memory is missing in the target.
//
// Notes:
// This will copy the memory over from the TargetBuffer, and then create a new IStream
// object around it.
//
void GetStreamFromTargetBuffer(CordbProcess * pProcess, TargetBuffer buffer, IStream ** ppStream)
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
_ASSERTE(ppStream != NULL);
_ASSERTE(*ppStream == NULL);
int cbSize = buffer.cbSize;
NewArrayHolder<BYTE> localBuffer(new BYTE[cbSize]);
pProcess->SafeReadBuffer(buffer, localBuffer);
HRESULT hr = E_FAIL;
hr = CInMemoryStream::CreateStreamOnMemoryCopy(localBuffer, cbSize, ppStream);
IfFailThrow(hr);
_ASSERTE(*ppStream != NULL);
}
//
// Helper API to get in-memory symbols from the target into a host stream object.
//
// Arguments:
// ppStream - out parameter to receive the new stream. *ppStream == NULL on input.
// caller owns the new object and must call Release.
//
// Returns:
// kSymbolFormatNone if no PDB stream is present. This is a common case for
// file-based modules, and also for dynamic modules that just aren't tracking
// debug information.
// The format of the symbols stored into ppStream. This is common:
// - Ref.Emit modules if the debuggee generated debug symbols,
// - in-memory modules (such as Load(Byte[], Byte[])
// - hosted modules.
// Throws on error
//
IDacDbiInterface::SymbolFormat CordbModule::GetInMemorySymbolStream(IStream ** ppStream)
{
// @dbgtodo : add a PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM contract
// This function is mainly called internally in dbi, and also by the shim to emulate the
// UpdateModuleSymbols callback on attach.
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
_ASSERTE(ppStream != NULL);
_ASSERTE(*ppStream == NULL);
*ppStream = NULL;
TargetBuffer bufferPdb;
IDacDbiInterface::SymbolFormat symFormat;
IfFailThrow(GetProcess()->GetDAC()->GetSymbolsBuffer(m_vmModule, &bufferPdb, &symFormat));
if (bufferPdb.IsEmpty())
{
// No in-memory PDB. Common case.
_ASSERTE(symFormat == IDacDbiInterface::kSymbolFormatNone);
return IDacDbiInterface::kSymbolFormatNone;
}
else
{
_ASSERTE(symFormat != IDacDbiInterface::kSymbolFormatNone);
GetStreamFromTargetBuffer(GetProcess(), bufferPdb, ppStream);
return symFormat;
}
}
//---------------------------------------------------------------------------------------
// Accessor for PE file.
//
// Returns:
// VMPTR_PEAssembly for this module. Should always be non-null
//
// Notes:
// A main usage of this is to find the proper internal MetaData importer.
// DACized code needs to map from PEAssembly --> IMDInternalImport.
//
VMPTR_PEAssembly CordbModule::GetPEFile()
{
return m_vmPEFile;
}
//---------------------------------------------------------------------------------------
//
// Top-level getter for the public metadata importer for this module
//
// Returns:
// metadata importer.
// Never returns NULL. Will throw some hr (likely CORDBG_E_MISSING_METADATA) instead.
//
// Notes:
// This will lazily create the metadata, possibly invoking back into the data-target.
IMetaDataImport * CordbModule::GetMetaDataImporter()
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
// If we already have it, then we're done.
// This is critical to do at the top of this function to avoid potential recursion.
if (m_pIMImport != NULL)
{
return m_pIMImport;
}
// Lazily initialize
// Fetch metadata from target
LOG((LF_CORDB,LL_INFO1000, "CM::GMI Lazy init refreshing metadata\n"));
ALLOW_DATATARGET_MISSING_MEMORY(
RefreshMetaData();
);
// If lookup failed from the Module & target memory, try the metadata locator interface
// from debugger, if we have one.
if (m_pIMImport == NULL)
{
// The process's LookupMetaData will ping the debugger's ICorDebugMetaDataLocator iface.
CordbProcess * pProcess = GetProcess();
RSLockHolder processLockHolder(pProcess->GetProcessLock());
m_pInternalMetaDataImport.Clear();
// Do not call code:CordbProcess::LookupMetaData from this function. It will try to load
// through the CordbModule again which will end up back here, and on failure you'll fill the stack.
// Since we've already done everything possible from the Module anyhow, just call the
// stuff that talks to the debugger.
// Don't do anything with the ptr returned here, since it's really m_pInternalMetaDataImport.
pProcess->LookupMetaDataFromDebugger(m_vmPEFile, this);
}
// If we still can't get it, throw.
if (m_pIMImport == NULL)
{
ThrowHR(CORDBG_E_MISSING_METADATA);
}
return m_pIMImport;
}
// Refresh the metadata cache if a profiler added new rows.
//
// Arguments:
// token - token that we want to ensure is in the metadata cache.
//
// Notes:
// In profiler case, this may be referred to new rows and we may need to update the metadata
// This only supports StandAloneSigs.
//
void CordbModule::UpdateMetaDataCacheIfNeeded(mdToken token)
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token=0x%x\n", token));
// If we aren't trying to keep parity with our legacy profiler metadata update behavior
// then we should avoid this temporary update mechanism entirely
if(GetProcess()->GetWriteableMetadataUpdateMode() != LegacyCompatPolicy)
{
return;
}
//
// 1) Check if in-range? Compare against tables, etc.
//
if(CheckIfTokenInMetaData(token))
{
LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token was present\n"));
return;
}
//
// 2) Copy over new MetaData. From now on we assume that the profiler is
// modifying module metadata and that we need to serialize in process
// at each refresh
//
LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN token was not present, refreshing\n"));
m_fForceMetaDataSerialize = TRUE;
RefreshMetaData();
// If we are dump debugging, we may still not have it. Nothing to be done.
}
// Returns TRUE if the token is present, FALSE if not.
BOOL CordbModule::CheckIfTokenInMetaData(mdToken token)
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
LOG((LF_CORDB,LL_INFO10000, "CM::CITIM token=0x%x\n", token));
_ASSERTE(TypeFromToken(token) == mdtSignature);
RSExtSmartPtr<IMetaDataTables> pTable;
HRESULT hr = GetMetaDataImporter()->QueryInterface(IID_IMetaDataTables, (void**) &pTable);
_ASSERTE(SUCCEEDED(hr));
if (FAILED(hr))
{
ThrowHR(hr);
}
ULONG cbRowsAvailable; // number of rows in the table
hr = pTable->GetTableInfo(
mdtSignature >> 24, // [IN] Which table.
NULL, // [OUT] Size of a row, bytes.
&cbRowsAvailable, // [OUT] Number of rows.
NULL, // [OUT] Number of columns in each row.
NULL, // [OUT] Key column, or -1 if none.
NULL); // [OUT] Name of the table.
_ASSERTE(SUCCEEDED(hr));
if (FAILED(hr))
{
ThrowHR(hr);
}
// Rows start counting with number 1.
ULONG rowRequested = RidFromToken(token);
LOG((LF_CORDB,LL_INFO10000, "CM::UMCIN requested=0x%x available=0x%x\n", rowRequested, cbRowsAvailable));
return (rowRequested <= cbRowsAvailable);
}
// This helper class ensures the remote serailzied buffer gets deleted in the RefreshMetaData
// function below
class CleanupRemoteBuffer
{
public:
CordbProcess* pProcess;
CordbModule* pModule;
TargetBuffer bufferMetaData;
BOOL fDoCleanup;
CleanupRemoteBuffer() :
fDoCleanup(FALSE) { }
~CleanupRemoteBuffer()
{
if(fDoCleanup)
{
//
// Send 2nd event to free buffer.
//
DebuggerIPCEvent event;
pProcess->InitIPCEvent(&event,
DB_IPCE_RESOLVE_UPDATE_METADATA_2,
true,
pModule->GetAppDomain()->GetADToken());
event.MetadataUpdateRequest.pMetadataStart = CORDB_ADDRESS_TO_PTR(bufferMetaData.pAddress);
// Note: two-way event here...
IfFailThrow(pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
_ASSERTE(event.type == DB_IPCE_RESOLVE_UPDATE_METADATA_2_RESULT);
}
}
};
// Called to refetch metadata. This occurs when a dynamic module grows or the profiler
// has edited the metadata
void CordbModule::RefreshMetaData()
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
LOG((LF_CORDB,LL_INFO1000, "CM::RM\n"));
// There are several different ways we can get the metadata
// 1) [Most common] Module is loaded into VM and never changed. The importer
// will be constructed referring to the file on disk. This is a significant
// working set win because the VM and debugger share the image. If there is
// an error reading by file we can fall back to case #2 for these modules
// 2) Most modules have a buffer in target memory that represents their
// metadata. We copy that data over the RS and construct an in-memory
// importer on top of it.
// 3) The only modules that don't have a suitable buffer (case #2) are those
// modified in memory via the profiling API (or ENC). A message can be sent from
// the debugger to the debuggee instructing it to allocate a buffer and
// serialize the metadata into it. Then we copy that data to the RS and
// construct an in-memory importer on top of it.
// We don't need to send this message in the ENC case because the debugger
// has the same changes applied as the debuggee.
// 4) Case #3 won't work when dump debugging because we can't send IPC events.
// Instead we can locate chunks of the metadata pointed to in the implementation
// details of a remote MDInternalRW object, marshal that memory over to the
// debugger process, and then put a metadata reader on top of it.
// In time this DAC'ized metadata could be used in almost any scenario,
// although its probably worth keeping the file mapping technique in case
// #1 around for its performance wins.
CordbProcess * pProcess = GetProcess();
TargetBuffer bufferMetaData;
CleanupRemoteBuffer cleanup; // this local has a destructor to do some finally work
// check for scenarios we might want to handle with case #4
if (GetProcess()->GetShim() == NULL &&
GetProcess()->GetWriteableMetadataUpdateMode() == AlwaysShowUpdates &&
!m_fDynamic)
{
//None of the above requirements are particularly hard to change in the future as needed...
// a) dump-debugging mode - If we do this on a process that can move forward we need a mechanism to determine
// when to refetch the metadata.
// b) AlwaysShowUpdates - this is purely a risk mitigation choice, there aren't any known back-compat issues
// using DAC'ized metadata. If you want back-compat with the in-proc debugging behavior
// you need to figure out how to ReOpen the same public MD interface with new data.
// c) !m_fDynamic - A risk mitigation choice. Initial testing suggests it would work fine.
// So far we've only got a reader for in-memory-writable metadata (MDInternalRW implementation)
// We could make a reader for MDInternalRO, but no need yet. This also ensures we don't encroach into common
// scenario where we can map a file on disk.
TADDR remoteMDInternalRWAddr = (TADDR)NULL;
IfFailThrow(GetProcess()->GetDAC()->GetPEFileMDInternalRW(m_vmPEFile, &remoteMDInternalRWAddr));
if (remoteMDInternalRWAddr != (TADDR)NULL)
{
// we should only be doing this once to initialize, we don't support reopen with this technique
_ASSERTE(m_pIMImport == NULL);
ULONG32 mdStructuresVersion;
HRESULT hr = GetProcess()->GetDAC()->GetMDStructuresVersion(&mdStructuresVersion);
IfFailThrow(hr);
ULONG32 mdStructuresDefines;
hr = GetProcess()->GetDAC()->GetDefinesBitField(&mdStructuresDefines);
IfFailThrow(hr);
IMetaDataDispenserCustom* pDispCustom = NULL;
hr = GetProcess()->GetDispenser()->QueryInterface(IID_IMetaDataDispenserCustom, (void**)&pDispCustom);
IfFailThrow(hr);
IMDCustomDataSource* pDataSource = NULL;
hr = CreateRemoteMDInternalRWSource(remoteMDInternalRWAddr, GetProcess()->GetDataTarget(), mdStructuresDefines, mdStructuresVersion, &pDataSource);
IfFailThrow(hr);
IMetaDataImport* pImport = NULL;
hr = pDispCustom->OpenScopeOnCustomDataSource(pDataSource, 0, IID_IMetaDataImport, (IUnknown**)&m_pIMImport);
IfFailThrow(hr);
UpdateInternalMetaData();
return;
}
}
if(!m_fForceMetaDataSerialize) // case 1 and 2
{
LOG((LF_CORDB,LL_INFO10000, "CM::RM !m_fForceMetaDataSerialize case\n"));
IfFailThrow(GetProcess()->GetDAC()->GetMetadata(m_vmModule, &bufferMetaData)); // throws
}
else if (GetProcess()->GetShim() == NULL) // case 3 won't work on a dump so don't try
{
return;
}
else // case 3 on a live process
{
LOG((LF_CORDB,LL_INFO10000, "CM::RM m_fForceMetaDataSerialize case\n"));
//
// Send 1 event to get metadata. This allocates a buffer
//
DebuggerIPCEvent event;
pProcess->InitIPCEvent(&event,
DB_IPCE_RESOLVE_UPDATE_METADATA_1,
true,
GetAppDomain()->GetADToken());
event.MetadataUpdateRequest.vmModule = m_vmModule;
// Note: two-way event here...
IfFailThrow(pProcess->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)));
_ASSERTE(event.type == DB_IPCE_RESOLVE_UPDATE_METADATA_1_RESULT);
//
// Update it on the RS
//
bufferMetaData.Init(PTR_TO_CORDB_ADDRESS(event.MetadataUpdateRequest.pMetadataStart), (ULONG) event.MetadataUpdateRequest.nMetadataSize);
// init the cleanup object to ensure the buffer gets destroyed later
cleanup.bufferMetaData = bufferMetaData;
cleanup.pProcess = pProcess;
cleanup.pModule = this;
cleanup.fDoCleanup = TRUE;
}
InitMetaData(bufferMetaData, IsFileMetaDataValid()); // throws
}
// Determines whether the on-disk metadata for this module is usable as the
// current metadata
BOOL CordbModule::IsFileMetaDataValid()
{
bool fOpenFromFile = true;
// Dynamic, In-memory, modules must be OpenScopeOnMemory.
// For modules that require the metadata to be serialized in memory, we must also OpenScopeOnMemory
// For Enc, we'll can use OpenScope(onFile) and it will get converted to Memory when we get an emitter.
// We're called from before the ModuleLoad callback, so EnC status hasn't been set yet, so
// EnC will be false.
if (m_fDynamic || m_fInMemory || m_fForceMetaDataSerialize)
{
LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: m_fDynamic=0x%x m_fInMemory=0x%x m_fForceMetaDataSerialize=0x%x\n",
m_fDynamic, m_fInMemory, m_fForceMetaDataSerialize));
fOpenFromFile = false;
}
#ifdef _DEBUG
// Reg key override to force us to use Open-by-memory. This can let us run perf tests to
// compare the Open-by-mem vs. Open-by-file.
static DWORD openFromFile = 99;
if (openFromFile == 99)
openFromFile = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgNoOpenMDByFile);
if (openFromFile)
{
LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: INTERNAL_DbgNoOpenMDByFile is set\n"));
fOpenFromFile = false;
}
#endif
LOG((LF_CORDB,LL_INFO10000, "CM::IFMV: returns 0x%x\n", fOpenFromFile));
return fOpenFromFile;
}
//---------------------------------------------------------------------------------------
// Accessor for Internal MetaData importer. This is lazily initialized.
//
// Returns:
// Internal MetaDataImporter, which can be handed off to DAC. Not AddRef().
// Should be non-null. Throws on error.
//
// Notes:
// An internal metadata importer is used extensively by DAC-ized code (And Edit-and-continue).
// This should not be handed out through ICorDebug.
IMDInternalImport * CordbModule::GetInternalMD()
{
if (m_pInternalMetaDataImport == NULL)
{
UpdateInternalMetaData(); // throws
}
return m_pInternalMetaDataImport;
}
//---------------------------------------------------------------------------------------
// The one-stop top-level initialization function the metadata (both public and private) for this module.
//
// Arguments:
// buffer - valid buffer into target containing the metadata.
// useFileMappingOptimization - if true this allows us to attempt just opening the importer
// by using the metadata in the module on disk. if false or
// if the attempt fails we open the metadata import on memory in
// target buffer
//
// Notes:
// This will initialize both the internal and public metadata from the buffer in the target.
// Only called as a helper from RefreshMetaData()
//
// This may throw (eg, target buffer is missing).
//
void CordbModule::InitMetaData(TargetBuffer buffer, BOOL allowFileMappingOptimization)
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
LOG((LF_CORDB,LL_INFO100000, "CM::IM: initing with remote buffer 0x%p length 0x%x\n",
CORDB_ADDRESS_TO_PTR(buffer.pAddress), buffer.cbSize));
// clear all the metadata
m_pInternalMetaDataImport.Clear();
if (m_pIMImport == NULL)
{
// The optimization we're going for here is that the OS will use the same physical memory to
// back multiple ReadOnly opens of the same file. Thus since we expect the target process in
// live debugging, or the debugger in dump debugging, has already opened the file we would
// like to not create a local buffer and spend time copying in metadata from the target when
// the OS will happily do address lookup magic against the same physical memory for everyone.
// Try getting the data from the file if allowed, and fall back to using the buffer
// if required
HRESULT hr = S_OK;
if (allowFileMappingOptimization)
{
hr = InitPublicMetaDataFromFile();
if(FAILED(hr))
{
LOG((LF_CORDB,LL_INFO1000000, "CM::IPM: File mapping failed with hr=0x%x\n", hr));
}
}
if(!allowFileMappingOptimization || FAILED(hr))
{
// This is where the expensive copy of all metadata content from target memory
// that we would like to try and avoid happens.
InitPublicMetaData(buffer);
}
}
else
{
// We've already handed out an Import object, and so we can't create a new pointer instance.
// Instead, we update the existing instance with new data.
UpdatePublicMetaDataFromRemote(buffer);
}
// if we haven't set it by this point UpdateInternalMetaData below is going to get us
// in an infinite loop of refreshing public metadata
_ASSERTE(m_pIMImport != NULL);
// Now that public metadata has changed, force internal metadata to update too.
// Public and internal metadata expose different access interfaces to the same underlying storage.
UpdateInternalMetaData();
}
//---------------------------------------------------------------------------------------
// Updates the Internal MetaData object from the public importer. Lazily fetch public importer if needed.
//
// Assumptions:
// Caller has cleared Internal metadata before even updating public metadata.
// This way, if the caller fails halfway through updating the public metadata, we don't have
// stale internal MetaData.
void CordbModule::UpdateInternalMetaData()
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
// Caller should have already cleared it.
_ASSERTE(m_pInternalMetaDataImport == NULL);
// Get the importer. If it's currently null, this will go fetch it.
IMetaDataImport * pImport = GetMetaDataImporter(); // throws
// If both the public and the private interfaces are NULL on entry to this function, the call above will
// recursively call this function. This can happen if the caller calls GetInternalMD() directly
// instead of InitMetaData(). In this case, the above function call will have initialized the internal
// interface as well, so we need to check for it here.
if (m_pInternalMetaDataImport == NULL)
{
HRESULT hr = GetMDInternalInterfaceFromPublic(
pImport,
IID_IMDInternalImport,
reinterpret_cast<void**> (&m_pInternalMetaDataImport));
if (m_pInternalMetaDataImport == NULL)
{
ThrowHR(hr);
}
}
_ASSERTE(m_pInternalMetaDataImport != NULL);
}
// Initialize the public metadata.
//
// The debuggee already has a copy of the metadata in its process.
// If we OpenScope on file as read-only, the OS file-system will share our metadata with the
// copy in the debuggee. This can be a major perf win. FX metadata can be over 8 MB+.
// OpenScopeOnMemory can't be shared b/c we allocate a buffer.
HRESULT CordbModule::InitPublicMetaDataFromFile()
{
INTERNAL_API_ENTRY(this->GetProcess());
// @dbgtodo metadata - In v3, we can't assume we have the same path namespace as the target (i.e. it could be
// a dump or remote), so we can't just try and open the file. Instead we have to rely on interfaces
// on the datatarget to map the metadata here. Note that this must also work for minidumps where the
// metadata isn't necessarily in the dump image.
const WCHAR * szFullPathName = GetModulePath();
// @dbgtodo metadata - This is really a CreateFile() call which we can't do. We must offload this to
// the data target for the dump-debugging scenarios.
//
// We're opening it as "read". If we QI for an IEmit interface (which we need for EnC),
// then the metadata engine will convert it to a "write" underneath us.
// We want "read" so that we can let the OS share the pages.
DWORD dwOpenFlags = 0;
// This is the only place we ever validate that the file matches, because we're potentially
// loading the file from disk ourselves. We're doing this without giving the debugger a chance
// to do anything. We should never load a file that isn't an exact match.
return InitPublicMetaDataFromFile(szFullPathName, dwOpenFlags, true);
}
// We should only ever validate we have the correct file if it's a file we found ourselves.
// We allow the debugger to choose their own policy with regard to using metadata from the IL image
// when debugging an NI, or even intentionally using mismatched metadata if they like.
HRESULT CordbModule::InitPublicMetaDataFromFile(const WCHAR * pszFullPathName,
DWORD dwOpenFlags,
bool validateFileInfo)
{
#ifdef HOST_UNIX
// UNIXTODO: Some intricate details of file mapping don't work on Linux as on Windows.
// We have to revisit this and try to fix it for POSIX system.
return E_FAIL;
#else
if (validateFileInfo)
{
// Check that we've got the right file to target.
// There's nothing to prevent some other file being copied in for live, and with
// dump debugging there's nothing to say that we're not on another machine where a different
// file is at the same path.
// If we can't validate we have a hold of the correct file, we should not open it.
// We will fall back on asking the debugger to get us the correct file, or copying
// target memory back to the debugger.
DWORD dwImageTimeStamp = 0;
DWORD dwImageSize = 0;
StringCopyHolder filePath;
_ASSERTE(!m_vmPEFile.IsNull());
// MetaData lookup favors the NGEN image, which is what we want here.
BOOL _mdFileInfoResult;
IfFailThrow(this->GetProcess()->GetDAC()->GetMetaDataFileInfoFromPEFile(m_vmPEFile,
&dwImageTimeStamp,
&dwImageSize,
&filePath,
&_mdFileInfoResult));
if (!_mdFileInfoResult)
{
LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't get metadata info for file \"%s\"\n", pszFullPathName));
return CORDBG_E_MISSING_METADATA;
}
// If the timestamp and size don't match, then this is the wrong file!
// Map the file and check them.
HandleHolder hMDFile = WszCreateFile(pszFullPathName,
GENERIC_READ,
FILE_SHARE_READ,
NULL, // default security descriptor
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hMDFile == INVALID_HANDLE_VALUE)
{
LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't open file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
return CORDBG_E_MISSING_METADATA;
}
DWORD dwFileHigh = 0;
DWORD dwFileLow = GetFileSize(hMDFile, &dwFileHigh);
if (dwFileLow == INVALID_FILE_SIZE)
{
LOG((LF_CORDB,LL_WARNING, "CM::IM: File \"%s\" had invalid size.\n", pszFullPathName));
return CORDBG_E_MISSING_METADATA;
}
_ASSERTE(dwFileHigh == 0);
HandleHolder hMap = CreateFileMapping(hMDFile, NULL, PAGE_READONLY, dwFileHigh, dwFileLow, NULL);
if (hMap == NULL)
{
LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't create mapping of file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
return CORDBG_E_MISSING_METADATA;
}
MapViewHolder hMapView{ MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0) };
if (hMapView == NULL)
{
LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't map view of file \"%s\" (GLE=%x)\n", pszFullPathName, GetLastError()));
return CORDBG_E_MISSING_METADATA;
}
// Mapped as flat file, have PEDecoder go find what we want.
PEDecoder pedecoder(hMapView, (COUNT_T)dwFileLow);
if (!pedecoder.HasNTHeaders())
{
LOG((LF_CORDB,LL_WARNING, "CM::IM: \"%s\" did not have PE headers!\n", pszFullPathName));
return CORDBG_E_MISSING_METADATA;
}
if ((dwImageSize != pedecoder.GetVirtualSize()) ||
(dwImageTimeStamp != pedecoder.GetTimeDateStamp()))
{
LOG((LF_CORDB,LL_WARNING, "CM::IM: Validation of \"%s\" failed. "
"Expected size=%x, Expected timestamp=%x, Actual size=%x, Actual timestamp=%x\n",
pszFullPathName,
pedecoder.GetVirtualSize(),
pedecoder.GetTimeDateStamp(),
dwImageSize,
dwImageTimeStamp));
return CORDBG_E_MISSING_METADATA;
}
// All checks passed, go ahead and load this file for real.
}
// Get metadata Dispenser.
IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser();
HRESULT hr = pDisp->OpenScope(pszFullPathName, dwOpenFlags, IID_IMetaDataImport, (IUnknown**)&m_pIMImport);
_ASSERTE(SUCCEEDED(hr) == (m_pIMImport != NULL));
if (FAILED(hr))
{
// This should never happen in normal scenarios. It could happen if someone has renamed
// the assembly after it was opened by the debugee process, but this should be rare enough
// that we don't mind taking the perf. hit and loading from memory.
// @dbgtodo metadata - would this happen in the shadow-copy scenario?
LOG((LF_CORDB,LL_WARNING, "CM::IM: Couldn't open metadata in file \"%s\" (hr=%x)\n", pszFullPathName, hr));
}
return hr;
#endif // TARGET_UNIX
}
//---------------------------------------------------------------------------------------
// Initialize the public metadata.
//
// Arguments:
// buffer - valid buffer into target containing the metadata.
//
// Assumptions:
// This is an internal function which should only be called once to initialize the
// metadata. Future attempts to re-initialize (in dynamic cases) should call code:CordbModule::UpdatePublicMetaDataFromRemote
// After the public metadata is initialized, initialize private metadata via code:CordbModule::UpdateInternalMetaData
//
void CordbModule::InitPublicMetaData(TargetBuffer buffer)
{
CONTRACTL
{
THROWS;
}
CONTRACTL_END;
INTERNAL_API_ENTRY(this->GetProcess());
LOG((LF_CORDB,LL_INFO100000, "CM::IPM: initing with remote buffer 0x%p length 0x%x\n",
CORDB_ADDRESS_TO_PTR(buffer.pAddress), buffer.cbSize));
ULONG nMetaDataSize = buffer.cbSize;
if (nMetaDataSize == 0)
{
// We should always have metadata, and if we don't, we want to know.
// @dbgtodo metadata - we know metadata from dynamic modules doesn't work in V3
// (non-shim) cases yet.
// But our caller should already have handled that case.
SIMPLIFYING_ASSUMPTION(!"Error: missing the metadata");
return;
}
HRESULT hr = S_OK;
// Get metadata Dispenser.
IMetaDataDispenserEx * pDisp = GetProcess()->GetDispenser();
// copy it over from the remote process
CoTaskMemHolder<VOID> pMetaDataCopy;
CopyRemoteMetaData(buffer, pMetaDataCopy.GetAddr());
//
// Setup our metadata import object, m_pIMImport
//
// Save the old mode for restoration
VARIANT valueOld;
hr = pDisp->GetOption(MetaDataSetUpdate, &valueOld);
SIMPLIFYING_ASSUMPTION(!FAILED(hr));
// Set R/W mode so that we can update the metadata when
// we do EnC operations.
VARIANT valueRW;
V_VT(&valueRW) = VT_UI4;
V_I4(&valueRW) = MDUpdateFull;
hr = pDisp->SetOption(MetaDataSetUpdate, &valueRW);
SIMPLIFYING_ASSUMPTION(!FAILED(hr));
hr = pDisp->OpenScopeOnMemory(pMetaDataCopy,
nMetaDataSize,
ofTakeOwnership,
IID_IMetaDataImport,
reinterpret_cast<IUnknown**>( &m_pIMImport ));
// MetaData has taken ownership -don't free the memory
pMetaDataCopy.SuppressRelease();
// Immediately restore the old setting.
HRESULT hrRestore = pDisp->SetOption(MetaDataSetUpdate, &valueOld);
SIMPLIFYING_ASSUMPTION(!FAILED(hrRestore));
// Throw on errors.
IfFailThrow(hr);
IfFailThrow(hrRestore);
// Done!
}
//---------------------------------------------------------------------------------------
// Update public MetaData by copying it from the target and updating our IMetaDataImport object.
//
// Arguments:
// buffer - buffer into target space containing metadata blob
//
// Notes:
// Useful for additional class-loads into a dynamic module. A new class means new metadata
// and so we need to update the RS metadata to stay in sync with the left-side.
//
// This will call code:CordbModule::CopyRemoteMetaData to copy the remote buffer locally, and then
// it can OpenScopeOnMemory().
//
void CordbModule::UpdatePublicMetaDataFromRemote(TargetBuffer bufferRemoteMetaData)
{
CONTRACTL
{
// @dbgtodo metadata - think about the error semantics here. These fails during dispatching an event; so
// address this during event pipeline.
THROWS;
}
CONTRACTL_END;
if (bufferRemoteMetaData.IsEmpty())
{
ThrowHR(E_INVALIDARG);
}
INTERNAL_API_ENTRY(this->GetProcess()); //
LOG((LF_CORDB,LL_INFO100000, "CM::UPMFR: updating with remote buffer 0x%p length 0x%x\n",