Statically link devmapper when using CGO - go

I'm building a go 1.20 program that use podman bindings (v4), one of its dependencies require CGO.
Dynamically build & LD
Dynamically building the project works on alpine linux (golang:1.20-alpine), ld returns the following:
/lib/ld-musl-x86_64.so.1 (0x7f741c064000)
libgpgme.so.11 => /usr/lib/libgpgme.so.11 (0x7f741c01f000)
libdevmapper.so.1.02 => /lib/libdevmapper.so.1.02 (0x7f741bfd4000)
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f741c064000)
libassuan.so.0 => /usr/lib/libassuan.so.0 (0x7f741bfc1000)
libgpg-error.so.0 => /usr/lib/libgpg-error.so.0 (0x7f741bfa0000)
The command I used is CGO_ENABLED=1 go build -o myprog ./cmd/cli/main.go
Trying statically build
But when I try to statically build (for use in a scratch container) with go build -ldflags '-linkmode external -w -extldflags "-static"' -o myprog ./cmd/cli/main.go I have this error:
/app # go build -ldflags '-linkmode external -w -extldflags "-static" ' -o myprog ./cmd/cli/main.go
# command-line-arguments
/usr/local/go/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
/usr/lib/gcc/x86_64-alpine-linux-musl/12.2.1/../../../../x86_64-alpine-linux-musl/bin/ld: cannot find -ldevmapper: No such file or directory
collect2: error: ld returned 1 exit status
Dependencies installation
But It's installed along with other dependencies previously in the container:
RUN apk add --no-cache make gcc musl-dev lvm2-dev gpgme-dev btrfs-progs-dev
What I've tried
I tried building from the ubuntu-based tag for go (golang:1.20), but same it can't find devmapper.
Explicitly setting to CGO_ENABLED to 0 or 1 don't change anything when trying to build statically.
I passed -v to go build to know how it's linking, here is the result:
/app # CC=gcc go build -ldflags '-linkmode external -w -extldflags "-static" -v' -o myprog ./cmd/cli/main.go
# command-line-arguments
HEADER = -H5 -T0x401000 -R0x1000
host link: "gcc" "-m64" "-o" "/tmp/go-build2898353667/b001/exe/a.out" "-static" "-Wl,--compress-debug-sections=zlib" "/tmp/go-link-3857943538/go.o" "/tmp/go-link-3857943538/000000.o" [...] "/tmp/go-link-3857943538/000053.o" "-O2" "-g" "-lresolv" "-O2" "-g" "-O2" "-g" "-lpthread" "-O2" "-g" "-O2" "-g" "-O2" "-g" "-ldl" "-O2" "-g" "-O2" "-g" "-lgpgme" "-O2" "-g" "-O2" "-g" "-L/lib" "-ldevmapper" "-O2" "-g" "-O2" "-g" "-O2" "-g" "-no-pie" "-static"
/usr/local/go/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
/usr/lib/gcc/x86_64-alpine-linux-musl/12.2.1/../../../../x86_64-alpine-linux-musl/bin/ld: cannot find -ldevmapper: No such file or directory
collect2: error: ld returned 1 exit status
libdevmapper is in several location, including in /lib (as seen in the linker) at /lib/libdevmapper.so.1.02
Reproduce
main.go:
package main
import (
"context"
"fmt"
"os"
"github.com/containers/podman/v2/pkg/bindings"
"github.com/containers/podman/v2/pkg/bindings/images"
)
func main() {
// Get Podman socket location
sock_dir := os.Getenv("XDG_RUNTIME_DIR")
socket := "unix:" + sock_dir + "/podman/podman.sock"
// Connect to Podman socket
ctx, err := bindings.NewConnection(context.Background(), socket)
if err != nil {
panic(err)
}
// list images
imgs, err := images.List(ctx, nil, nil)
if err != nil {
panic(err)
}
for _, img := range imgs {
fmt.Printf("Names: %#v\n", img.Names)
}
}
Containerfile (or Dockerfile):
FROM --platform=linux/amd64 docker.io/library/golang:1.20-alpine AS builder
RUN apk add --no-cache make gcc musl-dev lvm2-dev gpgme-dev btrfs-progs-dev
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Dont work
RUN go build -ldflags '-linkmode external -w -extldflags "-static" ' -o myprog ./main.go
# this works, but dynamically linked
# RUN go build -o myprog ./main.go
Then run:
go init sof-75459376
go mod tidy
# fail
podman build -t sof/75459376:latest .

Related

Can't compile when using archive/tar

