Skip to content

Conversation

@kikairoya
Copy link
Contributor

@kikairoya kikairoya commented Aug 30, 2025

When a symbol is imported from a DLL, a base relocation record is normally emitted.
However, if the import is pulled in via a wrapped symbol (using --wrap) and later dropped because it is only referenced weakly, a dangling base relocation remains in the output.
At runtime, this relocation changes the symbol value from null to a garbage pointer.

This patch adds checks to avoid emitting relocation records for non-Defined symbols, and to prevent creating an auto-import entry if an import entry for the wrapped symbol already exists.

Fixes #150739

@llvmbot
Copy link
Member

llvmbot commented Aug 30, 2025

@llvm/pr-subscribers-lld-coff

Author: Tomohiro Kashiwada (kikairoya)

Changes

Fixes #150739


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

5 Files Affected:

  • (modified) lld/COFF/Chunks.cpp (+1-1)
  • (modified) lld/COFF/MinGW.cpp (+20-4)
  • (added) lld/test/COFF/reloc-undefined-weak.s (+52)
  • (modified) lld/test/COFF/wrap-dllimport.s (+25-8)
  • (added) lld/test/COFF/wrap-inherit-weakness.ll (+16)
diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp
index 39fc25047f3b1..cb5cba5c414a1 100644
--- a/lld/COFF/Chunks.cpp
+++ b/lld/COFF/Chunks.cpp
@@ -564,7 +564,7 @@ void SectionChunk::getBaserels(std::vector<Baserel> *res) {
     if (ty == IMAGE_REL_BASED_ABSOLUTE)
       continue;
     Symbol *target = file->getSymbol(rel.SymbolTableIndex);
-    if (!target || isa<DefinedAbsolute>(target))
+    if (!isa_and_nonnull<Defined>(target) || isa<DefinedAbsolute>(target))
       continue;
     res->emplace_back(rva + rel.VirtualAddress, ty);
   }
diff --git a/lld/COFF/MinGW.cpp b/lld/COFF/MinGW.cpp
index e7117cbea2e88..7c6f4281145ce 100644
--- a/lld/COFF/MinGW.cpp
+++ b/lld/COFF/MinGW.cpp
@@ -233,6 +233,17 @@ void lld::coff::addWrappedSymbols(SymbolTable &symtab,
         symtab.addUndefined(mangle("__wrap_" + name, symtab.machine));
     v.push_back({sym, real, wrap});
 
+    if (auto *usym = dyn_cast<Undefined>(sym)) {
+      if (auto *ureal = dyn_cast<Undefined>(real);
+          ureal && ureal->weakAlias && !usym->weakAlias) {
+        usym->weakAlias = ureal->weakAlias;
+      }
+      if (auto *uwrap = dyn_cast<Undefined>(wrap);
+          uwrap && usym->weakAlias && !uwrap->weakAlias) {
+        uwrap->weakAlias = usym->weakAlias;
+      }
+    }
+
     // These symbols may seem undefined initially, but don't bail out
     // at symtab.reportUnresolvable() due to them, but let wrapSymbols
     // below sort things out before checking finally with
@@ -269,10 +280,15 @@ void lld::coff::wrapSymbols(SymbolTable &symtab) {
       // (We can't easily distinguish whether any object file actually
       // referenced it or not, though.)
       if (imp) {
-        DefinedLocalImport *wrapimp = make<DefinedLocalImport>(
-            symtab.ctx, saver().save("__imp_" + w.wrap->getName()), d);
-        symtab.localImportChunks.push_back(wrapimp->getChunk());
-        map[imp] = wrapimp;
+        if (Symbol *wrapimp =
+                symtab.find(("__imp_" + w.wrap->getName()).str())) {
+          map[imp] = wrapimp;
+        } else {
+          DefinedLocalImport *localwrapimp = make<DefinedLocalImport>(
+              symtab.ctx, saver().save("__imp_" + w.wrap->getName()), d);
+          symtab.localImportChunks.push_back(localwrapimp->getChunk());
+          map[imp] = localwrapimp;
+        }
       }
     }
   }
diff --git a/lld/test/COFF/reloc-undefined-weak.s b/lld/test/COFF/reloc-undefined-weak.s
new file mode 100644
index 0000000000000..5f9baa7a3dfb0
--- /dev/null
+++ b/lld/test/COFF/reloc-undefined-weak.s
@@ -0,0 +1,52 @@
+// REQUIRES: x86
+
+// Check that base-relocations for unresolved weak symbols will be omitted.
+
+// RUN: rm -rf %t.dir && split-file %s %t.dir && cd %t.dir
+// RUN: llvm-mc -filetype=obj -triple=x86_64-mingw main.s -o main.o
+// RUN: llvm-mc -filetype=obj -triple=x86_64-mingw other.s -o other.o
+
+// RUN: ld.lld -m i386pep -dll -o other.dll other.o -entry= --export-all-symbols --out-implib other.dll.a
+// RUN: ld.lld -m i386pep -o main.exe main.o other.dll.a -e entry --wrap foo --verbose
+// RUN: llvm-readobj --sections --symbols --coff-imports --coff-basereloc main.exe | FileCheck %s --implicit-check-not=other.dll
+
+// CHECK:      Number: 4
+// CHECK-NEXT: Name: .data
+// CHECK-NEXT: VirtualSize:
+// CHECK-NEXT: VirtualAddress: 0x[[#%x,SECTOP:0x4000]]
+// CHECK:      Name: ref_foo
+// CHECK-NEXT: Value: [[#%d,SYMVAL:]]
+// CHECK:      BaseReloc [
+// CHECK-NOT:    Address: 0x[[#%x,SECTOP+SYMVAL]]
+
+#--- main.s
+.global entry
+entry:
+  movq ref_foo(%rip), %rax
+  call *%rax
+
+.global __wrap_foo
+__wrap_foo:
+  ret
+
+.data
+.global ref_foo
+.p2align 3
+ref_foo:
+  .quad __real_foo
+
+.globl _pei386_runtime_relocator
+_pei386_runtime_relocator:
+  movl __RUNTIME_PSEUDO_RELOC_LIST__(%rip), %eax
+  movl __RUNTIME_PSEUDO_RELOC_LIST_END__(%rip), %eax
+
+.weak __real_foo
+.addrsig
+.addrsig_sym __real_foo
+.addrsig_sym ref_foo
+
+#--- other.s
+.global foo
+
+foo:
+  ret
diff --git a/lld/test/COFF/wrap-dllimport.s b/lld/test/COFF/wrap-dllimport.s
index d7662b29fdc78..a1b4521fd75fa 100644
--- a/lld/test/COFF/wrap-dllimport.s
+++ b/lld/test/COFF/wrap-dllimport.s
@@ -1,42 +1,59 @@
 // REQUIRES: x86
 
 // Check that we can wrap a dllimported symbol, so that references to
