Skip to content

LLD: Fix getFdePC with sdata2 or sdata4 #92228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

wzssyqa
Copy link
Contributor

@wzssyqa wzssyqa commented May 15, 2024

LLD uses uint64_t to hold PC value. If an Fde is DW_EH_PE_absptr with data type sdata, getFdePc may return a negtive value and then convert to uint64_t. It will fail to so some add/sub with other address values:
PC offset is too large: 0xffffffff00000040 is expected.

So if Fde is DW_EH_PE_absptr, let's convert its value to uint16_t(sdata2) or uint32_t(sdata4).

We also do similiar things to DW_EH_PE_pcrel: the value of it should be a signed offset, thus, let's convert them to signed values before use them to add with other address values.

Fixes: #88852.

LLD uses uint64_t to hold PC value. If an Fde is DW_EH_PE_absptr
with data type sdata, getFdePc may return a negtive value and
then convert to uint64_t. It will fail to so some add/sub with
other address values:
   PC offset is too large: 0xffffffff00000040 is expected.

So if Fde is DW_EH_PE_absptr, let's convert its value to
uint16_t(sdata2) or uint32_t(sdata4).

We also do similiar things to DW_EH_PE_pcrel: the value of it should
be a signed offset, thus, let's convert them to signed values before
use them to add with other address values.

Fixes: llvm#88852.
@llvmbot
Copy link
Member

llvmbot commented May 15, 2024

@llvm/pr-subscribers-lld-elf

@llvm/pr-subscribers-lld

Author: YunQiang Su (wzssyqa)

Changes

LLD uses uint64_t to hold PC value. If an Fde is DW_EH_PE_absptr with data type sdata, getFdePc may return a negtive value and then convert to uint64_t. It will fail to so some add/sub with other address values:
PC offset is too large: 0xffffffff00000040 is expected.

So if Fde is DW_EH_PE_absptr, let's convert its value to uint16_t(sdata2) or uint32_t(sdata4).

We also do similiar things to DW_EH_PE_pcrel: the value of it should be a signed offset, thus, let's convert them to signed values before use them to add with other address values.

Fixes: #88852.


Full diff: https://github.com/llvm/llvm-project/pull/92228.diff

3 Files Affected:

  • (modified) lld/ELF/SyntheticSections.cpp (+17-4)
  • (added) lld/test/ELF/Inputs/mips32-kseg0.lds (+8)
  • (modified) lld/test/ELF/mips-eh_frame-pic.s (+5)
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 7b9ada40c0f67..9749e601d8b16 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -612,10 +612,23 @@ uint64_t EhFrameSection::getFdePc(uint8_t *buf, size_t fdeOff,
   // the .eh_frame section.
   size_t off = fdeOff + 8;
   uint64_t addr = readFdeAddr(buf + off, enc & 0xf);
-  if ((enc & 0x70) == DW_EH_PE_absptr)
-    return addr;
-  if ((enc & 0x70) == DW_EH_PE_pcrel)
-    return addr + getParent()->addr + off + outSecOff;
+  if ((enc & 0x70) == DW_EH_PE_absptr) {
+    if ((enc & 0xf) == DW_EH_PE_sdata2)
+      return (uint64_t)(uint16_t)addr;
+    else if ((enc & 0xf) == DW_EH_PE_sdata4)
+      return (uint64_t)(uint32_t)addr;
+    else
+      return addr;
+  }
+  if ((enc & 0x70) == DW_EH_PE_pcrel) {
+    uint64_t addr_base = getParent()->addr + off + outSecOff;
+    if ((enc & 0xf) == DW_EH_PE_sdata2)
+      return (int16_t)addr + addr_base;
+    else if ((enc & 0xf) == DW_EH_PE_sdata4)
+      return (int32_t)addr + addr_base;
+    else
+      return addr + addr_base;
+  }
   fatal("unknown FDE size relative encoding");
 }
 
