【问题标题】:Issues with time() when running a C musl application in docker container on arm在 arm 上的 docker 容器中运行 C musl 应用程序时出现 time() 问题
【发布时间】:2021-04-02 02:20:21
【问题描述】:

当我的应用程序在 arm 设备上的 alpine docker 容器中运行时,它无法处理像 time(2) 这样的时间操作。

我有什么: 我正在构建一个本地 c 应用程序,它使用来自 musl.cc (arm-linux-musleabihf-gcc) 的工具链静态链接到 musl。我正在使用最新的 alpine 容器(没有图像标签)。

它的行为方式:

  • 直接在 arm 设备上运行二进制文件可以正常工作
  • 在 x64 设备上的 alpine 容器中运行按预期工作
  • 在 arm 设备上的 alpine 容器中运行工作

出了什么问题:

  • time(NULL); 返回 ((time_t) -1) and error=1: "Operation not allowed"
  • 日志输出中的时间戳具有曲柄时间戳
  • SSH 握手失败,因为远程证书的有效期在未来。

但是,如果我在容器的灰烬中执行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

【问题讨论】:

  • 暗中猜测:可能与vDSO有关。
  • 如果使用time_t 对象的地址调用time() 会发生什么?
  • @AndrewHenle:两者,返回值和指向 time() 的 time_t 具有相同的值:((time_t) -1)
  • 你能发布示例程序吗? which is statically linked 呃,正常链接也会发生同样的情况吗?你能发布你正在使用的编译命令吗? IE。一些minimal reproducible example
  • @KamilCuk:我添加了一个示例。希望例子没问题

标签: c docker arm alpine musl


【解决方案1】:

我能够完全按照问题中的描述重现这一点。在我的特定 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)
...

但是,如果我们在 Docker 命令中添加 --privileged 标志,它会起作用:

# ./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 引起的,该问题已在 this commit 中修复并转入 Docker 版本 19.03.12 (?) 及更高版本。

【讨论】:

  • 非常感谢。这真是一个烦人的问题。恐怕我需要在这里工作。您如何看待 __clock_gettime() 的包装器?
【解决方案2】:

ionFish 解决了这个难题。我会将他的帖子标记为答案。但是,我想发布一个可能的解决方法,我认为它应该适用于所有情况。大家觉得呢?

我为 __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")

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-12-28
    • 1970-01-01
    • 2023-02-11
    • 2021-11-29
    • 1970-01-01
    • 2014-04-20
    • 1970-01-01
    相关资源
    最近更新 更多