-// __imp_<symbol> gets redirected to a defined local import instead.
+// __imp_<symbol> gets redirected to a symbol already exists or a defined local import instead
 
 // RUN: split-file %s %t.dir
 // RUN: llvm-mc -filetype=obj -triple=i686-win32-gnu %t.dir/main.s -o %t.main.obj
 // RUN: llvm-mc -filetype=obj -triple=i686-win32-gnu %t.dir/other.s -o %t.other.obj
 
-// RUN: lld-link -dll -out:%t.dll %t.other.obj -noentry -safeseh:no -export:foo -implib:%t.lib
-// RUN: lld-link -out:%t.exe %t.main.obj %t.lib -entry:entry -subsystem:console -debug:symtab -safeseh:no -wrap:foo -lldmap:%t.map
+// RUN: lld-link -dll -out:%t.dll %t.other.obj -noentry -safeseh:no -export:foo -export:bar -implib:%t.lib
+// RUN: lld-link -out:%t.exe %t.main.obj %t.lib -entry:entry -subsystem:console -debug:symtab -safeseh:no -wrap:foo -wrap:bar -lldmap:%t.map
 // RUN: llvm-objdump -s -d --print-imm-hex %t.exe | FileCheck %s
 
 // CHECK:      Contents of section .rdata:
-// CHECK-NEXT:  402000 06104000
+// CHECK-NEXT:  402000 0c104000
 
 // CHECK:      Disassembly of section .text:
 // CHECK-EMPTY:
 // CHECK:      00401000 <_entry>:
 // CHECK-NEXT:   401000: ff 25 00 20 40 00             jmpl    *0x402000
+// CHECK-NEXT:   401006: ff 25 00 00 00 00             jmpl    *0x0
 // CHECK-EMPTY:
-// CHECK-NEXT: 00401006 <___wrap_foo>:
-// CHECK-NEXT:   401006: c3                            retl
+// CHECK-NEXT: 0040100c <___wrap_foo>:
+// CHECK-NEXT:   40100c: c3                            retl
+// CHECK-EMPTY:
+// CHECK-NEXT: 0040100d <___wrap_bar>:
+// CHECK-NEXT:   40100d: c3                            retl
 
-// The jmpl instruction in _entry points at an address in 0x402000,
+// The first jmpl instruction in _entry points at an address in 0x402000,
 // which is the first 4 bytes of the .rdata section (above), which is a
 // pointer that points at ___wrap_foo.
 
+// The second jmpl instruction in _entry points the null since the referenced symbol
+// `__imp____wrap_bar` is declared as a weak reference to prevent pull a reference
+// from an external DLL.
+
 #--- main.s
 .global _entry
 _entry:
   jmpl *__imp__foo
+  jmpl *__imp__bar
 
 .global ___wrap_foo
 ___wrap_foo:
   ret
 
+.weak __imp____wrap_bar
+.global ___wrap_bar
+___wrap_bar:
+  ret
+
 #--- other.s
 .global _foo
-
 _foo:
   ret
+
+.global _bar
+_bar:
+  ret
diff --git a/lld/test/COFF/wrap-inherit-weakness.ll b/lld/test/COFF/wrap-inherit-weakness.ll
new file mode 100644
index 0000000000000..e050e8fda37f2
--- /dev/null
+++ b/lld/test/COFF/wrap-inherit-weakness.ll
@@ -0,0 +1,16 @@
+; REQUIRES: x86
+
+; Check that 'weak' attribute will be inherited to the wrapped symbols.
+
+; RUN: llc %s -mtriple x86_64-mingw -o %t.o --filetype=obj
+; RUN: ld.lld -m i386pep -shared -o %t.dll %t.o --entry= --wrap fn
+
+declare extern_weak dso_local void @__real_fn() nounwind
+declare dso_local void @fn() nounwind
+declare dso_local void @__wrap_fn() nounwind
+
+define dllexport void @caller() nounwind {
+  call void @__real_fn()
+  call void @fn()
+  ret void
+}

@llvmbot
Copy link
Member

llvmbot commented Aug 30, 2025

@llvm/pr-subscribers-lld

Author: Tomohiro Kashiwada (kikairoya)

Changes

Fixes #150739


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

5 Files Affected:

  • (modified) lld/COFF/Chunks.cpp (+1-1)
  • (modified) lld/COFF/MinGW.cpp (+20-4)
  • (added) lld/test/COFF/reloc-undefined-weak.s (+52)
  • (modified) lld/test/COFF/wrap-dllimport.s (+25-8)
  • (added) lld/test/COFF/wrap-inherit-weakness.ll (+16)
diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp
index 39fc25047f3b1..cb5cba5c414a1 100644
--- a/lld/COFF/Chunks.cpp
+++ b/lld/COFF/Chunks.cpp
@@ -564,7 +564,7 @@ void SectionChunk::getBaserels(std::vector<Baserel> *res) {
     if (ty == IMAGE_REL_BASED_ABSOLUTE)
       continue;
     Symbol *target = file->getSymbol(rel.SymbolTableIndex);
-    if (!target || isa<DefinedAbsolute>(target))
+    if (!isa_and_nonnull<Defined>(target) || isa<DefinedAbsolute>(target))
       continue;
     res->emplace_back(rva + rel.VirtualAddress, ty);
   }
diff --git a/lld/COFF/MinGW.cpp b/lld/COFF/MinGW.cpp
index e7117cbea2e88..7c6f4281145ce 100644
--- a/lld/COFF/MinGW.cpp
+++ b/lld/COFF/MinGW.cpp
@@ -233,6 +233,17 @@ void lld::coff::addWrappedSymbols(SymbolTable &symtab,
         symtab.addUndefined(mangle("__wrap_" + name, symtab.machine));
     v.push_back({sym, real, wrap});
 
+    if (auto *usym = dyn_cast<Undefined>(sym)) {
+      if (auto *ureal = dyn_cast<Undefined>(real);
+          ureal && ureal->weakAlias && !usym->weakAlias) {
+        usym->weakAlias = ureal->weakAlias;
+      }
+      if (auto *uwrap = dyn_cast<Undefined>(wrap);
+          uwrap && usym->weakAlias && !uwrap->weakAlias) {
+        uwrap->weakAlias = usym->weakAlias;
+      }
+    }
+
     // These symbols may seem undefined initially, but don't bail out
     // at symtab.reportUnresolvable() due to them, but let wrapSymbols
     // below sort things out before checking finally with
@@ -269,10 +280,15 @@ void lld::coff::wrapSymbols(SymbolTable &symtab) {
       // (We can't easily distinguish whether any object file actually
       // referenced it or not, though.)
       if (imp) {
-        DefinedLocalImport *wrapimp = make<DefinedLocalImport>(
-            symtab.ctx, saver().save("__imp_" + w.wrap->getName()), d);
-        symtab.localImportChunks.push_back(wrapimp->getChunk());
-        map[imp] = wrapimp;
+        if (Symbol *wrapimp =
+                symtab.find(("__imp_" + w.wrap->getName()).str())) {
+          map[imp] = wrapimp;
+        } else {
+          DefinedLocalImport *localwrapimp = make<DefinedLocalImport>(
+              symtab.ctx, saver().save("__imp_" + w.wrap->getName()), d);
+          symtab.localImportChunks.push_back(localwrapimp->getChunk());
+          map[imp] = localwrapimp;
+        }
       }
     }
   }
diff --git a/lld/test/COFF/reloc-undefined-weak.s b/lld/test/COFF/reloc-undefined-weak.s
new file mode 100644
index 0000000000000..5f9baa7a3dfb0
--- /dev/null
+++ b/lld/test/COFF/reloc-undefined-weak.s
@@ -0,0 +1,52 @@
+// REQUIRES: x86
+
+// Check that base-relocations for unresolved weak symbols will be omitted.
+
+// RUN: rm -rf %t.dir && split-file %s %t.dir && cd %t.dir
+// RUN: llvm-mc -filetype=obj -triple=x86_64-mingw main.s -o main.o
+// RUN: llvm-mc -filetype=obj -triple=x86_64-mingw other.s -o other.o
+
+// RUN: ld.lld -m i386pep -dll -o other.dll other.o -entry= --export-all-symbols --out-implib other.dll.a
+// RUN: ld.lld -m i386pep -o main.exe main.o other.dll.a -e entry --wrap foo --verbose
+// RUN: llvm-readobj --sections --symbols --coff-imports --coff-basereloc main.exe | FileCheck %s --implicit-check-not=other.dll
+
+// CHECK:      Number: 4
+// CHECK-NEXT: Name: .data
+// CHECK-NEXT: VirtualSize:
+// CHECK-NEXT: VirtualAddress: 0x[[#%x,SECTOP:0x4000]]
+// CHECK:      Name: ref_foo
+// CHECK-NEXT: Value: [[#%d,SYMVAL:]]
+// CHECK:      BaseReloc [
+// CHECK-NOT:    Address: 0x[[#%x,SECTOP+SYMVAL]]
+
+#--- main.s
+.global entry
+entry:
+  movq ref_foo(%rip), %rax
+  call *%rax
+
+.global __wrap_foo
+__wrap_foo:
+  ret
+
+.data
+.global ref_foo
+.p2align 3
+ref_foo:
+  .quad __real_foo
+
+.globl _pei386_runtime_relocator
+_pei386_runtime_relocator:
+  movl __RUNTIME_PSEUDO_RELOC_LIST__(%rip), %eax
+  movl __RUNTIME_PSEUDO_RELOC_LIST_END__(%rip), %eax
+
+.weak __real_foo
+.addrsig
+.addrsig_sym __real_foo
+.addrsig_sym ref_foo
+
+#--- other.s
+.global foo
+
+foo:
+  ret
diff --git a/lld/test/COFF/wrap-dllimport.s b/lld/test/COFF/wrap-dllimport.s
index d7662b29fdc78..a1b4521fd75fa 100644
--- a/lld/test/COFF/wrap-dllimport.s
+++ b/lld/test/COFF/wrap-dllimport.s
@@ -1,42 +1,59 @@
 // REQUIRES: x86
 
 // Check that we can wrap a dllimported symbol, so that references to
