Skip to content

ARM64EC code ranges broken #168119

@confined8095

Description

@confined8095

Description

Clang 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 .

Affected versions

At least Clang 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

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions