运行 手臂上 docker 容器中的 C musl 应用程序时 time() 的问题
Issues with time() when running a C musl application in docker container on arm
当我的应用程序在 arm 设备上的 alpine docker 容器中运行时,我的应用程序无法处理像 time(2) 这样的时间操作。
我有:
我正在构建一个本机 c 应用程序,它使用来自 musl.cc (arm-linux-musleabihf-gcc) 的工具链静态链接到 musl。我正在使用最新的 alpine 容器(没有图像标签)。
它的行为方式:
- 运行 直接在 arm 设备上的二进制文件按预期工作
- 运行 在 x64 设备上的 alpine 容器中按预期工作
- 运行 在 arm 设备上的 alpine 容器中不工作
出了什么问题:
time(NULL);
returns ((time_t) -1) 和错误=1:“不允许操作”
- 日志输出中的时间戳已扭曲时间戳
- SSH 握手失败,因为远程证书的有效期在未来。
但是,如果我在容器的 ash 中执行 date
,输出是有效的。因此,似乎只有在 ARM 架构上的 alpine 容器中才会出现问题。有趣的是,我正在从 Ubuntu 切换到 Alpine,因为我们在那里有 similar problems。
有谁知道我做错了什么吗?
更新 #1:
ubuntu 上的同样问题。所以问题似乎出在任何 docker 基础图像上,但只出现在 arm 设备上。
更新#2:这是一个最小的例子
TimeTest.c
#include <stdio.h>
#include <string.h>
#include "time.h"
#include "errno.h"
int main()
{
printf("hallo\n");
time_t myTime;
time_t result = time(&myTime);
printf("result: %lld\n", result);
if ((long)result < 0)
{
printf("time() error=%d: %s\n", errno, strerror(errno));
}
else
{
struct tm* tm_info = localtime(&myTime);
printf("Current local time and date: %s\n", asctime(tm_info));
}
return 0;
}
CMakeLists.txt
cmake_minimum_required (VERSION 3.8)
project ("TimeTest")
if(BUILD_TARGET STREQUAL "ARM_MUSL")
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER /usr/bin/arm-linux-musleabi-gcc)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-stack-protector -mfloat-abi=softfp -static --static")
set(CMAKE_LINK_SEARCH_END_STATIC TRUE)
endif()
add_executable (TimeTest "TimeTest.c")
手臂设备输出
pi@raspberrypi:/tmp $ docker run --rm -it -v /tmp/TimeTest:/TimeTest alpine ash
/ # /TimeTest
hallo
result: -4696377169665647048
time() error=1: Operation not permitted
我能够完全按照问题中的描述重现这一点。在我特定的 ARM 硬件上:
$ docker --version
Docker version 19.03.6, build 369ce74a3c [released approx. 2020-02-12]
$ file demo
demo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV),
statically linked, with debug_info, not stripped
使用 strace
我们可以观察到报告中启动的容器内的这种行为:
...
clock_gettime64(CLOCK_REALTIME, 0xbe9c5e10) = -1 EPERM (Operation not permitted)
...
但是,如果我们将 --privileged
标志添加到 Docker 命令,它会起作用:
# ./demo
hallo
result: 1608983884
Current local time and date: Sat Dec 26 11:58:04 2020
此行为是由 Docker 问题引起的 :https://gitlab.alpinelinux.org/alpine/aports/-/issues/11774, which is fixed in this commit 并进入 Docker 版本 19.03.12
(?)及以上。
ionFish解决了这个难题。我会将他的 post 标记为答案。但是,我想 post 一个我认为应该适用于所有情况的可能解决方法。大家怎么看?
我为 __clock_gettime 创建了一个包装器,并且总是回退到 gettime32。
DockerFix.c
#include <time.h>
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/syscall.h>
static bool __wrap___clock_gettime_fallback = false;
int __wrap___clock_gettime(clockid_t clk, struct timespec* ts)
{
int r;
if (!__wrap___clock_gettime_fallback)
{
r = __real___clock_gettime(clk, ts);
if (!r) return r;
if (errno == EPERM || errno == EINVAL)
{
printf("WARN: clock_gettime() faulted with '%s'. Using SYS_clock_gettime32 as permanent fallback. Possible cause could be a faulty Docker version!\n", strerror(errno));
__wrap___clock_gettime_fallback = true; // skip in future
}
}
long ts32[2];
r = syscall(SYS_clock_gettime32, clk, ts32);
if (r == -ENOSYS && clk == CLOCK_REALTIME) {
r = syscall(SYS_gettimeofday_time32, ts32, 0);
ts32[1] *= 1000;
}
if (!r) {
ts->tv_sec = ts32[0];
ts->tv_nsec = ts32[1];
}
return r;
}
int __attribute__((weak, alias("__wrap___clock_gettime"))) clock_gettime(clockid_t clk, struct timespec* ts);
CMakeLists.txt
set(CMAKE_EXE_LINKER_FLAGS -Wl,--wrap=__clock_gettime")
add_executable (TimeTest "TimeTest.c" "DockerFix.c")
当我的应用程序在 arm 设备上的 alpine docker 容器中运行时,我的应用程序无法处理像 time(2) 这样的时间操作。
我有: 我正在构建一个本机 c 应用程序,它使用来自 musl.cc (arm-linux-musleabihf-gcc) 的工具链静态链接到 musl。我正在使用最新的 alpine 容器(没有图像标签)。
它的行为方式:
- 运行 直接在 arm 设备上的二进制文件按预期工作
- 运行 在 x64 设备上的 alpine 容器中按预期工作
- 运行 在 arm 设备上的 alpine 容器中不工作
出了什么问题:
time(NULL);
returns ((time_t) -1) 和错误=1:“不允许操作”- 日志输出中的时间戳已扭曲时间戳
- SSH 握手失败,因为远程证书的有效期在未来。
但是,如果我在容器的 ash 中执行 date
,输出是有效的。因此,似乎只有在 ARM 架构上的 alpine 容器中才会出现问题。有趣的是,我正在从 Ubuntu 切换到 Alpine,因为我们在那里有 similar problems。
有谁知道我做错了什么吗?
更新 #1: ubuntu 上的同样问题。所以问题似乎出在任何 docker 基础图像上,但只出现在 arm 设备上。
更新#2:这是一个最小的例子 TimeTest.c
#include <stdio.h>
#include <string.h>
#include "time.h"
#include "errno.h"
int main()
{
printf("hallo\n");
time_t myTime;
time_t result = time(&myTime);
printf("result: %lld\n", result);
if ((long)result < 0)
{
printf("time() error=%d: %s\n", errno, strerror(errno));
}
else
{
struct tm* tm_info = localtime(&myTime);
printf("Current local time and date: %s\n", asctime(tm_info));
}
return 0;
}
CMakeLists.txt
cmake_minimum_required (VERSION 3.8)
project ("TimeTest")
if(BUILD_TARGET STREQUAL "ARM_MUSL")
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER /usr/bin/arm-linux-musleabi-gcc)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-stack-protector -mfloat-abi=softfp -static --static")
set(CMAKE_LINK_SEARCH_END_STATIC TRUE)
endif()
add_executable (TimeTest "TimeTest.c")
手臂设备输出
pi@raspberrypi:/tmp $ docker run --rm -it -v /tmp/TimeTest:/TimeTest alpine ash
/ # /TimeTest
hallo
result: -4696377169665647048
time() error=1: Operation not permitted
我能够完全按照问题中的描述重现这一点。在我特定的 ARM 硬件上:
$ docker --version
Docker version 19.03.6, build 369ce74a3c [released approx. 2020-02-12]
$ file demo
demo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV),
statically linked, with debug_info, not stripped
使用 strace
我们可以观察到报告中启动的容器内的这种行为:
...
clock_gettime64(CLOCK_REALTIME, 0xbe9c5e10) = -1 EPERM (Operation not permitted)
...
但是,如果我们将 --privileged
标志添加到 Docker 命令,它会起作用:
# ./demo
hallo
result: 1608983884
Current local time and date: Sat Dec 26 11:58:04 2020
此行为是由 Docker 问题引起的 :https://gitlab.alpinelinux.org/alpine/aports/-/issues/11774, which is fixed in this commit 并进入 Docker 版本 19.03.12
(?)及以上。
ionFish解决了这个难题。我会将他的 post 标记为答案。但是,我想 post 一个我认为应该适用于所有情况的可能解决方法。大家怎么看?
我为 __clock_gettime 创建了一个包装器,并且总是回退到 gettime32。
DockerFix.c
#include <time.h>
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/syscall.h>
static bool __wrap___clock_gettime_fallback = false;
int __wrap___clock_gettime(clockid_t clk, struct timespec* ts)
{
int r;
if (!__wrap___clock_gettime_fallback)
{
r = __real___clock_gettime(clk, ts);
if (!r) return r;
if (errno == EPERM || errno == EINVAL)
{
printf("WARN: clock_gettime() faulted with '%s'. Using SYS_clock_gettime32 as permanent fallback. Possible cause could be a faulty Docker version!\n", strerror(errno));
__wrap___clock_gettime_fallback = true; // skip in future
}
}
long ts32[2];
r = syscall(SYS_clock_gettime32, clk, ts32);
if (r == -ENOSYS && clk == CLOCK_REALTIME) {
r = syscall(SYS_gettimeofday_time32, ts32, 0);
ts32[1] *= 1000;
}
if (!r) {
ts->tv_sec = ts32[0];
ts->tv_nsec = ts32[1];
}
return r;
}
int __attribute__((weak, alias("__wrap___clock_gettime"))) clock_gettime(clockid_t clk, struct timespec* ts);
CMakeLists.txt
set(CMAKE_EXE_LINKER_FLAGS -Wl,--wrap=__clock_gettime")
add_executable (TimeTest "TimeTest.c" "DockerFix.c")