-// __imp_<symbol> gets redirected to a defined local import instead.
+// __imp_<symbol> gets redirected to a symbol already exists or a defined local import instead
 
 // RUN: split-file %s %t.dir
 // RUN: llvm-mc -filetype=obj -triple=i686-win32-gnu %t.dir/main.s -o %t.main.obj
 // RUN: llvm-mc -filetype=obj -triple=i686-win32-gnu %t.dir/other.s -o %t.other.obj
 
-// RUN: lld-link -dll -out:%t.dll %t.other.obj -noentry -safeseh:no -export:foo -implib:%t.lib
-// RUN: lld-link -out:%t.exe %t.main.obj %t.lib -entry:entry -subsystem:console -debug:symtab -safeseh:no -wrap:foo -lldmap:%t.map
+// RUN: lld-link -dll -out:%t.dll %t.other.obj -noentry -safeseh:no -export:foo -export:bar -implib:%t.lib
+// RUN: lld-link -out:%t.exe %t.main.obj %t.lib -entry:entry -subsystem:console -debug:symtab -safeseh:no -wrap:foo -wrap:bar -lldmap:%t.map
 // RUN: llvm-objdump -s -d --print-imm-hex %t.exe | FileCheck %s
 
 // CHECK:      Contents of section .rdata:
-// CHECK-NEXT:  402000 06104000
+// CHECK-NEXT:  402000 0c104000
 
 // CHECK:      Disassembly of section .text:
 // CHECK-EMPTY:
 // CHECK:      00401000 <_entry>:
 // CHECK-NEXT:   401000: ff 25 00 20 40 00             jmpl    *0x402000
+// CHECK-NEXT:   401006: ff 25 00 00 00 00             jmpl    *0x0
 // CHECK-EMPTY:
-// CHECK-NEXT: 00401006 <___wrap_foo>:
-// CHECK-NEXT:   401006: c3                            retl
+// CHECK-NEXT: 0040100c <___wrap_foo>:
+// CHECK-NEXT:   40100c: c3                            retl
+// CHECK-EMPTY:
+// CHECK-NEXT: 0040100d <___wrap_bar>:
+// CHECK-NEXT:   40100d: c3                            retl
 
-// The jmpl instruction in _entry points at an address in 0x402000,
+// The first jmpl instruction in _entry points at an address in 0x402000,
 // which is the first 4 bytes of the .rdata section (above), which is a
 // pointer that points at ___wrap_foo.
 
+// The second jmpl instruction in _entry points the null since the referenced symbol
+// `__imp____wrap_bar` is declared as a weak reference to prevent pull a reference
+// from an external DLL.
+
 #--- main.s
 .global _entry
 _entry:
   jmpl *__imp__foo
+  jmpl *__imp__bar
 
 .global ___wrap_foo
 ___wrap_foo:
   ret
 
+.weak __imp____wrap_bar
+.global ___wrap_bar
+___wrap_bar:
+  ret
+
 #--- other.s
 .global _foo
-
 _foo:
   ret
+
+.global _bar
+_bar:
+  ret
diff --git a/lld/test/COFF/wrap-inherit-weakness.ll b/lld/test/COFF/wrap-inherit-weakness.ll
new file mode 100644
index 0000000000000..e050e8fda37f2
--- /dev/null
+++ b/lld/test/COFF/wrap-inherit-weakness.ll
@@ -0,0 +1,16 @@
+; REQUIRES: x86
+
+; Check that 'weak' attribute will be inherited to the wrapped symbols.
+
+; RUN: llc %s -mtriple x86_64-mingw -o %t.o --filetype=obj
+; RUN: ld.lld -m i386pep -shared -o %t.dll %t.o --entry= --wrap fn
+
+declare extern_weak dso_local void @__real_fn() nounwind
+declare dso_local void @fn() nounwind
+declare dso_local void @__wrap_fn() nounwind
+
+define dllexport void @caller() nounwind {
+  call void @__real_fn()
+  call void @fn()
+  ret void
+}

@llvmbot
Copy link
Member

llvmbot commented Aug 30, 2025

@llvm/pr-subscribers-platform-windows

Author: Tomohiro Kashiwada (kikairoya)

Changes

Fixes #150739


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

5 Files Affected:

  • (modified) lld/COFF/Chunks.cpp (+1-1)
  • (modified) lld/COFF/MinGW.cpp (+20-4)
  • (added) lld/test/COFF/reloc-undefined-weak.s (+52)
  • (modified) lld/test/COFF/wrap-dllimport.s (+25-8)
  • (added) lld/test/COFF/wrap-inherit-weakness.ll (+16)
diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp
index 39fc25047f3b1..cb5cba5c414a1 100644
--- a/lld/COFF/Chunks.cpp
+++ b/lld/COFF/Chunks.cpp
@@ -564,7 +564,7 @@ void SectionChunk::getBaserels(std::vector<Baserel> *res) {
     if (ty == IMAGE_REL_BASED_ABSOLUTE)
       continue;
     Symbol *target = file->getSymbol(rel.SymbolTableIndex);
-    if (!target || isa<DefinedAbsolute>(target))
+    if (!isa_and_nonnull<Defined>(target) || isa<DefinedAbsolute>(target))
       continue;
     res->emplace_back(rva + rel.VirtualAddress, ty);
   }