diff --git a/lld/test/ELF/Inputs/mips32-kseg0.lds b/lld/test/ELF/Inputs/mips32-kseg0.lds
new file mode 100644
index 0000000000000..613194adcca7c
--- /dev/null
+++ b/lld/test/ELF/Inputs/mips32-kseg0.lds
@@ -0,0 +1,8 @@
+OUTPUT_ARCH(mips)
+SECTIONS
+{
+    . = 0x80000000;
+    .text : { *(.text) }
+    .data : { *(.data) }
+    .bss : { *(.bss) }
+}
diff --git a/lld/test/ELF/mips-eh_frame-pic.s b/lld/test/ELF/mips-eh_frame-pic.s
index 79076e74a7e3f..b400114c8b7b7 100644
--- a/lld/test/ELF/mips-eh_frame-pic.s
+++ b/lld/test/ELF/mips-eh_frame-pic.s
@@ -23,15 +23,20 @@
 # RUN: llvm-dwarfdump --eh-frame %t-nopic32.o | FileCheck %s --check-prefix=ABS32-EH-FRAME
 # RUN: llvm-readobj -r %t-nopic32.o | FileCheck %s --check-prefixes=RELOCS,ABS32-RELOCS
 # RUN: not ld.lld -shared %t-nopic32.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=NOPIC32-ERR
+# RUN: ld.lld %t-nopic32.o -T %p/Inputs/mips32-kseg0.lds -eh-frame-hdr -static -o /dev/null 2>&1 \
+# RUN:      | FileCheck %s --check-prefix=NOPIC32-ABSPTR
 ## Note: ld.bfd can link this file because it rewrites the .eh_frame section to use
 ## relative addressing.
 # NOPIC32-ERR: ld.lld: error: relocation R_MIPS_32 cannot be used against local symbol
+# NOPIC32-ABSPTR: cannot find entry symbol __start
 
 ## For -fPIC, .eh_frame should contain DW_EH_PE_pcrel | DW_EH_PE_sdata4 values:
 # RUN: llvm-mc -filetype=obj -triple=mips-unknown-linux --position-independent %s -o %t-pic32.o
 # RUN: llvm-readobj -r %t-pic32.o | FileCheck %s --check-prefixes=RELOCS,PIC32-RELOCS
 # RUN: ld.lld -shared %t-pic32.o -o %t-pic32.so
+# RUN: ld.lld -shared -T %p/Inputs/mips32-kseg0.lds %t-pic32.o -o %t-pic32-kseg0.so
 # RUN: llvm-dwarfdump --eh-frame %t-pic32.so | FileCheck %s --check-prefix=PIC32-EH-FRAME
+# RUN: llvm-dwarfdump --eh-frame %t-pic32-kseg0.so | FileCheck %s --check-prefix=PIC32-EH-FRAME
 
 # RELOCS:            .rel{{a?}}.eh_frame {
 # ABS32-RELOCS-NEXT:   0x1C R_MIPS_32 .text

@wzssyqa wzssyqa requested review from MaskRay, dwblaikie, jrtc27 and brad0 May 15, 2024 08:45
Copy link
Member

@MaskRay MaskRay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

if ((enc & 0x70) == DW_EH_PE_pcrel) {
uint64_t addr_base = getParent()->addr + off + outSecOff;
if ((enc & 0xf) == DW_EH_PE_sdata2)
return (int16_t)addr + addr_base;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unnecessary.

if ((enc & 0x70) == DW_EH_PE_pcrel)
return addr + getParent()->addr + off + outSecOff;
if ((enc & 0x70) == DW_EH_PE_absptr) {
if ((enc & 0xf) == DW_EH_PE_sdata2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably not desired for ELFCLASS64.

See the alternative fix: #92438 , but thanks for investigation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh, yes. we won't support any platform with 16bit absptr with ELF.

@wzssyqa wzssyqa closed this May 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[MIPS] Got PC offset is too large 0xffffffff.... when linking using LLD
3 participants