Skip to content

ARM64EC code ranges broken #168119

@confined8095

Description

@confined8095

Description

LLD writes out ARM64EC code ranges on 4-byte granularity. Windows expects code ranges on page granularity (4096 bytes). The end result is catastrophically broken code ranges in CHPE metadata.

EDIT: Attempting to executing broken code ranges will result in access violation .
EDIT2: Corrected clang to lld.

Affected versions

At least LLD 21.1.4 and 21.1.5, I did not try older versions.

Reproducing

The easiest way to reproduce this is by forcefully aligning a function. This will produce a "hole" in range.

Files

code.amd64.cpp:

extern "C" {
    __declspec(code_seg(".text1$aaa")) int amd64_code_before() { return 0x12340001; }
    __declspec(code_seg(".text3$zzz")) int amd64_code_after() { return 0x12340005; }
}

code.arm64.cpp:

extern "C" {
    __declspec(code_seg(".text2$bbb")) __declspec(align(0x800)) int arm64_code_first() {
        return 0x12340002; 
    }
    __declspec(code_seg(".text2$ccc")) __declspec(align(0x800)) int arm64_code_middle() {
        return 0x12340003; 
    }
    __declspec(code_seg(".text2$ddd")) int arm64_code_last() {
        return 0x12340004; 
    }
}

main.cpp:

#include <stdio.h>

extern "C" int amd64_code_before();
extern "C" int arm64_code_first();
extern "C" int arm64_code_middle();
extern "C" int arm64_code_last();
extern "C" int amd64_code_after();

extern "C" char RtlIsEcCode(uintptr_t CodePointer);

struct range { unsigned start; unsigned length; };
extern "C" char __ImageBase;
extern "C" unsigned __chpe_metadata[];

int main() {
  range* code_map = (range*)((char*)&__ImageBase + __chpe_metadata[1]);
  unsigned code_map_count = __chpe_metadata[2];
  printf("Code ranges:\n");
  for (int i = 0; i < code_map_count; ++i) {
    printf("%08X %08X\n", code_map[i].start, code_map[i].length);
  }

  printf("amd64_code_before %zx is_ec %u\n", (size_t)amd64_code_before - (size_t)&__ImageBase, RtlIsEcCode((uintptr_t)amd64_code_before));
  printf("arm64_code_first %zx is_ec %u\n", (size_t)arm64_code_first - (size_t)&__ImageBase, RtlIsEcCode((uintptr_t)arm64_code_first));
  printf("arm64_code_middle %zx is_ec %u\n", (size_t)arm64_code_middle - (size_t)&__ImageBase, RtlIsEcCode((uintptr_t)arm64_code_middle));
  printf("arm64_code_last %zx is_ec %u\n", (size_t)arm64_code_last - (size_t)&__ImageBase, RtlIsEcCode((uintptr_t)arm64_code_last));
  printf("amd64_code_after %zx is_ec %u\n", (size_t)amd64_code_after - (size_t)&__ImageBase, RtlIsEcCode((uintptr_t)amd64_code_after));
}

build.bat:

"D:/LLVM/LLVM-21.1.5/bin/clang-cl.exe" -nologo -O2 -GS- -Fomain.cpp.obj -c main.cpp
"D:/LLVM/LLVM-21.1.5/bin/clang-cl.exe" -nologo -O2 -GS- -Focode.amd64.cpp.obj -c code.amd64.cpp
"D:/LLVM/LLVM-21.1.5/bin/clang-cl.exe" -nologo -O2 -GS- -arm64EC -Focode.arm64.cpp.obj -c code.arm64.cpp
"D:/LLVM/LLVM-21.1.5/bin/lld-link.exe" -nologo -Brepro -machine:ARM64EC -subsystem:console -out:range-test-llvm.exe "../arm64/arm64rt.lib" ucrt.lib msvcrt.lib ntdll.lib main.cpp.obj code.amd64.cpp.obj code.arm64.cpp.obj

Output

Code ranges:
00001001 000003D4
00002002 00000F06
00009002 00000006
0000A801 0000081C
0000C002 00000006
amd64_code_before 9000 is_ec 0
arm64_code_first a800 is_ec 1
arm64_code_middle b000 is_ec 0
arm64_code_last b010 is_ec 0
amd64_code_after c000 is_ec 0

Expected output

Code ranges:
00001001 000003D4
00002002 00000F06
00009002 00000006
0000A001 0000100C
0000C002 00000006
amd64_code_before 9000 is_ec 0
arm64_code_first a800 is_ec 1
arm64_code_middle b000 is_ec 1
arm64_code_last b010 is_ec 1
amd64_code_after c000 is_ec 0

Workaround

Patching out final PE file externally to fix clangs broken code ranges.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions