【问题标题】:GCC produces an empty _start functionGCC 产生一个空的 _start 函数
【发布时间】:2021-02-22 23:42:13
【问题描述】:

我在 Docker 桌面社区 2.4.0.0(Docker 引擎 19.03.13)中运行 GCC Alpine 9.3.0。时不时地,GCC 会构建一个引发分段错误的可执行文件。分段错误发生在程序执行的最开始。每当发生这种情况时,我只需重新编译代码而不更改其中的任何内容或我的 Makefile,这就解决了问题。

使用objdump --disassemble --disassemble-zeroes --full-contents 检查可执行文件我注意到,每当我遇到分段错误时,_start_start_c 函数都是空的:

0000000000001068 <_start>:
    1068:   00 00                   add    %al,(%rax)
    106a:   00 00                   add    %al,(%rax)
    106c:   00 00                   add    %al,(%rax)
    106e:   00 00                   add    %al,(%rax)
    1070:   00 00                   add    %al,(%rax)
    1072:   00 00                   add    %al,(%rax)
    1074:   00 00                   add    %al,(%rax)
    1076:   00 00                   add    %al,(%rax)
    1078:   00 00                   add    %al,(%rax)
    107a:   00 00                   add    %al,(%rax)
    107c:   00 00                   add    %al,(%rax)

000000000000107e <_start_c>:
    107e:   00 00                   add    %al,(%rax)
    1080:   00 00                   add    %al,(%rax)
    1082:   00 00                   add    %al,(%rax)
    1084:   00 00                   add    %al,(%rax)
    1086:   00 00                   add    %al,(%rax)
    1088:   00 00                   add    %al,(%rax)
    108a:   00 00                   add    %al,(%rax)
    108c:   00 00                   add    %al,(%rax)
    108e:   00 00                   add    %al,(%rax)
    1090:   00 00                   add    %al,(%rax)
    1092:   00 00                   add    %al,(%rax)
    1094:   00 00                   add    %al,(%rax)
    1096:   00 00                   add    %al,(%rax)
    1098:   00 00                   add    %al,(%rax)
    109a:   00 00                   add    %al,(%rax)
    109c:   00 00                   add    %al,(%rax)
    109e:   00 00                   add    %al,(%rax)
    10a0:   00 00                   add    %al,(%rax)

在可执行文件确实工作时将其与程序集进行比较:

0000000000001068 <_start>:
    1068:   48 31 ed                xor    %rbp,%rbp
    106b:   48 89 e7                mov    %rsp,%rdi
    106e:   48 8d 35 b3 2d 00 00    lea    0x2db3(%rip),%rsi        # 3e28 <_DYNAMIC>
    1075:   48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
    1079:   e8 00 00 00 00          callq  107e <_start_c>

000000000000107e <_start_c>:
    107e:   48 8b 37                mov    (%rdi),%rsi
    1081:   48 8d 57 08             lea    0x8(%rdi),%rdx
    1085:   45 31 c9                xor    %r9d,%r9d
    1088:   4c 8d 05 d1 03 00 00    lea    0x3d1(%rip),%r8        # 1460 <_fini>
    108f:   48 8d 0d 6a ff ff ff    lea    -0x96(%rip),%rcx        # 1000 <_init>
    1096:   48 8d 3d d7 01 00 00    lea    0x1d7(%rip),%rdi        # 1274 <main>
    109d:   e9 9e ff ff ff          jmpq   1040 <__libc_start_main@plt>

问题

这可能是什么原因造成的?有什么办法可以预防吗?

更新

  • 我尝试在我的 Mac 上编译相同的代码(不使用 Docker)。我运行了一个脚本,该脚本编译并运行了 1000 次代码,并且每次都有效。如果我尝试在 Docker 中编译代码,它会在第四次或第五次运行后失败(有时甚至在第一次尝试时失败)。

  • 我还尝试使用安装了 glibc 2.27 的 Ubuntu 18.04 容器编译相同的代码,但我观察到了与在 Alpine 中相同的问题,因此这似乎不是 MUSL 或 Alpine 特有的问题。

  • 该问题似乎与 GCC 无关;我在 Ubuntu Docker 容器中使用了 clang 6.0.0-1ubuntu2 并观察到了同样的问题。

  • 为了排除任何硬件问题,我使用 AWS Ubuntu 20.04.1 虚拟机在其上运行 Docker 19.03.13 并编译相同的代码。在这里我没有发现任何问题。因此,问题似乎仅限于我运行 Docker 的 Macbook,但我无法证明这是硬件问题。

最小的可重现示例

我试图将代码简化为一个更简单的示例,但由于某种原因,我不会遇到这个问题(或者至少我没有看到它)。所以这是(有时)导致分段错误的最小示例:

ex1.c

#include <stdio.h>

void DoNothing(int x) {
    printf("I'm inside %s\n", __FUNCTION__);
    printf("%d is stored at %p\n", x, &x);
    x += 1;
    printf("x is now %d\n", x);
}

void DoSomething(int* p) {
    printf("I'm inside %s\n", __FUNCTION__);
    printf("%d is stored at %p\n", *p, p);
    *p += 1;
    printf("x is now %d\n", *p);
}

