当在 windows 上使用 mingw 构建应用程序时,如何防止 Qt 提供的 Zlib 的 deflateend(&compr_str) 函数发生段错误?
How to prevent the Qt-provided Zlib's deflateend(&compr_str) function from segfaulting when the application is built with mingw on windows?
简介
我在我的代码中使用以下代码进行 Gzip 压缩,并在非常特定的条件下遇到代码段错误。我自己尝试解决后基本上完全迷路了。
代码是使用以下配置构建的:
- Linux x86-64 Ubuntu 18.04,Qt 5.12.7 从源代码构建,使用系统 zlib。
- Linux arm x86-64 Yocto,Qt 5.12.7 从源代码构建,使用系统 zlib。
- Windows MSVC2017 64 位,来自官方安装程序的 Qt 5.12.7,使用自建的 zlib,因为 Qt 没有为 MSVC
- Windows MinGW-x86-64,来自官方安装程序的 Qt 5.12.7,使用附带的 zlib
配置[1-3]中,压缩代码测试调用成功。
在配置 [4] 中,当从 Qt 创建者 IDE 中启动时,对压缩代码的测试调用成功。
在配置 [4] 中,当从 Power shell 命令行构建时,对压缩代码的测试调用会在调用 deflateEnd(&cmpr_stream);
时立即导致段错误,如下所示:
1. 在 power shell 中设置相同的环境变量,就像 Qt Creator ide 用作构建 environment.ACLineStatus
2. 为 MinGw-x86-64 配置调用与在 Qt Creator IDE 中配置的完全相同的命令。
即使调用 inside qt creator 测试时,我也可以在该特定行重现类似的崩溃,当我尝试使用缓冲区大小时MSVC 和 MinGw 均为 32768。 MSVC 以 16384 崩溃,而 MinGw 以 32768 崩溃,因此我的缓冲区大小代码中的 #ifdef
下面。
问题
- 下面的实际压缩代码大体上看起来没问题吗?它最初不是我写的,我也没有详细学习如何正确使用 zlib,但是,网络上的很多示例代码看起来都很相似。
- 什么可能导致 ZLIB 崩溃,他们的文档指出“库永远不会崩溃,即使在输入损坏的情况下”?
- 当我使用与 qt 创建者 ide 的 inside 相同的构建环境变量从命令行使用 mingw32 构建应用程序时发生段错误的原因可能是什么,其中结果构建不会在执行时崩溃?
我做了两个 mingw 构建(qt creator 构建调用测试表单 inside IDE,以及手动 cli 构建从命令行调用测试)来测试在只安装 QT 的干净虚拟机上的行为,在开始任何构建之前拥有绝对 ide 干净的环境。
出现段错误的代码
#define BASE2_ZLIB_WINDOWSIZE 15
#define GZIP_ZLIB_WINDOWSIZE (16 + BASE2_ZLIB_WINDOWSIZE)
#define MOD_GZIP_ZLIB_CFACTOR 9
#define MOD_GZIP_ZLIB_BSIZE 8096
#ifdef _MSC_VER
#define COMPR_BUFFER_SIZE 32768
#else
#define COMPR_BUFFER_SIZE 16384
#endif
QByteArray gzipCompress(QByteArray data, int compressionlevel)
{
char buffer[COMPR_BUFFER_SIZE];
z_stream cmpr_stream;
cmpr_stream.next_in = reinterpret_cast<unsigned char *>(data.data());
cmpr_stream.avail_in = static_cast<uInt>(data.size());
cmpr_stream.total_in = 0;
cmpr_stream.total_out = 0;
cmpr_stream.zalloc = Z_NULL;
cmpr_stream.zalloc = Z_NULL;
QByteArray compressed;
// the actual compression work.
if (deflateInit2(&cmpr_stream, compressionlevel, Z_DEFLATED, GZIP_ZLIB_WINDOWSIZE, MOD_GZIP_ZLIB_CFACTOR,
Z_DEFAULT_STRATEGY) != Z_OK)
{
return compressed;
}
// retrieve the compressed bytes blockwise
int ret;
do
{
cmpr_stream.next_out = reinterpret_cast<uint8_t *>(buffer);
cmpr_stream.avail_out = COMPR_BUFFER_SIZE;
ret = deflate(&cmpr_stream, Z_FINISH);
if (static_cast<unsigned long>(compressed.size()) < cmpr_stream.total_out)
{
// append the block to the output string
compressed.append(buffer, static_cast<int>(cmpr_stream.total_out) - compressed.size());
}
} while (ret == Z_OK);
deflateEnd(&cmpr_stream);
if (ret != Z_STREAM_END)
{
return QByteArray();
}
return compressed;
}
测试,调用代码
TEST(Compression, gzipSuccessfull)
{
QString string_data(
"Complex Test 256/n Data complete Lorem ipsum dolor sit amet, consetetur sadipscing elitr "
"Lorem ipsum dolor sit amet, consetetur sadipscing elitr");
QByteArray raw = string_data.toLatin1();
qDebug() << "Orig size: " << raw.size();
// Crashes in the next line
QByteArray compressed = gzipCompress(raw);
qDebug() << "Compressed size: " << compressed.size();
QByteArray uncompressed = gzipDecompress(compressed);
qDebug() << "Uncompressed size: " << uncompressed.size();
QString restored = QString::fromLatin1(uncompressed);
ASSERT_TRUE(string_data == restored);
}
不确定这是否会导致您的段错误,但您需要初始化 zfree
。看起来你的代码中有一个 copy/paste 错误,因为你初始化了 zalloc
两次。应该是:
cmpr_stream.zalloc = Z_NULL;
cmpr_stream.zfree = Z_NULL;
并完成:
cmpr_stream.opaque = Z_NULL;
您对追加多少的计算看起来是正确的,但这是一种迂回的方法,如果 long
是 32 位,则可能会出现可移植性问题。在那种情况下,total_out
将不代表超过 4 GB 时的总量。相反,您应该直接使用 deflate()
:
的结果
if (cmpr_stream.avail_out < COMPR_BUFFER_SIZE)
{
// append the block to the output string
compressed.append(buffer, COMPR_BUFFER_SIZE - cmpr_stream.avail_out);
}
由于 append
的参数是 int
而不是 unsigned
,您应该确保 COMPR_BUFFER_SIZE
适合 int
。
简介
我在我的代码中使用以下代码进行 Gzip 压缩,并在非常特定的条件下遇到代码段错误。我自己尝试解决后基本上完全迷路了。
代码是使用以下配置构建的:
- Linux x86-64 Ubuntu 18.04,Qt 5.12.7 从源代码构建,使用系统 zlib。
- Linux arm x86-64 Yocto,Qt 5.12.7 从源代码构建,使用系统 zlib。
- Windows MSVC2017 64 位,来自官方安装程序的 Qt 5.12.7,使用自建的 zlib,因为 Qt 没有为 MSVC
- Windows MinGW-x86-64,来自官方安装程序的 Qt 5.12.7,使用附带的 zlib
配置[1-3]中,压缩代码测试调用成功。
在配置 [4] 中,当从 Qt 创建者 IDE 中启动时,对压缩代码的测试调用成功。
在配置 [4] 中,当从 Power shell 命令行构建时,对压缩代码的测试调用会在调用 deflateEnd(&cmpr_stream);
时立即导致段错误,如下所示:
1. 在 power shell 中设置相同的环境变量,就像 Qt Creator ide 用作构建 environment.ACLineStatus
2. 为 MinGw-x86-64 配置调用与在 Qt Creator IDE 中配置的完全相同的命令。
即使调用 inside qt creator 测试时,我也可以在该特定行重现类似的崩溃,当我尝试使用缓冲区大小时MSVC 和 MinGw 均为 32768。 MSVC 以 16384 崩溃,而 MinGw 以 32768 崩溃,因此我的缓冲区大小代码中的 #ifdef
下面。
问题
- 下面的实际压缩代码大体上看起来没问题吗?它最初不是我写的,我也没有详细学习如何正确使用 zlib,但是,网络上的很多示例代码看起来都很相似。
- 什么可能导致 ZLIB 崩溃,他们的文档指出“库永远不会崩溃,即使在输入损坏的情况下”?
- 当我使用与 qt 创建者 ide 的 inside 相同的构建环境变量从命令行使用 mingw32 构建应用程序时发生段错误的原因可能是什么,其中结果构建不会在执行时崩溃?
我做了两个 mingw 构建(qt creator 构建调用测试表单 inside IDE,以及手动 cli 构建从命令行调用测试)来测试在只安装 QT 的干净虚拟机上的行为,在开始任何构建之前拥有绝对 ide 干净的环境。
出现段错误的代码
#define BASE2_ZLIB_WINDOWSIZE 15
#define GZIP_ZLIB_WINDOWSIZE (16 + BASE2_ZLIB_WINDOWSIZE)
#define MOD_GZIP_ZLIB_CFACTOR 9
#define MOD_GZIP_ZLIB_BSIZE 8096
#ifdef _MSC_VER
#define COMPR_BUFFER_SIZE 32768
#else
#define COMPR_BUFFER_SIZE 16384
#endif
QByteArray gzipCompress(QByteArray data, int compressionlevel)
{
char buffer[COMPR_BUFFER_SIZE];
z_stream cmpr_stream;
cmpr_stream.next_in = reinterpret_cast<unsigned char *>(data.data());
cmpr_stream.avail_in = static_cast<uInt>(data.size());
cmpr_stream.total_in = 0;
cmpr_stream.total_out = 0;
cmpr_stream.zalloc = Z_NULL;
cmpr_stream.zalloc = Z_NULL;
QByteArray compressed;
// the actual compression work.
if (deflateInit2(&cmpr_stream, compressionlevel, Z_DEFLATED, GZIP_ZLIB_WINDOWSIZE, MOD_GZIP_ZLIB_CFACTOR,
Z_DEFAULT_STRATEGY) != Z_OK)
{
return compressed;
}
// retrieve the compressed bytes blockwise
int ret;
do
{
cmpr_stream.next_out = reinterpret_cast<uint8_t *>(buffer);
cmpr_stream.avail_out = COMPR_BUFFER_SIZE;
ret = deflate(&cmpr_stream, Z_FINISH);
if (static_cast<unsigned long>(compressed.size()) < cmpr_stream.total_out)
{
// append the block to the output string
compressed.append(buffer, static_cast<int>(cmpr_stream.total_out) - compressed.size());
}
} while (ret == Z_OK);
deflateEnd(&cmpr_stream);
if (ret != Z_STREAM_END)
{
return QByteArray();
}
return compressed;
}
测试,调用代码
TEST(Compression, gzipSuccessfull)
{
QString string_data(
"Complex Test 256/n Data complete Lorem ipsum dolor sit amet, consetetur sadipscing elitr "
"Lorem ipsum dolor sit amet, consetetur sadipscing elitr");
QByteArray raw = string_data.toLatin1();
qDebug() << "Orig size: " << raw.size();
// Crashes in the next line
QByteArray compressed = gzipCompress(raw);
qDebug() << "Compressed size: " << compressed.size();
QByteArray uncompressed = gzipDecompress(compressed);
qDebug() << "Uncompressed size: " << uncompressed.size();
QString restored = QString::fromLatin1(uncompressed);
ASSERT_TRUE(string_data == restored);
}
不确定这是否会导致您的段错误,但您需要初始化 zfree
。看起来你的代码中有一个 copy/paste 错误,因为你初始化了 zalloc
两次。应该是:
cmpr_stream.zalloc = Z_NULL;
cmpr_stream.zfree = Z_NULL;
并完成:
cmpr_stream.opaque = Z_NULL;
您对追加多少的计算看起来是正确的,但这是一种迂回的方法,如果 long
是 32 位,则可能会出现可移植性问题。在那种情况下,total_out
将不代表超过 4 GB 时的总量。相反,您应该直接使用 deflate()
:
if (cmpr_stream.avail_out < COMPR_BUFFER_SIZE)
{
// append the block to the output string
compressed.append(buffer, COMPR_BUFFER_SIZE - cmpr_stream.avail_out);
}
由于 append
的参数是 int
而不是 unsigned
,您应该确保 COMPR_BUFFER_SIZE
适合 int
。