【发布时间】: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