[LINUX] Example of different light and dark of duplicate symbol error at link time

Introduction

background

When I was trying to make a small application by modifying the existing source, I experienced the phenomenon that ** I got a link error even though I was imitating the existing source configuration **. The content was "multiple definitions", but I was wondering why it did not occur in the existing source and only the person who managed it appeared. However, I thought I knew the approximate cause, so I will leave it as a memo.

environment

Windows10 / WSL + Ubuntu 18 + gcc7.4 + binutils 2.3 etc.

The set of sources used for verification is listed below in the github repository. https://github.com/angel-p57/qiita-sample/tree/master/linkerr

Event

Origin

Suppose you originally had an app called ʻappok.exe` that had the source dependencies shown in the Makefile (extracting only the relevant parts):

Makefile(Former)


appok.exe: appok.o specific.o libcommon.a
        gcc $(LDFLAGS) -o appok.exe appok.o specific.o -L. -lcommon

appok.o: appok.c
        gcc -c -o appok.o appok.c
specific.o: specific.c
        gcc -c -o specific.o specific.c
libcommon.a: common1.o common2.o
        ar r libcommon.a common1.o common2.o
common1.o: common1.c
        gcc -c -o common1.o common1.c
common2.o: common2.c
        gcc -c -o common2.o common2.c

appok.exe build / execute


$ make appok.exe
gcc -c -o appok.o appok.c
gcc -c -o specific.o specific.c
gcc -c -o common1.o common1.c
gcc -c -o common2.o common2.c
ar r libcommon.a common1.o common2.o
ar: creating libcommon.a
gcc -o appok.exe appok.o specific.o -L. -lcommon
$ ./appok.exe
itest1=1, itest2=2, idup=3

I added the Makefile description and source for ʻappng.exe, which imitated ʻappok.exe, and ran the build.

Makefile(Additions)


appng.exe: appng.o specific.o libcommon.a
        gcc $(LDFLAGS) -o appng.exe appng.o specific.o -L. -lcommon

appng.o: appng.c
        gcc -c -o appng.o appng.c

appng.exe build


$ make appng.exe
gcc -c -o appng.o appng.c
gcc -o appng.exe appng.o specific.o -L. -lcommon
./libcommon.a(common2.o):(.bss+0x0): multiple definition of `idup'
specific.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status
Makefile:5: recipe for target 'appng.exe' failed
make: *** [appng.exe] Error 1

However, as shown above, the link failed due to the duplicate definition of the symbol ʻidup`.

Direct cause

I will repost the error details.

make log excerpt


gcc -o appng.exe appng.o specific.o -L. -lcommon
./libcommon.a(common2.o):(.bss+0x0): multiple definition of `idup'
specific.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

The direct cause is that I have duplicated and linked ʻidup in multiple sources as in the error message. However, this ʻidup was not defined in the newly created ʻappng.c. It was included in the source specific.c of the linked object and common2.c(the object is archived inlibcommon.a`) as follows:

Source containing idup


$ grep idup *.c
common2.c:int idup = 0;
specific.c:int idup = 3;
specific.c:  printf("itest1=%d, itest2=%d, idup=%d\n", itest1, itest2, idup);

In other words, even though it was ** originally a situation where symbol duplication occurs **, it was ** not manifested **.

Situation arrangement

Now, let's use the nm command to see the status of the symbols in each intermediate file that can be built.

nm command output


$ nm specific.o libcommon.a

specific.o:
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T ftest
0000000000000000 D idup
                 U itest1
                 U itest2
                 U printf

libcommon.a:

common1.o:
0000000000000000 D itest1

common2.o:
0000000000000000 B idup
0000000000000000 D itest2

Looking only at this, the definitions of ʻitest1 and ʻitest2, which are undefined (U) in specific.o, are included in libcommon.a, respectively, common1.o and common2.o. It needs to be resolved with , and as a result, it seems inevitable that ʻidup contained in both common2.oandspecific.o` will cause duplication.

This situation can be seen with the linker flag -M ( -Wl, -M when passing via gcc). In fact, when linking ʻappng.exe`, it seems as expected.

appng.exe linker flag specification


$ LDFLAGS=-Wl,-M make appng.exe
gcc -Wl,-M -o appng.exe appng.o specific.o -L. -lcommon
Archive member included to satisfy reference by file (symbol)

./libcommon.a(common1.o)      specific.o (itest1)
./libcommon.a(common2.o)      specific.o (itest2)
…

However, in the case of ʻappok.exe`, I found that the situation was different.

appok.exe linker flag specification


$ rm -f appok.exe; LDFLAGS=-Wl,-M make appok.exe
gcc -Wl,-M -o appok.exe appok.o specific.o -L. -lcommon
Archive member included to satisfy reference by file (symbol)

./libcommon.a(common1.o)      specific.o (itest1)
…

Actually, I haven't loaded common2.o for symbol resolution of ʻitest2. In other words, it turned out that the symbol duplication of ʻidup did not occur because common2.o was not read.

Root cause

Then, read common2.o, or not. Where did this difference come from? It was in the original ʻappok.c, ʻappng.c.

Now let's look at the symbol status, including ʻappok.o, ʻappng.o, with the nm command.

See all in nm


$ nm appok.o appng.o specific.o libcommon.a

appok.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U ftest
0000000000000000 D itest2
0000000000000000 T main

appng.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U ftest
0000000000000000 T main

specific.o:
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T ftest
0000000000000000 D idup
                 U itest1
                 U itest2
                 U printf

libcommon.a:

common1.o:
0000000000000000 D itest1

common2.o:
0000000000000000 B idup
0000000000000000 D itest2

ʻAppok.o, unlike ʻappng.o, already has the ʻitest2` symbol.

This is because (in this verification source) I deliberately omitted the definition of ʻitest2 and created ʻappng.c.

shell-session:appok.c,appng.c diff


$ diff -u appok.c appng.c
--- appok.c     2020-05-04 13:06:26.593127900 +0900
+++ appng.c     2020-05-04 13:06:26.577501900 +0900
@@ -1,5 +1,4 @@
 void ftest(void);
-int itest2 = 2;

 int main(void) {
   ftest();

So, when you link specific.o, ʻitest2 is resolved and reading common2.ois skipped. In other words, you can see that ** the.o file in the .a` archive will not be read ** unless it is related to symbol resolution.

On the other hand, if you specify the .o file directly instead of archiving, it will be read regardless of whether it has been resolved, so ʻappok.exe` will also cause an error.

shell-session:.Without using a.Direct o


$ gcc -o appok.exe appok.o specific.o common1.o common2.o
common2.o:(.data+0x0): multiple definition of `itest2'
appok.o:(.data+0x0): first defined here
common2.o:(.bss+0x0): multiple definition of `idup'
specific.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

Conclusion and coping

In this verification, we found the following two points.

Personally, it's not a very refreshing story, but as far as the behavior is concerned, I think it's a story like this.

Recommended Posts

Example of different light and dark of duplicate symbol error at link time