[RuntimeDyld][COFF] 32-bit relocation addend not sign-extended in RuntimeDyldCOFFX86_64
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## 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(static_cast(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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// 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(std::move(*EPC));
// Create object linking layer with default SectionMemoryManager
llvm::orc::RTDyldObjectLinkingLayer ObjLayer(
*ES,
[](const llvm::MemoryBuffer&) {
return std::make_unique();
});
// 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(*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();
auto Mod = std::make_unique("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(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.
Contributor guide
Research direction
Start in llvm/lib/ExecutionEngine/RuntimeDyld/Targets/RuntimeDyldCOFFX86_64.h, especially processRelocationRef and resolveRelocation, to trace how 32-bit implicit addends are read and validated. Check the related processRelocationRef implementations in RuntimeDyldCOFFI386.h and RuntimeDyldCOFFThumb.h for the same behavior. Done means negative COFF relocation addends no longer trigger spurious overflow assertions and the related targets have been audited.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 42/100