【问题标题】:What causes vsprintf to throw a segmentation fault?是什么导致 vsprintf 抛出分段错误?
【发布时间】:2018-06-17 08:04:51
【问题描述】:

我正在为 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");,这是我希望在这里看到的。
  • 运行时,段错误发生在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 命令所示 (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", format=0x401437 "Test Debug message %d %s", args=0x7ffffffedb28) at iovsprintf.c: 32`

所以一切似乎都井井有条,我有点不知道问题是什么以及我下一步可以采取什么步骤。

【问题讨论】:

标签: c linux debugging segmentation-fault printf


【解决方案1】:

在我的情况下,当我在编写 C11 时意外返回标头中标记为 _Noreturn 的函数(但不是函数本身)时遇到了这种情况。

这个错误没有导致编译错误,没有发出警告(-Wall),也没有被地址清理程序 (asan) 或线程清理程序 (tsan) 捕获,但是在返回之后的代码执行是疯狂,它给了我误导性的呼叫痕迹。

【讨论】:

    【解决方案2】:

    我认为您使用 va_listvsprintf 的方式没有任何问题(忽略没有完整性检查),因此它可能需要超过 1000 个字符,而 buffer 根本不是足够大还是您以错误的方式传递论点?您是否尝试过使用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 标准进行编译可能会给您未指定的返回值。

    【讨论】:

    • 谢谢,原来是这个问题。
    • 您能否详细说明如何使用snprintf 获取格式化输出的长度?
    • 我并不是说它不正确,只是为了完整起见。
    • @ajay:这是snprintf返回的值。
    • @rici 是的,我知道,但正确的用法需要将0 作为第二个参数传递,我认为应该将其包含在答案中。
    【解决方案3】:
    • 没有检查格式是否与传递的参数匹配(请参阅__attribute__ ((format (printf);
    • 没有检查指针不为空;
    • 没有检查缓冲区是否足够大以容纳给定的字符串(使用获取缓冲区大小的函数,例如snprintf);
    • sprintf(entry, 使用未初始化变量 entry 而不是导致未定义行为的合适缓冲区,尝试在 entry 指向的随机位置写入是段错误的最可能原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-09-15
      • 2012-04-19
      • 2017-04-17
      • 1970-01-01
      • 2021-02-21
      • 1970-01-01
      相关资源
      最近更新 更多