-Xlinker -reproducible flag breaks debug symbols for static library which object files has same name

Hi! For example i have library with structure like ModuleA/test.cpp, ModuleB/test.cpp, then i build it like:

#Building library
clang++ -c ModuleA/test.cpp -o ModuleA/test.o
clang++ -c ModuleB/test.cpp -o ModuleB/test.o
libtool -o module.a ModuleA/test.o ModuleB/test.o

#Now reproducing build steps from Xcode
#Compiling...
clang++ -c main.cpp -o main.o
#Linking...
clang++ -Xlinker -reproducible -o main main.o module.a

Now if we launch lldb main, we cant set breakpoint in file ModuleB/test.cpp, only in ModuleA/test.cpp, if link without -Xlinker -reproducible, both breakpoints working as expected.

Also dsymutil completely delete symbols for "duplicate" library if link with reproducible flag.

It's just example, in real static library i can't change behavior of building process, and can't change names of producing object files.

Xcode always set -Xlinker -reproducible flag, and i don't know how to disable it, and my debugging process partially broken.

ENV: macos: 15.4.1 Xcode: 16.2

Answered by DTS Engineer in 840870022

Thanks for that.

I reproduced the problem and then, after tweaking your setup a little, did some digging. Consider this:

(lldb) image lookup -r -n ModuleA
1 match found in /Users/quinn/Test/cppmodule-ok/main:
        Address: main[0x00000001000010e4] (main.__TEXT.__text + 3068)
        Summary: main`ModuleA::test() at test.cpp:6
(lldb) image lookup -r -n ModuleB
1 match found in /Users/quinn/Test/cppmodule-ok/main:
        Address: main[0x0000000100001114] (main.__TEXT.__text + 3116)
        Summary: main`ModuleB::test() at test.cpp:9

In this context:

  • cppmodule-ok is a directory containing a copy of your test with the -Xlinker -reproducible removed.

  • Similarly, cppmodule-ng is your test with -Xlinker -reproducible still in place.

  • I added three blank lines to the front of ModuleB/test.cpp so that we can tell the two files apart.

Contrast this to:

(lldb) image lookup -r -n ModuleA
2 matches found in /Users/quinn/Test/cppmodule-ng/main:
        Address: main[0x00000001000010e4] (main.__TEXT.__text + 3068)
        Summary: main`ModuleA::test() at test.cpp:6
        Address: main[0x00000001000010e4] (main.__TEXT.__text + 3068)
        Summary: main`ModuleA::test() at test.cpp:6
(lldb) image lookup -r -n ModuleB
1 match found in /Users/quinn/Test/cppmodule-ng/main:
        Address: main[0x0000000100001114] (main.__TEXT.__text + 3116)
        Summary: main`ModuleB::test()

Note how the debug symbol for ModuleB has somehow disappeared and the one for ModuleA has somehow been duplicated.


The next thing I wanted to check was whether your use of a static library was a factor here. To do that, I tweaked the cppmodule-ng case to not use module.a but instead link directly with $BUILD_DIR/ModuleA/test.o and $BUILD_DIR/ModuleB/test.o. That got things working, confirming that, yep, the static library is a factor.

When I compare the debugger symbols in the static library vs no-static library case I see this:

% diff main-ok.txt main-ng.txt 
249c249
< 0000000000000000 - 00 0001   OSO /Users/quinn/Test/cppmodule-ng/build/ModuleA/test.o
---
> 0000000000000000 - 00 0001   OSO /Users/quinn/Test/cppmodule-ng/build/module.a(test.o)
257c257
< 0000000000000000 - 00 0001   OSO /Users/quinn/Test/cppmodule-ng/build/ModuleB/test.o
---
> 0000000000000000 - 00 0001   OSO /Users/quinn/Test/cppmodule-ng/build/module.a(test.o)

Note how in the main-ng.txt file both OSO symbols reference the same item within the archive, that is, module.a(test.o). This seems like it’d be bad |-:

The weird part here is that this is triggered by -reproducible. I see similar results in the cppmodule-ok, so it’s not the sole factor in play. I suspect that -reproducible is causing the linker to do more work which is then getting confused by this setup.


At this point I think I’m gonna have to draw a line under this investigation. Something weird is going on here, and the linker team should look at that. I recommend that you file a bug about this, making sure to attach your test case and include a link to this thread.

Please post your bug number, just for the record.

As to a workaround, I think that’s pretty straightforward. If you give both test.o files a unique name, things work. For example, in your test I changed the file names to ModuleA-test.o and ModuleB-test.o, and that fixed the problem you’re seeing in the cppmodule-ng case.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

This is not super surprising. One common source of unreproducible builds is full paths. I’m not 100% sure what’s going in this case, but it seems likely that -reproducible is removing path information that would otherwise help the debugger identify the files in question.

I talk about how this connection is formed in Understanding Mach-O Symbols.

I tried reproducing this here in my office and hit some snags. The example you posted as text (as opposed to the screenshot) doesn’t make sense because you didn’t include debug symbols (-g). It also doesn’t include the -a flag to libtool, which is likely to be important. I then tried to follow what you did in the screenshot and… yeah… I’m not sure I’m doing this right.

Can you post a more fleshed out example? Specifically, I’d like to see the contents of the C++ files you’re compiling (ModuleA/test.cpp, ModuleB/test.cpp, and main.cpp).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi! Here's full example

Accepted Answer

Thanks for that.

I reproduced the problem and then, after tweaking your setup a little, did some digging. Consider this:

(lldb) image lookup -r -n ModuleA
1 match found in /Users/quinn/Test/cppmodule-ok/main:
        Address: main[0x00000001000010e4] (main.__TEXT.__text + 3068)
        Summary: main`ModuleA::test() at test.cpp:6
