Issue 183123
Summary [RuntimeDyld][COFF] 32-bit relocation addend not sign-extended in RuntimeDyldCOFFX86_64
Labels new issue
Assignees
Reporter sumit2089
    ## Summary

In `RuntimeDyldCOFFX86_64::processRelocationRef`, the 32-bit implicit addend for `IMAGE_REL_AMD64_REL32*` and `IMAGE_REL_AMD64_ADDR32NB` relocations is not sign-extended when read into a 64-bit variable, causing spurious relocation overflow asserts.

## Details

The addend is read as:

```cpp
Addend = readBytesUnaligned(Displacement, 4);
```

`readBytesUnaligned` returns `uint64_t`, so a negative 32-bit addend such as `0xFFFFFFFC` (-4) is zero-extended to `0x00000000FFFFFFFC` (4294967292) instead of being sign-extended to `0xFFFFFFFFFFFFFFFC` (-4).

This causes the assert in `resolveRelocation` to fire spuriously:

```cpp
uint64_t Result = Value + RE.Addend;
assert(((int64_t)Result <= INT32_MAX) && "Relocation overflow");
```

The computed `Result` overflows because `RE.Addend` is ~4 billion instead of -4.

**File:** `llvm/lib/ExecutionEngine/RuntimeDyld/Targets/RuntimeDyldCOFFX86_64.h`, line 254

## Proposed Fix

```diff
-      Addend = readBytesUnaligned(Displacement, 4);
+      Addend = static_cast<int64_t>(static_cast<int32_t>(readBytesUnaligned(Displacement, 4)));
```

## Steps to Reproduce

### Environment
- **OS:** Windows 10/11 x64
- **Compiler:** MSVC (Visual Studio 2022)
- **LLVM version:** 21.1.8 (obtained via vcpkg from the official GitHub release `llvmorg-21.1.8`)

### Minimal reproducer