int main()
{
    int x = 101;
    int* p = &x;

    printf("%d is stored at address %p\n", x, &x);
    printf("%d is stored at address %p\n", *p, p);
    printf("%d is stored at address %p\n"
           "    which is the same as %p\n", x, p, &x);

    printf("%d is stored at %p\n", x, &x);
    DoNothing(x);
    printf("but here in %s, x is %d\n", __FUNCTION__, x);
    DoSomething(p);
    printf("now back in %s, x is %d\n", __FUNCTION__, x);

    x = 101;
    int** q = &p;

    printf("x equals %d == %d == %d\n", x, *p, **q);
    printf("x is at %p == %p == %p\n", &x, p, *q);
    printf("p equals %p == %p == %p\n", &x, p, *q);
    printf("p is at %p == %p\n", &p, q);
    printf("q equals %p == %p\n", &p, q);
    printf("q is at %p\n", &q);

    return 0;
}

生成文件

.PHONY: clean

CFLAGS=-Wall -Werror -Wextra -std=c99 -g
all:
    @mkdir -p build
    gcc $(CFLAGS) -o build/ex ex1.c

clean:
    rm -f build/*

Dockerfile

FROM alpine:3.12.0

RUN apk add \
    cmake=3.17.2-r0 \
    g++=9.3.0-r2 \
    gcc=9.3.0-r2 \
    gdb=9.2-r0 \
    libc-dev=0.7.2-r3 \
    make=4.3-r0

run.sh

docker run \
    --rm \
    -i \
    -t \
    -v $(pwd):/home \
    -w '/home' \
    ccompiler:$VERSION

为了触发错误,我使用了这个脚本,它可以将代码编译多达 1000 次:

keep_building.sh

#!/bin/bash

for i in {1..1000};
do
    echo $i
    make
    ./build/sample
    if [ $? -ne 0 ];
    then
        echo $i
        exit 1
    fi
    #sleep 5
done

【问题讨论】:

  • -save-temps 添加到 gcc 选项。检查ex1.s 文件和ex1.o。应该告诉您错误出现在哪一步。由于地址很好,这听起来像是汇编程序或链接器问题。
  • @NateEldredge,谢谢 :) 我已将可执行文件上传到 GitHub:github.com/MooreMachine/empty-_start.git
  • ex1.s 应该在这些标签上有代码。您还可以运行gcc ex1.s 以从该步骤继续构建,并查看是否始终或随机产生损坏的输出。我查看了您上传的二进制文件,它甚至在程序头中有零,肯定看起来像链接器问题。也可能是硬件故障。
  • @Jester:_start 中的代码应该来自 libc 启动 .o 文件(我的系统上的 Scrt1.o)并被链接,所以我不希望看到任何相关内容ex1.s 也不是 ex1.o。我猜要么链接器坏了,要么Scrt1.o 已损坏(在磁盘上/在内存中/由于 docker 错误)。
  • 我强烈怀疑是软件问题。坏 RAM 不会导致同样的问题重演。各种东西都会随机坏掉。我怀疑您的问题是某种并发问题,例如不小心并行构建了两次相同的可执行文件或修改了 Docker 容器内外的某些内容。

标签: c docker assembly gcc segmentation-fault


【解决方案1】:

您发布的文件已严重损坏。不仅_start,而且加载和执行ELF文件所需的整个程序头表都被零覆盖了。我强烈怀疑您的 RAM 损坏、CPU 超频或其他导致此类损坏的硬件故障;可能没有任何软件级别的解释。

【讨论】:

  • 谢谢! :) 我运行了一个诊断工具来检查我是否有任何 RAM 问题,但它没有发现任何问题。另外,我的笔记本电脑无法超频,所以我猜不可能是这样。但是,是的,这可能是硬件问题。我将尝试 Michael Petch 的建议,看看使用不同的 C 库是否可以解决问题。
  • @David:这只会掩盖问题。如果您的硬件出现故障,您在机器上所做的一切都可能以微妙的方式被破坏,直到很久以后您才会发现,您应该真正尝试找出问题的根源。
  • 我再次测试了 RAM,这次是使用 memtest86。我让它在三个多小时内完成了 3 遍,我找不到任何内存问题。我使用的是 MacBook,所以我也尝试了 Apple Diagnostics,但也没有发现任何问题。我搜索了类似的工具来查找 CPU 问题,但我找不到类似 memtest86 的东西,您有什么建议吗?
  • 我在 Docker 之外也尝试过同样的事情:我在 Mac 上编译了 1000 次代码,每次都能运行,而在 Docker 中,它在第四次或第五次尝试后失败。这可能是硬件问题,但我无法证明它是。
  • @David:Prime95是一个很好的CPU/缓存压力测试;在测试模式下,它知道正确答案是什么,并且它对沿途的任何位翻转都相当敏感。 (或者甚至是具有更大问题规模的内存总线压力测试。)有时问题仅在芯片运行很热时才会出现,而不是因为一个物理损坏的存储单元。
猜你喜欢
  • 2015-01-21
  • 2018-08-29
  • 1970-01-01
  • 2018-09-25
  • 1970-01-01
  • 2020-07-25
  • 2020-02-29
  • 1970-01-01
  • 2023-03-17
相关资源
最近更新 更多