diff --git a/lld/COFF/MinGW.cpp b/lld/COFF/MinGW.cpp
index e7117cbea2e88..7c6f4281145ce 100644
--- a/lld/COFF/MinGW.cpp
+++ b/lld/COFF/MinGW.cpp
@@ -233,6 +233,17 @@ void lld::coff::addWrappedSymbols(SymbolTable &symtab,
         symtab.addUndefined(mangle("__wrap_" + name, symtab.machine));
     v.push_back({sym, real, wrap});
 
+    if (auto *usym = dyn_cast<Undefined>(sym)) {
+      if (auto *ureal = dyn_cast<Undefined>(real);
+          ureal && ureal->weakAlias && !usym->weakAlias) {
+        usym->weakAlias = ureal->weakAlias;
+      }
+      if (auto *uwrap = dyn_cast<Undefined>(wrap);
+          uwrap && usym->weakAlias && !uwrap->weakAlias) {
+        uwrap->weakAlias = usym->weakAlias;
+      }
+    }
+
     // These symbols may seem undefined initially, but don't bail out
     // at symtab.reportUnresolvable() due to them, but let wrapSymbols
     // below sort things out before checking finally with
@@ -269,10 +280,15 @@ void lld::coff::wrapSymbols(SymbolTable &symtab) {
       // (We can't easily distinguish whether any object file actually
       // referenced it or not, though.)
       if (imp) {
-        DefinedLocalImport *wrapimp = make<DefinedLocalImport>(
-            symtab.ctx, saver().save("__imp_" + w.wrap->getName()), d);
-        symtab.localImportChunks.push_back(wrapimp->getChunk());
-        map[imp] = wrapimp;
+        if (Symbol *wrapimp =
+                symtab.find(("__imp_" + w.wrap->getName()).str())) {
+          map[imp] = wrapimp;
+        } else {
+          DefinedLocalImport *localwrapimp = make<DefinedLocalImport>(
+              symtab.ctx, saver().save("__imp_" + w.wrap->getName()), d);
+          symtab.localImportChunks.push_back(localwrapimp->getChunk());
+          map[imp] = localwrapimp;
+        }
       }
     }
   }
diff --git a/lld/test/COFF/reloc-undefined-weak.s b/lld/test/COFF/reloc-undefined-weak.s
new file mode 100644
index 0000000000000..5f9baa7a3dfb0
--- /dev/null
+++ b/lld/test/COFF/reloc-undefined-weak.s
@@ -0,0 +1,52 @@
+// REQUIRES: x86
+
+// Check that base-relocations for unresolved weak symbols will be omitted.
+
+// RUN: rm -rf %t.dir && split-file %s %t.dir && cd %t.dir
+// RUN: llvm-mc -filetype=obj -triple=x86_64-mingw main.s -o main.o
+// RUN: llvm-mc -filetype=obj -triple=x86_64-mingw other.s -o other.o
+
+// RUN: ld.lld -m i386pep -dll -o other.dll other.o -entry= --export-all-symbols --out-implib other.dll.a
+// RUN: ld.lld -m i386pep -o main.exe main.o other.dll.a -e entry --wrap foo --verbose
+// RUN: llvm-readobj --sections --symbols --coff-imports --coff-basereloc main.exe | FileCheck %s --implicit-check-not=other.dll
+
+// CHECK:      Number: 4
+// CHECK-NEXT: Name: .data
+// CHECK-NEXT: VirtualSize:
+// CHECK-NEXT: VirtualAddress: 0x[[#%x,SECTOP:0x4000]]
+// CHECK:      Name: ref_foo
+// CHECK-NEXT: Value: [[#%d,SYMVAL:]]
+// CHECK:      BaseReloc [
+// CHECK-NOT:    Address: 0x[[#%x,SECTOP+SYMVAL]]
+
+#--- main.s
+.global entry
+entry:
+  movq ref_foo(%rip), %rax
+  call *%rax
+
+.global __wrap_foo
+__wrap_foo:
+  ret
+
+.data
+.global ref_foo
+.p2align 3
+ref_foo:
+  .quad __real_foo
+
+.globl _pei386_runtime_relocator
+_pei386_runtime_relocator:
+  movl __RUNTIME_PSEUDO_RELOC_LIST__(%rip), %eax
+  movl __RUNTIME_PSEUDO_RELOC_LIST_END__(%rip), %eax
+
+.weak __real_foo
+.addrsig
+.addrsig_sym __real_foo
+.addrsig_sym ref_foo
+
+#--- other.s
+.global foo
+
+foo:
+  ret
diff --git a/lld/test/COFF/wrap-dllimport.s b/lld/test/COFF/wrap-dllimport.s
index d7662b29fdc78..a1b4521fd75fa 100644
--- a/lld/test/COFF/wrap-dllimport.s
+++ b/lld/test/COFF/wrap-dllimport.s
@@ -1,42 +1,59 @@
 // REQUIRES: x86
 
 // Check that we can wrap a dllimported symbol, so that references to
-// __imp_<symbol> gets redirected to a defined local import instead.
+// __imp_<symbol> gets redirected to a symbol already exists or a defined local import instead
 
 // RUN: split-file %s %t.dir
 // RUN: llvm-mc -filetype=obj -triple=i686-win32-gnu %t.dir/main.s -o %t.main.obj
 // RUN: llvm-mc -filetype=obj -triple=i686-win32-gnu %t.dir/other.s -o %t.other.obj
 
-// RUN: lld-link -dll -out:%t.dll %t.other.obj -noentry -safeseh:no -export:foo -implib:%t.lib
-// RUN: lld-link -out:%t.exe %t.main.obj %t.lib -entry:entry -subsystem:console -debug:symtab -safeseh:no -wrap:foo -lldmap:%t.map
+// RUN: lld-link -dll -out:%t.dll %t.other.obj -noentry -safeseh:no -export:foo -export:bar -implib:%t.lib
+// RUN: lld-link -out:%t.exe %t.main.obj %t.lib -entry:entry -subsystem:console -debug:symtab -safeseh:no -wrap:foo -wrap:bar -lldmap:%t.map
 // RUN: llvm-objdump -s -d --print-imm-hex %t.exe | FileCheck %s
 
 // CHECK:      Contents of section .rdata:
