我正在为 syslog 编写一个简单的包装器,以使我的程序的日志记录更容易一些,并允许在选择时将日志条目转储到控制台。我定义了以下日志函数
void logDebugFunction (int lineNumber, char* filename, const char* functionName, char* format, ...)
{
if (LOG_DEBUG >= argPtr->logLevel)
{
char buffer[1000];
char *entry;
va_list args;
va_start(args, format);
vsprintf(buffer, format, args);
va_end(args);
sprintf(entry, "%s:%d - %s - %s",filename, lineNumber, functionName, buffer);
syslog(LOG_MAKEPRI(0, (LOG_DEBUG)), "%s", entry);
if (argPtr->verbose)
{
// Print to stdout too
printf( "%s", entry);
printf("n");
}
}
}
通过以下宏调用:
#define logDebug(format,...) logDebugFunction(__LINE__, __FILE__, __func__, format, __VA_ARGS__)
从主函数,如下:
int main(int argc, char *argv[])
{
// Set up syslog connection
openlog("ARController", LOG_CONS|LOG_PID|LOG_NDELAY, LOG_DAEMON);
// Set up our global arguments
struct arguments arguments;
argPtr = &arguments;
// set default values
arguments.verbose = 0;
arguments.foreground = 0;
arguments.logLevel = LOG_WARNING;
// Send a test debug message
logDebug("Test Debug message %d %s", 5, "a string");
// Close our syslog connection
closelog();
}
现在,当我尝试运行时,我得到的唯一输出是Segmentation fault (core dumped)
,显然不是我想要的。
我已经使用 gdb 和--save-temps
标志进行了一些调查,以验证以下内容:
- 在
main.i
我可以看到main
中的 logDebug 调用已替换为logDebugFunction(72, "src/main.c", __func__, "Test Debug message %d %s", 5, "a string");
这就是我希望在这里看到的。 - 运行时,段错误发生在
logDebugFunction
的第一行vsprintf
- 在调用
vsprintf
函数的所有强制参数都是正确的之前:Breakpoint 2, logDebugFunction (lineNumber=72, filename=0x401450 "src/main.c", functionName=0x4014d3 <__func__.4035> "main", format=0x401437 "Test Debug message %d %s")
va_list
条目是我所期望的,如以下 gdb 命令所示(可在此处找到)(gdb) p *(int *)(((char*)args[0].reg_save_area)+args[0].gp_offset) $5 = 5
(gdb) p *(char * *)(((char*)args[0].reg_save_area)+args[0].gp_offset+8) $6 = 0x40142e "a string"
当我进入
vsprintf
调用时,参数似乎是正确的:__IO_vsprintf (string=0x7ffffffedb40 "200V
", format=0x401437 "Test Debug message %d %s", args=0x7ffffffedb28) at iovsprintf.c:32'
因此,由于一切似乎都井井有条,我对问题是什么以及下一步可以采取的步骤有点迷茫。
我认为您使用va_list
&vsprintf
的方式没有任何问题(忽略没有健全性检查),所以可能是它需要 1000 多个字符,buffer
根本不够大,或者你以错误的方式通过了 argumnts?您是否尝试过使用vprintf
进行调试?
但我在接下来的几行中看到了一个明确的问题:
char *entry;
...
sprintf(entry, "%s:%d - %s - %s",filename, lineNumber, functionName, buffer);
entry
是一个单位化的指针,指向无处可去。如果您尝试通过该指针读取/写入,则会得到未定义的行为。段错误是其结果。
使用snprintf
您可以获取表达式的长度,然后使用malloc
为其动态分配内存(之后不要忘记释放它)。或者你可以做
char entry[1024];
...
sprintf(entry, "%s:%d - %s - %s",filename, lineNumber, functionName, buffer);
假设没有条目将超过 1023 个字符。
来自评论的编辑请求,以详细说明从snprintf
获取长度
让我们从函数的签名开始
#include <stdio.h>
int snprintf(char *str, size_t size, const char *format, ...);
的手册页描述说:
手册页打印f(3)
函数
snprintf()
,vsnprintf()
最多写入size
字节 (包括终止空字节 (' '
)) 到str
。
如果只想获取长度,请将size
设置为 0,str
设置为 NULL
int msglen = snprintf(NULL, 0, fmt, exp1, exp2, exp3,...);
请记住,此行为符合 C99。使用较旧的编译器或较旧的 C 标准进行编译可能会提供未指定的返回值。
- 没有检查格式是否与传递的参数匹配(请参阅
__attribute__ ((format (printf
); - 没有检查指针不为空;
- 没有检查缓冲区是否足够大以容纳给定的字符串(使用采用缓冲区大小的函数,例如
snprintf
); sprintf(entry,
使用未初始化的变量entry
而不是导致未定义行为的合适缓冲区,请尝试在entry
指向的随机位置写入是段错误的最可能原因。
就我而言,当我在编写 C11 时意外返回一个在标头中标记为_Noreturn
的函数(但不是在函数本身中)时,我遇到了这个问题。
此错误不会导致编译错误,没有发出警告(带有-Wall
),也没有被地址清理器(asan)或线程清理器(tsan)捕获,但是返回后的代码执行是疯狂的,它给了我误导性的调用跟踪。