err/warn 函数如何获取程序名称?

How do the err/warn functions get the program name?

来自 err/warn 联机帮助页:

The  err()  and  warn()  family of functions display a formatted error
message on the standard error output.  In all cases, the last component
of the program name, a colon character, and a space are output.  If the
fmt argument is not NULL, the printf(3)-like formatted error message is
output.

如果我进行此调用:warn("message"); 它将输出如下内容:

a.out: message: (output of strerror here)

warn/err 函数如何找到程序名称(在本例中为 a.out),而似乎根本无法访问 argv ?这与它们是 BSD 扩展有什么关系吗?

How do the warn/err functions find the name of the program (in this case, a.out) without seemingly having any access to argv at all? Does it have anything to do with the fact that they are BSD extensions?

使用记录所有系统调用的 strace 实用程序可以轻松解决这些问题。

我写了高度复杂的程序test.c:

#include <err.h>
int main() { warn("foo"); }

gcc -o test -static test.c; strace ./test 产生(-static 以避免尝试加载大量库的噪音):

execve("./test", ["./test"], 0x7fffcbb7fd60 /* 101 vars */) = 0
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd388d6540) = -1 EINVAL (Invalid argument)
brk(NULL)                               = 0x201e000
brk(0x201edc0)                          = 0x201edc0
arch_prctl(ARCH_SET_FS, 0x201e3c0)      = 0
set_tid_address(0x201e690)              = 55889
set_robust_list(0x201e6a0, 24)          = 0
uname({sysname="Linux", nodename="workhorse", ...}) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
readlink("/proc/self/exe", "/tmp/test", 4096) = 9
getrandom("\x43\xff\x90\x4b\xa8\x82\x38\xdd", 8, GRND_NONBLOCK) = 8
brk(0x203fdc0)                          = 0x203fdc0
brk(0x2040000)                          = 0x2040000
mprotect(0x4b6000, 16384, PROT_READ)    = 0
write(2, "test: ", 6)                   = 6
write(2, "foo", 3)                      = 3
write(2, ": Success\n", 10)             = 10
exit_group(0)                           = ?
+++ exited with 0 +++

你知道了:你可以 readlink /proc/self/exe 知道你叫什么。

err/warn 函数在程序名称的基础名称前添加。根据 this SO post 的答案,有几种方法可以在不访问 argv.

的情况下获取程序名称

一种方法是在 /proc/self/exe 上调用 readlink,然后在其上调用 basename。演示这一点的简单程序:

#include <libgen.h>
#include <linux/limits.h>
#include <stdio.h>
#include <unistd.h>

char *
progname(void)
{
    char path[PATH_MAX];

    if (readlink("/proc/self/exe", path, PATH_MAX) == -1)
        return NULL;

    /* not sure if a basename-returned string should be
     * modified, maybe don't use this in production */
    return basename(path);
}

int
main(void)
{
    printf("%s: this is my fancy warn message!\n", progname());
    return 0;
}

您还可以使用非标准变量 __progname,它可能无法工作,具体取决于您的编译器,以及 program_invocation_short_name,它是 errno.h.[=21 中定义的 GNU 扩展=]

在纯标准 C 中,如果不直接或间接从 main 获取程序名,则无法获取作为 argv[0] 传递的程序名。您可以将其作为参数传递给函数,或将其保存在全局变量中。

但是系统函数也可以选择使用system-specific方法。在 open-source 操作系统上,您可以下载源代码并查看它是如何完成的。对于 Unix-like 系统,即 libc。

例如,在 FreeBSD 上:

  • warn and err functions调用系统内部函数_getprogname().
  • _getprogname()读取全局变量__progname.
  • __progname设置在handle_argv which is called from _start(). This code is not in libc, but in CSU中,是包含程序启动代码的独立库
  • _start() 是程序的入口点。它在 architecture-specific crt1*.c 中定义。它也是调用 main 的函数,并将相同的 argv 传递给 handle_argv()main()
  • _start是程序中调用的第一个C函数。它是从 assembly code 调用的,它从堆栈中读取 argv 指针。
  • 程序参数被内核复制到程序地址 space 作为 implementation of the execve system call 的一部分。

请注意,“程序名称”有多个概念,它们并不总是等同的。有关该主题的讨论,请参阅 Finding current executable's path without /proc/self/exe