从Ubuntu amd64到Arm7l进行交叉编译:exec用户进程导致“ exec格式错误”

时间:2019-06-17 15:34:38

标签: docker go arm cross-compiling

从amd64到arm7l的交叉编译我很头疼

我终于可以用Gitlab CI做到这一点,所以现在,我在docker映像中编译我的二进制文件,这是dockerfile:

FROM golang

WORKDIR /go/src/gitlab.com/company/edge_to_bc

COPY . .
RUN dpkg --add-architecture armhf && apt update && apt-get install -y gcc-arm-linux-gnueabihf libltdl-dev:armhf

我将其构建为 然后,我将建立名为ubuntu:cross-compil

的新容器“ cross-compil”。

现在,我可以使用以下命令编译二进制文件:

docker run -it -v ${EDGE_TO_BC_PATH}/release:/go/src/gitlab.com/company/edge_to_bc/release ubuntu:cross-compil  bash -c 'CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ CGO_ENABLED=1 GOOS=linux GOARCH=arm GOARM=7 go build -v -o ./release/edge_to_bc '

我可以看到我的可执行文件是在./release/edge_to_bc

中生成的

然后我构建我的docker映像:

docker build -t registry.gitlab.com/company/edge_to_bc:armhf .

然后我推它。

在Dockerfile中,我只是从主机复制可执行文件:

FROM alpine:3.7

RUN apk --no-cache add ca-certificates libtool

WORKDIR /sunclient/

COPY ./release/edge_to_bc ./
EXPOSE 5555

CMD [ "./edge_to_bc" ]

但是当我用以下命令在手臂板上运行它时:

docker run --rm registry.gitlab.com/company/edge_to_bc:armhf

我得到:

standard_init_linux.go:207: exec user process caused "no such file or directory"

调试时,如果要使用

获取文件列表
docker run --rm registry.gitlab.com/company/edge_to_bc:armhf

我得到:

standard_init_linux.go:207: exec user process caused "exec format error"

表明二进制文件仍然没有正确的格式...

我想念什么?我在这个主题上花了很多时间,并且没有更多的想法。

当我检查二进制文件的体系结构时,这就是我得到的:

 edge_to_bc git:(master) ✗ readelf -h ./release/edge_to_bc
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x19209
  Start of program headers:          52 (bytes into file)
  Start of section headers:          23993360 (bytes into file)
  Flags:                             0x5000400, Version5 EABI, hard-float ABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         10
  Size of section headers:           40 (bytes)
  Number of section headers:         49
  Section header string table index: 48

在目标操作系统上,这是我得到的:

[root@gw-sol1 ~]# uname -a
Linux gw-sol-1 4.4.113-UNRELEASED #1 SMP PREEMPT Thu Mar 7 16:46:40 CET 2019 armv7l armv7l armv7l GNU/Linux

编辑:

当我直接在ARM设备上构建应用程序时,它将起作用:

go build -o ./release/edge_to_bc -v -ldflags '-w -s -extldflags "-static"' ./...

ELF:

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - GNU
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x125f1
  Start of program headers:          52 (bytes into file)
  Start of section headers:          16594072 (bytes into file)
  Flags:                             0x5000400, Version5 EABI, hard-float ABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         7
  Size of section headers:           40 (bytes)
  Number of section headers:         38
  Section header string table index: 37

至少在体系结构上,它似乎与另一个非常相似。

然后构建docker映像:

docker build . -t image/peer-test:armhf

1 个答案:

答案 0 :(得分:1)

使用以下命令在arm7映像的amd64系统上运行构建时:

docker build -t registry.gitlab.com/company/edge_to_bc:armhf .

它将使用基本映像并在该映像中为amd64运行命令。因此,即使您的单个edge_to_bc二进制文件可以交叉编译,映像的其余部分也不会被交叉编译。接下来,二进制编译命令本身看起来就像是链接到库,而这些库很可能不在最终映像中。您可以运行ldd edge_to_bc来查看这些链接,如果缺少这些链接,则会收到“找不到文件”错误。


在我自己的交叉编译测试中,我在19.03.0-rc2上使用BuildKit,Buildx和一些实验性功能,因此其中的某些部分不向后兼容,但希望对您有所帮助。我正在使用一个多阶段构建,以避免在docker之外进行编译,然后再进行第二个构建。我还为构建主机指定了平台,并使用目标arch和OS变量来配置go进行交叉编译。在这种情况下,我使用了完全静态链接的二进制文件,因此没有要包含的库,并且在最终发行版映像中仅包含复制命令,因此避免了在其他平台上运行构建时出现的问题。

# syntax=docker/dockerfile:experimental
# ^ above line must be at the beginning of the Dockerfile for BuildKit

# --platform pulls an image for the build host rather than target OS/Arch
FROM --platform=$BUILDPLATFORM golang:1.12-alpine as dev
RUN apk add --no-cache git ca-certificates
RUN adduser -D appuser
WORKDIR /src
COPY . /src/
CMD CGO_ENABLED=0 go build -o app . && ./app

# my dev stage is separate from build to allow mounting source and rebuilding
# on developer machines    
FROM --platform=$BUILDPLATFORM dev as build
ARG TARGETPLATFORM
ARG TARGETOS
ARG TARGETARCH
# --mount is an experimental BuildKit feature
RUN --mount=type=cache,id=gomod,target=/go/pkg/mod/cache \
    --mount=type=cache,id=goroot,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
    go build -ldflags '-w -extldflags -static' -o app .
USER appuser
CMD [ "./app" ]

# this stage will have the target OS/Arch and cannot have any RUN commands
FROM scratch as release
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /etc/passwd /etc/group /etc/
COPY --from=build /src/app /app
USER appuser
CMD [ "/app" ]

FROM scratch as artifact
COPY --from=build /src/app /app

FROM release

要构建它,我可以使用BuildKit运行一次性构建命令:

DOCKER_BUILDKIT=1 docker build --platform=linux/amd64 -f Dockerfile -t golang-app:amd64 .
DOCKER_BUILDKIT=1 docker build --platform=linux/arm64 -f Dockerfile -t golang-app:arm64 .

但是Buildx版本更好,它创建了一个可在多个平台上运行的多体系结构映像。这确实需要您推送到注册表服务器:

docker buildx build -f Dockerfile --platform linux/amd64,linux/arm64 \
  -t ${docker_hub_id}/golang-app:multi-arch --output type=registry .

对于您的方案,您可以将引用从arm64交换到您自己的体系结构。在我运行的许多命令中,--platform选项都被列为试验性的,因此您可能需要在/etc/docker/daemon.json文件中配置以下内容才能启用:

{ "experimental": true }

我相信这需要完全重启docker守护进程(systemctl restart docker),而不仅仅是重新加载才能生效。

要从构建中提取工件(针对特定体系结构的已编译二进制文件),我使用:

docker build -f Dockerfile --target artifact -o type=local,dest=. .

这会将工件阶段(单个二进制文件)的内容输出到本地目录。


以上是Docker的构建多体系结构映像的方法列表中的选项3。

选项1是使用binfmt_misc配置qemu以构建和运行用于不同平台的映像。我尚未能够在Buildx的Linux上使用它,但是Docker在Mac上已经做到了,您可能可以找到有关LinuxKit项目中它们所做的更多详细信息。如果您需要在构建过程中运行命令并安装其他工具,而不仅仅是交叉编译单个静态链接的二进制文件,那么使用qemu可能是理想的选择。

选项2是在目标平台上运行构建,如您所见,它运行良好。借助Buildx,您可以添加多个构建节点,每个平台最多可以添加一个,从而可以从单个位置(CI服务器)运行构建。