Merge "emulator: Use distinct serial names for simultaneous qemu pipes." into nyc-dev
diff --git a/adb/adb_client.cpp b/adb/adb_client.cpp
index db9b710..d29c08e 100644
--- a/adb/adb_client.cpp
+++ b/adb/adb_client.cpp
@@ -50,6 +50,15 @@
__adb_serial = serial;
}
+void adb_get_transport(TransportType* type, const char** serial) {
+ if (type) {
+ *type = __adb_transport;
+ }
+ if (serial) {
+ *serial = __adb_serial;
+ }
+}
+
void adb_set_tcp_specifics(int server_port)
{
__adb_server_port = server_port;
diff --git a/adb/adb_client.h b/adb/adb_client.h
index a9df4d7..d5cd922 100644
--- a/adb/adb_client.h
+++ b/adb/adb_client.h
@@ -39,6 +39,9 @@
// Set the preferred transport to connect to.
void adb_set_transport(TransportType type, const char* _Nullable serial);
+// Get the preferred transport to connect to.
+void adb_get_transport(TransportType* _Nullable type, const char* _Nullable* _Nullable serial);
+
// Set TCP specifics of the transport to use.
void adb_set_tcp_specifics(int server_port);
diff --git a/adb/adb_utils.h b/adb/adb_utils.h
index f1149b3..89fcd66 100644
--- a/adb/adb_utils.h
+++ b/adb/adb_utils.h
@@ -19,6 +19,8 @@
#include <string>
+#include <android-base/macros.h>
+
void close_stdin();
bool getcwd(std::string* cwd);
@@ -39,4 +41,45 @@
bool set_file_block_mode(int fd, bool block);
+extern int adb_close(int fd);
+
+// Helper to automatically close an FD when it goes out of scope.
+class ScopedFd {
+ public:
+ ScopedFd() {
+ }
+
+ ~ScopedFd() {
+ Reset();
+ }
+
+ void Reset(int fd = -1) {
+ if (fd != fd_) {
+ if (valid()) {
+ adb_close(fd_);
+ }
+ fd_ = fd;
+ }
+ }
+
+ int Release() {
+ int temp = fd_;
+ fd_ = -1;
+ return temp;
+ }
+
+ bool valid() const {
+ return fd_ >= 0;
+ }
+
+ int fd() const {
+ return fd_;
+ }
+
+ private:
+ int fd_ = -1;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedFd);
+};
+
#endif
diff --git a/adb/commandline.cpp b/adb/commandline.cpp
index ff4eb22..e522119 100644
--- a/adb/commandline.cpp
+++ b/adb/commandline.cpp
@@ -1072,6 +1072,51 @@
return adb_command(cmd);
}
+static bool adb_root(const char* command) {
+ std::string error;
+ ScopedFd fd;
+
+ fd.Reset(adb_connect(android::base::StringPrintf("%s:", command), &error));
+ if (!fd.valid()) {
+ fprintf(stderr, "adb: unable to connect for %s: %s\n", command, error.c_str());
+ return false;
+ }
+
+ // Figure out whether we actually did anything.
+ char buf[256];
+ char* cur = buf;
+ ssize_t bytes_left = sizeof(buf);
+ while (bytes_left > 0) {
+ ssize_t bytes_read = adb_read(fd.fd(), cur, bytes_left);
+ if (bytes_read == 0) {
+ break;
+ } else if (bytes_read < 0) {
+ fprintf(stderr, "adb: error while reading for %s: %s\n", command, strerror(errno));
+ return false;
+ }
+ cur += bytes_read;
+ bytes_left -= bytes_read;
+ }
+
+ if (bytes_left == 0) {
+ fprintf(stderr, "adb: unexpected output length for %s\n", command);
+ return false;
+ }
+
+ fflush(stdout);
+ WriteFdExactly(STDOUT_FILENO, buf, sizeof(buf) - bytes_left);
+ if (cur != buf && strstr(buf, "restarting") == nullptr) {
+ return true;
+ }
+
+ // Give adbd 500ms to kill itself, then wait-for-device for it to come back up.
+ adb_sleep_ms(500);
+ TransportType type;
+ const char* serial;
+ adb_get_transport(&type, &serial);
+ return wait_for_device("wait-for-device", type, serial);
+}
+
// Connects to the device "shell" service with |command| and prints the
// resulting output.
static int send_shell_command(TransportType transport_type, const char* serial,
@@ -1220,6 +1265,9 @@
printf("Now unlock your device and confirm the restore operation.\n");
copy_to_file(tarFd, fd);
+ // Wait until the other side finishes, or it'll get sent SIGHUP.
+ copy_to_file(fd, STDOUT_FILENO);
+
adb_close(fd);
adb_close(tarFd);
return 0;
@@ -1632,8 +1680,6 @@
!strcmp(argv[0], "reboot") ||
!strcmp(argv[0], "reboot-bootloader") ||
!strcmp(argv[0], "usb") ||
- !strcmp(argv[0], "root") ||
- !strcmp(argv[0], "unroot") ||
!strcmp(argv[0], "disable-verity") ||
!strcmp(argv[0], "enable-verity")) {
std::string command;
@@ -1645,8 +1691,9 @@
command = android::base::StringPrintf("%s:", argv[0]);
}
return adb_connect_command(command);
- }
- else if (!strcmp(argv[0], "bugreport")) {
+ } else if (!strcmp(argv[0], "root") || !strcmp(argv[0], "unroot")) {
+ return adb_root(argv[0]) ? 0 : 1;
+ } else if (!strcmp(argv[0], "bugreport")) {
if (argc != 1) return usage();
// No need for shell protocol with bugreport, always disable for
// simplicity.
diff --git a/adb/shell_service.cpp b/adb/shell_service.cpp
index f84447f..ce10708 100644
--- a/adb/shell_service.cpp
+++ b/adb/shell_service.cpp
@@ -135,37 +135,6 @@
return received;
}
-// Helper to automatically close an FD when it goes out of scope.
-class ScopedFd {
- public:
- ScopedFd() {}
- ~ScopedFd() { Reset(); }
-
- void Reset(int fd=-1) {
- if (fd != fd_) {
- if (valid()) {
- adb_close(fd_);
- }
- fd_ = fd;
- }
- }
-
- int Release() {
- int temp = fd_;
- fd_ = -1;
- return temp;
- }
-
- bool valid() const { return fd_ >= 0; }
-
- int fd() const { return fd_; }
-
- private:
- int fd_ = -1;
-
- DISALLOW_COPY_AND_ASSIGN(ScopedFd);
-};
-
// Creates a socketpair and saves the endpoints to |fd1| and |fd2|.
bool CreateSocketpair(ScopedFd* fd1, ScopedFd* fd2) {
int sockets[2];
@@ -315,7 +284,9 @@
if (type_ == SubprocessType::kPty) {
int fd;
pid_ = forkpty(&fd, pts_name, nullptr, nullptr);
- stdinout_sfd_.Reset(fd);
+ if (pid_ > 0) {
+ stdinout_sfd_.Reset(fd);
+ }
} else {
if (!CreateSocketpair(&stdinout_sfd_, &child_stdinout_sfd)) {
*error = android::base::StringPrintf("failed to create socketpair for stdin/out: %s",
diff --git a/adb/sysdeps.h b/adb/sysdeps.h
index ce0f289..81d201e 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -576,8 +576,7 @@
// Closes a file descriptor that came from adb_open() or adb_open_mode(), but
// not designed to take a file descriptor from unix_open(). See the comments
// for adb_open() for more info.
-static __inline__ int adb_close(int fd)
-{
+__inline__ int adb_close(int fd) {
return close(fd);
}
#undef close
diff --git a/base/file.cpp b/base/file.cpp
index bcdfc5e..da1adba 100644
--- a/base/file.cpp
+++ b/base/file.cpp
@@ -29,10 +29,6 @@
#include "cutils/log.h"
#include "utils/Compat.h"
-#if !defined(_WIN32)
-#define O_BINARY 0
-#endif
-
namespace android {
namespace base {
diff --git a/base/include/android-base/file.h b/base/include/android-base/file.h
index 486befc..5342d98 100644
--- a/base/include/android-base/file.h
+++ b/base/include/android-base/file.h
@@ -20,6 +20,10 @@
#include <sys/stat.h>
#include <string>
+#if !defined(_WIN32) && !defined(O_BINARY)
+#define O_BINARY 0
+#endif
+
namespace android {
namespace base {
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index c199190..bcc8abd 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -170,7 +170,9 @@
// use this signal to mark the time of the factory reset.
if (!boot_event_store.GetBootEvent("factory_reset", &record)) {
boot_event_store.AddBootEventWithValue("factory_reset", current_time_utc);
- boot_event_store.AddBootEventWithValue("time_since_factory_reset", 0);
+
+ // Don't log the time_since_factory_reset until some time has elapsed.
+ // The data is not meaningful yet and skews the histogram buckets.
return;
}
diff --git a/bootstat/bootstat.rc b/bootstat/bootstat.rc
index 13ef27e..3c20fc8 100644
--- a/bootstat/bootstat.rc
+++ b/bootstat/bootstat.rc
@@ -3,10 +3,13 @@
on post-fs-data
mkdir /data/misc/bootstat 0700 root root
-# This marker, boot animation stopped, is considered the point at which the
+# The first marker, boot animation stopped, is considered the point at which
# the user may interact with the device, so it is a good proxy for the boot
# complete signal.
-on property:init.svc.bootanim=stopped
+#
+# The second marker ensures an encrypted device is decrypted before logging
+# boot time data.
+on property:init.svc.bootanim=stopped && property:vold.decrypt=trigger_restart_framework
# Record boot_complete timing event.
exec - root root -- /system/bin/bootstat -r boot_complete
diff --git a/fs_mgr/fs_mgr.c b/fs_mgr/fs_mgr.c
index 78d3450..bc072bc 100644
--- a/fs_mgr/fs_mgr.c
+++ b/fs_mgr/fs_mgr.c
@@ -477,8 +477,10 @@
// Deal with file level encryption
INFO("%s is file encrypted\n", rec->mount_point);
return FS_MGR_MNTALL_DEV_FILE_ENCRYPTED;
- } else {
+ } else if (fs_mgr_is_encryptable(rec)) {
return FS_MGR_MNTALL_DEV_NOT_ENCRYPTED;
+ } else {
+ return FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE;
}
}
@@ -490,7 +492,7 @@
int fs_mgr_mount_all(struct fstab *fstab)
{
int i = 0;
- int encryptable = FS_MGR_MNTALL_DEV_NOT_ENCRYPTED;
+ int encryptable = FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE;
int error_count = 0;
int mret = -1;
int mount_errno = 0;
@@ -561,8 +563,8 @@
return status;
}
- if (status != FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) {
- if (encryptable != FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) {
+ if (status != FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
+ if (encryptable != FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
// Log and continue
ERROR("Only one encryptable/encrypted partition supported\n");
}
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index d9a7c4c..0404dbd 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -74,11 +74,12 @@
struct fstab *fs_mgr_read_fstab(const char *fstab_path);
void fs_mgr_free_fstab(struct fstab *fstab);
-#define FS_MGR_MNTALL_DEV_FILE_ENCRYPTED 4
-#define FS_MGR_MNTALL_DEV_NEEDS_RECOVERY 3
-#define FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION 2
-#define FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED 1
-#define FS_MGR_MNTALL_DEV_NOT_ENCRYPTED 0
+#define FS_MGR_MNTALL_DEV_FILE_ENCRYPTED 5
+#define FS_MGR_MNTALL_DEV_NEEDS_RECOVERY 4
+#define FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION 3
+#define FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED 2
+#define FS_MGR_MNTALL_DEV_NOT_ENCRYPTED 1
+#define FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE 0
#define FS_MGR_MNTALL_FAIL -1
int fs_mgr_mount_all(struct fstab *fstab);
diff --git a/include/ziparchive/zip_archive.h b/include/ziparchive/zip_archive.h
index 3591a6b..7dc60ae 100644
--- a/include/ziparchive/zip_archive.h
+++ b/include/ziparchive/zip_archive.h
@@ -152,6 +152,9 @@
* if this file entry contains a data descriptor footer. To verify crc32s
* and length, a call to VerifyCrcAndLengths must be made after entry data
* has been processed.
+ *
+ * On non-Windows platforms this method does not modify internal state and
+ * can be called concurrently.
*/
int32_t FindEntry(const ZipArchiveHandle handle, const ZipString& entryName,
ZipEntry* data);
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 229487f..2e72832 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -501,9 +501,9 @@
property_set("vold.decrypt", "trigger_default_encryption");
} else if (ret == FS_MGR_MNTALL_DEV_NOT_ENCRYPTED) {
property_set("ro.crypto.state", "unencrypted");
- /* If fs_mgr determined this is an unencrypted device, then trigger
- * that action.
- */
+ ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
+ } else if (ret == FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) {
+ property_set("ro.crypto.state", "unsupported");
ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
} else if (ret == FS_MGR_MNTALL_DEV_NEEDS_RECOVERY) {
/* Setup a wipe via recovery, and reboot into recovery */
diff --git a/libmemunreachable/Allocator.cpp b/libmemunreachable/Allocator.cpp
index b75f1e5..68f654c 100644
--- a/libmemunreachable/Allocator.cpp
+++ b/libmemunreachable/Allocator.cpp
@@ -370,11 +370,11 @@
}
void* HeapImpl::AllocLocked(size_t size) {
- if (__predict_false(size > kMaxBucketAllocationSize)) {
+ if (size > kMaxBucketAllocationSize) {
return MapAlloc(size);
}
int bucket = size_to_bucket(size);
- if (__predict_false(free_chunks_[bucket].empty())) {
+ if (free_chunks_[bucket].empty()) {
Chunk *chunk = new Chunk(this, bucket);
free_chunks_[bucket].insert(chunk->node_);
}
diff --git a/libmemunreachable/Allocator.h b/libmemunreachable/Allocator.h
index b0a4d4c..a8f579e 100644
--- a/libmemunreachable/Allocator.h
+++ b/libmemunreachable/Allocator.h
@@ -24,6 +24,7 @@
#include <map>
#include <memory>
#include <set>
+#include <unordered_map>
#include <unordered_set>
#include <vector>
extern std::atomic<int> heap_count;
@@ -209,9 +210,12 @@
template<class T>
using list = std::list<T, Allocator<T>>;
-template<class T, class Key, class Compare = std::less<Key>>
+template<class Key, class T, class Compare = std::less<Key>>
using map = std::map<Key, T, Compare, Allocator<std::pair<const Key, T>>>;
+template<class Key, class T, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>>
+using unordered_map = std::unordered_map<Key, T, Hash, KeyEqual, Allocator<std::pair<const Key, T>>>;
+
template<class Key, class Hash = std::hash<Key>, class KeyEqual = std::equal_to<Key>>
using unordered_set = std::unordered_set<Key, Hash, KeyEqual, Allocator<Key>>;
diff --git a/libmemunreachable/Android.mk b/libmemunreachable/Android.mk
index 0df26a8..7b66d44 100644
--- a/libmemunreachable/Android.mk
+++ b/libmemunreachable/Android.mk
@@ -3,6 +3,7 @@
memunreachable_srcs := \
Allocator.cpp \
HeapWalker.cpp \
+ LeakFolding.cpp \
LeakPipe.cpp \
LineBuffer.cpp \
MemUnreachable.cpp \
@@ -12,7 +13,9 @@
memunreachable_test_srcs := \
tests/Allocator_test.cpp \
+ tests/DisableMalloc_test.cpp \
tests/HeapWalker_test.cpp \
+ tests/LeakFolding_test.cpp \
tests/MemUnreachable_test.cpp \
tests/ThreadCapture_test.cpp \
@@ -41,3 +44,22 @@
LOCAL_SHARED_LIBRARIES := libmemunreachable libbase liblog
include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := memunreachable_test
+LOCAL_SRC_FILES := \
+ Allocator.cpp \
+ HeapWalker.cpp \
+ LeakFolding.cpp \
+ tests/Allocator_test.cpp \
+ tests/HeapWalker_test.cpp \
+ tests/HostMallocStub.cpp \
+ tests/LeakFolding_test.cpp \
+
+LOCAL_CFLAGS := -std=c++14 -Wall -Wextra -Werror
+LOCAL_CLANG := true
+LOCAL_SHARED_LIBRARIES := libbase liblog
+LOCAL_MODULE_HOST_OS := linux
+
+include $(BUILD_HOST_NATIVE_TEST)
diff --git a/libmemunreachable/HeapWalker.cpp b/libmemunreachable/HeapWalker.cpp
index 1a0c33d..19393ec 100644
--- a/libmemunreachable/HeapWalker.cpp
+++ b/libmemunreachable/HeapWalker.cpp
@@ -21,17 +21,19 @@
#include "Allocator.h"
#include "HeapWalker.h"
+#include "LeakFolding.h"
#include "log.h"
bool HeapWalker::Allocation(uintptr_t begin, uintptr_t end) {
if (end == begin) {
end = begin + 1;
}
- auto inserted = allocations_.insert(std::pair<Range, RangeInfo>(Range{begin, end}, RangeInfo{false, false}));
+ Range range{begin, end};
+ auto inserted = allocations_.insert(std::pair<Range, AllocationInfo>(range, AllocationInfo{}));
if (inserted.second) {
valid_allocations_range_.begin = std::min(valid_allocations_range_.begin, begin);
valid_allocations_range_.end = std::max(valid_allocations_range_.end, end);
- allocation_bytes_ += end - begin;
+ allocation_bytes_ += range.size();
return true;
} else {
Range overlap = inserted.first->first;
@@ -44,27 +46,30 @@
}
}
-void HeapWalker::Walk(const Range& range, bool RangeInfo::*flag) {
- allocator::vector<Range> to_do(1, range, allocator_);
+bool HeapWalker::IsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info) {
+ if (ptr >= valid_allocations_range_.begin && ptr < valid_allocations_range_.end) {
+ AllocationMap::iterator it = allocations_.find(Range{ptr, ptr + 1});
+ if (it != allocations_.end()) {
+ *range = it->first;
+ *info = &it->second;
+ return true;
+ }
+ }
+ return false;
+}
+
+void HeapWalker::RecurseRoot(const Range& root) {
+ allocator::vector<Range> to_do(1, root, allocator_);
while (!to_do.empty()) {
Range range = to_do.back();
to_do.pop_back();
- uintptr_t begin = (range.begin + (sizeof(uintptr_t) - 1)) & ~(sizeof(uintptr_t) - 1);
- // TODO(ccross): we might need to consider a pointer to the end of a buffer
- // to be inside the buffer, which means the common case of a pointer to the
- // beginning of a buffer may keep two ranges live.
- for (uintptr_t i = begin; i < range.end; i += sizeof(uintptr_t)) {
- uintptr_t val = *reinterpret_cast<uintptr_t*>(i);
- if (val >= valid_allocations_range_.begin && val < valid_allocations_range_.end) {
- RangeMap::iterator it = allocations_.find(Range{val, val + 1});
- if (it != allocations_.end()) {
- if (!(it->second.*flag)) {
- to_do.push_back(it->first);
- it->second.*flag = true;
- }
- }
+
+ ForEachPtrInRange(range, [&](Range& ref_range, AllocationInfo* ref_info) {
+ if (!ref_info->referenced_from_root) {
+ ref_info->referenced_from_root = true;
+ to_do.push_back(ref_range);
}
- }
+ });
}
}
@@ -85,27 +90,22 @@
}
bool HeapWalker::DetectLeaks() {
+ // Recursively walk pointers from roots to mark referenced allocations
for (auto it = roots_.begin(); it != roots_.end(); it++) {
- Walk(*it, &RangeInfo::referenced_from_root);
+ RecurseRoot(*it);
}
Range vals;
vals.begin = reinterpret_cast<uintptr_t>(root_vals_.data());
vals.end = vals.begin + root_vals_.size() * sizeof(uintptr_t);
- Walk(vals, &RangeInfo::referenced_from_root);
- for (auto it = allocations_.begin(); it != allocations_.end(); it++) {
- if (!it->second.referenced_from_root) {
- Walk(it->first, &RangeInfo::referenced_from_leak);
- }
- }
+ RecurseRoot(vals);
return true;
}
bool HeapWalker::Leaked(allocator::vector<Range>& leaked, size_t limit,
size_t* num_leaks_out, size_t* leak_bytes_out) {
- DetectLeaks();
leaked.clear();
size_t num_leaks = 0;
@@ -120,7 +120,7 @@
size_t n = 0;
for (auto it = allocations_.begin(); it != allocations_.end(); it++) {
if (!it->second.referenced_from_root) {
- if (n++ <= limit) {
+ if (n++ < limit) {
leaked.push_back(it->first);
}
}
diff --git a/libmemunreachable/HeapWalker.h b/libmemunreachable/HeapWalker.h
index 4be1934..7b851c4 100644
--- a/libmemunreachable/HeapWalker.h
+++ b/libmemunreachable/HeapWalker.h
@@ -20,11 +20,14 @@
#include "android-base/macros.h"
#include "Allocator.h"
+#include "Tarjan.h"
// A range [begin, end)
struct Range {
uintptr_t begin;
uintptr_t end;
+
+ size_t size() const { return end - begin; };
};
// Comparator for Ranges that returns equivalence for overlapping ranges
@@ -34,7 +37,6 @@
}
};
-
class HeapWalker {
public:
HeapWalker(Allocator<HeapWalker> allocator) : allocator_(allocator),
@@ -55,16 +57,25 @@
size_t Allocations();
size_t AllocationBytes();
- private:
- struct RangeInfo {
+ template<class F>
+ void ForEachPtrInRange(const Range& range, F&& f);
+
+ template<class F>
+ void ForEachAllocation(F&& f);
+
+ struct AllocationInfo {
bool referenced_from_root;
- bool referenced_from_leak;
};
- void Walk(const Range& range, bool RangeInfo::* flag);
+
+ private:
+
+ void RecurseRoot(const Range& root);
+ bool IsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info);
+
DISALLOW_COPY_AND_ASSIGN(HeapWalker);
Allocator<HeapWalker> allocator_;
- using RangeMap = allocator::map<RangeInfo, Range, compare_range>;
- RangeMap allocations_;
+ using AllocationMap = allocator::map<Range, AllocationInfo, compare_range>;
+ AllocationMap allocations_;
size_t allocation_bytes_;
Range valid_allocations_range_;
@@ -72,4 +83,28 @@
allocator::vector<uintptr_t> root_vals_;
};
+template<class F>
+inline void HeapWalker::ForEachPtrInRange(const Range& range, F&& f) {
+ uintptr_t begin = (range.begin + (sizeof(uintptr_t) - 1)) & ~(sizeof(uintptr_t) - 1);
+ // TODO(ccross): we might need to consider a pointer to the end of a buffer
+ // to be inside the buffer, which means the common case of a pointer to the
+ // beginning of a buffer may keep two ranges live.
+ for (uintptr_t i = begin; i < range.end; i += sizeof(uintptr_t)) {
+ Range ref_range;
+ AllocationInfo* ref_info;
+ if (IsAllocationPtr(*reinterpret_cast<uintptr_t*>(i), &ref_range, &ref_info)) {
+ f(ref_range, ref_info);
+ }
+ }
+}
+
+template<class F>
+inline void HeapWalker::ForEachAllocation(F&& f) {
+ for (auto& it : allocations_) {
+ const Range& range = it.first;
+ HeapWalker::AllocationInfo& allocation = it.second;
+ f(range, allocation);
+ }
+}
+
#endif
diff --git a/libmemunreachable/Leak.h b/libmemunreachable/Leak.h
new file mode 100644
index 0000000..eaeeea7
--- /dev/null
+++ b/libmemunreachable/Leak.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LIBMEMUNREACHABLE_LEAK_H_
+#define LIBMEMUNREACHABLE_LEAK_H_
+
+#include <functional>
+#include <vector>
+
+#include "memunreachable/memunreachable.h"
+
+// Custom std::hash specialization so that Leak::Backtrace can be used
+// as a key in std::unordered_map.
+namespace std {
+
+template<>
+struct hash<Leak::Backtrace> {
+ std::size_t operator()(const Leak::Backtrace& key) const {
+ std::size_t seed = 0;
+
+ hash_combine(seed, key.num_frames);
+ for (size_t i = 0; i < key.num_frames; i++) {
+ hash_combine(seed, key.frames[i]);
+ }
+
+ return seed;
+ }
+
+ private:
+ template<typename T>
+ inline void hash_combine(std::size_t& seed, const T& v) const {
+ std::hash<T> hasher;
+ seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+ }
+};
+
+} // namespace std
+
+static bool operator==(const Leak::Backtrace& lhs, const Leak::Backtrace& rhs) {
+ return (lhs.num_frames == rhs.num_frames) &&
+ memcmp(lhs.frames, rhs.frames, lhs.num_frames * sizeof(lhs.frames[0])) == 0;
+}
+
+#endif
diff --git a/libmemunreachable/LeakFolding.cpp b/libmemunreachable/LeakFolding.cpp
new file mode 100644
index 0000000..be4d20c
--- /dev/null
+++ b/libmemunreachable/LeakFolding.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+
+#include "Allocator.h"
+#include "HeapWalker.h"
+#include "LeakFolding.h"
+#include "Tarjan.h"
+#include "log.h"
+
+// Converts possibly cyclic graph of leaks to a DAG by combining
+// strongly-connected components into a object, stored in the scc pointer
+// of each node in the component.
+void LeakFolding::ComputeDAG() {
+ SCCList<LeakInfo> scc_list{allocator_};
+ Tarjan(leak_graph_, scc_list);
+
+ Allocator<SCCInfo> scc_allocator = allocator_;
+
+ for (auto& scc_nodes: scc_list) {
+ Allocator<SCCInfo>::unique_ptr leak_scc;
+ leak_scc = scc_allocator.make_unique(scc_allocator);
+
+ for (auto& node: scc_nodes) {
+ node->ptr->scc = leak_scc.get();
+ leak_scc->count++;
+ leak_scc->size += node->ptr->range.size();
+ }
+
+ leak_scc_.emplace_back(std::move(leak_scc));
+ }
+
+ for (auto& it : leak_map_) {
+ LeakInfo& leak = it.second;
+ for (auto& ref: leak.node.references_out) {
+ if (leak.scc != ref->ptr->scc) {
+ leak.scc->node.Edge(&ref->ptr->scc->node);
+ }
+ }
+ }
+}
+
+void LeakFolding::AccumulateLeaks(SCCInfo* dominator) {
+ std::function<void(SCCInfo*)> walk(std::allocator_arg, allocator_,
+ [&](SCCInfo* scc) {
+ if (scc->accumulator != dominator) {
+ scc->accumulator = dominator;
+ dominator->cuumulative_size += scc->size;
+ dominator->cuumulative_count += scc->count;
+ scc->node.Foreach([&](SCCInfo* ref) {
+ walk(ref);
+ });
+ }
+ });
+ walk(dominator);
+}
+
+bool LeakFolding::FoldLeaks() {
+ Allocator<LeakInfo> leak_allocator = allocator_;
+
+ // Find all leaked allocations insert them into leak_map_ and leak_graph_
+ heap_walker_.ForEachAllocation(
+ [&](const Range& range, HeapWalker::AllocationInfo& allocation) {
+ if (!allocation.referenced_from_root) {
+ auto it = leak_map_.emplace(std::piecewise_construct,
+ std::forward_as_tuple(range),
+ std::forward_as_tuple(range, allocator_));
+ LeakInfo& leak = it.first->second;
+ leak_graph_.push_back(&leak.node);
+ }
+ });
+
+ // Find references between leaked allocations and connect them in leak_graph_
+ for (auto& it : leak_map_) {
+ LeakInfo& leak = it.second;
+ heap_walker_.ForEachPtrInRange(leak.range,
+ [&](Range& ptr_range, HeapWalker::AllocationInfo* ptr_info) {
+ if (!ptr_info->referenced_from_root) {
+ LeakInfo* ptr_leak = &leak_map_.at(ptr_range);
+ leak.node.Edge(&ptr_leak->node);
+ }
+ });
+ }
+
+ // Convert the cyclic graph to a DAG by grouping strongly connected components
+ ComputeDAG();
+
+ // Compute dominators and cuumulative sizes
+ for (auto& scc : leak_scc_) {
+ if (scc->node.references_in.size() == 0) {
+ scc->dominator = true;
+ AccumulateLeaks(scc.get());
+ }
+ }
+
+ return true;
+}
+
+bool LeakFolding::Leaked(allocator::vector<LeakFolding::Leak>& leaked,
+ size_t* num_leaks_out, size_t* leak_bytes_out) {
+ size_t num_leaks = 0;
+ size_t leak_bytes = 0;
+ for (auto& it : leak_map_) {
+ const LeakInfo& leak = it.second;
+ num_leaks++;
+ leak_bytes += leak.range.size();
+ }
+
+ for (auto& it : leak_map_) {
+ const LeakInfo& leak = it.second;
+ if (leak.scc->dominator) {
+ leaked.emplace_back(Leak{leak.range,
+ leak.scc->cuumulative_count - 1,
+ leak.scc->cuumulative_size - leak.range.size()});
+ }
+ }
+
+ if (num_leaks_out) {
+ *num_leaks_out = num_leaks;
+ }
+ if (leak_bytes_out) {
+ *leak_bytes_out = leak_bytes;
+ }
+
+ return true;
+}
diff --git a/libmemunreachable/LeakFolding.h b/libmemunreachable/LeakFolding.h
new file mode 100644
index 0000000..732d3f2
--- /dev/null
+++ b/libmemunreachable/LeakFolding.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LIBMEMUNREACHABLE_LEAK_FOLDING_H_
+#define LIBMEMUNREACHABLE_LEAK_FOLDING_H_
+
+#include "HeapWalker.h"
+
+class LeakFolding {
+ public:
+ LeakFolding(Allocator<void> allocator, HeapWalker& heap_walker)
+ : allocator_(allocator), heap_walker_(heap_walker),
+ leak_map_(allocator), leak_graph_(allocator), leak_scc_(allocator) {}
+
+ bool FoldLeaks();
+
+ struct Leak {
+ const Range range;
+ size_t referenced_count;
+ size_t referenced_size;
+ };
+
+ bool Leaked(allocator::vector<Leak>& leaked,
+ size_t* num_leaks_out, size_t* leak_bytes_out);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LeakFolding);
+ Allocator<void> allocator_;
+ HeapWalker& heap_walker_;
+
+ struct SCCInfo {
+ public:
+ Node<SCCInfo> node;
+
+ size_t count;
+ size_t size;
+
+ size_t cuumulative_count;
+ size_t cuumulative_size;
+
+ bool dominator;
+ SCCInfo* accumulator;
+
+ SCCInfo(Allocator<SCCInfo> allocator) : node(this, allocator),
+ count(0), size(0), cuumulative_count(0), cuumulative_size(0),
+ dominator(false), accumulator(nullptr) {}
+ private:
+ SCCInfo(SCCInfo&&) = delete;
+ DISALLOW_COPY_AND_ASSIGN(SCCInfo);
+ };
+
+ struct LeakInfo {
+ public:
+ Node<LeakInfo> node;
+
+ const Range range;
+
+ SCCInfo* scc;
+
+ LeakInfo(const Range& range, Allocator<LeakInfo> allocator)
+ : node(this, allocator), range(range),
+ scc(nullptr) {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LeakInfo);
+ };
+
+ void ComputeDAG();
+ void AccumulateLeaks(SCCInfo* dominator);
+
+ allocator::map<Range, LeakInfo, compare_range> leak_map_;
+ Graph<LeakInfo> leak_graph_;
+ allocator::vector<Allocator<SCCInfo>::unique_ptr> leak_scc_;
+};
+
+#endif // LIBMEMUNREACHABLE_LEAK_FOLDING_H_
diff --git a/libmemunreachable/MemUnreachable.cpp b/libmemunreachable/MemUnreachable.cpp
index eca26eb..ac19a66 100644
--- a/libmemunreachable/MemUnreachable.cpp
+++ b/libmemunreachable/MemUnreachable.cpp
@@ -21,12 +21,15 @@
#include <mutex>
#include <string>
#include <sstream>
+#include <unordered_map>
#include <backtrace.h>
#include <android-base/macros.h>
#include "Allocator.h"
#include "HeapWalker.h"
+#include "Leak.h"
+#include "LeakFolding.h"
#include "LeakPipe.h"
#include "ProcessMappings.h"
#include "PtracerThread.h"
@@ -117,32 +120,84 @@
return true;
}
-bool MemUnreachable::GetUnreachableMemory(allocator::vector<Leak>& leaks, size_t limit,
- size_t* num_leaks, size_t* leak_bytes) {
+bool MemUnreachable::GetUnreachableMemory(allocator::vector<Leak>& leaks,
+ size_t limit, size_t* num_leaks, size_t* leak_bytes) {
ALOGI("sweeping process %d for unreachable memory", pid_);
leaks.clear();
- allocator::vector<Range> leaked{allocator_};
- if (!heap_walker_.Leaked(leaked, limit, num_leaks, leak_bytes)) {
+ if (!heap_walker_.DetectLeaks()) {
return false;
}
- for (auto it = leaked.begin(); it != leaked.end(); it++) {
- Leak leak{};
- leak.begin = it->begin;
- leak.size = it->end - it->begin;;
- memcpy(leak.contents, reinterpret_cast<void*>(it->begin),
- std::min(leak.size, Leak::contents_length));
- ssize_t num_backtrace_frames = malloc_backtrace(reinterpret_cast<void*>(it->begin),
- leak.backtrace_frames, leak.backtrace_length);
- if (num_backtrace_frames > 0) {
- leak.num_backtrace_frames = num_backtrace_frames;
- }
- leaks.emplace_back(leak);
- }
+
+ allocator::vector<Range> leaked1{allocator_};
+ heap_walker_.Leaked(leaked1, 0, num_leaks, leak_bytes);
ALOGI("sweeping done");
+ ALOGI("folding related leaks");
+
+ LeakFolding folding(allocator_, heap_walker_);
+ if (!folding.FoldLeaks()) {
+ return false;
+ }
+
+ allocator::vector<LeakFolding::Leak> leaked{allocator_};
+
+ if (!folding.Leaked(leaked, num_leaks, leak_bytes)) {
+ return false;
+ }
+
+ allocator::unordered_map<Leak::Backtrace, Leak*> backtrace_map{allocator_};
+
+ // Prevent reallocations of backing memory so we can store pointers into it
+ // in backtrace_map.
+ leaks.reserve(leaked.size());
+
+ for (auto& it: leaked) {
+ leaks.emplace_back();
+ Leak* leak = &leaks.back();
+
+ ssize_t num_backtrace_frames = malloc_backtrace(reinterpret_cast<void*>(it.range.begin),
+ leak->backtrace.frames, leak->backtrace.max_frames);
+ if (num_backtrace_frames > 0) {
+ leak->backtrace.num_frames = num_backtrace_frames;
+
+ auto inserted = backtrace_map.emplace(leak->backtrace, leak);
+ if (!inserted.second) {
+ // Leak with same backtrace already exists, drop this one and
+ // increment similar counts on the existing one.
+ leaks.pop_back();
+ Leak* similar_leak = inserted.first->second;
+ similar_leak->similar_count++;
+ similar_leak->similar_size += it.range.size();
+ similar_leak->similar_referenced_count += it.referenced_count;
+ similar_leak->similar_referenced_size += it.referenced_size;
+ similar_leak->total_size += it.range.size();
+ similar_leak->total_size += it.referenced_size;
+ continue;
+ }
+ }
+
+ leak->begin = it.range.begin;
+ leak->size = it.range.size();
+ leak->referenced_count = it.referenced_count;
+ leak->referenced_size = it.referenced_size;
+ leak->total_size = leak->size + leak->referenced_size;
+ memcpy(leak->contents, reinterpret_cast<void*>(it.range.begin),
+ std::min(leak->size, Leak::contents_length));
+ }
+
+ ALOGI("folding done");
+
+ std::sort(leaks.begin(), leaks.end(), [](const Leak& a, const Leak& b) {
+ return a.total_size > b.total_size;
+ });
+
+ if (leaks.size() > limit) {
+ leaks.resize(limit);
+ }
+
return true;
}
@@ -203,6 +258,11 @@
return true;
}
+template<typename T>
+static inline const char* plural(T val) {
+ return (val == 1) ? "" : "s";
+}
+
bool GetUnreachableMemory(UnreachableMemoryInfo& info, size_t limit) {
int parent_pid = getpid();
int parent_tid = gettid();
@@ -339,9 +399,8 @@
ALOGI("unreachable memory detection done");
ALOGE("%zu bytes in %zu allocation%s unreachable out of %zu bytes in %zu allocation%s",
- info.leak_bytes, info.num_leaks, info.num_leaks == 1 ? "" : "s",
- info.allocation_bytes, info.num_allocations, info.num_allocations == 1 ? "" : "s");
-
+ info.leak_bytes, info.num_leaks, plural(info.num_leaks),
+ info.allocation_bytes, info.num_allocations, plural(info.num_allocations));
return true;
}
@@ -353,6 +412,23 @@
oss << " bytes unreachable at ";
oss << std::hex << begin;
oss << std::endl;
+ if (referenced_count > 0) {
+ oss << std::dec;
+ oss << " referencing " << referenced_size << " unreachable bytes";
+ oss << " in " << referenced_count << " allocation" << plural(referenced_count);
+ oss << std::endl;
+ }
+ if (similar_count > 0) {
+ oss << std::dec;
+ oss << " and " << similar_size << " similar unreachable bytes";
+ oss << " in " << similar_count << " allocation" << plural(similar_count);
+ oss << std::endl;
+ if (similar_referenced_count > 0) {
+ oss << " referencing " << similar_referenced_size << " unreachable bytes";
+ oss << " in " << similar_referenced_count << " allocation" << plural(similar_referenced_count);
+ oss << std::endl;
+ }
+ }
if (log_contents) {
const int bytes_per_line = 16;
@@ -361,7 +437,7 @@
if (bytes == size) {
oss << " contents:" << std::endl;
} else {
- oss << " first " << bytes << " bytes of contents:" << std::endl;
+ oss << " first " << bytes << " bytes of contents:" << std::endl;
}
for (size_t i = 0; i < bytes; i += bytes_per_line) {
@@ -385,21 +461,41 @@
oss << std::endl;
}
}
- if (num_backtrace_frames > 0) {
- oss << backtrace_string(backtrace_frames, num_backtrace_frames);
+ if (backtrace.num_frames > 0) {
+ oss << backtrace_string(backtrace.frames, backtrace.num_frames);
}
return oss.str();
}
+// Figure out the abi based on defined macros.
+#if defined(__arm__)
+#define ABI_STRING "arm"
+#elif defined(__aarch64__)
+#define ABI_STRING "arm64"
+#elif defined(__mips__) && !defined(__LP64__)
+#define ABI_STRING "mips"
+#elif defined(__mips__) && defined(__LP64__)
+#define ABI_STRING "mips64"
+#elif defined(__i386__)
+#define ABI_STRING "x86"
+#elif defined(__x86_64__)
+#define ABI_STRING "x86_64"
+#else
+#error "Unsupported ABI"
+#endif
+
std::string UnreachableMemoryInfo::ToString(bool log_contents) const {
std::ostringstream oss;
oss << " " << leak_bytes << " bytes in ";
- oss << num_leaks << " unreachable allocation" << (num_leaks == 1 ? "" : "s");
+ oss << num_leaks << " unreachable allocation" << plural(num_leaks);
+ oss << std::endl;
+ oss << " ABI: '" ABI_STRING "'" << std::endl;
oss << std::endl;
for (auto it = leaks.begin(); it != leaks.end(); it++) {
oss << it->ToString(log_contents);
+ oss << std::endl;
}
return oss.str();
diff --git a/libmemunreachable/ScopedAlarm.h b/libmemunreachable/ScopedAlarm.h
index 019deea..287f479 100644
--- a/libmemunreachable/ScopedAlarm.h
+++ b/libmemunreachable/ScopedAlarm.h
@@ -18,6 +18,7 @@
#define LIBMEMUNREACHABLE_SCOPED_ALARM_H_
#include <signal.h>
+#include <sys/time.h>
#include <chrono>
#include <functional>
diff --git a/libmemunreachable/Tarjan.h b/libmemunreachable/Tarjan.h
new file mode 100644
index 0000000..d7ecdb9
--- /dev/null
+++ b/libmemunreachable/Tarjan.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Based on system/update_engine/payload_generator/tarjan.cc
+
+#ifndef LIBMEMUNREACHABLE_TARJAN_H_
+#define LIBMEMUNREACHABLE_TARJAN_H_
+
+#include <algorithm>
+
+#include "Allocator.h"
+
+template<class T>
+class Node {
+ public:
+ allocator::set<Node<T>*> references_in;
+ allocator::set<Node<T>*> references_out;
+ size_t index;
+ size_t lowlink;
+
+ T* ptr;
+
+ Node(T* ptr, Allocator<Node> allocator) : references_in(allocator), references_out(allocator),
+ ptr(ptr) {};
+ Node(Node&& rhs) = default;
+ void Edge(Node<T>* ref) {
+ references_out.emplace(ref);
+ ref->references_in.emplace(this);
+ }
+ template<class F>
+ void Foreach(F&& f) {
+ for (auto& node: references_out) {
+ f(node->ptr);
+ }
+ }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Node<T>);
+};
+
+template<class T>
+using Graph = allocator::vector<Node<T>*>;
+
+template<class T>
+using SCC = allocator::vector<Node<T>*>;
+
+template<class T>
+using SCCList = allocator::vector<SCC<T>>;
+
+template<class T>
+class TarjanAlgorithm {
+ public:
+ TarjanAlgorithm(Allocator<void> allocator) : index_(0),
+ stack_(allocator), components_(allocator) {}
+
+ void Execute(Graph<T>& graph, SCCList<T>& out);
+ private:
+ static constexpr size_t UNDEFINED_INDEX = static_cast<size_t>(-1);
+ void Tarjan(Node<T>* vertex, Graph<T>& graph);
+
+ size_t index_;
+ allocator::vector<Node<T>*> stack_;
+ SCCList<T> components_;
+};
+
+template<class T>
+void TarjanAlgorithm<T>::Execute(Graph<T>& graph, SCCList<T>& out) {
+ stack_.clear();
+ components_.clear();
+ index_ = 0;
+ for (auto& it: graph) {
+ it->index = UNDEFINED_INDEX;
+ it->lowlink = UNDEFINED_INDEX;
+ }
+
+ for (auto& it: graph) {
+ if (it->index == UNDEFINED_INDEX) {
+ Tarjan(it, graph);
+ }
+ }
+ out.swap(components_);
+}
+
+template<class T>
+void TarjanAlgorithm<T>::Tarjan(Node<T>* vertex, Graph<T>& graph) {
+ assert(vertex->index == UNDEFINED_INDEX);
+ vertex->index = index_;
+ vertex->lowlink = index_;
+ index_++;
+ stack_.push_back(vertex);
+ for (auto& it: vertex->references_out) {
+ Node<T>* vertex_next = it;
+ if (vertex_next->index == UNDEFINED_INDEX) {
+ Tarjan(vertex_next, graph);
+ vertex->lowlink = std::min(vertex->lowlink, vertex_next->lowlink);
+ } else if (std::find(stack_.begin(), stack_.end(), vertex_next) != stack_.end()) {
+ vertex->lowlink = std::min(vertex->lowlink, vertex_next->index);
+ }
+ }
+ if (vertex->lowlink == vertex->index) {
+ SCC<T> component{components_.get_allocator()};
+ Node<T>* other_vertex;
+ do {
+ other_vertex = stack_.back();
+ stack_.pop_back();
+ component.push_back(other_vertex);
+ } while (other_vertex != vertex && !stack_.empty());
+
+ components_.emplace_back(component);
+ }
+}
+
+template<class T>
+void Tarjan(Graph<T>& graph, SCCList<T>& out) {
+ TarjanAlgorithm<T> tarjan{graph.get_allocator()};
+ tarjan.Execute(graph, out);
+}
+
+#endif // LIBMEMUNREACHABLE_TARJAN_H_
diff --git a/libmemunreachable/ThreadCapture.cpp b/libmemunreachable/ThreadCapture.cpp
index 6357840..e8a8392 100644
--- a/libmemunreachable/ThreadCapture.cpp
+++ b/libmemunreachable/ThreadCapture.cpp
@@ -86,7 +86,7 @@
void PtraceDetach(pid_t tid, unsigned int signal);
bool PtraceThreadInfo(pid_t tid, ThreadInfo& thread_info);
- allocator::map<unsigned int, pid_t> captured_threads_;
+ allocator::map<pid_t, unsigned int> captured_threads_;
Allocator<ThreadCaptureImpl> allocator_;
pid_t pid_;
std::function<void(pid_t)> inject_test_func_;
diff --git a/libmemunreachable/bionic.h b/libmemunreachable/bionic.h
index 92de24a..83d07a8 100644
--- a/libmemunreachable/bionic.h
+++ b/libmemunreachable/bionic.h
@@ -18,6 +18,8 @@
#define LIBMEMUNREACHABLE_BIONIC_H_
#include <sys/cdefs.h>
+#include <stdint.h>
+#include <stdlib.h>
__BEGIN_DECLS
diff --git a/libmemunreachable/include/memunreachable/memunreachable.h b/libmemunreachable/include/memunreachable/memunreachable.h
index f4f01ce..9b227fd 100644
--- a/libmemunreachable/include/memunreachable/memunreachable.h
+++ b/libmemunreachable/include/memunreachable/memunreachable.h
@@ -27,11 +27,26 @@
struct Leak {
uintptr_t begin;
size_t size;
- size_t num_backtrace_frames;
+
+ size_t referenced_count;
+ size_t referenced_size;
+
+ size_t similar_count;
+ size_t similar_size;
+ size_t similar_referenced_count;
+ size_t similar_referenced_size;
+
+ size_t total_size;
+
static const size_t contents_length = 32;
char contents[contents_length];
- static const size_t backtrace_length = 16;
- uintptr_t backtrace_frames[backtrace_length];
+
+ struct Backtrace {
+ size_t num_frames;
+
+ static const size_t max_frames = 16;
+ uintptr_t frames[max_frames];
+ } backtrace;
std::string ToString(bool log_contents) const;
};
diff --git a/libmemunreachable/tests/Allocator_test.cpp b/libmemunreachable/tests/Allocator_test.cpp
index d8e473e..fa76ae0 100644
--- a/libmemunreachable/tests/Allocator_test.cpp
+++ b/libmemunreachable/tests/Allocator_test.cpp
@@ -15,12 +15,6 @@
*/
#include <Allocator.h>
-#include <sys/time.h>
-
-#include <chrono>
-#include <functional>
-#include <list>
-#include <vector>
#include <gtest/gtest.h>
#include <ScopedDisableMalloc.h>
@@ -28,8 +22,6 @@
std::function<void()> ScopedAlarm::func_;
-using namespace std::chrono_literals;
-
class AllocatorTest : public testing::Test {
protected:
AllocatorTest() : heap(), disable_malloc_() {}
@@ -180,94 +172,3 @@
ASSERT_NE(ptr, nullptr);
}
-
-class DisableMallocTest : public ::testing::Test {
- protected:
- void alarm(std::chrono::microseconds us) {
- std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(us);
- itimerval t = itimerval();
- t.it_value.tv_sec = s.count();
- t.it_value.tv_usec = (us - s).count();
- setitimer(ITIMER_REAL, &t, NULL);
- }
-};
-
-TEST_F(DisableMallocTest, reenable) {
- ASSERT_EXIT({
- alarm(100ms);
- void *ptr1 = malloc(128);
- ASSERT_NE(ptr1, nullptr);
- free(ptr1);
- {
- ScopedDisableMalloc disable_malloc;
- }
- void *ptr2 = malloc(128);
- ASSERT_NE(ptr2, nullptr);
- free(ptr2);
- _exit(1);
- }, ::testing::ExitedWithCode(1), "");
-}
-
-TEST_F(DisableMallocTest, deadlock_allocate) {
- ASSERT_DEATH({
- void *ptr = malloc(128);
- ASSERT_NE(ptr, nullptr);
- free(ptr);
- {
- alarm(100ms);
- ScopedDisableMalloc disable_malloc;
- void* ptr = malloc(128);
- ASSERT_NE(ptr, nullptr);
- free(ptr);
- }
- }, "");
-}
-
-TEST_F(DisableMallocTest, deadlock_new) {
- ASSERT_DEATH({
- char* ptr = new(char);
- ASSERT_NE(ptr, nullptr);
- delete(ptr);
- {
- alarm(100ms);
- ScopedDisableMalloc disable_malloc;
- char* ptr = new(char);
- ASSERT_NE(ptr, nullptr);
- delete(ptr);
- }
- }, "");
-}
-
-TEST_F(DisableMallocTest, deadlock_delete) {
- ASSERT_DEATH({
- char* ptr = new(char);
- ASSERT_NE(ptr, nullptr);
- {
- alarm(250ms);
- ScopedDisableMalloc disable_malloc;
- delete(ptr);
- }
- }, "");
-}
-
-TEST_F(DisableMallocTest, deadlock_free) {
- ASSERT_DEATH({
- void *ptr = malloc(128);
- ASSERT_NE(ptr, nullptr);
- {
- alarm(100ms);
- ScopedDisableMalloc disable_malloc;
- free(ptr);
- }
- }, "");
-}
-
-TEST_F(DisableMallocTest, deadlock_fork) {
- ASSERT_DEATH({
- {
- alarm(100ms);
- ScopedDisableMalloc disable_malloc;
- fork();
- }
- }, "");
-}
diff --git a/libmemunreachable/tests/DisableMalloc_test.cpp b/libmemunreachable/tests/DisableMalloc_test.cpp
new file mode 100644
index 0000000..ea5c22c
--- /dev/null
+++ b/libmemunreachable/tests/DisableMalloc_test.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/time.h>
+
+#include <chrono>
+#include <functional>
+
+#include <gtest/gtest.h>
+#include <ScopedDisableMalloc.h>
+
+using namespace std::chrono_literals;
+
+class DisableMallocTest : public ::testing::Test {
+ protected:
+ void alarm(std::chrono::microseconds us) {
+ std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(us);
+ itimerval t = itimerval();
+ t.it_value.tv_sec = s.count();
+ t.it_value.tv_usec = (us - s).count();
+ setitimer(ITIMER_REAL, &t, NULL);
+ }
+};
+
+TEST_F(DisableMallocTest, reenable) {
+ ASSERT_EXIT({
+ alarm(100ms);
+ void *ptr1 = malloc(128);
+ ASSERT_NE(ptr1, nullptr);
+ free(ptr1);
+ {
+ ScopedDisableMalloc disable_malloc;
+ }
+ void *ptr2 = malloc(128);
+ ASSERT_NE(ptr2, nullptr);
+ free(ptr2);
+ _exit(1);
+ }, ::testing::ExitedWithCode(1), "");
+}
+
+TEST_F(DisableMallocTest, deadlock_allocate) {
+ ASSERT_DEATH({
+ void *ptr = malloc(128);
+ ASSERT_NE(ptr, nullptr);
+ free(ptr);
+ {
+ alarm(100ms);
+ ScopedDisableMalloc disable_malloc;
+ void* ptr = malloc(128);
+ ASSERT_NE(ptr, nullptr);
+ free(ptr);
+ }
+ }, "");
+}
+
+TEST_F(DisableMallocTest, deadlock_new) {
+ ASSERT_DEATH({
+ char* ptr = new(char);
+ ASSERT_NE(ptr, nullptr);
+ delete(ptr);
+ {
+ alarm(100ms);
+ ScopedDisableMalloc disable_malloc;
+ char* ptr = new(char);
+ ASSERT_NE(ptr, nullptr);
+ delete(ptr);
+ }
+ }, "");
+}
+
+TEST_F(DisableMallocTest, deadlock_delete) {
+ ASSERT_DEATH({
+ char* ptr = new(char);
+ ASSERT_NE(ptr, nullptr);
+ {
+ alarm(250ms);
+ ScopedDisableMalloc disable_malloc;
+ delete(ptr);
+ }
+ }, "");
+}
+
+TEST_F(DisableMallocTest, deadlock_free) {
+ ASSERT_DEATH({
+ void *ptr = malloc(128);
+ ASSERT_NE(ptr, nullptr);
+ {
+ alarm(100ms);
+ ScopedDisableMalloc disable_malloc;
+ free(ptr);
+ }
+ }, "");
+}
+
+TEST_F(DisableMallocTest, deadlock_fork) {
+ ASSERT_DEATH({
+ {
+ alarm(100ms);
+ ScopedDisableMalloc disable_malloc;
+ fork();
+ }
+ }, "");
+}
diff --git a/libmemunreachable/tests/HeapWalker_test.cpp b/libmemunreachable/tests/HeapWalker_test.cpp
index 9921eb6..c3e1c4d 100644
--- a/libmemunreachable/tests/HeapWalker_test.cpp
+++ b/libmemunreachable/tests/HeapWalker_test.cpp
@@ -80,6 +80,8 @@
HeapWalker heap_walker(heap_);
heap_walker.Allocation(buffer_begin(buffer2), buffer_end(buffer2));
+ ASSERT_EQ(true, heap_walker.DetectLeaks());
+
allocator::vector<Range> leaked(heap_);
size_t num_leaks = 0;
size_t leaked_bytes = 0;
@@ -106,9 +108,11 @@
heap_walker.Allocation(buffer_begin(buffer2), buffer_end(buffer2));
heap_walker.Root(buffer_begin(buffer1), buffer_end(buffer1));
+ ASSERT_EQ(true, heap_walker.DetectLeaks());
+
allocator::vector<Range> leaked(heap_);
- size_t num_leaks = SIZE_T_MAX;
- size_t leaked_bytes = SIZE_T_MAX;
+ size_t num_leaks = SIZE_MAX;
+ size_t leaked_bytes = SIZE_MAX;
ASSERT_EQ(true, heap_walker.Leaked(leaked, 100, &num_leaks, &leaked_bytes));
EXPECT_EQ(0U, num_leaks);
@@ -132,9 +136,11 @@
heap_walker.Allocation(buffer_begin(buffer2), buffer_end(buffer2));
heap_walker.Root(buffer_begin(buffer1) + i, buffer_end(buffer1) - j);
+ ASSERT_EQ(true, heap_walker.DetectLeaks());
+
allocator::vector<Range> leaked(heap_);
- size_t num_leaks = SIZE_T_MAX;
- size_t leaked_bytes = SIZE_T_MAX;
+ size_t num_leaks = SIZE_MAX;
+ size_t leaked_bytes = SIZE_MAX;
ASSERT_EQ(true, heap_walker.Leaked(leaked, 100, &num_leaks, &leaked_bytes));
EXPECT_EQ(0U, num_leaks);
@@ -143,3 +149,26 @@
}
}
}
+
+TEST_F(HeapWalkerTest, cycle) {
+ void* buffer1;
+ void* buffer2;
+
+ buffer1 = &buffer2;
+ buffer2 = &buffer1;
+
+ HeapWalker heap_walker(heap_);
+ heap_walker.Allocation(buffer_begin(buffer1), buffer_end(buffer1));
+ heap_walker.Allocation(buffer_begin(buffer2), buffer_end(buffer2));
+
+ ASSERT_EQ(true, heap_walker.DetectLeaks());
+
+ allocator::vector<Range> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, heap_walker.Leaked(leaked, 100, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(2U, num_leaks);
+ EXPECT_EQ(2*sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(2U, leaked.size());
+}
diff --git a/libmemunreachable/tests/HostMallocStub.cpp b/libmemunreachable/tests/HostMallocStub.cpp
new file mode 100644
index 0000000..a7e3f07
--- /dev/null
+++ b/libmemunreachable/tests/HostMallocStub.cpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "bionic.h"
+
+void malloc_disable() {
+}
+
+void malloc_enable() {
+}
diff --git a/libmemunreachable/tests/LeakFolding_test.cpp b/libmemunreachable/tests/LeakFolding_test.cpp
new file mode 100644
index 0000000..879a3a0
--- /dev/null
+++ b/libmemunreachable/tests/LeakFolding_test.cpp
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "HeapWalker.h"
+#include "LeakFolding.h"
+
+#include <gtest/gtest.h>
+#include <ScopedDisableMalloc.h>
+#include "Allocator.h"
+
+class LeakFoldingTest : public ::testing::Test {
+ public:
+ LeakFoldingTest() : disable_malloc_(), heap_() {}
+
+ void TearDown() {
+ ASSERT_TRUE(heap_.empty());
+ if (!HasFailure()) {
+ ASSERT_FALSE(disable_malloc_.timed_out());
+ }
+ }
+
+ protected:
+ ScopedDisableMallocTimeout disable_malloc_;
+ Heap heap_;
+};
+
+#define buffer_begin(buffer) reinterpret_cast<uintptr_t>(&buffer[0])
+#define buffer_end(buffer) (reinterpret_cast<uintptr_t>(&buffer[0]) + sizeof(buffer))
+#define ALLOCATION(heap_walker, buffer) \
+ ASSERT_EQ(true, heap_walker.Allocation(buffer_begin(buffer), buffer_end(buffer)))
+
+TEST_F(LeakFoldingTest, one) {
+ void* buffer1[1] = {nullptr};
+
+ HeapWalker heap_walker(heap_);
+
+ ALLOCATION(heap_walker, buffer1);
+
+ LeakFolding folding(heap_, heap_walker);
+
+ ASSERT_TRUE(folding.FoldLeaks());
+
+ allocator::vector<LeakFolding::Leak> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, folding.Leaked(leaked, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(1U, num_leaks);
+ EXPECT_EQ(sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(1U, leaked.size());
+ EXPECT_EQ(0U, leaked[0].referenced_count);
+ EXPECT_EQ(0U, leaked[0].referenced_size);
+}
+
+TEST_F(LeakFoldingTest, two) {
+ void* buffer1[1] = {nullptr};
+ void* buffer2[1] = {nullptr};
+
+ HeapWalker heap_walker(heap_);
+
+ ALLOCATION(heap_walker, buffer1);
+ ALLOCATION(heap_walker, buffer2);
+
+ LeakFolding folding(heap_, heap_walker);
+
+ ASSERT_TRUE(folding.FoldLeaks());
+
+ allocator::vector<LeakFolding::Leak> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, folding.Leaked(leaked, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(2U, num_leaks);
+ EXPECT_EQ(2*sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(2U, leaked.size());
+ EXPECT_EQ(0U, leaked[0].referenced_count);
+ EXPECT_EQ(0U, leaked[0].referenced_size);
+ EXPECT_EQ(0U, leaked[1].referenced_count);
+ EXPECT_EQ(0U, leaked[1].referenced_size);
+}
+
+TEST_F(LeakFoldingTest, dominator) {
+ void* buffer1[1];
+ void* buffer2[1] = {nullptr};
+
+ buffer1[0] = buffer2;
+
+ HeapWalker heap_walker(heap_);
+
+ ALLOCATION(heap_walker, buffer1);
+ ALLOCATION(heap_walker, buffer2);
+
+ LeakFolding folding(heap_, heap_walker);
+
+ ASSERT_TRUE(folding.FoldLeaks());
+
+ allocator::vector<LeakFolding::Leak> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, folding.Leaked(leaked, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(2U, num_leaks);
+ EXPECT_EQ(2*sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(1U, leaked.size());
+ EXPECT_EQ(1U, leaked[0].referenced_count);
+ EXPECT_EQ(sizeof(uintptr_t), leaked[0].referenced_size);
+}
+
+TEST_F(LeakFoldingTest, cycle) {
+ void* buffer1[1];
+ void* buffer2[1];
+ void* buffer3[1];
+
+ buffer1[0] = buffer2;
+ buffer2[0] = buffer3;
+ buffer3[0] = buffer2;
+
+ HeapWalker heap_walker(heap_);
+
+ ALLOCATION(heap_walker, buffer1);
+ ALLOCATION(heap_walker, buffer2);
+ ALLOCATION(heap_walker, buffer3);
+
+ LeakFolding folding(heap_, heap_walker);
+
+ ASSERT_TRUE(folding.FoldLeaks());
+
+ allocator::vector<LeakFolding::Leak> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, folding.Leaked(leaked, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(3U, num_leaks);
+ EXPECT_EQ(3*sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(1U, leaked.size());
+ EXPECT_EQ(2U, leaked[0].referenced_count);
+ EXPECT_EQ(2*sizeof(uintptr_t), leaked[0].referenced_size);
+}
+
+TEST_F(LeakFoldingTest, dominator_cycle) {
+ void* buffer1[2] = {nullptr, nullptr};
+ void* buffer2[2];
+ void* buffer3[1] = {nullptr};
+
+ buffer1[0] = &buffer2;
+ buffer2[0] = &buffer1;
+ buffer2[1] = &buffer3;
+
+ HeapWalker heap_walker(heap_);
+
+ ALLOCATION(heap_walker, buffer1);
+ ALLOCATION(heap_walker, buffer2);
+ ALLOCATION(heap_walker, buffer3);
+
+ LeakFolding folding(heap_, heap_walker);
+
+ ASSERT_TRUE(folding.FoldLeaks());
+
+ allocator::vector<LeakFolding::Leak> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, folding.Leaked(leaked, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(3U, num_leaks);
+ EXPECT_EQ(5*sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(2U, leaked.size());
+
+ EXPECT_EQ(2U, leaked[0].referenced_count);
+ EXPECT_EQ(3*sizeof(uintptr_t), leaked[0].referenced_size);
+ EXPECT_EQ(2U, leaked[1].referenced_count);
+ EXPECT_EQ(3*sizeof(uintptr_t), leaked[1].referenced_size);
+}
+
+TEST_F(LeakFoldingTest, two_cycles) {
+ void* buffer1[1];
+ void* buffer2[1];
+ void* buffer3[1];
+ void* buffer4[1];
+ void* buffer5[1];
+ void* buffer6[1];
+
+ buffer1[0] = buffer3;
+ buffer2[0] = buffer5;
+ buffer3[0] = buffer4;
+ buffer4[0] = buffer3;
+ buffer5[0] = buffer6;
+ buffer6[0] = buffer5;
+
+ HeapWalker heap_walker(heap_);
+
+ ALLOCATION(heap_walker, buffer1);
+ ALLOCATION(heap_walker, buffer2);
+ ALLOCATION(heap_walker, buffer3);
+ ALLOCATION(heap_walker, buffer4);
+ ALLOCATION(heap_walker, buffer5);
+ ALLOCATION(heap_walker, buffer6);
+
+ LeakFolding folding(heap_, heap_walker);
+
+ ASSERT_TRUE(folding.FoldLeaks());
+
+ allocator::vector<LeakFolding::Leak> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, folding.Leaked(leaked, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(6U, num_leaks);
+ EXPECT_EQ(6*sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(2U, leaked.size());
+ EXPECT_EQ(2U, leaked[0].referenced_count);
+ EXPECT_EQ(2*sizeof(uintptr_t), leaked[0].referenced_size);
+ EXPECT_EQ(2U, leaked[1].referenced_count);
+ EXPECT_EQ(2*sizeof(uintptr_t), leaked[1].referenced_size);
+}
+
+TEST_F(LeakFoldingTest, two_dominator_cycles) {
+ void* buffer1[1];
+ void* buffer2[1];
+ void* buffer3[1];
+ void* buffer4[1];
+
+ buffer1[0] = buffer2;
+ buffer2[0] = buffer1;
+ buffer3[0] = buffer4;
+ buffer4[0] = buffer3;
+
+ HeapWalker heap_walker(heap_);
+
+ ALLOCATION(heap_walker, buffer1);
+ ALLOCATION(heap_walker, buffer2);
+ ALLOCATION(heap_walker, buffer3);
+ ALLOCATION(heap_walker, buffer4);
+
+ LeakFolding folding(heap_, heap_walker);
+
+ ASSERT_TRUE(folding.FoldLeaks());
+
+ allocator::vector<LeakFolding::Leak> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, folding.Leaked(leaked, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(4U, num_leaks);
+ EXPECT_EQ(4*sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(4U, leaked.size());
+ EXPECT_EQ(1U, leaked[0].referenced_count);
+ EXPECT_EQ(sizeof(uintptr_t), leaked[0].referenced_size);
+ EXPECT_EQ(1U, leaked[1].referenced_count);
+ EXPECT_EQ(sizeof(uintptr_t), leaked[1].referenced_size);
+ EXPECT_EQ(1U, leaked[2].referenced_count);
+ EXPECT_EQ(sizeof(uintptr_t), leaked[2].referenced_size);
+ EXPECT_EQ(1U, leaked[3].referenced_count);
+ EXPECT_EQ(sizeof(uintptr_t), leaked[3].referenced_size);
+}
+
+TEST_F(LeakFoldingTest, giant_dominator_cycle) {
+ const size_t n = 1000;
+ void* buffer[n];
+
+ HeapWalker heap_walker(heap_);
+
+ for (size_t i = 0; i < n; i ++) {
+ ASSERT_TRUE(heap_walker.Allocation(reinterpret_cast<uintptr_t>(&buffer[i]),
+ reinterpret_cast<uintptr_t>(&buffer[i+1])));
+ }
+
+ for (size_t i = 0; i < n - 1; i++) {
+ buffer[i] = &buffer[i+1];
+ }
+ buffer[n - 1] = &buffer[0];
+
+ LeakFolding folding(heap_, heap_walker);
+
+ ASSERT_TRUE(folding.FoldLeaks());
+
+ allocator::vector<LeakFolding::Leak> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, folding.Leaked(leaked, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(n, num_leaks);
+ EXPECT_EQ(n * sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(1000U, leaked.size());
+ EXPECT_EQ(n - 1, leaked[0].referenced_count);
+ EXPECT_EQ((n - 1) * sizeof(uintptr_t), leaked[0].referenced_size);
+}
+
+TEST_F(LeakFoldingTest, giant_cycle) {
+ const size_t n = 1000;
+ void* buffer[n];
+ void* buffer1[1];
+
+ HeapWalker heap_walker(heap_);
+
+ for (size_t i = 0; i < n - 1; i++) {
+ buffer[i] = &buffer[i+1];
+ }
+ buffer[n - 1] = &buffer[0];
+
+ buffer1[0] = &buffer[0];
+
+ for (size_t i = 0; i < n; i ++) {
+ ASSERT_TRUE(heap_walker.Allocation(reinterpret_cast<uintptr_t>(&buffer[i]),
+ reinterpret_cast<uintptr_t>(&buffer[i+1])));
+ }
+
+ ALLOCATION(heap_walker, buffer1);
+
+ LeakFolding folding(heap_, heap_walker);
+
+ ASSERT_TRUE(folding.FoldLeaks());
+
+ allocator::vector<LeakFolding::Leak> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, folding.Leaked(leaked, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(n + 1, num_leaks);
+ EXPECT_EQ((n + 1) * sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(1U, leaked.size());
+ EXPECT_EQ(n, leaked[0].referenced_count);
+ EXPECT_EQ(n * sizeof(uintptr_t), leaked[0].referenced_size);
+}
+
+TEST_F(LeakFoldingTest, multipath) {
+ void* buffer1[2];
+ void* buffer2[1];
+ void* buffer3[1];
+ void* buffer4[1] = {nullptr};
+
+ // 1
+ // / \
+ // v v
+ // 2 3
+ // \ /
+ // v
+ // 4
+
+ buffer1[0] = &buffer2;
+ buffer1[1] = &buffer3;
+ buffer2[0] = &buffer4;
+ buffer3[0] = &buffer4;
+
+ HeapWalker heap_walker(heap_);
+
+ ALLOCATION(heap_walker, buffer1);
+ ALLOCATION(heap_walker, buffer2);
+ ALLOCATION(heap_walker, buffer3);
+ ALLOCATION(heap_walker, buffer4);
+
+ LeakFolding folding(heap_, heap_walker);
+
+ ASSERT_TRUE(folding.FoldLeaks());
+
+ allocator::vector<LeakFolding::Leak> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, folding.Leaked(leaked, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(4U, num_leaks);
+ EXPECT_EQ(5 * sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(1U, leaked.size());
+ EXPECT_EQ(3U, leaked[0].referenced_count);
+ EXPECT_EQ(3 * sizeof(uintptr_t), leaked[0].referenced_size);
+}
+
+TEST_F(LeakFoldingTest, multicycle) {
+ void* buffer1[2]{};
+ void* buffer2[2]{};
+ void* buffer3[2]{};
+ void* buffer4[2]{};
+
+ // 1
+ // / ^
+ // v \
+ // 2 -> 3
+ // \ ^
+ // v /
+ // 4
+
+ buffer1[0] = &buffer2;
+ buffer2[0] = &buffer3;
+ buffer2[1] = &buffer4;
+ buffer3[0] = &buffer1;
+ buffer4[0] = &buffer3;
+
+ HeapWalker heap_walker(heap_);
+
+ ALLOCATION(heap_walker, buffer1);
+ ALLOCATION(heap_walker, buffer2);
+ ALLOCATION(heap_walker, buffer3);
+ ALLOCATION(heap_walker, buffer4);
+
+ LeakFolding folding(heap_, heap_walker);
+
+ ASSERT_TRUE(folding.FoldLeaks());
+
+ allocator::vector<LeakFolding::Leak> leaked(heap_);
+ size_t num_leaks = 0;
+ size_t leaked_bytes = 0;
+ ASSERT_EQ(true, folding.Leaked(leaked, &num_leaks, &leaked_bytes));
+
+ EXPECT_EQ(4U, num_leaks);
+ EXPECT_EQ(8 * sizeof(uintptr_t), leaked_bytes);
+ ASSERT_EQ(4U, leaked.size());
+ EXPECT_EQ(3U, leaked[0].referenced_count);
+ EXPECT_EQ(6 * sizeof(uintptr_t), leaked[0].referenced_size);
+ EXPECT_EQ(3U, leaked[1].referenced_count);
+ EXPECT_EQ(6 * sizeof(uintptr_t), leaked[1].referenced_size);
+ EXPECT_EQ(3U, leaked[2].referenced_count);
+ EXPECT_EQ(6 * sizeof(uintptr_t), leaked[2].referenced_size);
+ EXPECT_EQ(3U, leaked[3].referenced_count);
+ EXPECT_EQ(6 * sizeof(uintptr_t), leaked[3].referenced_size);
+}
diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp
index ce893db..3e4b15a 100644
--- a/libnativeloader/native_loader.cpp
+++ b/libnativeloader/native_loader.cpp
@@ -130,7 +130,12 @@
// TODO (dimitry): This is a workaround for http://b/26436837
// will be removed before the release.
if (target_sdk_version <= 23) {
- publicNativeLibraries += ":libart.so";
+ // check if libart.so is loaded.
+ void* handle = dlopen("libart.so", RTLD_NOW | RTLD_NOLOAD);
+ if (handle != nullptr) {
+ publicNativeLibraries += ":libart.so";
+ dlclose(handle);
+ }
}
// END OF WORKAROUND
diff --git a/libziparchive/Android.mk b/libziparchive/Android.mk
index 056b3e1..3cd8b87 100644
--- a/libziparchive/Android.mk
+++ b/libziparchive/Android.mk
@@ -95,13 +95,12 @@
LOCAL_CFLAGS := $(libziparchive_common_c_flags)
LOCAL_CPPFLAGS := -Wno-unnamed-type-template-args $(libziparchive_common_cpp_flags)
LOCAL_SRC_FILES := $(libziparchive_test_files)
-LOCAL_SHARED_LIBRARIES := \
- libziparchive-host \
- liblog \
- libbase \
-
LOCAL_STATIC_LIBRARIES := \
- libutils \
+ libziparchive-host \
libz \
+ libbase \
+ libutils \
+ liblog \
+LOCAL_MODULE_HOST_OS := darwin linux windows
include $(BUILD_HOST_NATIVE_TEST)
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index 3b1e972..1f27500 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -224,9 +224,7 @@
strerror(errno));
return kIoError;
}
- ssize_t actual = TEMP_FAILURE_RETRY(
- read(fd, scan_buffer, static_cast<size_t>(read_amount)));
- if (actual != static_cast<ssize_t>(read_amount)) {
+ if (!android::base::ReadFully(fd, scan_buffer, static_cast<size_t>(read_amount))) {
ALOGW("Zip: read %" PRId64 " failed: %s", static_cast<int64_t>(read_amount),
strerror(errno));
return kIoError;
@@ -481,8 +479,7 @@
static int32_t UpdateEntryFromDataDescriptor(int fd,
ZipEntry *entry) {
uint8_t ddBuf[sizeof(DataDescriptor) + sizeof(DataDescriptor::kOptSignature)];
- ssize_t actual = TEMP_FAILURE_RETRY(read(fd, ddBuf, sizeof(ddBuf)));
- if (actual != sizeof(ddBuf)) {
+ if (!android::base::ReadFully(fd, ddBuf, sizeof(ddBuf))) {
return kIoError;
}
@@ -498,25 +495,19 @@
}
// Attempts to read |len| bytes into |buf| at offset |off|.
+// On non-Windows platforms, callers are guaranteed that the |fd|
+// offset is unchanged and there is no side effect to this call.
//
-// This method uses pread64 on platforms that support it and
-// lseek64 + read on platforms that don't. This implies that
-// callers should not rely on the |fd| offset being incremented
-// as a side effect of this call.
-static inline ssize_t ReadAtOffset(int fd, uint8_t* buf, size_t len,
- off64_t off) {
+// On Windows platforms this is not thread-safe.
+static inline bool ReadAtOffset(int fd, uint8_t* buf, size_t len, off64_t off) {
#if !defined(_WIN32)
return TEMP_FAILURE_RETRY(pread64(fd, buf, len, off));
#else
- // The only supported platform that doesn't support pread at the moment
- // is Windows. Only recent versions of windows support unix like forks,
- // and even there the semantics are quite different.
if (lseek64(fd, off, SEEK_SET) != off) {
ALOGW("Zip: failed seek to offset %" PRId64, off);
- return kIoError;
+ return false;
}
-
- return TEMP_FAILURE_RETRY(read(fd, buf, len));
+ return android::base::ReadFully(fd, buf, len);
#endif
}
@@ -567,9 +558,7 @@
}
uint8_t lfh_buf[sizeof(LocalFileHeader)];
- ssize_t actual = ReadAtOffset(archive->fd, lfh_buf, sizeof(lfh_buf),
- local_header_offset);
- if (actual != sizeof(lfh_buf)) {
+ if (!ReadAtOffset(archive->fd, lfh_buf, sizeof(lfh_buf), local_header_offset)) {
ALOGW("Zip: failed reading lfh name from offset %" PRId64,
static_cast<int64_t>(local_header_offset));
return kIoError;
@@ -610,10 +599,7 @@
}
uint8_t* name_buf = reinterpret_cast<uint8_t*>(malloc(nameLen));
- ssize_t actual = ReadAtOffset(archive->fd, name_buf, nameLen,
- name_offset);
-
- if (actual != nameLen) {
+ if (!ReadAtOffset(archive->fd, name_buf, nameLen, name_offset)) {
ALOGW("Zip: failed reading lfh name from offset %" PRId64, static_cast<int64_t>(name_offset));
free(name_buf);
return kIoError;
@@ -942,10 +928,9 @@
do {
/* read as much as we can */
if (zstream.avail_in == 0) {
- const ZD_TYPE getSize = (compressed_length > kBufSize) ? kBufSize : compressed_length;
- const ZD_TYPE actual = TEMP_FAILURE_RETRY(read(fd, &read_buf[0], getSize));
- if (actual != getSize) {
- ALOGW("Zip: inflate read failed (" ZD " vs " ZD ")", actual, getSize);
+ const size_t getSize = (compressed_length > kBufSize) ? kBufSize : compressed_length;
+ if (!android::base::ReadFully(fd, read_buf.data(), getSize)) {
+ ALOGW("Zip: inflate read failed, getSize = %zu: %s", getSize, strerror(errno));
return kIoError;
}
@@ -1005,11 +990,9 @@
// Safe conversion because kBufSize is narrow enough for a 32 bit signed
// value.
- const ssize_t block_size = (remaining > kBufSize) ? kBufSize : remaining;
- const ssize_t actual = TEMP_FAILURE_RETRY(read(fd, &buf[0], block_size));
-
- if (actual != block_size) {
- ALOGW("CopyFileToFile: copy read failed (" ZD " vs " ZD ")", actual, block_size);
+ const size_t block_size = (remaining > kBufSize) ? kBufSize : remaining;
+ if (!android::base::ReadFully(fd, buf.data(), block_size)) {
+ ALOGW("CopyFileToFile: copy read failed, block_size = %zu: %s", block_size, strerror(errno));
return kIoError;
}
diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc
index d426dc4..6aee1bb 100644
--- a/libziparchive/zip_archive_test.cc
+++ b/libziparchive/zip_archive_test.cc
@@ -21,9 +21,11 @@
#include <string.h>
#include <unistd.h>
+#include <memory>
#include <vector>
#include <android-base/file.h>
+#include <android-base/test_utils.h>
#include <gtest/gtest.h>
#include <ziparchive/zip_archive.h>
#include <ziparchive/zip_archive_stream_entry.h>
@@ -91,7 +93,7 @@
}
TEST(ziparchive, OpenAssumeFdOwnership) {
- int fd = open((test_data_dir + "/" + kValidZip).c_str(), O_RDONLY);
+ int fd = open((test_data_dir + "/" + kValidZip).c_str(), O_RDONLY | O_BINARY);
ASSERT_NE(-1, fd);
ZipArchiveHandle handle;
ASSERT_EQ(0, OpenArchiveFd(fd, "OpenWithAssumeFdOwnership", &handle));
@@ -101,7 +103,7 @@
}
TEST(ziparchive, OpenDoNotAssumeFdOwnership) {
- int fd = open((test_data_dir + "/" + kValidZip).c_str(), O_RDONLY);
+ int fd = open((test_data_dir + "/" + kValidZip).c_str(), O_RDONLY | O_BINARY);
ASSERT_NE(-1, fd);
ZipArchiveHandle handle;
ASSERT_EQ(0, OpenArchiveFd(fd, "OpenWithAssumeFdOwnership", &handle, false));
@@ -373,30 +375,13 @@
static const std::string kAbTxtName("ab.txt");
static const size_t kAbUncompressedSize = 270216;
-static int make_temporary_file(const char* file_name_pattern) {
- char full_path[1024];
- // Account for differences between the host and the target.
- //
- // TODO: Maybe reuse bionic/tests/TemporaryFile.h.
- snprintf(full_path, sizeof(full_path), "/data/local/tmp/%s", file_name_pattern);
- int fd = mkstemp(full_path);
- if (fd == -1) {
- snprintf(full_path, sizeof(full_path), "/tmp/%s", file_name_pattern);
- fd = mkstemp(full_path);
- }
-
- return fd;
-}
-
TEST(ziparchive, EmptyEntries) {
- char temp_file_pattern[] = "empty_entries_test_XXXXXX";
- int fd = make_temporary_file(temp_file_pattern);
- ASSERT_NE(-1, fd);
- const ssize_t file_size = sizeof(kEmptyEntriesZip);
- ASSERT_EQ(file_size, TEMP_FAILURE_RETRY(write(fd, kEmptyEntriesZip, file_size)));
+ TemporaryFile tmp_file;
+ ASSERT_NE(-1, tmp_file.fd);
+ ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, kEmptyEntriesZip, sizeof(kEmptyEntriesZip)));
ZipArchiveHandle handle;
- ASSERT_EQ(0, OpenArchiveFd(fd, "EmptyEntriesTest", &handle));
+ ASSERT_EQ(0, OpenArchiveFd(tmp_file.fd, "EmptyEntriesTest", &handle));
ZipEntry entry;
ZipString empty_name;
@@ -406,27 +391,23 @@
uint8_t buffer[1];
ASSERT_EQ(0, ExtractToMemory(handle, &entry, buffer, 1));
- char output_file_pattern[] = "empty_entries_output_XXXXXX";
- int output_fd = make_temporary_file(output_file_pattern);
- ASSERT_NE(-1, output_fd);
- ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, output_fd));
+
+ TemporaryFile tmp_output_file;
+ ASSERT_NE(-1, tmp_output_file.fd);
+ ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, tmp_output_file.fd));
struct stat stat_buf;
- ASSERT_EQ(0, fstat(output_fd, &stat_buf));
+ ASSERT_EQ(0, fstat(tmp_output_file.fd, &stat_buf));
ASSERT_EQ(0, stat_buf.st_size);
-
- close(fd);
- close(output_fd);
}
TEST(ziparchive, EntryLargerThan32K) {
- char temp_file_pattern[] = "entry_larger_than_32k_test_XXXXXX";
- int fd = make_temporary_file(temp_file_pattern);
- ASSERT_NE(-1, fd);
- ASSERT_TRUE(android::base::WriteFully(fd, reinterpret_cast<const uint8_t*>(kAbZip),
+ TemporaryFile tmp_file;
+ ASSERT_NE(-1, tmp_file.fd);
+ ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, reinterpret_cast<const uint8_t*>(kAbZip),
sizeof(kAbZip) - 1));
ZipArchiveHandle handle;
- ASSERT_EQ(0, OpenArchiveFd(fd, "EntryLargerThan32KTest", &handle));
+ ASSERT_EQ(0, OpenArchiveFd(tmp_file.fd, "EntryLargerThan32KTest", &handle));
ZipEntry entry;
ZipString ab_name;
@@ -439,21 +420,21 @@
ASSERT_EQ(0, ExtractToMemory(handle, &entry, &buffer[0], buffer.size()));
// Extract the entry to a file.
- char output_file_pattern[] = "entry_larger_than_32k_test_output_XXXXXX";
- int output_fd = make_temporary_file(output_file_pattern);
- ASSERT_NE(-1, output_fd);
- ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, output_fd));
+ TemporaryFile tmp_output_file;
+ ASSERT_NE(-1, tmp_output_file.fd);
+ ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, tmp_output_file.fd));
// Make sure the extracted file size is as expected.
struct stat stat_buf;
- ASSERT_EQ(0, fstat(output_fd, &stat_buf));
+ ASSERT_EQ(0, fstat(tmp_output_file.fd, &stat_buf));
ASSERT_EQ(kAbUncompressedSize, static_cast<size_t>(stat_buf.st_size));
// Read the file back to a buffer and make sure the contents are
// the same as the memory buffer we extracted directly to.
std::vector<uint8_t> file_contents(kAbUncompressedSize);
- ASSERT_EQ(0, lseek64(output_fd, 0, SEEK_SET));
- ASSERT_TRUE(android::base::ReadFully(output_fd, &file_contents[0], file_contents.size()));
+ ASSERT_EQ(0, lseek64(tmp_output_file.fd, 0, SEEK_SET));
+ ASSERT_TRUE(android::base::ReadFully(tmp_output_file.fd, &file_contents[0],
+ file_contents.size()));
ASSERT_EQ(file_contents, buffer);
for (int i = 0; i < 90072; ++i) {
@@ -462,35 +443,28 @@
ASSERT_EQ('b', line[1]);
ASSERT_EQ('\n', line[2]);
}
-
- close(fd);
- close(output_fd);
}
TEST(ziparchive, TrailerAfterEOCD) {
- char temp_file_pattern[] = "trailer_after_eocd_test_XXXXXX";
- int fd = make_temporary_file(temp_file_pattern);
- ASSERT_NE(-1, fd);
+ TemporaryFile tmp_file;
+ ASSERT_NE(-1, tmp_file.fd);
// Create a file with 8 bytes of random garbage.
static const uint8_t trailer[] = { 'A' ,'n', 'd', 'r', 'o', 'i', 'd', 'z' };
- const ssize_t file_size = sizeof(kEmptyEntriesZip);
- const ssize_t trailer_size = sizeof(trailer);
- ASSERT_EQ(file_size, TEMP_FAILURE_RETRY(write(fd, kEmptyEntriesZip, file_size)));
- ASSERT_EQ(trailer_size, TEMP_FAILURE_RETRY(write(fd, trailer, trailer_size)));
+ ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, kEmptyEntriesZip, sizeof(kEmptyEntriesZip)));
+ ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, trailer, sizeof(trailer)));
ZipArchiveHandle handle;
- ASSERT_GT(0, OpenArchiveFd(fd, "EmptyEntriesTest", &handle));
+ ASSERT_GT(0, OpenArchiveFd(tmp_file.fd, "EmptyEntriesTest", &handle));
}
TEST(ziparchive, ExtractToFile) {
- char kTempFilePattern[] = "zip_archive_input_XXXXXX";
- int fd = make_temporary_file(kTempFilePattern);
- ASSERT_NE(-1, fd);
+ TemporaryFile tmp_file;
+ ASSERT_NE(-1, tmp_file.fd);
const uint8_t data[8] = { '1', '2', '3', '4', '5', '6', '7', '8' };
- const ssize_t data_size = sizeof(data);
+ const size_t data_size = sizeof(data);
- ASSERT_EQ(data_size, TEMP_FAILURE_RETRY(write(fd, data, data_size)));
+ ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, data, data_size));
ZipArchiveHandle handle;
ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
@@ -499,28 +473,25 @@
ZipString name;
SetZipString(&name, kATxtName);
ASSERT_EQ(0, FindEntry(handle, name, &entry));
- ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, fd));
+ ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, tmp_file.fd));
// Assert that the first 8 bytes of the file haven't been clobbered.
uint8_t read_buffer[data_size];
- ASSERT_EQ(0, lseek64(fd, 0, SEEK_SET));
- ASSERT_EQ(data_size, TEMP_FAILURE_RETRY(read(fd, read_buffer, data_size)));
+ ASSERT_EQ(0, lseek64(tmp_file.fd, 0, SEEK_SET));
+ ASSERT_TRUE(android::base::ReadFully(tmp_file.fd, read_buffer, data_size));
ASSERT_EQ(0, memcmp(read_buffer, data, data_size));
// Assert that the remainder of the file contains the incompressed data.
std::vector<uint8_t> uncompressed_data(entry.uncompressed_length);
- ASSERT_EQ(static_cast<ssize_t>(entry.uncompressed_length),
- TEMP_FAILURE_RETRY(
- read(fd, &uncompressed_data[0], entry.uncompressed_length)));
+ ASSERT_TRUE(android::base::ReadFully(tmp_file.fd, uncompressed_data.data(),
+ entry.uncompressed_length));
ASSERT_EQ(0, memcmp(&uncompressed_data[0], kATxtContents.data(),
kATxtContents.size()));
// Assert that the total length of the file is sane
- ASSERT_EQ(data_size + static_cast<ssize_t>(kATxtContents.size()),
- lseek64(fd, 0, SEEK_END));
-
- close(fd);
+ ASSERT_EQ(static_cast<ssize_t>(data_size + kATxtContents.size()),
+ lseek64(tmp_file.fd, 0, SEEK_END));
}
static void ZipArchiveStreamTest(
diff --git a/libziparchive/zip_writer_test.cc b/libziparchive/zip_writer_test.cc
index fe0846d..16a574d 100644
--- a/libziparchive/zip_writer_test.cc
+++ b/libziparchive/zip_writer_test.cc
@@ -154,12 +154,22 @@
tm->tm_mday = (zip_time >> 16) & 0x1f;
}
+static struct tm MakeTm() {
+ struct tm tm;
+ memset(&tm, 0, sizeof(struct tm));
+ tm.tm_year = 2001 - 1900;
+ tm.tm_mon = 1;
+ tm.tm_mday = 12;
+ tm.tm_hour = 18;
+ tm.tm_min = 30;
+ tm.tm_sec = 20;
+ return tm;
+}
+
TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedFlagAndTime) {
ZipWriter writer(file_);
- struct tm tm;
- memset(&tm, 0, sizeof(struct tm));
- ASSERT_TRUE(strptime("18:30:20 1/12/2001", "%H:%M:%S %d/%m/%Y", &tm) != nullptr);
+ struct tm tm = MakeTm();
time_t time = mktime(&tm);
ASSERT_EQ(0, writer.StartEntryWithTime("align.txt", ZipWriter::kAlign32, time));
ASSERT_EQ(0, writer.WriteBytes("he", 2));
@@ -210,9 +220,7 @@
TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedValueAndTime) {
ZipWriter writer(file_);
- struct tm tm;
- memset(&tm, 0, sizeof(struct tm));
- ASSERT_TRUE(strptime("18:30:20 1/12/2001", "%H:%M:%S %d/%m/%Y", &tm) != nullptr);
+ struct tm tm = MakeTm();
time_t time = mktime(&tm);
ASSERT_EQ(0, writer.StartAlignedEntryWithTime("align.txt", 0, time, 4096));
ASSERT_EQ(0, writer.WriteBytes("he", 2));
diff --git a/mkbootimg/bootimg.h b/mkbootimg/bootimg.h
index 5ab6195..2bd8230 100644
--- a/mkbootimg/bootimg.h
+++ b/mkbootimg/bootimg.h
@@ -43,7 +43,11 @@
uint32_t tags_addr; /* physical addr for kernel tags */
uint32_t page_size; /* flash page size we assume */
- uint32_t unused[2]; /* future expansion: should be 0 */
+
+ /* operating system version; "1.2.34" -> 010234 */
+ uint32_t os_version;
+ /* operating system patch level; "2016-01-01" -> 20160101 */
+ uint32_t os_patch_level;
uint8_t name[BOOT_NAME_SIZE]; /* asciiz product name */
diff --git a/mkbootimg/mkbootimg b/mkbootimg/mkbootimg
index aea2585..be342c9 100755
--- a/mkbootimg/mkbootimg
+++ b/mkbootimg/mkbootimg
@@ -19,6 +19,8 @@
from os import fstat
from struct import pack
from hashlib import sha1
+import sys
+import re
def filesize(f):
if f is None:
@@ -46,7 +48,7 @@
def write_header(args):
BOOT_MAGIC = 'ANDROID!'.encode()
args.output.write(pack('8s', BOOT_MAGIC))
- args.output.write(pack('8I',
+ args.output.write(pack('10I',
filesize(args.kernel), # size in bytes
args.base + args.kernel_offset, # physical load addr
filesize(args.ramdisk), # size in bytes
@@ -54,8 +56,9 @@
filesize(args.second), # size in bytes
args.base + args.second_offset, # physical load addr
args.base + args.tags_offset, # physical addr for kernel tags
- args.pagesize)) # flash page size we assume
- args.output.write(pack('8x')) # future expansion: should be 0
+ args.pagesize, # flash page size we assume
+ args.os_version, # operating system version
+ args.os_patch_level)) # security patch level
args.output.write(pack('16s', args.board.encode())) # asciiz product name
args.output.write(pack('512s', args.cmdline[:512].encode()))
@@ -96,6 +99,18 @@
def parse_int(x):
return int(x, 0)
+def match_to_int(x):
+ if (x and x.lastindex == 3):
+ return (parse_int(x.group(3)) +
+ parse_int(x.group(2)) * 100 +
+ parse_int(x.group(1)) * 10000)
+ return 0
+
+def parse_os_version(x):
+ return match_to_int(re.search(r'^(\d+)\.(\d{1,2})\.(\d{1,2})', x))
+
+def parse_os_patch_level(x):
+ return match_to_int(re.search(r'^(\d{4,})-(\d{2})-(\d{2})', x))
def parse_cmdline():
parser = ArgumentParser()
@@ -110,6 +125,10 @@
parser.add_argument('--ramdisk_offset', help='ramdisk offset', type=parse_int, default=0x01000000)
parser.add_argument('--second_offset', help='2nd bootloader offset', type=parse_int,
default=0x00f00000)
+ parser.add_argument('--os_version', help='operating system version', type=parse_os_version,
+ default=0)
+ parser.add_argument('--os_patch_level', help='operating system patch level',
+ type=parse_os_patch_level, default=0)
parser.add_argument('--tags_offset', help='tags offset', type=parse_int, default=0x00000100)
parser.add_argument('--board', help='board name', default='', action=ValidateStrLenAction,
maxlen=16)
@@ -133,8 +152,10 @@
img_id = write_header(args)
write_data(args)
if args.id:
- print('0x' + ''.join('{:02x}'.format(ord(c)) for c in img_id))
-
+ if isinstance(img_id, str):
+ # Python 2's struct.pack returns a string, but py3 returns bytes.
+ img_id = [ord(x) for x in img_id]
+ print('0x' + ''.join('{:02x}'.format(c) for c in img_id))
if __name__ == '__main__':
main()
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index d90f988..d53af2f 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -70,6 +70,11 @@
; mkdir -p $(dir $(TARGET_ROOT_OUT)/$(word 2,$(p))) \
; ln -sf $(word 1,$(p)) $(TARGET_ROOT_OUT)/$(word 2,$(p)))
endif
+# The A/B updater uses a top-level /postinstall directory to mount the new
+# system before reboot.
+ifeq ($(AB_OTA_UPDATER),true)
+ LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/postinstall
+endif
include $(BUILD_SYSTEM)/base_rules.mk
diff --git a/rootdir/init.rc b/rootdir/init.rc
index b26e6fc..194b41b 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -23,6 +23,9 @@
# Shouldn't be necessary, but sdcard won't start without it. http://b/22568628.
mkdir /mnt 0775 root system
+ # Set the security context of /postinstall if present.
+ restorecon /postinstall
+
start ueventd
on init
diff --git a/toolbox/generate-input.h-labels.py b/toolbox/generate-input.h-labels.py
index 30485a0..a2b9111 100755
--- a/toolbox/generate-input.h-labels.py
+++ b/toolbox/generate-input.h-labels.py
@@ -16,6 +16,7 @@
#
# pylint: disable=bad-indentation,bad-continuation
+from __future__ import print_function
import os
import re
import sys
@@ -72,11 +73,11 @@
ff_list.append(name)
def Dump(struct_name, values):
- print 'static struct label %s[] = {' % (struct_name)
+ print('static struct label %s[] = {' % (struct_name))
for value in values:
- print ' LABEL(%s),' % (value)
- print ' LABEL_END,'
- print '};'
+ print(' LABEL(%s),' % (value))
+ print(' LABEL_END,')
+ print('};')
Dump("input_prop_labels", input_prop_list)
Dump("ev_labels", ev_list)