Skip to content

PersistedAssemblyBuilder: unable to retrieve resources added with AddManifestResource #110686

Open
@mattjacobsen

Description

@mattjacobsen

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 :-)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions