summaryrefslogtreecommitdiffstats
path: root/mozglue/linker/SeekableZStream.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mozglue/linker/SeekableZStream.cpp')
-rw-r--r--mozglue/linker/SeekableZStream.cpp261
1 files changed, 261 insertions, 0 deletions
diff --git a/mozglue/linker/SeekableZStream.cpp b/mozglue/linker/SeekableZStream.cpp
new file mode 100644
index 000000000..6dd0ef6d5
--- /dev/null
+++ b/mozglue/linker/SeekableZStream.cpp
@@ -0,0 +1,261 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include "SeekableZStream.h"
+#include "Logging.h"
+
+bool
+SeekableZStream::Init(const void *buf, size_t length)
+{
+ const SeekableZStreamHeader *header = SeekableZStreamHeader::validate(buf);
+ if (!header) {
+ ERROR("Not a seekable zstream");
+ return false;
+ }
+
+ buffer = reinterpret_cast<const unsigned char *>(buf);
+ totalSize = header->totalSize;
+ chunkSize = header->chunkSize;
+ lastChunkSize = header->lastChunkSize;
+ windowBits = header->windowBits;
+ dictionary.Init(buffer + sizeof(SeekableZStreamHeader), header->dictSize);
+ offsetTable.Init(buffer + sizeof(SeekableZStreamHeader) + header->dictSize,
+ header->nChunks);
+ filter = GetFilter(header->filter);
+
+ /* Sanity check */
+ if ((chunkSize == 0) ||
+ (!IsPageAlignedSize(chunkSize)) ||
+ (chunkSize > 8 * PageSize()) ||
+ (offsetTable.numElements() < 1) ||
+ (lastChunkSize == 0) ||
+ (lastChunkSize > chunkSize) ||
+ (length < totalSize)) {
+ ERROR("Malformed or broken seekable zstream");
+ return false;
+ }
+
+ return true;
+}
+
+bool
+SeekableZStream::Decompress(void *where, size_t chunk, size_t length)
+{
+ while (length) {
+ size_t len = std::min(length, static_cast<size_t>(chunkSize));
+ if (!DecompressChunk(where, chunk, len))
+ return false;
+ where = reinterpret_cast<unsigned char *>(where) + len;
+ length -= len;
+ chunk++;
+ }
+ return true;
+}
+
+bool
+SeekableZStream::DecompressChunk(void *where, size_t chunk, size_t length)
+{
+ if (chunk >= offsetTable.numElements()) {
+ ERROR("DecompressChunk: chunk #%" PRIdSize " out of range [0-%" PRIdSize ")",
+ chunk, offsetTable.numElements());
+ return false;
+ }
+
+ bool isLastChunk = (chunk == offsetTable.numElements() - 1);
+
+ size_t chunkLen = isLastChunk ? lastChunkSize : chunkSize;
+
+ if (length == 0 || length > chunkLen)
+ length = chunkLen;
+
+ DEBUG_LOG("DecompressChunk #%" PRIdSize " @%p (%" PRIdSize "/% " PRIdSize ")",
+ chunk, where, length, chunkLen);
+ zxx_stream zStream(&allocator);
+ zStream.avail_in = (isLastChunk ? totalSize : uint32_t(offsetTable[chunk + 1]))
+ - uint32_t(offsetTable[chunk]);
+ zStream.next_in = const_cast<Bytef *>(buffer + uint32_t(offsetTable[chunk]));
+ zStream.avail_out = length;
+ zStream.next_out = reinterpret_cast<Bytef *>(where);
+
+ /* Decompress chunk */
+ if (inflateInit2(&zStream, windowBits) != Z_OK) {
+ ERROR("inflateInit failed: %s", zStream.msg);
+ return false;
+ }
+ if (dictionary && inflateSetDictionary(&zStream, dictionary,
+ dictionary.numElements()) != Z_OK) {
+ ERROR("inflateSetDictionary failed: %s", zStream.msg);
+ return false;
+ }
+ if (inflate(&zStream, (length == chunkLen) ? Z_FINISH : Z_SYNC_FLUSH)
+ != (length == chunkLen) ? Z_STREAM_END : Z_OK) {
+ ERROR("inflate failed: %s", zStream.msg);
+ return false;
+ }
+ if (inflateEnd(&zStream) != Z_OK) {
+ ERROR("inflateEnd failed: %s", zStream.msg);
+ return false;
+ }
+ if (filter)
+ filter(chunk * chunkSize, UNFILTER, (unsigned char *)where, chunkLen);
+
+ return true;
+}
+
+/* Branch/Call/Jump conversion filter for Thumb, derived from xz-utils
+ * by Igor Pavlov and Lasse Collin, published in the public domain */
+static void
+BCJ_Thumb_filter(off_t offset, SeekableZStream::FilterDirection dir,
+ unsigned char *buf, size_t size)
+{
+ size_t i;
+ for (i = 0; i + 4 <= size; i += 2) {
+ if ((buf[i + 1] & 0xf8) == 0xf0 && (buf[i + 3] & 0xf8) == 0xf8) {
+ uint32_t src = (buf[i] << 11)
+ | ((buf[i + 1] & 0x07) << 19)
+ | buf[i + 2]
+ | ((buf[i + 3] & 0x07) << 8);
+ src <<= 1;
+ uint32_t dest;
+ if (dir == SeekableZStream::FILTER)
+ dest = offset + (uint32_t)(i) + 4 + src;
+ else
+ dest = src - (offset + (uint32_t)(i) + 4);
+
+ dest >>= 1;
+ buf[i] = dest >> 11;
+ buf[i + 1] = 0xf0 | ((dest >> 19) & 0x07);
+ buf[i + 2] = dest;
+ buf[i + 3] = 0xf8 | ((dest >> 8) & 0x07);
+ i += 2;
+ }
+ }
+}
+
+/* Branch/Call/Jump conversion filter for ARM, derived from xz-utils
+ * by Igor Pavlov and Lasse Collin, published in the public domain */
+static void
+BCJ_ARM_filter(off_t offset, SeekableZStream::FilterDirection dir,
+ unsigned char *buf, size_t size)
+{
+ size_t i;
+ for (i = 0; i + 4 <= size; i += 4) {
+ if (buf[i + 3] == 0xeb) {
+ uint32_t src = buf[i]
+ | (buf[i + 1] << 8)
+ | (buf[i + 2] << 16);
+ src <<= 2;
+ uint32_t dest;
+ if (dir == SeekableZStream::FILTER)
+ dest = offset + (uint32_t)(i) + 8 + src;
+ else
+ dest = src - (offset + (uint32_t)(i) + 8);
+
+ dest >>= 2;
+ buf[i] = dest;
+ buf[i + 1] = dest >> 8;
+ buf[i + 2] = dest >> 16;
+ }
+ }
+}
+
+/* Branch/Call/Jump conversion filter for x86, derived from xz-utils
+ * by Igor Pavlov and Lasse Collin, published in the public domain */
+
+#define Test86MSByte(b) ((b) == 0 || (b) == 0xff)
+
+static void
+BCJ_X86_filter(off_t offset, SeekableZStream::FilterDirection dir,
+ unsigned char *buf, size_t size)
+{
+ static const bool MASK_TO_ALLOWED_STATUS[8] =
+ { true, true, true, false, true, false, false, false };
+
+ static const uint32_t MASK_TO_BIT_NUMBER[8] =
+ { 0, 1, 2, 2, 3, 3, 3, 3 };
+
+ uint32_t prev_mask = 0;
+ uint32_t prev_pos = 0;
+
+ for (size_t i = 0; i + 5 <= size;) {
+ uint8_t b = buf[i];
+ if (b != 0xe8 && b != 0xe9) {
+ ++i;
+ continue;
+ }
+
+ const uint32_t off = offset + (uint32_t)(i) - prev_pos;
+ prev_pos = offset + (uint32_t)(i);
+
+ if (off > 5) {
+ prev_mask = 0;
+ } else {
+ for (uint32_t i = 0; i < off; ++i) {
+ prev_mask &= 0x77;
+ prev_mask <<= 1;
+ }
+ }
+
+ b = buf[i + 4];
+
+ if (Test86MSByte(b) && MASK_TO_ALLOWED_STATUS[(prev_mask >> 1) & 0x7]
+ && (prev_mask >> 1) < 0x10) {
+
+ uint32_t src = ((uint32_t)(b) << 24)
+ | ((uint32_t)(buf[i + 3]) << 16)
+ | ((uint32_t)(buf[i + 2]) << 8)
+ | (buf[i + 1]);
+
+ uint32_t dest;
+ while (true) {
+ if (dir == SeekableZStream::FILTER)
+ dest = src + (offset + (uint32_t)(i) + 5);
+ else
+ dest = src - (offset + (uint32_t)(i) + 5);
+
+ if (prev_mask == 0)
+ break;
+
+ const uint32_t i = MASK_TO_BIT_NUMBER[prev_mask >> 1];
+
+ b = (uint8_t)(dest >> (24 - i * 8));
+
+ if (!Test86MSByte(b))
+ break;
+
+ src = dest ^ ((1 << (32 - i * 8)) - 1);
+ }
+
+ buf[i + 4] = (uint8_t)(~(((dest >> 24) & 1) - 1));
+ buf[i + 3] = (uint8_t)(dest >> 16);
+ buf[i + 2] = (uint8_t)(dest >> 8);
+ buf[i + 1] = (uint8_t)(dest);
+ i += 5;
+ prev_mask = 0;
+
+ } else {
+ ++i;
+ prev_mask |= 1;
+ if (Test86MSByte(b))
+ prev_mask |= 0x10;
+ }
+ }
+}
+
+SeekableZStream::ZStreamFilter
+SeekableZStream::GetFilter(SeekableZStream::FilterId id)
+{
+ switch (id) {
+ case BCJ_THUMB:
+ return BCJ_Thumb_filter;
+ case BCJ_ARM:
+ return BCJ_ARM_filter;
+ case BCJ_X86:
+ return BCJ_X86_filter;
+ default:
+ return nullptr;
+ }
+ return nullptr;
+}