1 /*
2 * Copyright (c) 2013 Luca Clementi <luca.clementi@gmail.com>
3 * Copyright (c) 2013-2021 The strace developers.
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 */
7
8 #include "defs.h"
9 #include <limits.h>
10
11 #include "largefile_wrappers.h"
12 #include "mmap_cache.h"
13 #include "mmap_notify.h"
14 #include "xstring.h"
15
16 static unsigned int mmap_cache_generation;
17
18 static void
19 mmap_cache_invalidate(struct tcb *tcp, void *unused)
20 {
21 mmap_cache_generation++;
22 debug_func_msg("tgen=%u, ggen=%u, tcp=%p, cache=%p",
23 tcp->mmap_cache ? tcp->mmap_cache->generation : 0,
24 mmap_cache_generation, tcp,
25 tcp->mmap_cache ? tcp->mmap_cache->entry : 0);
26 }
27
28 void
29 mmap_cache_enable(void)
30 {
31 static bool use_mmap_cache;
32
33 if (!use_mmap_cache) {
34 mmap_notify_register_client(mmap_cache_invalidate, NULL);
35 use_mmap_cache = true;
36 }
37 }
38
39 /* deleting the cache */
40 static void
41 delete_mmap_cache(struct tcb *tcp, const char *caller)
42 {
43 debug_func_msg("tgen=%u, ggen=%u, tcp=%p, cache=%p, caller=%s",
44 tcp->mmap_cache ? tcp->mmap_cache->generation : 0,
45 mmap_cache_generation, tcp,
46 tcp->mmap_cache ? tcp->mmap_cache->entry : 0, caller);
47
48 if (!tcp->mmap_cache)
49 return;
50
51 while (tcp->mmap_cache->size) {
52 unsigned int i = --tcp->mmap_cache->size;
53 free(tcp->mmap_cache->entry[i].binary_filename);
54 tcp->mmap_cache->entry[i].binary_filename = NULL;
55 }
56
57 free(tcp->mmap_cache->entry);
58 tcp->mmap_cache->entry = NULL;
59
60 free(tcp->mmap_cache);
61 tcp->mmap_cache = NULL;
62 }
63
64 /*
65 * caching of /proc/ID/maps for each process to speed up stack tracing
66 *
67 * The cache must be refreshed after syscalls that affect memory mappings,
68 * e.g. mmap, mprotect, munmap, execve.
69 */
70 extern enum mmap_cache_rebuild_result
71 mmap_cache_rebuild_if_invalid(struct tcb *tcp, const char *caller)
72 {
73 if (tcp->mmap_cache
74 && tcp->mmap_cache->generation != mmap_cache_generation)
75 delete_mmap_cache(tcp, caller);
76
77 if (tcp->mmap_cache)
78 return MMAP_CACHE_REBUILD_READY;
79
80 char filename[sizeof("/proc/4294967296/maps")];
81 xsprintf(filename, "/proc/%u/maps", get_proc_pid(tcp->pid));
82
83 FILE *fp = fopen_stream(filename, "r");
84 if (!fp) {
85 perror_msg("fopen: %s", filename);
86 return MMAP_CACHE_REBUILD_NOCACHE;
87 }
88
89 struct mmap_cache_t cache = {
90 .free_fn = delete_mmap_cache,
91 .generation = mmap_cache_generation
92 };
93
94 /* start with a small dynamically-allocated array and then expand it */
95 size_t allocated = 0;
96 char buffer[PATH_MAX + 80];
97
98 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
99 unsigned long start_addr, end_addr, mmap_offset;
100 char read_bit;
101 char write_bit;
102 char exec_bit;
103 char shared_bit;
104 unsigned long major, minor;
105 char binary_path[sizeof(buffer)];
106
107 if (sscanf(buffer, "%lx-%lx %c%c%c%c %lx %lx:%lx %*d %[^\n]",
108 &start_addr, &end_addr,
109 &read_bit, &write_bit, &exec_bit, &shared_bit,
110 &mmap_offset,
111 &major, &minor,
112 binary_path) != 10)
113 continue;
114
115 /* skip mappings that have unknown protection */
116 if (!(read_bit == '-' || read_bit == 'r'))
117 continue;
118 if (!(write_bit == '-' || write_bit == 'w'))
119 continue;
120 if (!(exec_bit == '-' || exec_bit == 'x'))
121 continue;
122 if (!(shared_bit == 'p' || shared_bit == 's'))
123 continue;
124
125 if (end_addr < start_addr) {
126 error_msg("%s: unrecognized file format", filename);
127 break;
128 }
129
130 struct mmap_cache_entry_t *entry;
131 /*
132 * sanity check to make sure that we're storing
133 * non-overlapping regions in ascending order
134 */
135 if (cache.size > 0) {
136 entry = &cache.entry[cache.size - 1];
137 if (entry->start_addr == start_addr &&
138 entry->end_addr == end_addr) {
139 /* duplicate entry, e.g. [vsyscall] */
140 continue;
141 }
142 if (start_addr <= entry->start_addr ||
143 start_addr < entry->end_addr) {
144 debug_msg("%s: overlapping memory region: "
145 "\"%s\" [%08lx-%08lx] overlaps with "
146 "\"%s\" [%08lx-%08lx]",
147 filename, binary_path, start_addr,
148 end_addr, entry->binary_filename,
149 entry->start_addr, entry->end_addr);
150 continue;
151 }
152 }
153
154 if (cache.size >= allocated)
155 cache.entry = xgrowarray(cache.entry, &allocated,
156 sizeof(*cache.entry));
157
158 entry = &cache.entry[cache.size];
159 entry->start_addr = start_addr;
160 entry->end_addr = end_addr;
161 entry->mmap_offset = mmap_offset;
162 entry->protections = (
163 0
164 | ((read_bit == 'r')? MMAP_CACHE_PROT_READABLE : 0)
165 | ((write_bit == 'w')? MMAP_CACHE_PROT_WRITABLE : 0)
166 | ((exec_bit == 'x')? MMAP_CACHE_PROT_EXECUTABLE: 0)
167 | ((shared_bit == 's')? MMAP_CACHE_PROT_SHARED : 0)
168 );
169 entry->major = major;
170 entry->minor = minor;
171 entry->binary_filename = xstrdup(binary_path);
172 cache.size++;
173 }
174 fclose(fp);
175
176 if (!cache.size)
177 return MMAP_CACHE_REBUILD_NOCACHE;
178
179 tcp->mmap_cache = xobjdup(&cache);
180
181 debug_func_msg("tgen=%u, ggen=%u, tcp=%p, cache=%p, caller=%s",
182 tcp->mmap_cache->generation, mmap_cache_generation,
183 tcp, tcp->mmap_cache->entry, caller);
184
185 return MMAP_CACHE_REBUILD_RENEWED;
186 }
187
188 struct mmap_cache_entry_t *
189 mmap_cache_search(struct tcb *tcp, unsigned long ip)
190 {
191 if (!tcp->mmap_cache)
192 return NULL;
193
194 int lower = 0;
195 int upper = (int) tcp->mmap_cache->size - 1;
196
197 while (lower <= upper) {
198 int mid = (upper + lower) / 2;
199 struct mmap_cache_entry_t *entry = &tcp->mmap_cache->entry[mid];
200
201 if (ip >= entry->start_addr &&
202 ip < entry->end_addr)
203 return entry;
204 else if (ip < entry->start_addr)
205 upper = mid - 1;
206 else
207 lower = mid + 1;
208 }
209 return NULL;
210 }
211
212 struct mmap_cache_entry_t *
213 mmap_cache_search_custom(struct tcb *tcp, mmap_cache_search_fn fn, void *data)
214 {
215 for (unsigned int i = 0; i < tcp->mmap_cache->size; i++) {
216 if (fn(tcp->mmap_cache->entry + i, data))
217 return tcp->mmap_cache->entry + i;
218 }
219 return NULL;
220 }