-// CHECK-NEXT:  402000 06104000
+// CHECK-NEXT:  402000 0c104000
 
 // CHECK:      Disassembly of section .text:
 // CHECK-EMPTY:
 // CHECK:      00401000 <_entry>:
 // CHECK-NEXT:   401000: ff 25 00 20 40 00             jmpl    *0x402000
+// CHECK-NEXT:   401006: ff 25 00 00 00 00             jmpl    *0x0
 // CHECK-EMPTY:
-// CHECK-NEXT: 00401006 <___wrap_foo>:
-// CHECK-NEXT:   401006: c3                            retl
+// CHECK-NEXT: 0040100c <___wrap_foo>:
+// CHECK-NEXT:   40100c: c3                            retl
+// CHECK-EMPTY:
+// CHECK-NEXT: 0040100d <___wrap_bar>:
+// CHECK-NEXT:   40100d: c3                            retl
 
-// The jmpl instruction in _entry points at an address in 0x402000,
+// The first jmpl instruction in _entry points at an address in 0x402000,
 // which is the first 4 bytes of the .rdata section (above), which is a
 // pointer that points at ___wrap_foo.
 
+// The second jmpl instruction in _entry points the null since the referenced symbol
+// `__imp____wrap_bar` is declared as a weak reference to prevent pull a reference
+// from an external DLL.
+
 #--- main.s
 .global _entry
 _entry:
   jmpl *__imp__foo
+  jmpl *__imp__bar
 
 .global ___wrap_foo
 ___wrap_foo:
   ret
 
+.weak __imp____wrap_bar
+.global ___wrap_bar
+___wrap_bar:
+  ret
+
 #--- other.s
 .global _foo
-
 _foo:
   ret
+
+.global _bar
+_bar:
+  ret
diff --git a/lld/test/COFF/wrap-inherit-weakness.ll b/lld/test/COFF/wrap-inherit-weakness.ll
new file mode 100644
index 0000000000000..e050e8fda37f2
--- /dev/null
+++ b/lld/test/COFF/wrap-inherit-weakness.ll
@@ -0,0 +1,16 @@
+; REQUIRES: x86
+
+; Check that 'weak' attribute will be inherited to the wrapped symbols.
+
+; RUN: llc %s -mtriple x86_64-mingw -o %t.o --filetype=obj
+; RUN: ld.lld -m i386pep -shared -o %t.dll %t.o --entry= --wrap fn
+
+declare extern_weak dso_local void @__real_fn() nounwind
+declare dso_local void @fn() nounwind
+declare dso_local void @__wrap_fn() nounwind
+
+define dllexport void @caller() nounwind {
+  call void @__real_fn()
+  call void @fn()
+  ret void
+}

@jeremyd2019
Copy link
Contributor

It looks like it's still pulling in all the __wrap_ symbols from cygwin1.dll:

00002220       000022b4 00000000 00000000 000029f0 0000242c

        DLL Name: cygwin1.dll
        vma:     Ordinal  Hint  Member-Name  Bound-To
        0000242c  <none>  0010  __cxa_atexit
        00002434  <none>  002f  __main
        0000243c  <none>  005d  __wrap__ZdaPv
        00002444  <none>  005e  __wrap__ZdaPvRKSt9nothrow_t
        0000244c  <none>  005f  __wrap__ZdaPvSt11align_val_t
        00002454  <none>  0060  __wrap__ZdaPvSt11align_val_tRKSt9nothrow_t
        0000245c  <none>  0061  __wrap__ZdaPvm
        00002464  <none>  0062  __wrap__ZdaPvmSt11align_val_t
        0000246c  <none>  0063  __wrap__ZdlPv
        00002474  <none>  0064  __wrap__ZdlPvRKSt9nothrow_t
        0000247c  <none>  0065  __wrap__ZdlPvSt11align_val_t
        00002484  <none>  0066  __wrap__ZdlPvSt11align_val_tRKSt9nothrow_t
        0000248c  <none>  0067  __wrap__ZdlPvm
        00002494  <none>  0068  __wrap__ZdlPvmSt11align_val_t
        0000249c  <none>  0069  __wrap__Znam
        000024a4  <none>  006a  __wrap__ZnamRKSt9nothrow_t
        000024ac  <none>  006b  __wrap__ZnamSt11align_val_t
        000024b4  <none>  006c  __wrap__ZnamSt11align_val_tRKSt9nothrow_t
        000024bc  <none>  006d  __wrap__Znwm
        000024c4  <none>  006e  __wrap__ZnwmRKSt9nothrow_t
        000024cc  <none>  006f  __wrap__ZnwmSt11align_val_t
        000024d4  <none>  0070  __wrap__ZnwmSt11align_val_tRKSt9nothrow_t
        000024dc  <none>  0079  _dll_crt0
        000024e4  <none>  0080  _impure_ptr
        000024ec  <none>  0118  calloc
        000024f4  <none>  0192  cygwin_detach_dll
        000024fc  <none>  0194  cygwin_internal
        00002504  <none>  01b0  dll_dllcrt0
        0000250c  <none>  0262  free
        00002514  <none>  03ad  malloc
        0000251c  <none>  0421  posix_memalign
        00002524  <none>  0444  printf
        0000252c  <none>  04de  realloc

vs this with bfd

