Skip to content

GetExportSymbolAddress returns corrupt addresses for 32-bit ELF modules #1407

@max-charlamb

Description

@max-charlamb

Summary

ModuleInfo.GetExportSymbolAddress() returns addresses with spurious upper bits when resolving exports from 32-bit (ARM32) ELF modules in a crash dump. The root cause is that ElfSymbol32.Value is declared as ulong (8 bytes) instead of uint (4 bytes), causing the struct to over-read into the adjacent st_size field.

Reproduction

Load a 32-bit ARM Linux crash dump on an x64 host and resolve an export:

using var dt = DataTarget.LoadDump("arm32-dump.dmp");
foreach (var module in dt.DataReader.EnumerateModules())
{
    if (!module.FileName.Contains("libcoreclr.so"))
        continue;

    ulong addr = module.GetExportSymbolAddress("DotNetRuntimeContractDescriptor");
    Console.WriteLine($"Base:   0x{module.ImageBase:X}");
    Console.WriteLine($"Export: 0x{addr:X}");
    // Expected: 0xEFCE9BA8 (valid 32-bit address)
    // Actual:   0x20EFCE9BA8 (40-bit — upper byte 0x20 is the symbol's st_size leaking through)
}

Memory reads at the returned address fail because it is outside the 32-bit address space.

Root cause

ElfSymbol32 declares Value as ulong:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct ElfSymbol32
{
    public uint Name;      // 4 bytes
    public ulong Value;    // 8 bytes ← BUG: should be uint (4 bytes)
    public uint Size;      // 4 bytes
    public byte Info;
    public byte Other;
    public ushort Shndx;
}

The ELF spec defines Elf32_Sym as:

typedef struct {
    Elf32_Word    st_name;   // 4 bytes
    Elf32_Addr    st_value;  // 4 bytes  ← uint, not ulong
    Elf32_Word    st_size;   // 4 bytes
    unsigned char st_info;   // 1 byte
    unsigned char st_other;  // 1 byte
    Elf32_Half    st_shndx;  // 2 bytes
} Elf32_Sym;                 // Total: 16 bytes

Because the Value field is 8 bytes instead of 4, Read<ElfSymbol32>() reads 20 bytes from a 16-byte entry. The 4-byte st_value and 4-byte st_size are combined into a single 8-byte ulong, placing st_size in the upper 32 bits of Value. All subsequent fields (Size, Info, Other, Shndx) shift by 4 bytes and read garbage.

In my case the DotNetRuntimeContractDescriptor symbol has st_size = 0x20 (32 bytes — the size of the 32-bit contract descriptor struct), so the returned offset had 0x20 in bits 32–39, producing a 40-bit address.

Suggested fix

[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct ElfSymbol32
{
    public uint Name;
    public uint Value;     // Fix: was ulong
    public uint Size;
    public byte Info;
    public byte Other;
    public ushort Shndx;
}

Note: ElfSymbol64 also appears to have a mirrored issue — Value is uint (4 bytes) but Elf64_Addr st_value is 8 bytes. This silently truncates symbol values on 64-bit ELF, though it rarely manifests because RVAs are typically small:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct ElfSymbol64
{
    public uint Name;
    public byte Info;
    public byte Other;
    public ushort Shndx;
    public uint Value;     // BUG: should be ulong (Elf64_Addr is 8 bytes)
    public ulong Size;
}

It looks like the Value types were accidentally swapped between the two structs.

Impact

All GetExportSymbolAddress calls on 32-bit ELF modules return wrong addresses. This blocks any cross-platform dump analysis of 32-bit ARM Linux processes (e.g., cDAC dump tests in dotnet/runtime).

Environment

  • ClrMD version: 3.1.512801 (NuGet)
  • Host: Windows x64
  • Dump: Linux ARM32 (ELF32) minidump from createdump

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions