我现在有一个问题,就是从共享库打印到屏幕。目前我打印到syslog()
,但输出的顺序不正确。所以我想到使用cout
看看是否有帮助,但没有显示任何内容。有没有办法从共享库中将输出发送到屏幕?
答案 0 :(得分:0)
主程序(或其调用者)已经使用了stdout
,因为这通常可以正常工作。
您的下一次尝试应该是写cerr
(stderr
)。
如果失败,请打开写入/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...
打开标志告诉内核不要使终端成为控制终端,因此使用上面的最后一种方法,打印到终端并不会使进程失去控制权。)
有问题吗?