为什么这个堆栈粉碎?

时间:2013-07-14 12:51:10

标签: c debugging stack-overflow

我得到了以下代码(log函数的一部分):

/* set to 32 on purpose */
#define MAX_LOG_MSG_SZ 32

void log(const char *fmt, ...) {
    ....

    char msg[MAX_LOG_MSG_SZ] = {0};
    int nb_bytes = 0;

    /* get current time */
    time_t now = time(NULL);

    char time_buf[32] = {0};

    /* format time as `14 Jul 20:00:08`, and exactly 16 bytes */
    strftime(time_buf, sizeof(time_buf), "%d %b %H:%M:%S", localtime(&now));

    nb_bytes = snprintf(msg, sizeof(msg), "%s", time_buf);

    va_list ap;
    va_start(ap, fmt);
    vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap);
    va_end(ap);

    ....
}

棘手的是,当传递长参数(使其长于32个字节)并将time_buf更改为小于32(大于16,例如31)的其他值时,这些代码将引发堆栈粉碎。几分钟调试后,我将vsnprintf呼叫线路改为

 vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ - nb_bytes, fmt, ap);

并且堆栈粉碎消失了,我认为问题已经解决了。

但是:在time_buf[32](或其他较大尺寸),为什么错误调用

 vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap);
不扔垃圾砸?更准确地说,为什么msg的堆栈粉碎与无关的堆栈(time_buf)空间相关?

更新:这是我的uname -a输出:

 Linux coanor 3.5.0-34-generic #55-Ubuntu SMP Thu Jun 6 20:20:19 UTC 2013 i686 i686 i686 GNU/Linux

2 个答案:

答案 0 :(得分:1)

char time_buf[32] = {0};
/* format time as `14 Jul 20:00:08`, and exactly 16 bytes */
strftime(time_buf, sizeof(time_buf), "%d %b %H:%M:%S", localtime(&now));

nb_bytes = snprintf(msg, sizeof(msg), "%s", time_buf);

所以有效的time_buf和msg包含相同的数据。 snprintf返回已成功写入msg但未计算空字符的字符数。

vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap);

您正尝试使用msg+nb_bytes提供的地址进行书写。 msg中有16个字符。但是你声称你有MAX_LOG_MSG_SZ,这是32个字符。您正在尝试写入字符串的结尾。也许fmt包含超过15个字符。

vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ - nb_bytes, fmt, ap);

这次你正确地减去已经写入msg的字符,并给16个字符写入vsnprintf。它服从并且不会超出字符数组的末尾。

答案 1 :(得分:1)

使用基于堆栈的缓冲区,您必须注意不要超出分配的缓冲区。正如您自己发现的那样,您使用vsnprintf(msg + nb_bytes, MAX_LOG_MSG_SZ, fmt, ap);引入了可能的缓冲区溢出。这是因为你告诉vsnprintf,有更多的空间可用(因为nb_bytes = snprintf(msg, sizeof(msg), "%s", time_buf);已经向缓冲区写了一些字节)。

因此,为了避免这种影响,您通过MAX_LOG_MSG_SZ - nb_bytes代替MAX_LOG_MSG_SIZE进行修正是正确的。

知道也很重要,snprintf和它的变体总是会返回本来要编写的字节数,而不管实际写入的字节数是多少。缓冲。

编辑:所以在你的情况下,你必须在它的组合期间跟踪字符串的总长度,以确保你不会超出总的消息缓冲区长度。