Skip to content
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

cmd/link: libraries are not resolved with cgo_import_dynamic #39257

Open
4a6f656c opened this issue May 26, 2020 · 7 comments
Open

cmd/link: libraries are not resolved with cgo_import_dynamic #39257

4a6f656c opened this issue May 26, 2020 · 7 comments

Comments

@4a6f656c
Copy link
Contributor

@4a6f656c 4a6f656c commented May 26, 2020

On Go -tip (bcda684), building the following two files:

$cat x.go
package main

import (
        _ "net"
        _ "unsafe"
)

//go:cgo_import_dynamic libc_getpid getpid "libc.so"

//go:linkname libc_getpid libc_getpid

func trampoline()

func main() {
        trampoline()
}

$cat x.s
TEXT ·trampoline(SB),0,$0
        CALL    libc_getpid(SB)
        RET

Results in a DT_NEEDED entry being created for libc.so, rather than libc.so being resolved to the current available library version (with major/minor included), as is done by a typical linker. This leads to various issues - under Linux (ubuntu/focal) running ldd on the binary (or attempting to execute the binary) results in:

$ ldd x
./x: error while loading shared libraries: /usr/lib/x86_64-linux-gnu/libc.so: invalid ELF header

This is due to the fact that /usr/lib/x86_64-linux-gnu/libc.so is a linker script containing:

$ cat /usr/lib/x86_64-linux-gnu/libc.so
/* GNU ld script
   Use the shared library, but some functions are only in
   the static library, so try that secondarily.  */
OUTPUT_FORMAT(elf64-x86-64)
GROUP ( /lib/x86_64-linux-gnu/libc.so.6 /usr/lib/x86_64-linux-gnu/libc_nonshared.a  AS_NEEDED ( /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 ) )

Under both Linux and OpenBSD, we end up with what are effectively duplicate DT_NEEDED entries:

$ readelf -d x | grep NEEDED
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so]
$ readelf -d x | grep NEEDED  
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.26.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.96.0]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so]

This results in warnings/failures, at least on OpenBSD, as it attempts to load the library twice, triggering initialisation that cannot be repeated.

The issue can be worked around by specifying the full library version in the cgo_import_dynamic statement, however this then means that the source has to be updated each time a library major or minor version bump occurs.

The fix is likely to either implement an equivalent of ld(1)'s library resolution or somehow extracting this information from ld(1) (which I suspect would be the better option).

@andybons
Copy link
Member

@andybons andybons commented May 26, 2020

@andybons andybons added this to the Unplanned milestone May 26, 2020
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented May 26, 2020

Why is your code using a go:cgo_import_dynamic directive? The current expectation is that those are only used by cgo.

@cherrymui
Copy link
Contributor

@cherrymui cherrymui commented May 27, 2020

If you want to shell out ld, you may as well just use the external linkmode, which should avoid this problem.

@4a6f656c
Copy link
Contributor Author

@4a6f656c 4a6f656c commented May 28, 2020

@ianlancetaylor @cherrymui - the back story is that I'm working on issue #36435, which means using go:cgo_import_dynamic to be able to call syscalls via libc.so for both the runtime and syscall packages (as is done for macOS and Solaris).

If I use //go:cgo_import_dynamic libc_getpid getpid "libc.so" then I end up with Go creating DT_NEEDED entries for libc.so, while the system compiler/linker creates objects with DT_NEEDED entries for libc.so.96.0 (or whatever the current libc version on the system is). This then leads to problems since ld.so attempts to load the same library twice.

I can workaround the issue by using //go:cgo_import_dynamic libc_getpid getpid "libc.so.96.0", but that then means that the Go code needs to be changed every time the library version is bumped and it also means that it will only work on systems that have that specific version.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented May 28, 2020

On GNU/Linux systems the SONAME of libc never changes, exactly to avoid the kinds of problems you mention. It's always libc.so.6. Is that not the case on OpenBSD?

@4a6f656c
Copy link
Contributor Author

@4a6f656c 4a6f656c commented May 29, 2020

@ianlancetaylor - no, this is not the case on OpenBSD. The libc.so is major/minor versioned - the minor is bumped on ABI additions and the major is bumped on ABI changes or removals.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented May 29, 2020

Ah, well. That is what symbol versioning is for.

I guess OpenBSD systems have lots of different .so files, one for each soname?

If we need to support this for internal linking, then I think we'll need to use a special string in the go:cgo_import_dynamic comments and have cmd/link map that special string to some appropriately chosen libc.so version. Perhaps we can find the algorithm that /usr/bin/ld uses and emulate that.

Or, we could require external linking on OpenBSD, which would not be so bad.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
4 participants
You can’t perform that action at this time.