/src/perfetto/buildtools/android-unwinding/libunwindstack/MemoryXz.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2021 The Android Open Source Project |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <stdint.h> |
18 | | #include <stdlib.h> |
19 | | #include <string.h> |
20 | | |
21 | | #include <algorithm> |
22 | | #include <atomic> |
23 | | #include <memory> |
24 | | #include <mutex> |
25 | | #include <string> |
26 | | #include <utility> |
27 | | |
28 | | #include <7zCrc.h> |
29 | | #include <Xz.h> |
30 | | #include <XzCrc64.h> |
31 | | |
32 | | #include <unwindstack/Log.h> |
33 | | |
34 | | #include "MemoryXz.h" |
35 | | |
36 | | namespace unwindstack { |
37 | | |
38 | | // Statistics (used only for optional debug log messages). |
39 | | static constexpr bool kLogMemoryXzUsage = false; |
40 | | std::atomic_size_t MemoryXz::total_used_ = 0; |
41 | | std::atomic_size_t MemoryXz::total_size_ = 0; |
42 | | std::atomic_size_t MemoryXz::total_open_ = 0; |
43 | | |
44 | | MemoryXz::MemoryXz(Memory* memory, uint64_t addr, uint64_t size, const std::string& name) |
45 | 36 | : compressed_memory_(memory), compressed_addr_(addr), compressed_size_(size), name_(name) { |
46 | 36 | total_open_ += 1; |
47 | 36 | } |
48 | | |
49 | 36 | bool MemoryXz::Init() { |
50 | 36 | static std::once_flag crc_initialized; |
51 | 36 | std::call_once(crc_initialized, []() { |
52 | 1 | CrcGenerateTable(); |
53 | 1 | Crc64GenerateTable(); |
54 | 1 | }); |
55 | 36 | if (compressed_size_ >= kMaxCompressedSize) { |
56 | 34 | return false; |
57 | 34 | } |
58 | 2 | if (!ReadBlocks()) { |
59 | 2 | return false; |
60 | 2 | } |
61 | | |
62 | | // All blocks (except the last one) must have the same power-of-2 size. |
63 | 0 | if (blocks_.size() > 1) { |
64 | 0 | size_t block_size_log2 = __builtin_ctz(blocks_.front().decompressed_size); |
65 | 0 | auto correct_size = [=](XzBlock& b) { return b.decompressed_size == (1 << block_size_log2); }; |
66 | 0 | if (std::all_of(blocks_.begin(), std::prev(blocks_.end()), correct_size) && |
67 | 0 | blocks_.back().decompressed_size <= (1 << block_size_log2)) { |
68 | 0 | block_size_log2_ = block_size_log2; |
69 | 0 | } else { |
70 | | // Inconsistent block-sizes. Decompress and merge everything now. |
71 | 0 | std::unique_ptr<uint8_t[]> data(new uint8_t[size_]); |
72 | 0 | size_t offset = 0; |
73 | 0 | for (XzBlock& block : blocks_) { |
74 | 0 | if (!Decompress(&block)) { |
75 | 0 | return false; |
76 | 0 | } |
77 | 0 | memcpy(data.get() + offset, block.decompressed_data.get(), block.decompressed_size); |
78 | 0 | offset += block.decompressed_size; |
79 | 0 | } |
80 | 0 | blocks_.clear(); |
81 | 0 | blocks_.push_back(XzBlock{ |
82 | 0 | .decompressed_data = std::move(data), |
83 | 0 | .decompressed_size = size_, |
84 | 0 | }); |
85 | 0 | block_size_log2_ = 31; // Because 32 bits is too big (shift right by 32 is not allowed). |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | 0 | return true; |
90 | 0 | } |
91 | | |
92 | 36 | MemoryXz::~MemoryXz() { |
93 | 36 | total_used_ -= used_; |
94 | 36 | total_size_ -= size_; |
95 | 36 | total_open_ -= 1; |
96 | 36 | } |
97 | | |
98 | 0 | size_t MemoryXz::Read(uint64_t addr, void* buffer, size_t size) { |
99 | 0 | if (addr >= size_) { |
100 | 0 | return 0; // Read past the end. |
101 | 0 | } |
102 | 0 | uint8_t* dst = reinterpret_cast<uint8_t*>(buffer); // Position in the output buffer. |
103 | 0 | for (size_t i = addr >> block_size_log2_; i < blocks_.size(); i++) { |
104 | 0 | XzBlock* block = &blocks_[i]; |
105 | 0 | if (block->decompressed_data == nullptr) { |
106 | 0 | if (!Decompress(block)) { |
107 | 0 | break; |
108 | 0 | } |
109 | 0 | } |
110 | 0 | size_t offset = (addr - (i << block_size_log2_)); // Start inside the block. |
111 | 0 | size_t copy_bytes = std::min<size_t>(size, block->decompressed_size - offset); |
112 | 0 | memcpy(dst, block->decompressed_data.get() + offset, copy_bytes); |
113 | 0 | dst += copy_bytes; |
114 | 0 | addr += copy_bytes; |
115 | 0 | size -= copy_bytes; |
116 | 0 | if (size == 0) { |
117 | 0 | break; |
118 | 0 | } |
119 | 0 | } |
120 | 0 | return dst - reinterpret_cast<uint8_t*>(buffer); |
121 | 0 | } |
122 | | |
123 | 2 | bool MemoryXz::ReadBlocks() { |
124 | 2 | static ISzAlloc alloc; |
125 | 2 | alloc.Alloc = [](ISzAllocPtr, size_t size) { return malloc(size); }; |
126 | 2 | alloc.Free = [](ISzAllocPtr, void* ptr) { return free(ptr); }; |
127 | | |
128 | | // Read the compressed data, so we can quickly scan through the headers. |
129 | 2 | std::unique_ptr<uint8_t[]> compressed_data(new (std::nothrow) uint8_t[compressed_size_]); |
130 | 2 | if (compressed_data.get() == nullptr) { |
131 | 0 | return false; |
132 | 0 | } |
133 | 2 | if (!compressed_memory_->ReadFully(compressed_addr_, compressed_data.get(), compressed_size_)) { |
134 | 2 | return false; |
135 | 2 | } |
136 | | |
137 | | // Implement the required interface for communication |
138 | | // (written in C so we can not use virtual methods or member functions). |
139 | 0 | struct XzLookInStream : public ILookInStream, public ICompressProgress { |
140 | 0 | static SRes LookImpl(const ILookInStream* p, const void** buf, size_t* size) { |
141 | 0 | auto* ctx = reinterpret_cast<const XzLookInStream*>(p); |
142 | 0 | *buf = ctx->data + ctx->offset; |
143 | 0 | *size = std::min(*size, ctx->size - ctx->offset); |
144 | 0 | return SZ_OK; |
145 | 0 | } |
146 | 0 | static SRes SkipImpl(const ILookInStream* p, size_t len) { |
147 | 0 | auto* ctx = reinterpret_cast<XzLookInStream*>(const_cast<ILookInStream*>(p)); |
148 | 0 | ctx->offset += len; |
149 | 0 | return SZ_OK; |
150 | 0 | } |
151 | 0 | static SRes ReadImpl(const ILookInStream* p, void* buf, size_t* size) { |
152 | 0 | auto* ctx = reinterpret_cast<const XzLookInStream*>(p); |
153 | 0 | *size = std::min(*size, ctx->size - ctx->offset); |
154 | 0 | memcpy(buf, ctx->data + ctx->offset, *size); |
155 | 0 | return SZ_OK; |
156 | 0 | } |
157 | 0 | static SRes SeekImpl(const ILookInStream* p, Int64* pos, ESzSeek origin) { |
158 | 0 | auto* ctx = reinterpret_cast<XzLookInStream*>(const_cast<ILookInStream*>(p)); |
159 | 0 | switch (origin) { |
160 | 0 | case SZ_SEEK_SET: |
161 | 0 | ctx->offset = *pos; |
162 | 0 | break; |
163 | 0 | case SZ_SEEK_CUR: |
164 | 0 | ctx->offset += *pos; |
165 | 0 | break; |
166 | 0 | case SZ_SEEK_END: |
167 | 0 | ctx->offset = ctx->size + *pos; |
168 | 0 | break; |
169 | 0 | } |
170 | 0 | *pos = ctx->offset; |
171 | 0 | return SZ_OK; |
172 | 0 | } |
173 | 0 | static SRes ProgressImpl(const ICompressProgress*, UInt64, UInt64) { return SZ_OK; } |
174 | 0 | size_t offset; |
175 | 0 | uint8_t* data; |
176 | 0 | size_t size; |
177 | 0 | }; |
178 | 0 | XzLookInStream callbacks; |
179 | 0 | callbacks.Look = &XzLookInStream::LookImpl; |
180 | 0 | callbacks.Skip = &XzLookInStream::SkipImpl; |
181 | 0 | callbacks.Read = &XzLookInStream::ReadImpl; |
182 | 0 | callbacks.Seek = &XzLookInStream::SeekImpl; |
183 | 0 | callbacks.Progress = &XzLookInStream::ProgressImpl; |
184 | 0 | callbacks.offset = 0; |
185 | 0 | callbacks.data = compressed_data.get(); |
186 | 0 | callbacks.size = compressed_size_; |
187 | | |
188 | | // Iterate over the internal XZ blocks without decompressing them. |
189 | 0 | CXzs xzs; |
190 | 0 | Xzs_Construct(&xzs); |
191 | 0 | Int64 end_offset = compressed_size_; |
192 | 0 | if (Xzs_ReadBackward(&xzs, &callbacks, &end_offset, &callbacks, &alloc) == SZ_OK) { |
193 | 0 | blocks_.reserve(Xzs_GetNumBlocks(&xzs)); |
194 | 0 | size_t dst_offset = 0; |
195 | 0 | for (int s = xzs.num - 1; s >= 0; s--) { |
196 | 0 | const CXzStream& stream = xzs.streams[s]; |
197 | 0 | size_t src_offset = stream.startOffset + XZ_STREAM_HEADER_SIZE; |
198 | 0 | for (size_t b = 0; b < stream.numBlocks; b++) { |
199 | 0 | const CXzBlockSizes& block = stream.blocks[b]; |
200 | 0 | blocks_.push_back(XzBlock{ |
201 | 0 | .decompressed_data = nullptr, // Lazy allocation and decompression. |
202 | 0 | .decompressed_size = static_cast<uint32_t>(block.unpackSize), |
203 | 0 | .compressed_offset = static_cast<uint32_t>(src_offset), |
204 | 0 | .compressed_size = static_cast<uint32_t>((block.totalSize + 3) & ~3u), |
205 | 0 | .stream_flags = stream.flags, |
206 | 0 | }); |
207 | 0 | dst_offset += blocks_.back().decompressed_size; |
208 | 0 | src_offset += blocks_.back().compressed_size; |
209 | 0 | } |
210 | 0 | } |
211 | 0 | size_ = dst_offset; |
212 | 0 | total_size_ += dst_offset; |
213 | 0 | } |
214 | 0 | Xzs_Free(&xzs, &alloc); |
215 | 0 | return !blocks_.empty(); |
216 | 2 | } |
217 | | |
218 | 0 | bool MemoryXz::Decompress(XzBlock* block) { |
219 | 0 | static ISzAlloc alloc; |
220 | 0 | alloc.Alloc = [](ISzAllocPtr, size_t size) { return malloc(size); }; |
221 | 0 | alloc.Free = [](ISzAllocPtr, void* ptr) { return free(ptr); }; |
222 | | |
223 | | // Read the compressed data for this block. |
224 | 0 | std::unique_ptr<uint8_t[]> compressed_data(new (std::nothrow) uint8_t[block->compressed_size]); |
225 | 0 | if (compressed_data.get() == nullptr) { |
226 | 0 | return false; |
227 | 0 | } |
228 | 0 | if (!compressed_memory_->ReadFully(compressed_addr_ + block->compressed_offset, |
229 | 0 | compressed_data.get(), block->compressed_size)) { |
230 | 0 | return false; |
231 | 0 | } |
232 | | |
233 | | // Allocate decompressed memory. |
234 | 0 | std::unique_ptr<uint8_t[]> decompressed_data(new uint8_t[block->decompressed_size]); |
235 | 0 | if (decompressed_data == nullptr) { |
236 | 0 | return false; |
237 | 0 | } |
238 | | |
239 | | // Decompress. |
240 | 0 | CXzUnpacker state{}; |
241 | 0 | XzUnpacker_Construct(&state, &alloc); |
242 | 0 | state.streamFlags = block->stream_flags; |
243 | 0 | XzUnpacker_PrepareToRandomBlockDecoding(&state); |
244 | 0 | size_t decompressed_size = block->decompressed_size; |
245 | 0 | size_t compressed_size = block->compressed_size; |
246 | 0 | ECoderStatus status; |
247 | 0 | XzUnpacker_SetOutBuf(&state, decompressed_data.get(), decompressed_size); |
248 | 0 | int return_val = |
249 | 0 | XzUnpacker_Code(&state, /*decompressed_data=*/nullptr, &decompressed_size, |
250 | 0 | compressed_data.get(), &compressed_size, true, CODER_FINISH_END, &status); |
251 | 0 | XzUnpacker_Free(&state); |
252 | 0 | if (return_val != SZ_OK || status != CODER_STATUS_FINISHED_WITH_MARK) { |
253 | 0 | Log::Error("Cannot decompress \"%s\"", name_.c_str()); |
254 | 0 | return false; |
255 | 0 | } |
256 | | |
257 | 0 | used_ += block->decompressed_size; |
258 | 0 | total_used_ += block->decompressed_size; |
259 | 0 | if (kLogMemoryXzUsage) { |
260 | 0 | Log::Info("decompressed memory: %zi%% of %ziKB (%zi files), %i%% of %iKB (%s)", |
261 | 0 | 100 * total_used_ / total_size_, total_size_ / 1024, total_open_.load(), |
262 | 0 | 100 * used_ / size_, size_ / 1024, name_.c_str()); |
263 | 0 | } |
264 | |
|
265 | 0 | block->decompressed_data = std::move(decompressed_data); |
266 | 0 | return true; |
267 | 0 | } |
268 | | |
269 | | } // namespace unwindstack |