-
Notifications
You must be signed in to change notification settings - Fork 2.7k
[Local GC] Move workstation GC DAC globals to a struct shared between GC and DAC #9255
Conversation
src/gc/gcinterface.dac.h
Outdated
dac_heap_segment* start_segment; | ||
uint8_t _padding2[8]; | ||
uint8_t* allocation_start; | ||
uint8_t _padding3[149]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sizes of these structures; and offsets of the interesting members in them should be in the DAC globals to avoid these magic padding values; (and/or these structures should be re-arranged such that all interesting fields for diagnostic are first or together).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will also fix the #ifdef DEBUG
TODO that you got below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, will do!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about if the DAC-interesting fields are first, and the size and alignment of the structure are both DAC vars? I think this simplifies the DAC code somewhat.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you make DAC-interesting field first, the DAC should not need to know about total size or alignment of the structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DAC has to do pointer arithmetic when traversing arrays of these structures, doesn't it need to know both of those things to do that correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, make sense. I have not realized that these come in arrays.
It needs to know the aligned size of structure to do that (one number - what C++ sizeof returns). It does not need to know alignment separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, sounds good!
src/gc/gc.cpp
Outdated
assert(gcDacVars != nullptr); | ||
*gcDacVars = {}; | ||
gcDacVars->major_version_number = &g_gc_dac_major_version; | ||
gcDacVars->minor_version_number = &g_gc_dac_minor_version; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest putting the version numbers directly in the structure rather than using pointers to a global.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do!
// GC-internal type's fields, while still maintaining the same layout. | ||
// This interface is strictly versioned, see gcinterface.dacvars.def for more information. | ||
|
||
#define NUM_GC_DATA_POINTS 9 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want the flexibility to change any of these define values in future local builds of the GC? If so I suggest using a global int to store the value and put it in gcGlobals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine that flexibility will be good - will do. Everything except for NUMBERGENERATIONS is okay to change without changing any type layouts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@noahfalk So, unfortunately, these values influence the layout of DacpGCInterestingInfoData
: https://github.com/dotnet/coreclr/blob/master/src/inc/dacprivate.h#L787, so we'll need to bump a version number if we alter them anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked a little closer at these and we are blurring two different contracts together that we should keep separate. One is a contract between the GC and DAC, the other is a contract between DAC and SOS. DacpGCInterestingInfoData is part of that latter contract. The DAC<->SOS contract is based on COM so the pre-existing interface is immutable and the way we would change it one day is to define a new API that uses a new datatype. There is a second set of #defines for macros of the same name right above the definition of DacpGCInterestingInfoData so that type wouldn't change, but it does lead to significant ambiguity and likely bugs if someone ever changed the value of the define you just added here. For example its not obvious which instance of the define this code now refers to:
https://github.com/dotnet/coreclr/blob/master/src/debug/daccess/request.cpp#L3099.
I'd suggest:
a) Promote these enums to be part of the official GC<->DAC contract, because implicitly they already are: interesting_data_point, gc_heap_compact_reason, gc_heap_expand_mechanism, gc_mechanism_bit_per_heap.
2) Instead of duplicating the existing DAC defines, refer to the pre-existing enum counts in those enums
3) Add static asserts that those enums keep the exact counts they have now, as that is what the DAC is hard-coded to expect.
If we wanted to go further and allow the counts to vary we still could, but it would require changes in both the GC<->DAC interface and the DAC<->SOS interface to get the flexibility all the way through. I defer to you and Maoni to decide if that is worthwhile to do.
src/gc/gcpriv.h
Outdated
@@ -781,6 +779,9 @@ class generation | |||
int gen_num; | |||
|
|||
#ifdef FREE_USAGE_STATS | |||
#pragma message ("FREE_USAGE_STATS changes the layout of the 'generation' class, which is known to the DAC. " \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can put the sizeof(generation) into a global and then compute the size dynamically in the DAC if you need to do debugging on builds where FREE_USAGE_STATS is enabled. It does mean however that you can't use sizeof(dac_generation), dac_generation[], pointer arithmetic on dac_generation or anything else where the size of the statically defined structure is implicitly used by the compiler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately dac_generation
is the one type in gcinterface.dac.h
with which I need to do pointer arithmetic. Jan suggested above that make the size a dacvar anyway and do the pointer math myself in the DAC - it would give some additional flexibility in this case. Do you or @Maoni0 have any thoughts? (I'm not familiar with what this feature define is used for in the GC)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this wouldn't be defined for shipped ret builds for sure. but this could be heavily used during experimenting/giving out private ret builds. since one of the purposes for the LocalGC project is to make giving out private builds easier it would be really nice to make this work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not in office today...can we look at this tomorrow to see how much work this introduces?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, its still doable, just extra work to do the pointer arithmetic yourself. Other options would be:
a) including the additional fields on generation always but not using them in some builds
b) using an indirection so that you add one pointer to generation always, but then use of that pointer is optional depending on build type. Then add any number of optional fields in the data structure the pointer points to and it won't affect the layout that DAC deals with.
Any of the options are fine from my end, just different tradeoffs of runtime memory overhead in the GC vs implementation complexity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Maoni0 Sure!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should prefer design choices that allow you to make changes in the GC without changing its contract. I would go via the size in dacvar.
implementation complexity.
This should be pretty minor - one or two macros or helper methods should hide the detail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed with Jan. I just wanted to see if it adds a great deal of work to do the arithmetic; it sounds that's not the case so it's a clear choice to do the arithmetic in dac instead of doing things in GC to accommodate this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do, then!
if (!pGCHeap) | ||
|
||
IGCHeap *pGCHeap; | ||
if (!InitializeGarbageCollector(gcToClr, &pGCHeap, &g_gc_dac_vars)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small race condition here. If the debugger were to attach while InitializeGarbageCollector was running it would be able to access the global variables in a partially initialized state. I'd suggest making g_gcDacGlobals the DACVAR and g_gc_dac_vars as a standard global structure in the runtime. This way you can assign g_gcDacGlobals = &g_gc_dac_vars after Initialize returns to publish all the pointers to the debugger atomically. It should also mean you don't need to that same assignment in DAC_ENTER way up top.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do!
Sorry I misused the review feature I think. I did the review yesterday, but in the pending state I don't think anyone else could see it until just now. |
src/debug/daccess/gcinterface.dac.h
Outdated
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
#include "../gc/gcinterface.dac.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gc is two levels up from here ... I am surprised that it works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, that is surprising - I guess the GC directory is still on the include path and #include "gcinterface.dac.h"
includes the original header and not this one? I'll fix this header.
I ran into trouble with the Windows linker in the last commit - link.exe was removing Other than that, the last few commits should address the feedback I've gotten so far, so I think I'm ready to address any additional feedback and/or apply this to Server GC if there aren't any objections. Also @noahfalk, are there any automated tests I can run to validate these changes from an SOS and DBI perspective? I've been doing manual validation using SOS so far. |
src/debug/daccess/request.cpp
Outdated
@@ -737,10 +744,12 @@ ClrDataAccess::GetHeapAllocData(unsigned int count, struct DacpGenerationAllocDa | |||
|
|||
if (data && count >= 1) | |||
{ | |||
auto table = g_gcDacGlobals->generation_table; | |||
for (int i=0;i<NUMBERGENERATIONS;i++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NUMBERGENERATIONS [](start = 27, length = 17)
I may have missed it, but if there isn't already a static_assert(DAC_NUMBERGENERATIONS == NUMBERGENERATIONS) we should add it, this loop appears to depend on that fact.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do!
{ | ||
// enumerating the generations from max (which is normally gen2) to max+1 gives you | ||
// the segment list for all the normal segements plus the large heap segment (max+1) | ||
// this is the convention in the GC so it is repeated here | ||
for (ULONG i = GCHeapUtilities::GetMaxGeneration(); i <= GCHeapUtilities::GetMaxGeneration()+1; i++) | ||
for (ULONG i = *g_gcDacGlobals->max_gen; i <= *g_gcDacGlobals->max_gen +1; i++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
g_gcDacGlobals->max_gen +1 [](start = 59, length = 26)
In some places we seem to be using NUMBERGENERATIONS and in others we are using the max_gen + 1 calculation. I assume the result is always identical, but this seems like it would be a good opportunity to converge on a canonical way the debugger should calculate the number of generations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we should consolidate. Note that NUMBERGENERATIONS is 4 and (max_gen + 1) is 3 so make sure you account for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do!
src/debug/daccess/dacimpl.h
Outdated
@@ -3904,7 +3904,8 @@ class EnumMethodInstances | |||
#define DAC_ENTER() \ | |||
EnterCriticalSection(&g_dacCritSec); \ | |||
ClrDataAccess* __prevDacImpl = g_dacImpl; \ | |||
g_dacImpl = this; | |||
g_dacImpl = this; \ | |||
g_gcDacGlobals = &g_gc_dac_vars; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
g_gcDacGlobals = &g_gc_dac_vars; [](start = 4, length = 32)
I think you need to remove this line now that g_gcDacGlobals is the published DAC global already. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikem8361 - Can you let Sean know what he needs to do to run the SOS tests? I'm not sure if there are any docs yet that would help someone not on the diagnostics team know what they need to do? |
// from the debugee process to the debugger process when dereferenced. | ||
struct GcDacVars { | ||
uint8_t major_version_number; | ||
uint8_t minor_version_number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment that explains what warrants an increment of major and minor version numbers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a comment of that sort here: https://github.com/swgillespie/coreclr/blob/gc-dac/src/gc/gcinterface.dacvars.def#L8 but it needs to be revised now that we can tolerate some changes in layout. I'll update it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. My apologies.
The SOS tests are part of the debuggertests repo. We have some docs on building and running (see below).
1) Get permission to the debuggertests repo (I’ve already given Sean permission).
2) Either create a fork of https://github.com/dotnet/debuggertests and clone that or clone it directly on your Windows machine
3) see https://github.com/dotnet/debuggertests/blob/master/Documentation/Engineering/BuildAndTestWindows.md
4) One thing the directions don’t cover is building for Linux or OSX. You still need to build the managed binaries on Windows with “build linux” or “build osx”, but you also need to build the native bits on the target platform with “./build.sh skipmanaged”.
5) Before you run the tests, copy the coreclr binaries with your changes (need to be close to the latest master branch) to “debuggertests\bin\<platform>\Runtimes\Coreclr1” where platform is something like Windows.x64.Debug or Linux.x64.Debug.
|
@mikem8361 Thank you! |
An update on this PR: I have a few debugger test failures that I am in the process of fixing, but I have code in place that implements this scheme for Workstation and Server GC: swgillespie/coreclr@gc-dac...swgillespie:gc-dac-staging . Once I have all debugger tests passing, I will merge my PR staging branch into this branch and will be ready for a more formal review. (There's still some cleanup that I need to do before I'm ready to submit it for review.) I have a couple questions, though (cc @mikem8361):
|
Yes, the debuggertests repo tests DBI (kind of the whole point of most of the tests). There is no crash (mini-)dump coverage yet. You will probably need to install dotnet/coreclr on Windows with the MSI (the only way coreclr is registered with WER), replace the binaries with yours and cause a crash. WER should call the DAC enum memory region apis to create the dump.
|
Thanks!
Yeah, sorry - I was under the impression that SOS didn't use the DBI, but apparently it does. I never encountered it when manual testing with SOS.
Does |
the GC and the DAC
1. Make g_gcDacGlobals a pointer and dacvar on the VM side, so that publishing the GC dac vars is done atomically (through a pointer assignment). This fixes a race that Noah noticed. 2. Remove the requirement for the GC's generation class struct to be known at compile-time, by using a dacvar as the size of the generation class at run-time (for pointer arithmetic) 3. Move all DAC-interesting fields to be at the start of GC internal classes, so that the DAC does not need to know the size or exact layout of the class past the fields it cares about.
… and GC/DAC interfaces, and add static asserts that they are the same
…g the EE store a copy of the global during initialization
I do not think that this change is functionally correct because of you have changed the variable from array to pointer to an array, and the assembly helpers still operate as if it was an array. Have you verified that it actually works? (e.g. by forcing this codepath; or by running on single proc VM) Also, it does not look right that this information is passed via WriteBarrier parameters because of it has nothing to do with write barrier parameters. It should be better done as a flavor of FixAllocContext instead. |
I think the debugger tests are failing because I forgot to tell you to delete ildasm and ilasm after you copy the coreclr binaries.
|
"!ClrStack -i" uses DBI, but I don't think any other commands do. I didn't know that .dump in windbg calls the DAC enum memory APIs. I just coming up to speed on these APIs currently myself. It sounds like .dump should be sufficient for testing. |
No - will do that today.
Will do. Thanks! Trying that now. |
@jkotas (and @Maoni0) I think I may back out of the |
Agree. |
I found an explanation for my two failing SOS tests (https://github.com/dotnet/debuggertests/issues/253) and those are now passing, so now I am down to one failing debugger test. The test (https://github.com/dotnet/debuggertests/blob/master/tests/Exceptions/Debugees/TaskCancel/taskcancel.cs#L35) fails in this manner:
I am not sure how this is related to my changes - does anyone have any thoughts? This is the last remaining functional issue for this PR. |
Does it fail if you run it on a runtime that doesn't have your changes? We can certainly help you debug into it, but if it repros without your change then you won't need to worry about it. |
@noahfalk It looks like it does, I just did a test with the tip of master and it still failed for me. It doesn't fail when using the coreclr package it obtains through |
@swgillespie - I think you can safely ignore it then - thanks for checking! |
It looks like the exception we are expecting isn’t wrapped in an AggregateException anymore. It sounds like this only happens if you use a newer version (master) of the runtime and has nothing to do with your changes.
|
The changes look good to me. |
Thanks Mike! If there aren't any other objections, I think this is ready to merge - I believe I've addressed all the feedback in this thread. |
thanks! |
… GC and DAC (dotnet#9255) * [Local GC] Move workstation GC DAC globals to a struct shared between the GC and the DAC * (Some) code review feedback and bug fixes for issues found while debugging on OSX * Address some code review feedback: 1. Make g_gcDacGlobals a pointer and dacvar on the VM side, so that publishing the GC dac vars is done atomically (through a pointer assignment). This fixes a race that Noah noticed. 2. Remove the requirement for the GC's generation class struct to be known at compile-time, by using a dacvar as the size of the generation class at run-time (for pointer arithmetic) 3. Move all DAC-interesting fields to be at the start of GC internal classes, so that the DAC does not need to know the size or exact layout of the class past the fields it cares about. * Split the definition of the size of several arrays across the SOS/DAC and GC/DAC interfaces, and add static asserts that they are the same * Repair the Windows Release build * Implement the GC DAC scheme for Server GC and eliminate the duplicate GC dac vars * Some work * Decouple use of the GC generation table from a write barrier by having the EE store a copy of the global during initialization * Actually make it work with server GC * Checkpoint * Checkpoint where everything works * Code cleanup * Fix debugger test failures * Additional code cleanup * Address code review feedback by adding a static assert and standardizing the way that we iterate over the generation table * Repair the Windows x86 build * Revert "Decouple use of the GC generation table from a write barrier by having the EE store a copy of the global during initialization" This reverts commit 573f61a. * Revert "Repair the Windows x86 build" This reverts commit 188c22d. * Partial revert, move `generation_table` back the global namespace for a single-proc allocation helper * Fix a debugger test failure * Repair crash dump scenarios
… GC and DAC (dotnet/coreclr#9255) * [Local GC] Move workstation GC DAC globals to a struct shared between the GC and the DAC * (Some) code review feedback and bug fixes for issues found while debugging on OSX * Address some code review feedback: 1. Make g_gcDacGlobals a pointer and dacvar on the VM side, so that publishing the GC dac vars is done atomically (through a pointer assignment). This fixes a race that Noah noticed. 2. Remove the requirement for the GC's generation class struct to be known at compile-time, by using a dacvar as the size of the generation class at run-time (for pointer arithmetic) 3. Move all DAC-interesting fields to be at the start of GC internal classes, so that the DAC does not need to know the size or exact layout of the class past the fields it cares about. * Split the definition of the size of several arrays across the SOS/DAC and GC/DAC interfaces, and add static asserts that they are the same * Repair the Windows Release build * Implement the GC DAC scheme for Server GC and eliminate the duplicate GC dac vars * Some work * Decouple use of the GC generation table from a write barrier by having the EE store a copy of the global during initialization * Actually make it work with server GC * Checkpoint * Checkpoint where everything works * Code cleanup * Fix debugger test failures * Additional code cleanup * Address code review feedback by adding a static assert and standardizing the way that we iterate over the generation table * Repair the Windows x86 build * Revert "Decouple use of the GC generation table from a write barrier by having the EE store a copy of the global during initialization" This reverts commit dotnet/coreclr@573f61a. * Revert "Repair the Windows x86 build" This reverts commit dotnet/coreclr@188c22d. * Partial revert, move `generation_table` back the global namespace for a single-proc allocation helper * Fix a debugger test failure * Repair crash dump scenarios Commit migrated from dotnet/coreclr@6f6fda9
This PR is a Workstation GC-only implementation of the separation of DAC variables for Local GC. The scheme implemented in this PR is that, upon initialization, the GC populates a struct with pointers to its global variables that are known to the DAC. The DAC can then read this struct and utilize its members to peer in to GC internals using the usual DAC machinery (
__DPtr
and friends).I'm sending this review out a bit early in the hopes of getting some feedback on this design before bringing it to Server GC. This PR is mergeable, but still in need of more strict validation than what I have done so far (All GC-related SOS behavior that I can test is working for me on x64, though I am still doing manual testing). I also haven't done any testing of the ICorDebug* interfaces - if we have any tests or validation steps for those, I'd like to run those on this PR.
Not implemented in this PR (yet?) is the logic to detect version mismatches between the DAC and the GC. I'd love to hear any feedback about where this check should go - I didn't identify any particular place where it would fit.