I am trying to use archive/tar within my Golang project. However, when I compile it, I get the following error:
/usr/local/go/pkg/tool/linux_amd64/link: /go-cache/10/1020ddd253c007d4f0bbed6c73d75297ac475bbc40d485e357efc1e7584bc24f-d(_go_.o): cannot use dynamic imports with -d flag
/usr/local/go/pkg/tool/linux_amd64/link: /go-cache/73/735aa16c44473681079e1f6b4a517de41fcac801aa803f5f84f6ebcb6436f3e6-d(_go_.o): cannot use dynamic imports with -d flag
Here is how I am compiling my project, within an golang:1.17-alpine3.14 Docker container:
go build -ldflags "-d -X main.Something=$SOMETHING -X main.Another=$ANOTHER -linkmode external -extldflags -static" -tags netgo -o prog cmd/prog/*.go
Without the import, everything compiles fine. All I need to do to trigger this is the following:
import (
archive/tar
...
)
...
func someFunc() {
...
tarWriter := tar.NewWriter(file)
defer tarWriter.Close()
}
Allowing this to be dynamically linked isn't something I can do, given requirements of the program. How can I get this to link statically?
Instead of using -ldflags to avoid your program being dynamically linked, you can just disable it through the environment:
CGO_ENABLED=0 go build -ldflags "-X main.Something=$SOMETHING -X main.Another=$ANOTHER" -o prog $PACKAGE_PATH
E.g, this minimal (panicking) program:
package main
import (
"archive/tar"
)
func main() {
tarWriter := tar.NewWriter(nil)
defer tarWriter.Close()
}
compiles perfectly with:
CGO_ENABLED=0 go build -o prog .

Cross compiling from Windows to other OSs

I read this and this about this activity, and the action mentioned as:
% env GOOS=darwin GOARCH=386 go build hello.go
// or
% env GOOS=linux GOARCH=arm GOARM=7 go build hello.go
// and so on
But in Windows there is no command named env, I got the below:
'env' is not recognized as an internal or external command,
operable program or batch file.
On windows powershell you should be able to do:
$env:GOOS = "linux"
$env:GOARCH = "arm"
$env:GOARM = "7"
go build hello.go
To check supported compilation tools: go tool dist list
Cross compiling not working with cgo, i.e. not working with any file has import "C"
At Powershell
# For ARM
$env:GOOS = "linux"
$env:GOARCH = "arm"
$env:GOARM = "7"
go build -o main main.go
# For darwin
$env:GOOS = "darwin"
$env:GOARCH = "amd64"
go build -o main.dmg main.go
# same for others
$env:GOOS = "windows"
$env:GOARCH = "amd64"
go build -o main.exe main.go
At CMD Command Prompt:
set GOOS=darwin
set GOARCH=amd64
go build -o main.dmg main.go
To do it in Linux or Mac and compiling to Win
GOOS=windows GOARCH=amd64 go build -o main.exe main.go
Cross compiling will silently rebuild most of standard library, and for this reason will be quite slow. To speed-up the process, you can install all the standard packages required for cross compiling on your system, for example to install at Linux/Mac the cross compiling requirements for windows-amd64 use:
GOOS=windows GOARCH=amd64 go install
Similar for any other OS you need to cross compile for it at Windows

standard_init_linux.go:207: exec user process caused "no such file or directory" while trying to statically link c libs

I am unable to dockerize and use a utility written in c in go.
I have run this program locally without docker and it works
I tried using gccgo like so go build -compiler gccgo -gccgoflags -static-libgo but I get the same error
The preamble that calls the C functions looks like so:
/*
#cgo amd64 x86 LDFLAGS: -L. -lsomelib -lsomeotherlib
#include <stdio.h>
#include <stdlib.h>
#include "someheader.h"
*/
My docker file looks like so:
FROM golang:1.12 AS build
WORKDIR /go/src/app
COPY . .
ENV GOOS=linux
ENV GOARCH=amd64
ENV CGO_LDFLAGS_ALLOW='-linkmode external -extldflags -static-libgcc'
COPY packageFolder $GOPATH/src/packageFolder
COPY mainPackage $GOPATH/src/mainPackage
RUN cd packageFolder
RUN go get -d -v ./...
RUN CGO_ENABLED=1 go build --ldflags '-linkmode external -extldflags -static-libgcc' -o $GOPATH/pkg/linux_amd64/packageFolder.a -x
RUN cd ../packageFolder
RUN go get -d -v ./...
RUN CGO_ENABLED=1 go build --ldflags '-linkmode external -extldflags -static-libgcc' -o $GOPATH/pkg/linux_amd64/mainPackage.a -x
RUN cd ..
RUN go get -d -v ./...
RUN go build -a -x
FROM ourPackager:latest AS packager
WORKDIR /
COPY ./resources ./resources/
RUN appman-packager create-package "package.tar.gz" ./resources
FROM scratch AS runtime
COPY --from=build /go/src/app/app /
COPY --from=packager "/package.tar.gz" ./resources/
EXPOSE 8080/tcp
ENTRYPOINT ["/app"]
I keep running into standard_init_linux.go:207: exec user process caused "no such file or directory" when I do a docker run
What am I missing?
I was able to fix it with Mark's suggestion. Using a Golang image for runtime exposed the actual problem of the shared object file not being packaged. So I copy it to /usr/lib/x86_64-linux-gnu in my runtime. I ended up using ubuntu:18.04 instead of the Golang image at runtime
FROM golang:1.12 AS build
WORKDIR /go/src/app
COPY . .
ENV GOOS=linux
ENV GOARCH=amd64
ENV CGO_ENABLED=1
COPY acrcloud $GOPATH/src/packageFolder
COPY musicrec $GOPATH/src/mainPackage
RUN cd packageFolder
RUN go get -d -v ./...
RUN go build -o $GOPATH/pkg/linux_amd64/packageFolder -x
RUN cd ../mainPackage
RUN go get -d -v ./...
RUN go build -o $GOPATH/pkg/linux_amd64/mainPackage -x
RUN cd ..
RUN go get -d -v ./...
RUN go build -a -x
FROM ourPackager:latest AS packager
WORKDIR /
COPY ./resources ./resources/
RUN appman-packager create-package "package.tar.gz" ./resources
FROM ubuntu:18.04 AS runtime
COPY --from=build /go/src/app/app /
COPY --from=build /go/src/app/myExternalTool.so /usr/lib/x86_64-linux-gnu
COPY --from=packager "/package.tar.gz" ./resources/
EXPOSE 8080/tcp
ENTRYPOINT ["/app"]

