[llvm] [X86AsmBackend] Check fixup value overflow (PR #176827)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Mon Jan 19 13:58:08 PST 2026
https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/176827
GNU Assembler has a generic error checking for overflowed fixup values
```
y.s:5: Error: value of 8000000000000000 too large for field of 4 bytes at 0000000000000004
```
In contrast, we have had an assertion that may fail for a long time. https://reviews.llvm.org/D70652 improved the status by adding an overflow check for PC-relative fixups, but missed other cases (#116899).
This patch improves the fixup value overflow check:
* Signed (PC-relative): value must be sign-extended, i.e., high bits are all 0s or all 1s.
* Unsigned: both positive and negative values are allowed as long as abs(value) fits (e.g., 1-byte field allows [-255,255]).
The new behavior seems to better resemble GAS.
Note: Currently only PC-relative fixups are treated as signed. GAS treats more fixups (e.g. R_X86_64_32S used in `mov`) as signed.
>From d4a34af31cf5d2875e5d4ffd4723a065459261dc Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Mon, 19 Jan 2026 10:05:37 -0800
Subject: [PATCH] [X86AsmBackend] Check fixup value overflow
GNU Assembler has a generic error checking for overflowed fixup values
```
y.s:5: Error: value of 8000000000000000 too large for field of 4 bytes at 0000000000000004
```
In contrast, we have had an assertion that may fail for a long time.
https://reviews.llvm.org/D70652 improved the status by adding an
overflow check for PC-relative fixups, but missed other cases (#116899).
This patch improves the fixup value overflow check:
* Signed (PC-relative): value must be sign-extended, i.e., high bits are
all 0s or all 1s.
* Unsigned: both positive and negative values are allowed as long as
abs(value) fits (e.g., 1-byte field allows [-255,255]).
The new behavior seems to better resemble GAS.
Note: Currently only PC-relative fixups are treated as signed.
GAS treats more fixups (e.g. R_X86_64_32S used in `mov`) as signed.
---
.../Target/X86/MCTargetDesc/X86AsmBackend.cpp | 27 ++++-----
llvm/test/MC/X86/Relocations/fixup-overflow.s | 55 +++++++++++++++++++
.../MC/X86/Relocations/jcxz-loop-overflow.s | 29 ++++++++++
llvm/test/MC/X86/x86-jcxz-loop-fixup.s | 26 ---------
4 files changed, 98 insertions(+), 39 deletions(-)
create mode 100644 llvm/test/MC/X86/Relocations/fixup-overflow.s
create mode 100644 llvm/test/MC/X86/Relocations/jcxz-loop-overflow.s
delete mode 100644 llvm/test/MC/X86/x86-jcxz-loop-fixup.s
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp b/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp
index 0a98331da4771..b5bcb9202ba25 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86AsmBackend.cpp
@@ -698,21 +698,22 @@ void X86AsmBackend::applyFixup(const MCFragment &F, const MCFixup &Fixup,
assert(Fixup.getOffset() + Size <= F.getSize() && "Invalid fixup offset!");
- int64_t SignedValue = static_cast<int64_t>(Value);
- if (IsResolved && Fixup.isPCRel()) {
- // check that PC relative fixup fits into the fixup size.
- if (Size > 0 && !isIntN(Size * 8, SignedValue))
+ // Check fixup value overflow similar to GAS. Fixups emitted as RELA
+ // relocations have a value of 0.
+ //
+ // - Signed: high bits must be all 0s or all 1s (sign extension).
+ // - Unsigned: abs(value) must fit (e.g. 1-byte field allows [-255,255]).
+ //
+ // Currently only PC-relative fixups are treated as signed. GAS treats
+ // more (e.g. FK_Data_4 for R_X86_64_32S) as signed.
+ if (Size && Size < 8) {
+ bool Signed = Fixup.isPCRel();
+ uint64_t Mask = ~uint64_t(0) << (Size * 8 - (Signed ? 1 : 0));
+ if ((Value & Mask) && (Signed ? (Value & Mask) != Mask : (-Value & Mask)))
getContext().reportError(Fixup.getLoc(),
- "value of " + Twine(SignedValue) +
+ "value of " + Twine(int64_t(Value)) +
" is too large for field of " + Twine(Size) +
- ((Size == 1) ? " byte." : " bytes."));
- } else {
- // Check that uppper bits are either all zeros or all ones.
- // Specifically ignore overflow/underflow as long as the leakage is
- // limited to the lower bits. This is to remain compatible with
- // other assemblers.
- assert((Size == 0 || isIntN(Size * 8 + 1, SignedValue)) &&
- "Value does not fit in the Fixup field");
+ (Size == 1 ? " byte" : " bytes"));
}
for (unsigned i = 0; i != Size; ++i)
diff --git a/llvm/test/MC/X86/Relocations/fixup-overflow.s b/llvm/test/MC/X86/Relocations/fixup-overflow.s
new file mode 100644
index 0000000000000..96699820e4aab
--- /dev/null
+++ b/llvm/test/MC/X86/Relocations/fixup-overflow.s
@@ -0,0 +1,55 @@
+# RUN: not llvm-mc -filetype=obj -triple=x86_64 %s -o /dev/null 2>&1 | FileCheck %s --implicit-check-not=error:
+
+## Test fixup value overflow diagnostics for non-PC-relative fixups
+## with large immediate values that don't fit in the fixup field.
+
+.intel_syntax noprefix
+# CHECK: :[[#@LINE+1]]:10: error: value of 4294967296 is too large for field of 4 bytes
+mov rcx, s4294967296
+
+## GAS rejects [2147483648, 4294967295] for R_X86_64_32S
+mov rcx, s4294967295
+
+.long s4294967295
+# CHECK: :[[#@LINE+1]]:7: error: value of 4294967296 is too large for field of 4 bytes
+.long s4294967296
+
+.quad s4294967296
+
+# CHECK: :[[#@LINE+1]]:10: error: value of -4294967296 is too large for field of 4 bytes
+mov rcx, sn4294967296
+
+## GAS rejects [-4294967295, -2147483649] for R_X86_64_32S
+mov rcx, sn4294967295
+
+.long sn4294967295
+# CHECK: :[[#@LINE+1]]:7: error: value of -4294967296 is too large for field of 4 bytes
+.long sn4294967296
+
+.set s4294967295, (1<<32)-1
+.set s4294967296, 1<<32
+.set sn4294967295, -(1<<32)+1
+.set sn4294967296, -(1<<32)
+
+.section rip_relative_fixups,"ax", at progbits
+## RIP-relative addressing uses signed 32-bit displacement.
+## Forward: 0x7fffffff fits, 0x80000000 overflows
+mov eax, [rip + .Lend1]
+.space 0x7fffffff
+.Lend1:
+
+# CHECK: :[[#@LINE+1]]:17: error: value of 2147483648 is too large for field of 4 bytes
+mov eax, [rip + .Lend2]
+.space 0x80000000
+.Lend2:
+
+## Backward: -0x80000000 fits, -0x80000001 overflows
+## The mov instruction is 6 bytes, so .space 0x7ffffffa gives displacement -0x80000000.
+.Lstart1:
+.space 0x7ffffffa
+mov eax, [rip + .Lstart1]
+
+# CHECK: :[[#@LINE+3]]:17: error: value of -2147483649 is too large for field of 4 bytes
+.Lstart2:
+.space 0x7ffffffb
+mov eax, [rip + .Lstart2]
diff --git a/llvm/test/MC/X86/Relocations/jcxz-loop-overflow.s b/llvm/test/MC/X86/Relocations/jcxz-loop-overflow.s
new file mode 100644
index 0000000000000..45c1fbcbecfd4
--- /dev/null
+++ b/llvm/test/MC/X86/Relocations/jcxz-loop-overflow.s
@@ -0,0 +1,29 @@
+# RUN: not llvm-mc -filetype=obj -triple=x86_64-linux-gnu %s 2>&1 | FileCheck %s
+
+## Test fixup value overflow diagnostics for PC-relative fixups.
+## jecxz/loop instructions only support 8-bit displacement.
+
+ .balign 128
+label00:
+// CHECK: value of 253 is too large for field of 1 byte
+ jecxz label01
+// CHECK: value of 251 is too large for field of 1 byte
+ jrcxz label01
+// CHECK: value of 249 is too large for field of 1 byte
+ loop label01
+// CHECK: value of 247 is too large for field of 1 byte
+ loope label01
+// CHECK: value of 245 is too large for field of 1 byte
+ loopne label01
+ .balign 256
+label01:
+// CHECK: value of -259 is too large for field of 1 byte
+ jecxz label00
+// CHECK: value of -261 is too large for field of 1 byte
+ jrcxz label00
+// CHECK: value of -263 is too large for field of 1 byte
+ loop label00
+// CHECK: value of -265 is too large for field of 1 byte
+ loope label00
+// CHECK: value of -267 is too large for field of 1 byte
+ loopne label00
diff --git a/llvm/test/MC/X86/x86-jcxz-loop-fixup.s b/llvm/test/MC/X86/x86-jcxz-loop-fixup.s
deleted file mode 100644
index 219c1bb52eb6b..0000000000000
--- a/llvm/test/MC/X86/x86-jcxz-loop-fixup.s
+++ /dev/null
@@ -1,26 +0,0 @@
-# RUN: not llvm-mc -filetype=obj -triple=x86_64-linux-gnu %s 2>&1 | FileCheck %s
-
- .balign 128
-label00:
-// CHECK: value of 253 is too large for field of 1 byte.
- jecxz label01
-// CHECK: value of 251 is too large for field of 1 byte.
- jrcxz label01
-// CHECK: value of 249 is too large for field of 1 byte.
- loop label01
-// CHECK: value of 247 is too large for field of 1 byte.
- loope label01
-// CHECK: value of 245 is too large for field of 1 byte.
- loopne label01
- .balign 256
-label01:
-// CHECK: value of -259 is too large for field of 1 byte.
- jecxz label00
-// CHECK: value of -261 is too large for field of 1 byte.
- jrcxz label00
-// CHECK: value of -263 is too large for field of 1 byte.
- loop label00
-// CHECK: value of -265 is too large for field of 1 byte.
- loope label00
-// CHECK: value of -267 is too large for field of 1 byte.
- loopne label00
More information about the llvm-commits
mailing list