从共享库打印时无显示

时间:2014-07-04 08:07:54

标签: c linux shared-libraries

我现在有一个问题,就是从共享库打印到屏幕。目前我打印到syslog(),但输出的顺序不正确。所以我想到使用cout看看是否有帮助,但没有显示任何内容。有没有办法从共享库中将输出发送到屏幕?

2 个答案:

答案 0 :(得分:0)

主程序(或其调用者)已经使用了stdout,因为这通常可以正常工作。

您的下一次尝试应该是写cerrstderr)。

如果失败,请打开写入/dev/tty的文件。这适用于所有交互式控制台程序(但不适用于cron作业等)

如果这不起作用,那么您唯一的选择是写入日志文件。

答案 1 :(得分:0)

创建一个标记为__attribute__((constructor))的函数,使其在二进制main()之前自动运行 - 请参阅(请参阅GCC Function Attributes了解详细信息 - 并在其中打开一个处理所需的输出。例如,如果您希望能够在共享库中打印到终端,可以使用

#define  _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

static FILE *terminal = NULL;

#define printterm(format...)            \
    do { if (terminal) {                \
             fprintf(terminal, format); \
             fflush(terminal);          \
       }                                \
    } while (0)

static FILE *reopentty(const int fd, const char *const mode)
{
    const char *tty = ttyname(fd);
    if (!tty)
        return NULL;
    return fopen(tty, mode);
}

static void init(void) __attribute__((constructor));

static void init(void)
{
    const int saved_errno = errno;

    if (!terminal && isatty(STDERR_FILENO))
        terminal = reopentty(STDERR_FILENO, "wb");

    if (!terminal && isatty(STDOUT_FILENO))
        terminal = reopentty(STDOUT_FILENO, "wb");

    if (!terminal && isatty(STDIN_FILENO))
        terminal = reopentty(STDIN_FILENO, "wb");

    errno = saved_errno;
}

使用printterm()代替printf()打印到终端。它只是一个包装宏,它验证terminal是非NULL,并在每次打印调用后调用fflush(),以确保输出被刷新。 (你可以改为使terminal无缓冲,但我更喜欢这种方式,以便在需要时可以缓冲。)

以上检查标准错误,标准输出或标准输入是否指向终端(按此顺序),如果是,则向该终端打开terminal。 (如果进程没有附加终端,terminal将保持为NULL。)

terminal变量和函数都标记为static,因此它们不会污染二进制命名空间。如果必须具有非静态变量或函数,请在名称前加上您的库名称,以便尽可能不与应用程序中的现有名称冲突:而不是{ {1}},使用terminal


添加了:

某些应用程序无条件地重用低描述符。打开新文件使用可用的最低描述符编号,因此终端文件句柄下的文件描述符可能会被应用程序关闭/重新打开/以其他方式使用。 其他一些应用程序更加小心,并且无条件地关闭所有文件描述符(或者至少是那些对终端开放的文件描述符)。

围绕这些方法有两种方式。

首先,您可以将描述符移动到尽可能高的数字:

yourlib_terminal

其次,更强大但更慢的选项是在打印时只打开原始终端的句柄:

#include <unistd.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>

static FILE *terminal = NULL;

static void init(void) __attribute__((constructor));
static void fini(void) __attribute__((destructor));

static int highfd(const int oldfd)
{
    struct rlimit  nofile;
    int            maxfd, newfd;

    if (oldfd == -1)
        return -1;

    maxfd = (int)sysconf(_SC_OPEN_MAX) - 1;

    if (!getrlimit(RLIMIT_NOFILE, &nofile))
        if (maxfd < 0L || nofile.rlim_cur > (rlim_t)maxfd)
            maxfd = nofile.rlim_cur - 1;

    while (1) {

        while (1) {

            if (maxfd < 0 || maxfd <= oldfd ||
                maxfd == STDIN_FILENO ||
                maxfd == STDOUT_FILENO ||
                maxfd == STDERR_FILENO)
                return oldfd;

            if (fcntl(maxfd, F_GETFL) == -1 && errno == EBADF)
                break;

            maxfd--;
        }

        newfd = fcntl(oldfd, F_DUPFD, (long)maxfd);
        if (newfd != -1)
            break;

        maxfd--;
    }

    close(oldfd);
    errno = 0;
    return newfd;
}

static FILE *open_tty(const int descriptor)
{
    if (isatty(descriptor)) {
        const char *const tty = ttyname(descriptor);
        if (tty) {
            const int fd = highfd(open(tty, O_WRONLY | O_NOCTTY));
            if (fd != -1) {
                return fdopen(fd, "wb");
            }
        }
    }
    return NULL;
}

void init(void)
{
    if (!terminal)
        terminal = open_tty(STDERR_FILENO);
    if (!terminal)
        terminal = open_tty(STDOUT_FILENO);
    if (!terminal)
        terminal = open_tty(STDIN_FILENO);

    if (terminal) {
        fprintf(terminal, "libprinting.so: Terminal acquired.\n");
        fflush(terminal);
    }
}

void fini(void)
{
    if (terminal) {
        fprintf(terminal, "libprinting.so: Still have terminal.\n");
        fflush(terminal);
    }
}

(您不一定需要使用低级I / O和GNU #define _GNU_SOURCE #define _POSIX_C_SOURCE 200809L #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <fcntl.h> #include <stdarg.h> #include <string.h> #include <errno.h> #include <stdio.h> static char *terminal_device = NULL; static void init(void) __attribute__((constructor)); static void fini(void) __attribute__((destructor)); static void terminal_printf(const char *const fmt, ...) { char *str = NULL; va_list args; int len, fd; if (!terminal_device) return; do { fd = open(terminal_device, O_WRONLY | O_NOCTTY); } while (fd == -1 && errno == EINTR); if (fd == -1) return; va_start(args, fmt); len = vasprintf(&str, fmt, args); va_end(args); if (len == -1) return; { const char *p = str; const char *const q = str + len; ssize_t n; while (p < q) { n = write(fd, p, (size_t)(q - p)); if (n > (ssize_t)0) p += n; else if (n != (ssize_t)-1 || errno != EINTR) break; } } free(str); close(fd); } void init(void) { if (!terminal_device && isatty(STDERR_FILENO)) { const char *const tty = ttyname(STDERR_FILENO); if (tty && *tty) terminal_device = strdup(tty); } if (!terminal_device && isatty(STDOUT_FILENO)) { const char *const tty = ttyname(STDOUT_FILENO); if (tty && *tty) terminal_device = strdup(tty); } if (!terminal_device && isatty(STDIN_FILENO)) { const char *const tty = ttyname(STDIN_FILENO); if (tty && *tty) terminal_device = strdup(tty); } terminal_printf("libprinting.so: Terminal acquired.\n"); } void fini(void) { terminal_printf("libprinting.so: Still have terminal.\n"); } ,您也可以使用vasprintf()打开描述符,正常写入,然后{ {1}}它。fdopen()确实关闭了底层描述符。我只是喜欢这种方法。)

如果实际二进制文件是以标准输入,输出和错误启动所有重定向到文件或/ dev / null,那么上面的内容将无法找到终端的真实内容。

虽然在大多数情况下可以使用fclose()fclose()来查找父进程使用的终端,甚至可以使用getppid()及其终端通过{{跟踪祖先进程1}},如果作为与当前应用程序相同的用户运行,我不会这样做。将标准流重定向到文件总是有原因的。

我建议使用上面的最新方法,但添加

/proc/PID/fd/

/proc/PID/status的开头。这意味着如果用户希望将共享库输出到特定的日志文件或终端设备,则只需设置/proc/PID/fd/环境变量。对于当前的终端,他们可以做到

if (!terminal_device) {
    const char *const dev = getenv("YOURLIB_LOGFILE");
    if (dev && *dev)
        terminal_device = strdup(dev);
}

其中init()是Coreutils命令(基本上包含在所有Linux发行版中),它输出到当前终端的路径。即使YOURLIB_LOGFILE最终被完全守护(并从终端分离),您的共享库仍然可以输出到终端。 (LD_PRELOAD=/path/libyour.so YOURLIB_LOGFILE=$(tty) command... 打开标志告诉内核不要使终端成为控制终端,因此使用上面的最后一种方法,打印到终端并不会使进程失去控制权。)

有问题吗?