Golang c-archive with libc instead of glibc

Is it possible?
I have the following Go function I'd like to call from a C program:
// package name: test
package main
import "C"
//export Start
func Start() {
println("Hello world")
}
func main() {
}
I'm using the following command to build the archive
go build -buildmode=c-archive -o test.a main.go
Using gcc I can get this C program working:
#include <stdio.h>
#include "test.h"
int main() {
Start();
return 0;
}
I'm using the following command to build the executable:
gcc main.c sdlgotest.a -o main -lpthread
This all works fine for amd64, but I'd like to use this archive in a aarch64 targed development environment (using libtransistor)
libtransistor build system uses LLVM but it has it's own set of standard includes (libc, etc.) and it doesn't use glibc.
So when I'm trying to get libtransistor to link my archive I get the following errors:
/usr/lib/llvm-5.0/bin/ld.lld: error: undefined symbol: stderr
>>> referenced by gcc_libinit.c:29
>>> 000006.o:(x_cgo_sys_thread_create) in archive ./sdlgotest.a
/usr/lib/llvm-5.0/bin/ld.lld: error: undefined symbol: stderr
>>> referenced by gcc_libinit.c:29
>>> 000006.o:(x_cgo_sys_thread_create) in archive ./sdlgotest.a
libtransistor by the way is compiling the code with flags like this:
-nostdlib -nostdlibinc -isystem /opt/libtransistor/include/
So I guess the problem is that the linker can't resolve those glibc symbols.
Is there a way to compile the Go runtime without those glibc symbols so I can use the archive like I intend to? (without relying on GCC toolchain or glibc)
From the docs:
The default C and C++ compilers may be changed by the CC and CXX environment variables, respectively; those environment variables may include command line options.
So I'd try something like
$ CC=clang go build -buildmode=c-archive -o test.a main.go
and see what happens.
Run go env and see the list of env parameters set to go. You can change certain parameters based on what you need.
Following are some of the samples that I have used to build a shared dll from go program.
GOARCH=386 GOOS=windows CGO_ENABLED=1 GOPATH=`pwd` CC=i686-w64-mingw32-gcc go build -o go-shared-lib.dll -buildmode=c-shared go-shared-libs
GOARCH=amd64 GOOS=windows CGO_ENABLED=1 GOPATH=`pwd` CC=x86_64-w64-mingw32-gcc go build -o go-shared-lib.dll -buildmode=c-shared go-shared-libs

Go v 1.5 with -linkshared option produces linking error

Go v 1.5.
1) Compile the package worker:
go build -buildmode=shared -linkshared
2) Install this package
3) Try to compile another package, which imports worker:
go build -linkshared
go/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
gcc: error: missing argument to ā€˜-lā€™
The same command with '-x --compiler=gccgo' option produces the following:
/usr/bin/gccgo -o $WORK/godev/testgo/_obj/exe/a.out $WORK/godev/testgo/_obj/_go_.o -Wl,-( -m64 -Wl,-) -L/home/user/dev/godev/pkg/gccgo_linux_amd64_fPIC/shlibs -Wl,-rpath=/home/user/dev/godev/pkg/gccgo_linux_amd64_fPIC/shlibs -l -Wl,-E -fPIC
Here is a bug on github. It turned out that go tool generated wrong names.

Resources