00007028       000070a0 00000000 00000000 00007428 00007178

        DLL Name: cygwin1.dll
        vma:     Ordinal  Hint  Member-Name  Bound-To
        00007178  <none>  0010  __cxa_atexit
        00007180  <none>  002f  __main
        00007188  <none>  0067  __wrap__ZdlPvm
        00007190  <none>  0068  __wrap__ZdlPvmSt11align_val_t
        00007198  <none>  006d  __wrap__Znwm
        000071a0  <none>  006f  __wrap__ZnwmSt11align_val_t
        000071a8  <none>  0079  _dll_crt0
        000071b0  <none>  0080  _impure_ptr
        000071b8  <none>  0118  calloc
        000071c0  <none>  0192  cygwin_detach_dll
        000071c8  <none>  0194  cygwin_internal
        000071d0  <none>  01b0  dll_dllcrt0
        000071d8  <none>  0262  free
        000071e0  <none>  03ad  malloc
        000071e8  <none>  0421  posix_memalign
        000071f0  <none>  0444  printf
        000071f8  <none>  04de  realloc
Test source (no defines)
#include <stdio.h>
#include <new>
#include <string>

#ifdef OVERRIDE_NEW_DELETE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

void *operator new (std::size_t sz)
{
        printf ("In new\n");
        return LocalAlloc (LMEM_FIXED, sz);
}

void operator delete (void *p)
{
        printf ("In delete\n");
        LocalFree (p);
}

void operator delete (void *p, std::size_t)
{
        printf ("In delete sz\n");
        LocalFree (p);
}
#endif

struct alignas(32) foo {};

int main (void)
{
        char *p = new char (0); // _Znwm
        printf ("%p\n", p);
        delete p; // _ZdlPvm
        foo *bar = new foo; // _ZnwmSt11align_val_t
        printf ("%p\n", bar);
        delete bar; // _ZdlPvmSt11align_val_t

        {
                std::string foo (1024, 'x');
                printf ("%p\n", foo.data ());
        }
        return 0;
}

@kikairoya
Copy link
Contributor Author

That looks a further different problem. The operator news (e.g. _ZdaPv) is loaded from libstdc++.dll.a as LazyArchiveKind, which can't be a weak symbol thus weakness of __real__ZdaPv can't derive to _ZdaPv and __wrap__ZdaPv.
(Ah, then, 'inherit weak' might not be needed?)
Anyway, I believe that pulling __wrap__ZdaPv feels odd but not a problem.

@jeremyd2019
Copy link
Contributor

jeremyd2019 commented Aug 31, 2025

Hmm. How about

LLD:

(gdb) b _cygwin_crt0_common
Breakpoint 2 at 0x7ff6f37e12a0: file /usr/src/debug/cygwin-3.7.0-0.287.g3a03874f73db/winsup/cygwin/lib/_cygwin_crt0_common.cc, line 124.
(gdb) r
Thread 1 hit Breakpoint 2, _cygwin_crt0_common (f=0x7ff6d15b1080 <main()>,
    u=u@entry=0x0)
    at /usr/src/debug/cygwin-3.7.0-0.287.g3a03874f73db/winsup/cygwin/lib/_cygwin_crt0_common.cc:124
