|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include <fcntl.h> |
|
6 #include <unistd.h> |
|
7 #include <sys/mman.h> |
|
8 #include <sys/stat.h> |
|
9 #include <cstring> |
|
10 #include <cstdlib> |
|
11 #include <cstdio> |
|
12 #include "Mappable.h" |
|
13 #ifdef ANDROID |
|
14 #include <linux/ashmem.h> |
|
15 #endif |
|
16 #include <sys/stat.h> |
|
17 #include <errno.h> |
|
18 #include "ElfLoader.h" |
|
19 #include "SeekableZStream.h" |
|
20 #include "Logging.h" |
|
21 |
|
22 Mappable * |
|
23 MappableFile::Create(const char *path) |
|
24 { |
|
25 int fd = open(path, O_RDONLY); |
|
26 if (fd != -1) |
|
27 return new MappableFile(fd); |
|
28 return nullptr; |
|
29 } |
|
30 |
|
31 MemoryRange |
|
32 MappableFile::mmap(const void *addr, size_t length, int prot, int flags, |
|
33 off_t offset) |
|
34 { |
|
35 MOZ_ASSERT(fd != -1); |
|
36 MOZ_ASSERT(!(flags & MAP_SHARED)); |
|
37 flags |= MAP_PRIVATE; |
|
38 |
|
39 return MemoryRange::mmap(const_cast<void *>(addr), length, prot, flags, |
|
40 fd, offset); |
|
41 } |
|
42 |
|
43 void |
|
44 MappableFile::finalize() |
|
45 { |
|
46 /* Close file ; equivalent to close(fd.forget()) */ |
|
47 fd = -1; |
|
48 } |
|
49 |
|
50 size_t |
|
51 MappableFile::GetLength() const |
|
52 { |
|
53 struct stat st; |
|
54 return fstat(fd, &st) ? 0 : st.st_size; |
|
55 } |
|
56 |
|
57 Mappable * |
|
58 MappableExtractFile::Create(const char *name, Zip *zip, Zip::Stream *stream) |
|
59 { |
|
60 const char *cachePath = getenv("MOZ_LINKER_CACHE"); |
|
61 if (!cachePath || !*cachePath) { |
|
62 LOG("Warning: MOZ_LINKER_EXTRACT is set, but not MOZ_LINKER_CACHE; " |
|
63 "not extracting"); |
|
64 return nullptr; |
|
65 } |
|
66 mozilla::ScopedDeleteArray<char> path; |
|
67 path = new char[strlen(cachePath) + strlen(name) + 2]; |
|
68 sprintf(path, "%s/%s", cachePath, name); |
|
69 struct stat cacheStat; |
|
70 if (stat(path, &cacheStat) == 0) { |
|
71 struct stat zipStat; |
|
72 stat(zip->GetName(), &zipStat); |
|
73 if (cacheStat.st_mtime > zipStat.st_mtime) { |
|
74 DEBUG_LOG("Reusing %s", static_cast<char *>(path)); |
|
75 return MappableFile::Create(path); |
|
76 } |
|
77 } |
|
78 DEBUG_LOG("Extracting to %s", static_cast<char *>(path)); |
|
79 AutoCloseFD fd; |
|
80 fd = open(path, O_TRUNC | O_RDWR | O_CREAT | O_NOATIME, |
|
81 S_IRUSR | S_IWUSR); |
|
82 if (fd == -1) { |
|
83 LOG("Couldn't open %s to decompress library", path.get()); |
|
84 return nullptr; |
|
85 } |
|
86 AutoUnlinkFile file; |
|
87 file = path.forget(); |
|
88 if (stream->GetType() == Zip::Stream::DEFLATE) { |
|
89 if (ftruncate(fd, stream->GetUncompressedSize()) == -1) { |
|
90 LOG("Couldn't ftruncate %s to decompress library", file.get()); |
|
91 return nullptr; |
|
92 } |
|
93 /* Map the temporary file for use as inflate buffer */ |
|
94 MappedPtr buffer(MemoryRange::mmap(nullptr, stream->GetUncompressedSize(), |
|
95 PROT_WRITE, MAP_SHARED, fd, 0)); |
|
96 if (buffer == MAP_FAILED) { |
|
97 LOG("Couldn't map %s to decompress library", file.get()); |
|
98 return nullptr; |
|
99 } |
|
100 |
|
101 z_stream zStream = stream->GetZStream(buffer); |
|
102 |
|
103 /* Decompress */ |
|
104 if (inflateInit2(&zStream, -MAX_WBITS) != Z_OK) { |
|
105 LOG("inflateInit failed: %s", zStream.msg); |
|
106 return nullptr; |
|
107 } |
|
108 if (inflate(&zStream, Z_FINISH) != Z_STREAM_END) { |
|
109 LOG("inflate failed: %s", zStream.msg); |
|
110 return nullptr; |
|
111 } |
|
112 if (inflateEnd(&zStream) != Z_OK) { |
|
113 LOG("inflateEnd failed: %s", zStream.msg); |
|
114 return nullptr; |
|
115 } |
|
116 if (zStream.total_out != stream->GetUncompressedSize()) { |
|
117 LOG("File not fully uncompressed! %ld / %d", zStream.total_out, |
|
118 static_cast<unsigned int>(stream->GetUncompressedSize())); |
|
119 return nullptr; |
|
120 } |
|
121 } else if (stream->GetType() == Zip::Stream::STORE) { |
|
122 SeekableZStream zStream; |
|
123 if (!zStream.Init(stream->GetBuffer(), stream->GetSize())) { |
|
124 LOG("Couldn't initialize SeekableZStream for %s", name); |
|
125 return nullptr; |
|
126 } |
|
127 if (ftruncate(fd, zStream.GetUncompressedSize()) == -1) { |
|
128 LOG("Couldn't ftruncate %s to decompress library", file.get()); |
|
129 return nullptr; |
|
130 } |
|
131 MappedPtr buffer(MemoryRange::mmap(nullptr, zStream.GetUncompressedSize(), |
|
132 PROT_WRITE, MAP_SHARED, fd, 0)); |
|
133 if (buffer == MAP_FAILED) { |
|
134 LOG("Couldn't map %s to decompress library", file.get()); |
|
135 return nullptr; |
|
136 } |
|
137 |
|
138 if (!zStream.Decompress(buffer, 0, zStream.GetUncompressedSize())) { |
|
139 LOG("%s: failed to decompress", name); |
|
140 return nullptr; |
|
141 } |
|
142 } else { |
|
143 return nullptr; |
|
144 } |
|
145 |
|
146 return new MappableExtractFile(fd.forget(), file.forget()); |
|
147 } |
|
148 |
|
149 MappableExtractFile::~MappableExtractFile() |
|
150 { |
|
151 /* When destroying from a forked process, we don't want the file to be |
|
152 * removed, as the main process is still using the file. Although it |
|
153 * doesn't really matter, it helps e.g. valgrind that the file is there. |
|
154 * The string still needs to be delete[]d, though */ |
|
155 if (pid != getpid()) |
|
156 delete [] path.forget(); |
|
157 } |
|
158 |
|
159 /** |
|
160 * _MappableBuffer is a buffer which content can be mapped at different |
|
161 * locations in the virtual address space. |
|
162 * On Linux, uses a (deleted) temporary file on a tmpfs for sharable content. |
|
163 * On Android, uses ashmem. |
|
164 */ |
|
165 class _MappableBuffer: public MappedPtr |
|
166 { |
|
167 public: |
|
168 /** |
|
169 * Returns a _MappableBuffer instance with the given name and the given |
|
170 * length. |
|
171 */ |
|
172 static _MappableBuffer *Create(const char *name, size_t length) |
|
173 { |
|
174 AutoCloseFD fd; |
|
175 #ifdef ANDROID |
|
176 /* On Android, initialize an ashmem region with the given length */ |
|
177 fd = open("/" ASHMEM_NAME_DEF, O_RDWR, 0600); |
|
178 if (fd == -1) |
|
179 return nullptr; |
|
180 char str[ASHMEM_NAME_LEN]; |
|
181 strlcpy(str, name, sizeof(str)); |
|
182 ioctl(fd, ASHMEM_SET_NAME, str); |
|
183 if (ioctl(fd, ASHMEM_SET_SIZE, length)) |
|
184 return nullptr; |
|
185 |
|
186 /* The Gecko crash reporter is confused by adjacent memory mappings of |
|
187 * the same file and chances are we're going to map from the same file |
|
188 * descriptor right away. To avoid problems with the crash reporter, |
|
189 * create an empty anonymous page before or after the ashmem mapping, |
|
190 * depending on how mappings grow in the address space. |
|
191 */ |
|
192 #if defined(__arm__) |
|
193 void *buf = ::mmap(nullptr, length + PAGE_SIZE, PROT_READ | PROT_WRITE, |
|
194 MAP_SHARED, fd, 0); |
|
195 if (buf != MAP_FAILED) { |
|
196 ::mmap(AlignedEndPtr(reinterpret_cast<char *>(buf) + length, PAGE_SIZE), |
|
197 PAGE_SIZE, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
|
198 DEBUG_LOG("Decompression buffer of size 0x%x in ashmem \"%s\", mapped @%p", |
|
199 length, str, buf); |
|
200 return new _MappableBuffer(fd.forget(), buf, length); |
|
201 } |
|
202 #elif defined(__i386__) |
|
203 size_t anon_mapping_length = length + PAGE_SIZE; |
|
204 void *buf = ::mmap(nullptr, anon_mapping_length, PROT_NONE, |
|
205 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
|
206 if (buf != MAP_FAILED) { |
|
207 char *first_page = reinterpret_cast<char *>(buf); |
|
208 char *map_page = first_page + PAGE_SIZE; |
|
209 |
|
210 void *actual_buf = ::mmap(map_page, length, PROT_READ | PROT_WRITE, |
|
211 MAP_FIXED | MAP_SHARED, fd, 0); |
|
212 if (actual_buf == MAP_FAILED) { |
|
213 ::munmap(buf, anon_mapping_length); |
|
214 DEBUG_LOG("Fixed allocation of decompression buffer at %p failed", map_page); |
|
215 return nullptr; |
|
216 } |
|
217 |
|
218 DEBUG_LOG("Decompression buffer of size 0x%x in ashmem \"%s\", mapped @%p", |
|
219 length, str, actual_buf); |
|
220 return new _MappableBuffer(fd.forget(), actual_buf, length); |
|
221 } |
|
222 #else |
|
223 #error need to add a case for your CPU |
|
224 #endif |
|
225 #else |
|
226 /* On Linux, use /dev/shm as base directory for temporary files, assuming |
|
227 * it's on tmpfs */ |
|
228 /* TODO: check that /dev/shm is tmpfs */ |
|
229 char path[256]; |
|
230 sprintf(path, "/dev/shm/%s.XXXXXX", name); |
|
231 fd = mkstemp(path); |
|
232 if (fd == -1) |
|
233 return nullptr; |
|
234 unlink(path); |
|
235 ftruncate(fd, length); |
|
236 |
|
237 void *buf = ::mmap(nullptr, length, PROT_READ | PROT_WRITE, |
|
238 MAP_SHARED, fd, 0); |
|
239 if (buf != MAP_FAILED) { |
|
240 DEBUG_LOG("Decompression buffer of size %ld in \"%s\", mapped @%p", |
|
241 length, path, buf); |
|
242 return new _MappableBuffer(fd.forget(), buf, length); |
|
243 } |
|
244 #endif |
|
245 return nullptr; |
|
246 } |
|
247 |
|
248 void *mmap(const void *addr, size_t length, int prot, int flags, off_t offset) |
|
249 { |
|
250 MOZ_ASSERT(fd != -1); |
|
251 #ifdef ANDROID |
|
252 /* Mapping ashmem MAP_PRIVATE is like mapping anonymous memory, even when |
|
253 * there is content in the ashmem */ |
|
254 if (flags & MAP_PRIVATE) { |
|
255 flags &= ~MAP_PRIVATE; |
|
256 flags |= MAP_SHARED; |
|
257 } |
|
258 #endif |
|
259 return ::mmap(const_cast<void *>(addr), length, prot, flags, fd, offset); |
|
260 } |
|
261 |
|
262 #ifdef ANDROID |
|
263 ~_MappableBuffer() { |
|
264 /* Free the additional page we allocated. See _MappableBuffer::Create */ |
|
265 #if defined(__arm__) |
|
266 ::munmap(AlignedEndPtr(*this + GetLength(), PAGE_SIZE), PAGE_SIZE); |
|
267 #elif defined(__i386__) |
|
268 ::munmap(*this - PAGE_SIZE, GetLength() + PAGE_SIZE); |
|
269 #else |
|
270 #error need to add a case for your CPU |
|
271 #endif |
|
272 } |
|
273 #endif |
|
274 |
|
275 private: |
|
276 _MappableBuffer(int fd, void *buf, size_t length) |
|
277 : MappedPtr(buf, length), fd(fd) { } |
|
278 |
|
279 /* File descriptor for the temporary file or ashmem */ |
|
280 AutoCloseFD fd; |
|
281 }; |
|
282 |
|
283 |
|
284 Mappable * |
|
285 MappableDeflate::Create(const char *name, Zip *zip, Zip::Stream *stream) |
|
286 { |
|
287 MOZ_ASSERT(stream->GetType() == Zip::Stream::DEFLATE); |
|
288 _MappableBuffer *buf = _MappableBuffer::Create(name, stream->GetUncompressedSize()); |
|
289 if (buf) |
|
290 return new MappableDeflate(buf, zip, stream); |
|
291 return nullptr; |
|
292 } |
|
293 |
|
294 MappableDeflate::MappableDeflate(_MappableBuffer *buf, Zip *zip, |
|
295 Zip::Stream *stream) |
|
296 : zip(zip), buffer(buf), zStream(stream->GetZStream(*buf)) { } |
|
297 |
|
298 MappableDeflate::~MappableDeflate() { } |
|
299 |
|
300 MemoryRange |
|
301 MappableDeflate::mmap(const void *addr, size_t length, int prot, int flags, off_t offset) |
|
302 { |
|
303 MOZ_ASSERT(buffer); |
|
304 MOZ_ASSERT(!(flags & MAP_SHARED)); |
|
305 flags |= MAP_PRIVATE; |
|
306 |
|
307 /* The deflate stream is uncompressed up to the required offset + length, if |
|
308 * it hasn't previously been uncompressed */ |
|
309 ssize_t missing = offset + length + zStream.avail_out - buffer->GetLength(); |
|
310 if (missing > 0) { |
|
311 uInt avail_out = zStream.avail_out; |
|
312 zStream.avail_out = missing; |
|
313 if ((*buffer == zStream.next_out) && |
|
314 (inflateInit2(&zStream, -MAX_WBITS) != Z_OK)) { |
|
315 LOG("inflateInit failed: %s", zStream.msg); |
|
316 return MemoryRange(MAP_FAILED, 0); |
|
317 } |
|
318 int ret = inflate(&zStream, Z_SYNC_FLUSH); |
|
319 if (ret < 0) { |
|
320 LOG("inflate failed: %s", zStream.msg); |
|
321 return MemoryRange(MAP_FAILED, 0); |
|
322 } |
|
323 if (ret == Z_NEED_DICT) { |
|
324 LOG("zstream requires a dictionary. %s", zStream.msg); |
|
325 return MemoryRange(MAP_FAILED, 0); |
|
326 } |
|
327 zStream.avail_out = avail_out - missing + zStream.avail_out; |
|
328 if (ret == Z_STREAM_END) { |
|
329 if (inflateEnd(&zStream) != Z_OK) { |
|
330 LOG("inflateEnd failed: %s", zStream.msg); |
|
331 return MemoryRange(MAP_FAILED, 0); |
|
332 } |
|
333 if (zStream.total_out != buffer->GetLength()) { |
|
334 LOG("File not fully uncompressed! %ld / %d", zStream.total_out, |
|
335 static_cast<unsigned int>(buffer->GetLength())); |
|
336 return MemoryRange(MAP_FAILED, 0); |
|
337 } |
|
338 } |
|
339 } |
|
340 #if defined(ANDROID) && defined(__arm__) |
|
341 if (prot & PROT_EXEC) { |
|
342 /* We just extracted data that may be executed in the future. |
|
343 * We thus need to ensure Instruction and Data cache coherency. */ |
|
344 DEBUG_LOG("cacheflush(%p, %p)", *buffer + offset, *buffer + (offset + length)); |
|
345 cacheflush(reinterpret_cast<uintptr_t>(*buffer + offset), |
|
346 reinterpret_cast<uintptr_t>(*buffer + (offset + length)), 0); |
|
347 } |
|
348 #endif |
|
349 |
|
350 return MemoryRange(buffer->mmap(addr, length, prot, flags, offset), length); |
|
351 } |
|
352 |
|
353 void |
|
354 MappableDeflate::finalize() |
|
355 { |
|
356 /* Free zlib internal buffers */ |
|
357 inflateEnd(&zStream); |
|
358 /* Free decompression buffer */ |
|
359 buffer = nullptr; |
|
360 /* Remove reference to Zip archive */ |
|
361 zip = nullptr; |
|
362 } |
|
363 |
|
364 size_t |
|
365 MappableDeflate::GetLength() const |
|
366 { |
|
367 return buffer->GetLength(); |
|
368 } |
|
369 |
|
370 Mappable * |
|
371 MappableSeekableZStream::Create(const char *name, Zip *zip, |
|
372 Zip::Stream *stream) |
|
373 { |
|
374 MOZ_ASSERT(stream->GetType() == Zip::Stream::STORE); |
|
375 mozilla::ScopedDeletePtr<MappableSeekableZStream> mappable; |
|
376 mappable = new MappableSeekableZStream(zip); |
|
377 |
|
378 pthread_mutexattr_t recursiveAttr; |
|
379 pthread_mutexattr_init(&recursiveAttr); |
|
380 pthread_mutexattr_settype(&recursiveAttr, PTHREAD_MUTEX_RECURSIVE); |
|
381 |
|
382 if (pthread_mutex_init(&mappable->mutex, &recursiveAttr)) |
|
383 return nullptr; |
|
384 |
|
385 if (!mappable->zStream.Init(stream->GetBuffer(), stream->GetSize())) |
|
386 return nullptr; |
|
387 |
|
388 mappable->buffer = _MappableBuffer::Create(name, |
|
389 mappable->zStream.GetUncompressedSize()); |
|
390 if (!mappable->buffer) |
|
391 return nullptr; |
|
392 |
|
393 mappable->chunkAvail = new unsigned char[mappable->zStream.GetChunksNum()]; |
|
394 memset(mappable->chunkAvail, 0, mappable->zStream.GetChunksNum()); |
|
395 |
|
396 return mappable.forget(); |
|
397 } |
|
398 |
|
399 MappableSeekableZStream::MappableSeekableZStream(Zip *zip) |
|
400 : zip(zip), chunkAvailNum(0) { } |
|
401 |
|
402 MappableSeekableZStream::~MappableSeekableZStream() |
|
403 { |
|
404 pthread_mutex_destroy(&mutex); |
|
405 } |
|
406 |
|
407 MemoryRange |
|
408 MappableSeekableZStream::mmap(const void *addr, size_t length, int prot, |
|
409 int flags, off_t offset) |
|
410 { |
|
411 /* Map with PROT_NONE so that accessing the mapping would segfault, and |
|
412 * bring us to ensure() */ |
|
413 void *res = buffer->mmap(addr, length, PROT_NONE, flags, offset); |
|
414 if (res == MAP_FAILED) |
|
415 return MemoryRange(MAP_FAILED, 0); |
|
416 |
|
417 /* Store the mapping, ordered by offset and length */ |
|
418 std::vector<LazyMap>::reverse_iterator it; |
|
419 for (it = lazyMaps.rbegin(); it < lazyMaps.rend(); ++it) { |
|
420 if ((it->offset < offset) || |
|
421 ((it->offset == offset) && (it->length < length))) |
|
422 break; |
|
423 } |
|
424 LazyMap map = { res, length, prot, offset }; |
|
425 lazyMaps.insert(it.base(), map); |
|
426 return MemoryRange(res, length); |
|
427 } |
|
428 |
|
429 void |
|
430 MappableSeekableZStream::munmap(void *addr, size_t length) |
|
431 { |
|
432 std::vector<LazyMap>::iterator it; |
|
433 for (it = lazyMaps.begin(); it < lazyMaps.end(); ++it) |
|
434 if ((it->addr = addr) && (it->length == length)) { |
|
435 lazyMaps.erase(it); |
|
436 ::munmap(addr, length); |
|
437 return; |
|
438 } |
|
439 MOZ_CRASH("munmap called with unknown mapping"); |
|
440 } |
|
441 |
|
442 void |
|
443 MappableSeekableZStream::finalize() { } |
|
444 |
|
445 class AutoLock { |
|
446 public: |
|
447 AutoLock(pthread_mutex_t *mutex): mutex(mutex) |
|
448 { |
|
449 if (pthread_mutex_lock(mutex)) |
|
450 MOZ_CRASH("pthread_mutex_lock failed"); |
|
451 } |
|
452 ~AutoLock() |
|
453 { |
|
454 if (pthread_mutex_unlock(mutex)) |
|
455 MOZ_CRASH("pthread_mutex_unlock failed"); |
|
456 } |
|
457 private: |
|
458 pthread_mutex_t *mutex; |
|
459 }; |
|
460 |
|
461 bool |
|
462 MappableSeekableZStream::ensure(const void *addr) |
|
463 { |
|
464 DEBUG_LOG("ensure @%p", addr); |
|
465 const void *addrPage = PageAlignedPtr(addr); |
|
466 /* Find the mapping corresponding to the given page */ |
|
467 std::vector<LazyMap>::iterator map; |
|
468 for (map = lazyMaps.begin(); map < lazyMaps.end(); ++map) { |
|
469 if (map->Contains(addrPage)) |
|
470 break; |
|
471 } |
|
472 if (map == lazyMaps.end()) |
|
473 return false; |
|
474 |
|
475 /* Find corresponding chunk */ |
|
476 off_t mapOffset = map->offsetOf(addrPage); |
|
477 off_t chunk = mapOffset / zStream.GetChunkSize(); |
|
478 |
|
479 /* In the typical case, we just need to decompress the chunk entirely. But |
|
480 * when the current mapping ends in the middle of the chunk, we want to |
|
481 * stop at the end of the corresponding page. |
|
482 * However, if another mapping needs the last part of the chunk, we still |
|
483 * need to continue. As mappings are ordered by offset and length, we don't |
|
484 * need to scan the entire list of mappings. |
|
485 * It is safe to run through lazyMaps here because the linker is never |
|
486 * going to call mmap (which adds lazyMaps) while this function is |
|
487 * called. */ |
|
488 size_t length = zStream.GetChunkSize(chunk); |
|
489 off_t chunkStart = chunk * zStream.GetChunkSize(); |
|
490 off_t chunkEnd = chunkStart + length; |
|
491 std::vector<LazyMap>::iterator it; |
|
492 for (it = map; it < lazyMaps.end(); ++it) { |
|
493 if (chunkEnd <= it->endOffset()) |
|
494 break; |
|
495 } |
|
496 if ((it == lazyMaps.end()) || (chunkEnd > it->endOffset())) { |
|
497 /* The mapping "it" points at now is past the interesting one */ |
|
498 --it; |
|
499 length = it->endOffset() - chunkStart; |
|
500 } |
|
501 |
|
502 length = PageAlignedSize(length); |
|
503 |
|
504 /* The following lock can be re-acquired by the thread holding it. |
|
505 * If this happens, it means the following code is interrupted somehow by |
|
506 * some signal, and ends up retriggering a chunk decompression for the |
|
507 * same MappableSeekableZStream. |
|
508 * If the chunk to decompress is different the second time, then everything |
|
509 * is safe as the only common data touched below is chunkAvailNum, and it is |
|
510 * atomically updated (leaving out any chance of an interruption while it is |
|
511 * updated affecting the result). If the chunk to decompress is the same, the |
|
512 * worst thing that can happen is chunkAvailNum being incremented one too |
|
513 * many times, which doesn't affect functionality. The chances of it |
|
514 * happening being pretty slim, and the effect being harmless, we can just |
|
515 * ignore the issue. Other than that, we'd just be wasting time decompressing |
|
516 * the same chunk twice. */ |
|
517 AutoLock lock(&mutex); |
|
518 |
|
519 /* The very first page is mapped and accessed separately of the rest, and |
|
520 * as such, only the first page of the first chunk is decompressed this way. |
|
521 * When we fault in the remaining pages of that chunk, we want to decompress |
|
522 * the complete chunk again. Short of doing that, we would end up with |
|
523 * no data between PageSize() and chunkSize, which would effectively corrupt |
|
524 * symbol resolution in the underlying library. */ |
|
525 if (chunkAvail[chunk] < PageNumber(length)) { |
|
526 if (!zStream.DecompressChunk(*buffer + chunkStart, chunk, length)) |
|
527 return false; |
|
528 |
|
529 #if defined(ANDROID) && defined(__arm__) |
|
530 if (map->prot & PROT_EXEC) { |
|
531 /* We just extracted data that may be executed in the future. |
|
532 * We thus need to ensure Instruction and Data cache coherency. */ |
|
533 DEBUG_LOG("cacheflush(%p, %p)", *buffer + chunkStart, *buffer + (chunkStart + length)); |
|
534 cacheflush(reinterpret_cast<uintptr_t>(*buffer + chunkStart), |
|
535 reinterpret_cast<uintptr_t>(*buffer + (chunkStart + length)), 0); |
|
536 } |
|
537 #endif |
|
538 /* Only count if we haven't already decompressed parts of the chunk */ |
|
539 if (chunkAvail[chunk] == 0) |
|
540 chunkAvailNum++; |
|
541 |
|
542 chunkAvail[chunk] = PageNumber(length); |
|
543 } |
|
544 |
|
545 /* Flip the chunk mapping protection to the recorded flags. We could |
|
546 * also flip the protection for other mappings of the same chunk, |
|
547 * but it's easier to skip that and let further segfaults call |
|
548 * ensure again. */ |
|
549 const void *chunkAddr = reinterpret_cast<const void *> |
|
550 (reinterpret_cast<uintptr_t>(addrPage) |
|
551 - mapOffset % zStream.GetChunkSize()); |
|
552 const void *chunkEndAddr = reinterpret_cast<const void *> |
|
553 (reinterpret_cast<uintptr_t>(chunkAddr) + length); |
|
554 |
|
555 const void *start = std::max(map->addr, chunkAddr); |
|
556 const void *end = std::min(map->end(), chunkEndAddr); |
|
557 length = reinterpret_cast<uintptr_t>(end) |
|
558 - reinterpret_cast<uintptr_t>(start); |
|
559 |
|
560 if (mprotect(const_cast<void *>(start), length, map->prot) == 0) { |
|
561 DEBUG_LOG("mprotect @%p, 0x%" PRIxSize ", 0x%x", start, length, map->prot); |
|
562 return true; |
|
563 } |
|
564 |
|
565 LOG("mprotect @%p, 0x%" PRIxSize ", 0x%x failed with errno %d", |
|
566 start, length, map->prot, errno); |
|
567 LOG("mprotect failed"); |
|
568 return false; |
|
569 } |
|
570 |
|
571 void |
|
572 MappableSeekableZStream::stats(const char *when, const char *name) const |
|
573 { |
|
574 size_t nEntries = zStream.GetChunksNum(); |
|
575 DEBUG_LOG("%s: %s; %" PRIdSize "/%" PRIdSize " chunks decompressed", |
|
576 name, when, static_cast<size_t>(chunkAvailNum), nEntries); |
|
577 |
|
578 size_t len = 64; |
|
579 mozilla::ScopedDeleteArray<char> map; |
|
580 map = new char[len + 3]; |
|
581 map[0] = '['; |
|
582 |
|
583 for (size_t i = 0, j = 1; i < nEntries; i++, j++) { |
|
584 map[j] = chunkAvail[i] ? '*' : '_'; |
|
585 if ((j == len) || (i == nEntries - 1)) { |
|
586 map[j + 1] = ']'; |
|
587 map[j + 2] = '\0'; |
|
588 DEBUG_LOG("%s", static_cast<char *>(map)); |
|
589 j = 0; |
|
590 } |
|
591 } |
|
592 } |
|
593 |
|
594 size_t |
|
595 MappableSeekableZStream::GetLength() const |
|
596 { |
|
597 return buffer->GetLength(); |
|
598 } |