Skip to content

Fix universal symbol conversion for overlapping mappings#2252

Merged
brianrob merged 6 commits intomainfrom
copilot/fix-2250
Jul 11, 2025
Merged

Fix universal symbol conversion for overlapping mappings#2252
brianrob merged 6 commits intomainfrom
copilot/fix-2250

Conversation

Copy link
Contributor

Copilot AI commented Jul 5, 2025

This PR fixes a critical issue where overlapping memory mappings in universal traces (nettrace files) cause a NullReferenceException during symbol processing.

Problem

When loading nettrace files with overlapping mappings, PerfView would crash with a NullReferenceException. The issue occurred when:

  1. Multiple mappings share the same start address but have different end addresses
  2. The first mapping creates a TraceModuleFile with a smaller image size
  3. The second mapping reuses the existing TraceModuleFile without updating its size
  4. Later, symbol processing tries to resolve addresses within the larger range but fails because the cached module file has the wrong size

Example scenario from the issue:

Mapping 1: Start=0x0000738e0b840000, End=0x0000738e0b8a2fff (length=0x62fff)
Mapping 2: Start=0x0000738e0b840000, End=0x0000738e0b8a3fff (length=0x63fff) 

Solution

1. Enhanced UniversalMapping method:

  • Calculate the new image size from mapping data
  • Expand existing ModuleFile.imageSize when new mappings have larger ranges
  • Handle both new and existing loaded modules properly

2. Added defensive null checks in AddUniversalDynamicSymbol:

  • Check if loadedModule is null before using it
  • Only create managed modules when a valid loaded module is found
  • Prevent NullReferenceException when symbol addresses fall outside known mappings

Changes

  • TraceLoadedModules.UniversalMapping(): Now properly expands ModuleFile size for overlapping mappings
  • TraceLoadedModules.UniversalMapping(): Adds a new LoadedModuleFile when the necessary
  • TraceCodeAddresses.AddUniversalDynamicSymbol(): Added null checks to handle missing modules gracefully
  • TraceCodeAddresses.AddUniversalDynamicSymbol(): Adds an assert to ensure that all dynamic symbols have a corresponding LoadedModule.

Coverage

This fix handles all overlapping mapping scenarios:
✅ Complete replacement of existing mapping
✅ Mapping that overlaps and extends beyond existing mapping
✅ Mapping contained within existing mapping
✅ Partial overlaps with shared start addresses

The changes are minimal and surgical, maintaining backward compatibility while resolving the crash in user_events scenarios.

Fixes #2250.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com>
Copilot AI changed the title [WIP] Universal symbol conversion doesn't handle overlapping mappings Fix universal symbol conversion for overlapping mappings Jul 5, 2025
Copilot AI requested a review from brianrob July 5, 2025 23:27
}

// Get or create the loaded module.
TraceLoadedModule loadedModule = FindModuleAndIndexContainingAddress(data.StartAddress, data.TimeStampQPC, out index);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetOrCreateModule will only return a TraceModuleFile whose ImageBase matches the incoming mapping, so if the mapping happens to have the same name, as does the mappings for jitted code on Linux, then GetOrCreateModule will just create a new module.

FindModuleAndIndexContainingAddress will return a TraceLoadedModule that contains data.StartAddress, but doesn't necessarily start with it. If it doesn't start with it, then we need to create a new TraceLooadedModule and insert it - this is needed to handle the overlapping mapping case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6caab6b. Now checking if loadedModule.ImageBase != data.StartAddress and creating a new TraceLoadedModule when they don't match, ensuring proper handling of overlapping mappings where the module containing the address doesn't start at the mapping's start address.


InsertAndSetOverlap(index + 1, loadedModule);
}
else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This else clause is valid if the ImageBase of loadedModule matches the StartAddress of the new mapping. But, in this case, we still should update loadTimeQPC to match the new mapping timestamp. We also need to determine if overlaps needs to be set because we're not re-inserting it. If we can't do this here, we will need to just create a new TraceLoadedModule and insert it so that the underlying code can properly set overlaps if necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6caab6b. The else clause now properly updates loadTimeQPC to match the new mapping timestamp with loadedModule.loadTimeQPC = data.TimeStampQPC. The InsertAndSetOverlap call in the creation path will handle the overlaps flag correctly.

@@ -7125,6 +7131,14 @@ internal TraceModuleFile UniversalMapping(ProcessMappingTraceData data, ProcessM

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On line 7130, we set the timestamp to the start of the trace because mappings are enumerated at the start of the trace. This doesn't always happen - we should set the timestamp properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6caab6b. Changed from process.Log.sessionStartTimeQPC to data.TimeStampQPC to use the proper timestamp from the mapping data.

Co-authored-by: brianrob <6210322+brianrob@users.noreply.github.com>
Copilot AI requested a review from brianrob July 6, 2025 00:23
@brianrob
Copy link
Member

brianrob commented Jul 6, 2025

Copilot Post-Mortem:

  • Helped me to not "over-code" a solution since there is already support in TraceLoadedModules for overlapping mappings.
  • It would be nice to for copilot to update the PR description automatically in response to comments that cause it to diverge the PR from the original description.

@brianrob brianrob marked this pull request as ready for review July 6, 2025 02:12
@brianrob brianrob requested a review from noahfalk July 6, 2025 02:12
@brianrob
Copy link
Member

brianrob commented Jul 6, 2025

@noahfalk, this change is a bit more involved than just the repro case that you hit, but FWIW, I did confirm that your trace works, and that there are no symbols that don't have a corresponding loaded module.

Copy link
Member

@noahfalk noahfalk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine to me!

@noahfalk
Copy link
Member

Is there anything blocking this PR from getting merged?

@brianrob
Copy link
Member

This one just needs a review from someone in the CODEOWNERS file. I have a request out on that, and I will see if I can get traction on it tomorrow.

// We should always get a loadedModule here because if we have a symbol, then we should have a module that contains it.
// Assert so that we can detect bugs here during development.
Debug.Assert(false, "loadedModule is missing for symbol");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not Debug.Assert(loadedModule != null, "loadedModule is missing for symbol") before the if statement?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I can't remember why I went with this, but I've updated to use your suggestion.

mconnew
mconnew previously approved these changes Jul 10, 2025
Copy link
Member

@mconnew mconnew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Copy link
Member

@mconnew mconnew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@brianrob brianrob enabled auto-merge (squash) July 10, 2025 22:46
@brianrob
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@brianrob brianrob merged commit 4cb44a7 into main Jul 11, 2025
5 checks passed
@brianrob brianrob deleted the copilot/fix-2250 branch July 11, 2025 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Universal symbol conversion doesn't handle overlapping mappings

4 participants