Possible bug in binutils for MinGW-w64 related to linking weak symbols

Martin Storsjö martin@martin.st
Thu Mar 19 14:27:43 GMT 2026


On Thu, 19 Mar 2026, Eric Botcazou wrote:

>> We had that discussion on the mingw list already, but in my opinion
>> both foo and bar are properly defined, so not being able to link foo
>> looks like a bug to me. It works with clang/llvm.
>
> __attribute__((weak))__ is a GNU extension so the GNU toolchain is the de
> facto reference implementation for it.

FWIW, when I implemented support for these aspects of the GNU extension in 
LLVM, I tried to model it according to how it works in the GNU toolchain.

But as far as I saw when implementing it, there's no reason why it should 
need to be marked as weak on the side that uses the symbol.

> Requiring that the attribute be used consistently on all the 
> declarations seems fairly reasonable to me.

Maybe, but if the point of the use of the weak attribute here, is that 
there _may_ be a non-weak definition of the symbol, which should be used 
in that case, otherwise the weak symbol should be used - then I don't 
think it should be required.

However, there definitely are other bugs involved here as well:

If I modified the testcase as provided at 
https://github.com/johannesthoma/mingw-weaktest.git to declare the symbol 
"foo" as weak for where it is being used, then linking does indeed 
succeed, and it works correctly at runtime:

diff --git a/foo.h b/foo.h
index a482e57..b65a891 100644
--- a/foo.h
+++ b/foo.h
@@ -1,2 +1,2 @@
-int foo(void);
+int __attribute__((weak)) foo(void);
  int __attribute__((weak)) bar(void);

But if I switch the order that the objects are linked, so that the use of 
the weak symbol comes first, and the definition of the weak symbol comes 
later, then we get a crash at runtime:

diff --git a/Makefile b/Makefile
index fd2e035..c76d0e8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
  CC=i686-w64-mingw32-gcc

  foobar: foo.o foobar.o
-       $(CC) foo.o foobar.o -o $@
+       $(CC) foobar.o foo.o -o $@

  clean:
         rm -f *.o


> The difference is that bar is declared weak also in the foo.h
> header file while foo is not. The objdump of the foobar.o file
> lists a (suspicious) entry:
>
>     [ 17](sec -1)(fl 0x00)(ty    0)(scl   2) (nx 0) 0x00000000 .weak._bar._main
> 
> for bar but not for foo.

This is a "default symbol" for the use of the weak reference to "bar" 
within this object file.

In COFF, when you have a weak symbol "bar", this is made as a "COFF weak 
external", which is essentially an alias symbol, which needs to point at a 
different symbol.

When you have a weak definition of a symbol, then the symbol "bar" points 
at another symbol which is the actual definition. In this case, that 
symbol is named ".weak._bar." (which actually is a regular, non-weak 
symbol).

When you use a declaration of a weak symbol (which means that the symbol 
either may be defined, or may be undefined, when it essentially is a null 
pointer), then the symbol "bar" points at an absolute symbol with the 
address zero. The idea here is that if nothing else overrides the symbol, 
then it uses the fallback value of absolute zero (and in that case, the 
user should check if it is null before referencing it).

The COFF weak external symbol with the name "bar" needs to point at 
another symbol. And due to details in how this works in COFF, the symbol 
it points at needs to be another external symbol (it can't be a 
static/local/internal symbol).

The weird symbol naming, e.g. ".weak._bar._main", comes from the fact that 
you might want to have multiple objects all referencing the same weak 
symbol. If all of them define the same ".weak._bar", then there's multiple 
definitions of that. Therefore, both GNU as and LLVM try to disambiguate 
these; if there's a non-weak external symbol in the same object file, then 
this symbol name (which needs to be unique, otherwise there'd be multiple 
definition errors elsewhere anyway) allows making an unique identifier for 
the weak fallback. I.e. ".weak._bar._main" means "the fallback symbol for 
_bar, within the object file that otherwise defines e.g. _main".


Now for the crash when linking foobar.o before foo.o; foobar.o (which has 
a weak declaration of "foo") has its own default fallback as a null 
pointer, and foo.o has a weak declaration of "foo" as an actual function 
implementation. It seems like GNU ld just picks the first definition of 
"foo" here, which is the null pointer fallback, rather than the one 
pointing at an actual implementation.

// Martin



More information about the Binutils mailing list