C语言 是什么原因导致 vsprintf 抛出分段错误?



我正在为 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)捕获,但是返回后的代码执行是疯狂的,它给了我误导性的调用跟踪。

相关内容

  • 没有找到相关文章