124     {
(gdb) p __cygwin_cxx_malloc
$1 = {oper_new = 0x7ff6d15b11d8 <operator new(unsigned long)>,
  oper_new__ = 0x0, oper_delete = 0x0, oper_delete__ = 0x0, oper_new_nt = 0x0,
  oper_new___nt = 0x0, oper_delete_nt = 0x0, oper_delete___nt = 0x0,
  oper_delete_sz = 0x7ff6d15b11e8 <operator delete(void*, unsigned long)>,
  oper_delete___sz = 0x0,
  oper_new_al = 0x7ff6d15b11d0 <operator new(unsigned long, std::align_val_t)>, oper_new___al = 0x0, oper_delete_al = 0x0, oper_delete___al = 0x0,
  oper_delete_sz_al = 0x7ff6d15b11e0 <operator delete(void*, unsigned long, std::align_val_t)>, oper_delete___sz_al = 0x0, oper_new_al_nt = 0x0,
  oper_new___al_nt = 0x0, oper_delete_al_nt = 0x0, oper_delete___al_nt = 0x0}

BFD:

(gdb) b _cygwin_crt0_common
Breakpoint 1 at 0x1004012c0: file /usr/src/debug/cygwin-3.7.0-0.287.g3a03874f73db/winsup/cygwin/lib/_cygwin_crt0_common.cc, line 124.
(gdb) r
Thread 1 hit Breakpoint 1, _cygwin_crt0_common (f=0x100401080 <main()>,
    u=u@entry=0x0)
    at /usr/src/debug/cygwin-3.7.0-0.287.g3a03874f73db/winsup/cygwin/lib/_cygwin_crt0_common.cc:124
124     {
(gdb) p __cygwin_cxx_malloc
$1 = {oper_new = 0x0, oper_new__ = 0x0, oper_delete = 0x0,
  oper_delete__ = 0x0, oper_new_nt = 0x0, oper_new___nt = 0x0,
  oper_delete_nt = 0x0, oper_delete___nt = 0x0, oper_delete_sz = 0x0,
  oper_delete___sz = 0x0, oper_new_al = 0x0, oper_new___al = 0x0,
  oper_delete_al = 0x0, oper_delete___al = 0x0, oper_delete_sz_al = 0x0,
  oper_delete___sz_al = 0x0, oper_new_al_nt = 0x0, oper_new___al_nt = 0x0,
  oper_delete_al_nt = 0x0, oper_delete___al_nt = 0x0}

Meaning, Cygwin will think the exe is overriding these operators, when in fact they are just the stubs from linking cygstdc++-6.dll

(I am actually kind of surprised about BFD behavior here, but I guess it makes sense because the unadorned operators are not referenced just the wrapped ones?)

@jeremyd2019
Copy link
Contributor

But at least it's not crashing anymore, which is definitely an improvement!

Copy link
Member

@mstorsjo mstorsjo left a comment

Choose a reason for hiding this comment

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

The code looks reasonable and kinda ok, but I'm not entirely wrapping my head around all what's happening here, so I have a bunch of questions.


// Check that we can wrap a dllimported symbol, so that references to
// __imp_<symbol> gets redirected to a defined local import instead.
// __imp_<symbol> gets redirected to a symbol already exists or a defined local import instead
Copy link
Member

Choose a reason for hiding this comment

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

This comment seems to lack some word to be proper English - "to a symbol that already exists", or something like that?

// which is the first 4 bytes of the .rdata section (above), which is a
// pointer that points at ___wrap_foo.

// The second jmpl instruction in _entry points the null since the referenced symbol
Copy link
Member

Choose a reason for hiding this comment

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

Please rephrase this sentence, "points the null" also seems to lack a couple words, or has some typos.

// RUN: llvm-mc -filetype=obj -triple=x86_64-mingw main.s -o main.o
// RUN: llvm-mc -filetype=obj -triple=x86_64-mingw other.s -o other.o

// RUN: ld.lld -m i386pep -dll -o other.dll other.o -entry= --export-all-symbols --out-implib other.dll.a
Copy link
Member

Choose a reason for hiding this comment

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

We generally don't use the ld.lld interface in lld/test/COFF, but only use the direct lld-link interface - even if it understandably would make more sense to use the GNU ld-like interface for being able to crosscheck things with GNU ld.

@@ -0,0 +1,16 @@
; REQUIRES: x86

; Check that 'weak' attribute will be inherited to the wrapped symbols.
Copy link
Member

Choose a reason for hiding this comment

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

Can you elaborate a bit more about what happens here? I don't quite understand the involved mechanics here.


declare extern_weak dso_local void @__real_fn() nounwind
declare dso_local void @fn() nounwind
declare dso_local void @__wrap_fn() nounwind
Copy link
Member

Choose a reason for hiding this comment

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

Is __wrap_fn also treated as weak, as __real_fn is weak, or what's the relation here? Wouldn't we otherwise have undefined references as the reference to fn gets redirected to __wrap_fn?


define dllexport void @caller() nounwind {
call void @__real_fn()
call void @fn()
Copy link
Member

Choose a reason for hiding this comment

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

If these symbols are weak, and we expect that they may be null after linking, wouldn't it be better to do the equivalent of this:

if (__real_fn)
    __real_fn();

?

This raises the question about how the call to fn() works here; as fn() isn't declared as weak on the C/IR level, wouldn't an if (fn) fn() be considered redundant as fn can be assumed to be non-null, as it isn't declared weak?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I completely missed that problem. Thank you for catching that.
This part of the change is supposed to prevent pulling unused __wrap__Znwm family, but it doesn't work, as @jeremyd2019 said. This is because the unwrapped _Znwm family is always LazyArchiveKind.
Therefore, I'd drop this part.

Copy link
Member

@mstorsjo mstorsjo left a comment

Choose a reason for hiding this comment

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

LGTM

@kikairoya
Copy link
Contributor Author

Thanks!
I've updated the description.

@mstorsjo mstorsjo merged commit e976622 into llvm:main Sep 9, 2025
9 checks passed
jeremyd2019 pushed a commit to jeremyd2019/llvm-project that referenced this pull request Sep 9, 2025
…mbols (llvm#156214)

When a symbol is imported from a DLL, a base relocation record is
normally emitted.
However, if the import is pulled in via a wrapped symbol (using
`--wrap`) and later dropped because it is only referenced weakly, a
dangling base relocation remains in the output.
At runtime, this relocation changes the symbol value from null to a
garbage pointer.

This patch adds checks to avoid emitting relocation records for
non-`Defined` symbols, and to prevent creating an auto-import entry if
an import entry for the wrapped symbol already exists.

Fixes llvm#150739

(cherry picked from commit e976622)
@kikairoya kikairoya deleted the lld-weak-wrap branch September 11, 2025 14:01
jeremyd2019 pushed a commit to jeremyd2019/llvm-project that referenced this pull request Sep 12, 2025
…mbols (llvm#156214)

When a symbol is imported from a DLL, a base relocation record is
normally emitted.
However, if the import is pulled in via a wrapped symbol (using
`--wrap`) and later dropped because it is only referenced weakly, a
dangling base relocation remains in the output.
At runtime, this relocation changes the symbol value from null to a
garbage pointer.

This patch adds checks to avoid emitting relocation records for
non-`Defined` symbols, and to prevent creating an auto-import entry if
an import entry for the wrapped symbol already exists.

Fixes llvm#150739

(cherry picked from commit e976622)
jeremyd2019 pushed a commit to jeremyd2019/llvm-project that referenced this pull request Sep 23, 2025
…mbols (llvm#156214)

When a symbol is imported from a DLL, a base relocation record is
normally emitted.
However, if the import is pulled in via a wrapped symbol (using
`--wrap`) and later dropped because it is only referenced weakly, a
dangling base relocation remains in the output.
At runtime, this relocation changes the symbol value from null to a
garbage pointer.

This patch adds checks to avoid emitting relocation records for
non-`Defined` symbols, and to prevent creating an auto-import entry if
an import entry for the wrapped symbol already exists.

Fixes llvm#150739

(cherry picked from commit e976622)
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.

lld mingw undefined weak symbol, --wrap, import library, and relocation results in garbage pointer

4 participants