什么原因导致vsprintf抛出分段错误?

时间:2018-01-08 10:46:05

标签: c linux debugging segmentation-fault printf

我正在为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__)

从main函数,如下:

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");,这是我期望在此处看到的内容。
  • 运行时,段错误发生在vsprintf
  • 的第logDebugFunction
  • 在调用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命令所示(found here

    • (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“,格式= 0x401437”测试调试消息%d%s“,args = 0x7ffffffedb28)at iovsprintf.c:32`

因此,一切似乎都井井有条,我对于问题是什么以及我接下来可以采取的步骤有点失落。

3 个答案:

答案 0 :(得分:4)

我没有看到任何错误(忽略没有健全性检查)与您使用va_list&amp;的方式vsprintf,所以它可能需要超过1000个charcaters而且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, ...);

手册页描述说:

  

手册页printf(3)

     

函数snprintf()vsnprintf()最多写size个字节   (包括终止空字节('\0'))到str

如果您想获得长度,请将size设置为0并将str设置为NULL

int msglen = snprintf(NULL, 0, fmt, exp1, exp2, exp3,...);

请记住,此行为符合C99。使用较旧的编译器或较旧的C标准进行编译可能会为您提供未指定的返回值。

答案 1 :(得分:0)

  • 没有检查格式是否匹配传递的参数(请参阅__attribute__ ((format (printf);
  • 没有检查指针不为空;
  • 没有检查缓冲区是否足以容纳给定的字符串(使用缓存大小的函数,如snprintf);
  • sprintf(entry,使用未初始化的变量entry而不是导致未定义行为的合适缓冲区,尝试在entry指向的随机位置写入是最可能发生段错误的原因。

答案 2 :(得分:0)

在我的情况下,当我在编写C11时意外返回的标头中标有_Noreturn的函数中遇到了此问题(但在函数本身中没有)。

此错误没有引起编译错误,没有发出警告(带有-Wall),也没有被地址清理器(asan)或线程清理器(tsan)捕获,但是此后的代码执行回报很高,它给了我误导性的通话记录。