我得到了以下代码(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
答案 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和它的变体总是会返回本来要编写的字节数,而不管实际写入的字节数是多少。缓冲。
编辑:所以在你的情况下,你必须在它的组合期间跟踪字符串的总长度,以确保你不会超出总的消息缓冲区长度。