(lldb) image lookup -r -n ModuleB
1 match found in /Users/quinn/Test/cppmodule-ok/main:
        Address: main[0x0000000100001114] (main.__TEXT.__text + 3116)
        Summary: main`ModuleB::test() at test.cpp:9

In this context:

  • cppmodule-ok is a directory containing a copy of your test with the -Xlinker -reproducible removed.

  • Similarly, cppmodule-ng is your test with -Xlinker -reproducible still in place.

  • I added three blank lines to the front of ModuleB/test.cpp so that we can tell the two files apart.

Contrast this to:

(lldb) image lookup -r -n ModuleA
2 matches found in /Users/quinn/Test/cppmodule-ng/main:
        Address: main[0x00000001000010e4] (main.__TEXT.__text + 3068)
        Summary: main`ModuleA::test() at test.cpp:6
        Address: main[0x00000001000010e4] (main.__TEXT.__text + 3068)
        Summary: main`ModuleA::test() at test.cpp:6
(lldb) image lookup -r -n ModuleB
1 match found in /Users/quinn/Test/cppmodule-ng/main:
        Address: main[0x0000000100001114] (main.__TEXT.__text + 3116)
        Summary: main`ModuleB::test()

Note how the debug symbol for ModuleB has somehow disappeared and the one for ModuleA has somehow been duplicated.


The next thing I wanted to check was whether your use of a static library was a factor here. To do that, I tweaked the cppmodule-ng case to not use module.a but instead link directly with $BUILD_DIR/ModuleA/test.o and $BUILD_DIR/ModuleB/test.o. That got things working, confirming that, yep, the static library is a factor.

When I compare the debugger symbols in the static library vs no-static library case I see this:

% diff main-ok.txt main-ng.txt 
249c249
< 0000000000000000 - 00 0001   OSO /Users/quinn/Test/cppmodule-ng/build/ModuleA/test.o
---
> 0000000000000000 - 00 0001   OSO /Users/quinn/Test/cppmodule-ng/build/module.a(test.o)
257c257
< 0000000000000000 - 00 0001   OSO /Users/quinn/Test/cppmodule-ng/build/ModuleB/test.o
---
> 0000000000000000 - 00 0001   OSO /Users/quinn/Test/cppmodule-ng/build/module.a(test.o)

Note how in the main-ng.txt file both OSO symbols reference the same item within the archive, that is, module.a(test.o). This seems like it’d be bad |-:

The weird part here is that this is triggered by -reproducible. I see similar results in the cppmodule-ok, so it’s not the sole factor in play. I suspect that -reproducible is causing the linker to do more work which is then getting confused by this setup.


At this point I think I’m gonna have to draw a line under this investigation. Something weird is going on here, and the linker team should look at that. I recommend that you file a bug about this, making sure to attach your test case and include a link to this thread.

Please post your bug number, just for the record.

As to a workaround, I think that’s pretty straightforward. If you give both test.o files a unique name, things work. For example, in your test I changed the file names to ModuleA-test.o and ModuleB-test.o, and that fixed the problem you’re seeing in the cppmodule-ng case.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

-Xlinker -reproducible flag breaks debug symbols for static library which object files has same name
 
 
Q