PersistedAssemblyBuilder: unable to retrieve resources added with AddManifestResource
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 35/100
Research direction
Start by running the supplied PersistedAssemblyBuilder reproduction and inspect the resource metadata it emits. Read src/coreclr/vm/peassembly.cpp and src/coreclr/utilcode/pedecoder.cpp, especially GetEmbeddedResource and CheckResource, along with the linked documentation example. Done means the generated assembly can load the named resource and ResourceReader can read it without BadImageFormatException.
Written by the indexing model from the issue text.
Description
Description
Hello,
I'm struggling to read out a resource that was added to an Assembly with PersistedAssemblyBuilder. I'm not sure if I'm doing it wrong, the documentation is wrong, or there's actually a bug.
Reproduction Steps
Given the following code (which is just the example code with 4 lines tacked on the end):
SetResource();
static void SetResource()
{
PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(new AssemblyName("MyAssembly"), typeof(object).Assembly);
ab.DefineDynamicModule("MyModule");
MetadataBuilder metadata = ab.GenerateMetadata(out BlobBuilder ilStream, out _);
using MemoryStream stream = new MemoryStream();
ResourceWriter myResourceWriter = new ResourceWriter(stream);
myResourceWriter.AddResource("AddResource 1", "First added resource");
myResourceWriter.AddResource("AddResource 2", "Second added resource");
myResourceWriter.AddResource("AddResource 3", "Third added resource");
myResourceWriter.Close();
BlobBuilder resourceBlob = new BlobBuilder();
resourceBlob.WriteBytes(stream.ToArray());
metadata.AddManifestResource(ManifestResourceAttributes.Public, metadata.GetOrAddString("MyResource"), default, (uint)resourceBlob.Count);
ManagedPEBuilder peBuilder = new ManagedPEBuilder(
header: new PEHeaderBuilder(imageCharacteristics: Characteristics.Dll | Characteristics.ExecutableImage),
metadataRootBuilder: new MetadataRootBuilder(metadata),
ilStream: ilStream,
managedResources: resourceBlob);
BlobBuilder blob = new BlobBuilder();
peBuilder.Serialize(blob);
using var fileStream = new FileStream("MyAssemblyWithResource.dll", FileMode.Create, FileAccess.Write);
blob.WriteContentTo(fileStream);
fileStream.Close();
var readAssembly = Assembly.LoadFile(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "MyAssemblyWithResource.dll"));
string[] names = readAssembly.GetManifestResourceNames();
using var readStream = readAssembly.GetManifestResourceStream(names[0]);
ResourceReader reader = new ResourceReader(readStream);
}
Expected behavior
I expect to be able to read the resource back out using ResourceReader.
Actual behavior
BadImageFormatException
Regression?
I tried the HEAD and the 9.0 tag, but nothing other than that.
Known Workarounds
No response
Configuration
.NET 9.0, Windows 10 x64
Other information
The example here shows how you can add a byte array as a resource. Having added that array, I'd now like to read it back out later on.
If, after writing the assembly to disk, I try and load the resource with
var readAssembly = Assembly.LoadFile(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "MyAssemblyWithResource.dll"));
string[] names = readAssembly.GetManifestResourceNames();
using var readStream = readAssembly.GetManifestResourceStream(names[0]);
ResourceReader reader = new ResourceReader(readStream);
I receive the error
An attempt was made to load a program with an incorrect format. (0x8007000B)
at
using var readStream = readAssembly.GetManifestResourceStream(names[0]);
As far as I can tell (using dnSpy, looking at the assembly in a hex editor) the assembly contains the resource. If I modify the serialisation code to spit out a huge byte array, the size of the assembly grows accordingly, and I'm able to see the bytes in my hex editor.
When I debug the runtime I see the error is thrown here peassembly.cpp:463
void PEAssembly::GetEmbeddedResource(DWORD dwOffset, DWORD *cbResource, PBYTE *pbInMemoryResource)
{
CONTRACTL
{
INSTANCE_CHECK;
THROWS;
GC_TRIGGERS;
MODE_ANY;
INJECT_FAULT(ThrowOutOfMemory(););
}
CONTRACTL_END;
PEImage* image = GetPEImage();
PEImageLayout* theImage = image->GetOrCreateLayout(PEImageLayout::LAYOUT_ANY);
if (!theImage->CheckResource(dwOffset))
ThrowHR(COR_E_BADIMAGEFORMAT);
COUNT_T size;
const void *resource = theImage->GetResource(dwOffset, &size);
*cbResource = size;
*pbInMemoryResource = (PBYTE) resource;
}
specifically, CHECK(CheckOverflow(VAL32(pDir->VirtualAddress), offset)); in pedecoder.cpp:1243
CHECK PEDecoder::CheckResource(COUNT_T offset) const
{
CONTRACT_CHECK
{
INSTANCE_CHECK;
NOTHROW;
GC_NOTRIGGER;
PRECONDITION(CheckCorHeader());
}
CONTRACT_CHECK_END;
IMAGE_DATA_DIRECTORY *pDir = &GetCorHeader()->Resources;
CHECK(CheckOverflow(VAL32(pDir->VirtualAddress), offset));
RVA rva = VAL32(pDir->VirtualAddress) + offset;
// Make sure we have at least enough data for a length
CHECK(CheckRva(rva, sizeof(DWORD)));
// Make sure resource is within resource section
CHECK(CheckBounds(VAL32(pDir->VirtualAddress), VAL32(pDir->Size),
rva + sizeof(DWORD), GET_UNALIGNED_VAL32((LPVOID)GetRvaData(rva))));
CHECK_OK;
}
CheckOverflow is passed the VirtualAddress and the offset given with
metadata.GetOrAddString("MyResource"), default, (uint)resourceBlob.Count);
in the example code, which is over the end of the data. Looking at that, it doesn't seem that I didn't need an offset, but just passing in 0 didn't help much. I've started reading around about the PE Header - it's really interesting and great to come down from the Typescript clouds - but I think I'm out of my depth for the allotted time I have to learn to swim :-)
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/runtime
-
agentic-workflows untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
area-System.Reflection blocking-clean-ci-optional Known Build Error os-mac-os-x untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
area-CodeGen-coreclr untriaged
Difficulty 1/5 Under an hour Newbie friendliness 92/100
-
agentic-workflows untriaged
Difficulty 1/5 Under an hour Newbie friendliness 78/100
-
area-VM-meta-mono untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
-
:watch: Not Triaged 11.0 fundamentals/subsvc
Difficulty 2/5 1-3 hours Newbie friendliness 92/100
dotnet/AspNetCore.Docs#37699 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SubtitleEdit/subtitleedit#15108 · 1 comment ·
-
area/docs-content Bug pulumi/docs
Difficulty 1/5 1-3 hours Newbie friendliness 94/100
-
Create parent directories only after the containment check in InstallHelper.TryExtractToDirectory Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
PowerShell/PSResourceGet#2056 ·