Skip to content

Conversation

@ivdiazsa
Copy link
Contributor

Fixes #72425. When trying to use Crossgen2 with dll's that do not have the proper debug directory data (e.g. obfuscated dll's), a BadImageException error is thrown and thus failing the process. In this case, the dll is still usable though debugging tools will probably not work. So, instead of failing, we ought to let the user continue but we shall warn them about this. As a history tidbit, this was the default behavior in .NET 5.

…d by Crossgen2. Instead of failing unnecessarily, it now emits a warning.
}
catch (BadImageFormatException e)
{
if (e.Message.Equals("Invalid directory size."))
Copy link
Member

Choose a reason for hiding this comment

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

guess this can be factored out to avoid duplication?

// This comes from the Size property of the DebugDirectoryEntry class.
int expectedDbgDirSizeBase = 28;

Console.WriteLine("WARNING: This image's Debug Directory Entry size should be a"
Copy link
Member

Choose a reason for hiding this comment

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

these can be created as exception strings instead of string literals?

Copy link
Member

Choose a reason for hiding this comment

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

I agree we should make this an exception string. I would throw away the "artificial intelligence" analyzing the exception message and just print something generic like "WARNING: %s contains invalid debug information: %s" with the name of the file and the message of the exception caught.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we can afford to keep the part that says "Debugging tools may not work correctly with this image". I feel our users would like to know what the warning entails for them.

}
catch (BadImageFormatException e)
{
if (e.Message.Equals("Invalid directory size."))
Copy link
Member

Choose a reason for hiding this comment

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

not sure if this should be special cased, since on non-english locales this would probably fail anyways

Copy link
Contributor Author

@ivdiazsa ivdiazsa Aug 11, 2022

Choose a reason for hiding this comment

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

I special cased this as well because it failed otherwise with the same BadImageFormatException.

Copy link
Member

Choose a reason for hiding this comment

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

Since e.Message will be localized, this check will not work for any language except English.

Copy link
Member

@trylek trylek left a comment

Choose a reason for hiding this comment

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

Mostly looks good, thanks Ivan! I concur with Manish that we should somewhat simplify the warning logic, I have shared my thoughts on that in the PR comments.

@AntonLapounov
Copy link
Member

AntonLapounov commented Aug 11, 2022

Could we create a helper method like below and use it instead of ReadDebugDirectory everywhere?

public static ImmutableArray<DebugDirectoryEntry> ReadDebugDirectoryIgnoringErrors(this PEReader moduleReader)
{
    try
    {
        return moduleReader.ReadDebugDirectory();
    }
    catch (BadImageFormatException e)
    {
        // Report a warning using e.Message
        return ImmutableArray<DebugDirectoryEntry>.Empty;
    }
}

@ivdiazsa
Copy link
Contributor Author

Thank you all for the feedback! I have a few questions whatsoever.

First, I'm a big advocate of helper functions as long as it doesn't end up being a big scramble of chained functions (which this is clearly not the case). My question I would like guidance on is where would be the most appropriate place/file to write the helper function, since these two files are in different csproj's. One is in ILCompiler.Compiler and the other one is in ILCompiler.ReadyToRun.

My other question is regarding the string literal, and it's in a very similar boat. The original throwing function in System.Reflection.Metadata, gets the string from a class called SR. Here is the original throwing line:

if (debugDirectory.Size % DebugDirectoryEntry.Size != 0)
{
throw new BadImageFormatException(SR.InvalidDirectorySize);
}

This SR class then called a method named GetResourceString() to fetch it from here:

<data name="InvalidDirectorySize" xml:space="preserve">
<value>Invalid directory size.</value>
</data>

I might have missed something, but I couldn't get any sort of access to there from our codebase. So, we might just have to create a constant like you mentioned, but my question is where would be most appropriate.

@AntonLapounov
Copy link
Member

Would it be possible to add both the helper and the resource string to a shared assembly, e.g., src\coreclr\tools\Common\TypeSystem\Common\Properties\Resources.resx?

@trylek
Copy link
Member

trylek commented Aug 12, 2022

@ivdiazsa - what you're looking at is standardized support for localized resources. Crossgen2 uses them too, e.g. here:

syntax.DefineOptionList("codegenopt|codegen-options", ref CodegenOptions, SR.CodeGenOptions);

This is coming from the file

<data name="CodeGenOptions" xml:space="preserve">

that you can edit with VS (or manually). Having said that, proper location depends on the assembly you put the helper in. Are you sure that you see a failure in ILCompiler.Compiler? I was under the impression that is only used by NativeAOT.

@AntonLapounov
Copy link
Member

Are you sure that you see a failure in ILCompiler.Compiler?

It would be nice to have the same behavior for both. And there are other places that should be fixed, e.g.,

ImmutableArray<DebugDirectoryEntry> entries = _module.PEReader.ReadDebugDirectory();

@trylek
Copy link
Member

trylek commented Aug 12, 2022

If we want a common fix for Crossgen2 and NativeAOT, it should be probably under src/coreclr/tools/Common that is shared between them. Perhaps this file seems loosely related:

https://github.com/dotnet/runtime/blob/main/src/coreclr/tools/Common/System/FormattingHelpers.cs


Console.WriteLine("WARNING: This image's Debug Directory Entry size should be a"
+ $" multiple of {expectedDbgDirSizeBase}, but was {actualDbgDirSize}."
+ " Debugging tools may not work correctly with this image.");
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
+ " Debugging tools may not work correctly with this image.");
+ " Diagnostic tools may not work correctly with this image.");