```cpp
#include <llvm/ExecutionEngine/Orc/Core.h>
#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
#include <llvm/ExecutionEngine/Orc/IRCompileLayer.h>
#include <llvm/ExecutionEngine/Orc/CompileUtils.h>
#include <llvm/ExecutionEngine/Orc/AbsoluteSymbols.h>
#include <llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h>
#include <llvm/ExecutionEngine/Orc/ThreadSafeModule.h>
#include <llvm/ExecutionEngine/SectionMemoryManager.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/Verifier.h>
#include <llvm/Support/TargetSelect.h>
#include <llvm/Target/TargetMachine.h>
#include <llvm/MC/TargetRegistry.h>
#include <llvm/TargetParser/Host.h>
#include <cmath>
#include <cstdio>

// External function the JIT'd code will call
extern "C" float my_external_func(float x) { return x * 2.0f; }

int main() {
    llvm::InitializeNativeTarget();
    llvm::InitializeNativeTargetAsmPrinter();

    // Create execution session
    auto EPC = llvm::orc::SelfExecutorProcessControl::Create();
    if (!EPC) {
        fprintf(stderr, "Failed to create EPC\n");
        return 1;
    }
    auto ES = std::make_unique<llvm::orc::ExecutionSession>(std::move(*EPC));

    // Create object linking layer with default SectionMemoryManager
    llvm::orc::RTDyldObjectLinkingLayer ObjLayer(
        *ES,
        [](const llvm::MemoryBuffer&) {
            return std::make_unique<llvm::SectionMemoryManager>();
        });

    // Create target machine with CodeModel::Small
    std::string ErrStr;
    auto Triple = llvm::sys::getDefaultTargetTriple();
    auto* Target = llvm::TargetRegistry::lookupTarget(Triple, ErrStr);
    llvm::TargetOptions Opts;
    auto* TM = Target->createTargetMachine(
        Triple, "generic", "", Opts,
        llvm::Reloc::PIC_,
        llvm::CodeModel::Small,
        llvm::CodeGenOptLevel::Default);

    // Create IR compile layer
    llvm::orc::IRCompileLayer CompileLayer(
        *ES, ObjLayer, std::make_unique<llvm::orc::SimpleCompiler>(*TM));

    // Create JITDylib and add external symbol
    auto& JD = ES->createBareJITDylib("main");
    llvm::orc::SymbolMap ExternalSymbols;
    auto& Intern = ES->getExecutorSymbolStringPool();
    ExternalSymbols[ES->intern("my_external_func")] = {
        llvm::orc::ExecutorAddr::fromPtr(&my_external_func),
        llvm::JITSymbolFlags::Exported | llvm::JITSymbolFlags::Callable
    };
    if (auto Err = JD.define(llvm::orc::absoluteSymbols(std::move(ExternalSymbols)))) {
        fprintf(stderr, "Failed to define symbols\n");
        return 1;
    }

    // Build a simple IR module that calls the external function
    auto Ctx = std::make_unique<llvm::LLVMContext>();
    auto Mod = std::make_unique<llvm::Module>("test", *Ctx);
    Mod->setDataLayout(TM->createDataLayout());
    Mod->setTargetTriple(llvm::Triple(Triple));

    auto* FT = llvm::FunctionType::get(
        llvm::Type::getFloatTy(*Ctx),
        {llvm::Type::getFloatTy(*Ctx)},
        false);
    auto* F = llvm::Function::Create(FT, llvm::Function::ExternalLinkage, "test_func", *Mod);
    auto* BB = llvm::BasicBlock::Create(*Ctx, "entry", F);
    llvm::IRBuilder<> Builder(BB);

    // Declare external function
    auto* ExtFT = llvm::FunctionType::get(
        llvm::Type::getFloatTy(*Ctx),
        {llvm::Type::getFloatTy(*Ctx)},
        false);
    auto* ExtF = llvm::Function::Create(ExtFT, llvm::Function::ExternalLinkage,
                                         "my_external_func", *Mod);

    // Call it — this generates IMAGE_REL_AMD64_REL32 relocations in the COFF object
    auto* CallResult = Builder.CreateCall(ExtF, {F->getArg(0)});
    Builder.CreateRet(CallResult);

    llvm::verifyFunction(*F);

    // JIT compile — this triggers the assert in resolveRelocation
    // when the implicit addend at the call site is negative
    llvm::orc::ThreadSafeModule TSM(std::move(Mod), std::move(Ctx));
    if (auto Err = CompileLayer.add(JD, std::move(TSM))) {
        fprintf(stderr, "Failed to add module\n");
        return 1;
    }

    auto Sym = ES->lookup({&JD}, "test_func");
    if (!Sym) {
        fprintf(stderr, "Lookup failed\n");
        return 1;
    }

    printf("JIT compilation succeeded at %p\n",
           reinterpret_cast<void*>(Sym->getAddress().getValue()));

    if (auto Err = ES->endSession())
        llvm::consumeError(std::move(Err));

    return 0;
}
```

### How to trigger

The crash occurs when:
1. The COFF object emitted by LLVM's code generator contains an `IMAGE_REL_AMD64_REL32` relocation with a negative implicit addend at the relocation site (e.g., `0xFFFFFFFC`).
2. `RuntimeDyldCOFFX86_64::processRelocationRef` reads this 4-byte addend via `readBytesUnaligned(Displacement, 4)`, which returns `uint64_t`.
3. The value `0xFFFFFFFC` is zero-extended to `0x00000000FFFFFFFC` (4294967292) instead of being sign-extended to `0xFFFFFFFFFFFFFFFC` (-4).
4. In `resolveRelocation`, `Value + RE.Addend` produces an incorrect result that exceeds `INT32_MAX`, triggering the assert.

**Note:** Whether negative implicit addends appear depends on the code generator, optimization level, and layout of the emitted object. They are more likely with `CodeModel::Small` and PIC relocations on Windows x64.

## LLVM Source

Obtained from the official LLVM GitHub repository via vcpkg: `llvmorg-21.1.8` tag at https://github.com/llvm/llvm-project.

The bug is also present in LLVM 18.x and likely all prior versions — the `processRelocationRef` code in `RuntimeDyldCOFFX86_64.h` has not changed in this area.

## Additional Note

`RuntimeDyldCOFFI386.h` and `RuntimeDyldCOFFThumb.h` may have the same issue in their `processRelocationRef` implementations and should be audited.

_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to