1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <err.h>
18 #include <errno.h>
19 #include <stdint.h>
20 #include <sys/mman.h>
21 #include <sys/param.h>
22 #include <sys/types.h>
23 #include <sys/wait.h>
24 #include <unistd.h>
25
26 #include <string>
27 #include <unordered_map>
28
29 #include <android-base/file.h>
30 #include <android-base/strings.h>
31 #include <ziparchive/zip_archive.h>
32
33 #include <memory_trace/MemoryTrace.h>
34
35 #include "File.h"
36
ZipGetContents(const char * filename)37 std::string ZipGetContents(const char* filename) {
38 ZipArchiveHandle archive;
39 if (OpenArchive(filename, &archive) != 0) {
40 return "";
41 }
42
43 // It is assumed that the archive contains only a single entry.
44 void* cookie;
45 std::string contents;
46 if (StartIteration(archive, &cookie) == 0) {
47 ZipEntry entry;
48 std::string name;
49 if (Next(cookie, &entry, &name) == 0) {
50 contents.resize(entry.uncompressed_length);
51 if (ExtractToMemory(archive, &entry, reinterpret_cast<uint8_t*>(contents.data()),
52 entry.uncompressed_length) != 0) {
53 contents = "";
54 }
55 }
56 }
57
58 CloseArchive(archive);
59 return contents;
60 }
61
WaitPid(pid_t pid)62 static void WaitPid(pid_t pid) {
63 int wstatus;
64 pid_t wait_pid = TEMP_FAILURE_RETRY(waitpid(pid, &wstatus, 0));
65 if (wait_pid != pid) {
66 if (wait_pid == -1) {
67 err(1, "waitpid() failed");
68 } else {
69 errx(1, "Unexpected pid from waitpid(): expected %d, returned %d", pid, wait_pid);
70 }
71 }
72 if (!WIFEXITED(wstatus)) {
73 errx(1, "Forked process did not terminate with exit() call");
74 }
75 if (WEXITSTATUS(wstatus) != 0) {
76 errx(1, "Bad exit value from forked process: returned %d", WEXITSTATUS(wstatus));
77 }
78 }
79
UpdatePresentBytes(std::unordered_map<uint64_t,memory_trace::Entry * > & entries_by_ptr,memory_trace::Entry * entry)80 static void UpdatePresentBytes(std::unordered_map<uint64_t, memory_trace::Entry*>& entries_by_ptr,
81 memory_trace::Entry* entry) {
82 switch (entry->type) {
83 case memory_trace::FREE:
84 if (entry->present_bytes != -1) {
85 // Need to find the pointer for this free and update the present bytes
86 // on the original pointer.
87 auto iter = entries_by_ptr.find(entry->ptr);
88 if (iter != entries_by_ptr.end()) {
89 // Present bytes can be larger than the recorded size when the
90 // real size returned by malloc_usable_size is greater than that.
91 // Therefore, always choose the smaller of the two.
92 iter->second->present_bytes =
93 MIN(entry->present_bytes, static_cast<int64_t>(iter->second->size));
94 entries_by_ptr.erase(entry->ptr);
95 }
96 }
97 break;
98 case memory_trace::CALLOC:
99 case memory_trace::MALLOC:
100 case memory_trace::MEMALIGN:
101 entries_by_ptr[entry->ptr] = entry;
102 break;
103
104 case memory_trace::REALLOC:
105 if (entry->ptr != 0) {
106 entries_by_ptr[entry->ptr] = entry;
107 }
108 if (entry->u.old_ptr != 0 && entry->present_bytes != -1) {
109 // The old pointer got freed, so add it that way.
110 auto iter = entries_by_ptr.find(entry->u.old_ptr);
111 if (iter != entries_by_ptr.end()) {
112 // Present bytes can be larger than the recorded size when the
113 // real size returned by malloc_usable_size is greater than that.
114 // Therefore, always choose the smaller of the two.
115 iter->second->present_bytes =
116 MIN(entry->present_bytes, static_cast<int64_t>(iter->second->size));
117 entries_by_ptr.erase(entry->u.old_ptr);
118 entry->present_bytes = -1;
119 }
120 }
121 break;
122 default:
123 break;
124 }
125 }
126
127 // This function should not do any memory allocations in the main function.
128 // Any true allocation should happen in fork'd code.
GetUnwindInfo(const char * filename,memory_trace::Entry ** entries,size_t * num_entries)129 void GetUnwindInfo(const char* filename, memory_trace::Entry** entries, size_t* num_entries) {
130 void* mem =
131 mmap(nullptr, sizeof(size_t), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
132 if (mem == MAP_FAILED) {
133 err(1, "Unable to allocate a shared map of size %zu", sizeof(size_t));
134 }
135 *reinterpret_cast<size_t*>(mem) = 0;
136
137 pid_t pid;
138 if ((pid = fork()) == 0) {
139 // First get the number of lines in the trace file. It is assumed
140 // that there are no blank lines, and every line contains a valid
141 // allocation operation.
142 std::string contents;
143 if (android::base::EndsWith(filename, ".zip")) {
144 contents = ZipGetContents(filename);
145 } else if (!android::base::ReadFileToString(filename, &contents)) {
146 errx(1, "Unable to get contents of %s", filename);
147 }
148 if (contents.empty()) {
149 errx(1, "Unable to get contents of %s", filename);
150 }
151
152 size_t lines = 0;
153 size_t index = 0;
154 while (true) {
155 index = contents.find('\n', index);
156 if (index == std::string::npos) {
157 break;
158 }
159 index++;
160 lines++;
161 }
162 if (contents[contents.size() - 1] != '\n') {
163 // Add one since the last line doesn't end in '\n'.
164 lines++;
165 }
166 *reinterpret_cast<size_t*>(mem) = lines;
167 _exit(0);
168 } else if (pid == -1) {
169 err(1, "fork() call failed");
170 }
171 WaitPid(pid);
172 *num_entries = *reinterpret_cast<size_t*>(mem);
173 munmap(mem, sizeof(size_t));
174
175 mem = mmap(nullptr, *num_entries * sizeof(memory_trace::Entry), PROT_READ | PROT_WRITE,
176 MAP_ANONYMOUS | MAP_SHARED, -1, 0);
177 if (mem == MAP_FAILED) {
178 err(1, "Unable to allocate a shared map of size %zu",
179 *num_entries * sizeof(memory_trace::Entry));
180 }
181 *entries = reinterpret_cast<memory_trace::Entry*>(mem);
182
183 if ((pid = fork()) == 0) {
184 std::string contents;
185 if (android::base::EndsWith(filename, ".zip")) {
186 contents = ZipGetContents(filename);
187 } else if (!android::base::ReadFileToString(filename, &contents)) {
188 errx(1, "Unable to get contents of %s", filename);
189 }
190 if (contents.empty()) {
191 errx(1, "Contents of zip file %s is empty.", filename);
192 }
193
194 std::unordered_map<uint64_t, memory_trace::Entry*> entries_by_ptr;
195 size_t entry_idx = 0;
196 size_t start_str = 0;
197 size_t end_str = 0;
198 while (true) {
199 end_str = contents.find('\n', start_str);
200 if (end_str == std::string::npos) {
201 break;
202 }
203 if (entry_idx == *num_entries) {
204 errx(1, "Too many entries, stopped at entry %zu", entry_idx);
205 }
206 contents[end_str] = '\0';
207
208 std::string error;
209 memory_trace::Entry* entry = &(*entries)[entry_idx++];
210 if (!memory_trace::FillInEntryFromString(&contents[start_str], *entry, error)) {
211 errx(1, "%s", error.c_str());
212 }
213 start_str = end_str + 1;
214
215 // If this operation does a free, set the present_bytes on the original
216 // allocation.
217 UpdatePresentBytes(entries_by_ptr, entry);
218 }
219 if (entry_idx != *num_entries) {
220 errx(1, "Mismatched number of entries found: expected %zu, found %zu", *num_entries,
221 entry_idx);
222 }
223 _exit(0);
224 } else if (pid == -1) {
225 err(1, "fork() call failed");
226 }
227 WaitPid(pid);
228 }
229
FreeEntries(memory_trace::Entry * entries,size_t num_entries)230 void FreeEntries(memory_trace::Entry* entries, size_t num_entries) {
231 munmap(entries, num_entries * sizeof(memory_trace::Entry));
232 }
233