It is also profilers, etc.

}
catch (BadImageFormatException e)
{
if (e.Message.Equals("Invalid directory size."))
Copy link
Member

Choose a reason for hiding this comment

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

There are many other ways that the debug symbols can be corrupted, we just have not seen them. Should we catch all exceptions here and print that the debug symbols are corrupted, and include the error message from the exception?

Copy link
Member

Choose a reason for hiding this comment

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

There are many other ways that the debug symbols can be corrupted, we just have not seen them. Should we catch all exceptions here and print that the debug symbols are corrupted, and include the error message from the exception?

None of the compiler is hardened for obfuscators. I wouldn't treat debug information very differently.

Copy link
Member

Choose a reason for hiding this comment

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

(We might successfully open the debug information but there can be exceptions once we start looking into it.)

Comment on lines 203 to 208
try
{
pdbReader = PortablePdbSymbolReader.TryOpenEmbedded(peReader, GetMetadataStringDecoder())
?? OpenAssociatedSymbolFile(filePath, peReader);
}
catch (BadImageFormatException e)
Copy link
Member

Choose a reason for hiding this comment

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

This is placed in a very broad spot. A lot of code is going to run and could throw a BadImageFormat (S.R.Metadata will throw it for any sort of corruption).

Can you instead add a e.g. SafeReadDebugDirectory(this PEReader peReader) extension method that simply checks the directory size before calling the API and returns ImmutableArray<DebugDirectoryEntry>.Empty if it doesn't make sense?

But doing Console.WriteLine in arbitrary spots is not a great practice because we're multithreaded. I would honestly just swallow this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did not consider the fact of multithreading. Thanks for your feedback Michal! I think this is a great suggestion, I'll get to work on it.

@AntonLapounov
Copy link
Member

Thank you, that is much cleaner. I think we should also fix one more place I mentioned above:

ImmutableArray<DebugDirectoryEntry> entries = _module.PEReader.ReadDebugDirectory();

There are also two occurrences of ReadDebugDirectory() under src\coreclr\tools\dotnet-pgo.

int actualDbgDirSize = peReader.PEHeaders.PEHeader.DebugTableDirectory.Size;

// This comes from the Size property of the DebugDirectoryEntry class.
int expectedDbgDirSizeBase = 28;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Might use const here:

Suggested change
int expectedDbgDirSizeBase = 28;
const int ExpectedDbgDirSize = 28;

@ivdiazsa
Copy link
Contributor Author

Thank you, that is much cleaner. I think we should also fix one more place I mentioned above:

ImmutableArray<DebugDirectoryEntry> entries = _module.PEReader.ReadDebugDirectory();

There are also two occurrences of ReadDebugDirectory() under src\coreclr\tools\dotnet-pgo.

Sure thing. I wasn't sure whether I should be meddling with PGO as well, but you're right, it doesn't seem like it would break anything in this case.

@AntonLapounov
Copy link
Member

It would be nice to have consistent behavior at least within our own AoT toolchain.

Copy link
Member

@AntonLapounov AntonLapounov left a comment

Choose a reason for hiding this comment

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

Thank you for fixing this!

@ivdiazsa ivdiazsa merged commit 9814b1b into dotnet:main Aug 15, 2022
@ivdiazsa ivdiazsa deleted the dealing_with_dlls branch August 15, 2022 16:30
@ghost ghost locked as resolved and limited conversation to collaborators Sep 14, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

msbuild Publish fails with Error: Invalid directory size.

6 participants