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
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 thatElfSymbol32.Valueis declared asulong(8 bytes) instead ofuint(4 bytes), causing the struct to over-read into the adjacentst_sizefield.Reproduction
Load a 32-bit ARM Linux crash dump on an x64 host and resolve an export:
Memory reads at the returned address fail because it is outside the 32-bit address space.
Root cause
ElfSymbol32declaresValueasulong:The ELF spec defines
Elf32_Symas:Because the
Valuefield is 8 bytes instead of 4,Read<ElfSymbol32>()reads 20 bytes from a 16-byte entry. The 4-bytest_valueand 4-bytest_sizeare combined into a single 8-byteulong, placingst_sizein the upper 32 bits ofValue. All subsequent fields (Size,Info,Other,Shndx) shift by 4 bytes and read garbage.In my case the
DotNetRuntimeContractDescriptorsymbol hasst_size = 0x20(32 bytes — the size of the 32-bit contract descriptor struct), so the returned offset had0x20in bits 32–39, producing a 40-bit address.Suggested fix
Note:
ElfSymbol64also appears to have a mirrored issue —Valueisuint(4 bytes) butElf64_Addr st_valueis 8 bytes. This silently truncates symbol values on 64-bit ELF, though it rarely manifests because RVAs are typically small:It looks like the
Valuetypes were accidentally swapped between the two structs.Impact
All
GetExportSymbolAddresscalls 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
createdump