1 /* libunwind - a platform-independent unwind library
2 Copyright (C) 2014 The Android Open Source Project
3
4 This file is part of libunwind.
5
6 Permission is hereby granted, free of charge, to any person obtaining
7 a copy of this software and associated documentation files (the
8 "Software"), to deal in the Software without restriction, including
9 without limitation the rights to use, copy, modify, merge, publish,
10 distribute, sublicense, and/or sell copies of the Software, and to
11 permit persons to whom the Software is furnished to do so, subject to
12 the following conditions:
13
14 The above copyright notice and this permission notice shall be
15 included in all copies or substantial portions of the Software.
16
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
24
25 #define UNW_LOCAL_ONLY
26 #include <libunwind.h>
27 #include "libunwind_i.h"
28
29 /* Global to hold the map for all local unwinds. */
30 extern struct map_info *local_map_list;
31 extern lock_rdwr_var (local_rdwr_lock);
32
33 static pthread_once_t local_rdwr_lock_init = PTHREAD_ONCE_INIT;
34
35 static void
map_local_init_once(void)36 map_local_init_once (void)
37 {
38 lock_rdwr_init (&local_rdwr_lock);
39 }
40
41 HIDDEN void
map_local_init(void)42 map_local_init (void)
43 {
44 pthread_once (&local_rdwr_lock_init, map_local_init_once);
45 }
46
47 static void
move_cached_elf_data(struct map_info * old_list,struct map_info * new_list)48 move_cached_elf_data (struct map_info *old_list, struct map_info *new_list)
49 {
50 while (old_list)
51 {
52 if (!old_list->ei.valid)
53 {
54 old_list = old_list->next;
55 continue;
56 }
57 /* Both lists are in order, so it's not necessary to scan through
58 from the beginning of new_list each time looking for a match to
59 the current map. As we progress, simply start from the last element
60 in new_list we checked. */
61 while (new_list && old_list->start <= new_list->start)
62 {
63 if (old_list->start == new_list->start
64 && old_list->end == new_list->end)
65 {
66 /* No need to do any lock, the entire local_map_list is locked
67 at this point. */
68 new_list->ei = old_list->ei;
69 /* If it was mapped before, make sure to mark it unmapped now. */
70 old_list->ei.mapped = false;
71 /* Don't bother breaking out of the loop, the next while check
72 is guaranteed to fail, causing us to break out of the loop
73 after advancing to the next map element. */
74 }
75 new_list = new_list->next;
76 }
77 old_list = old_list->next;
78 }
79 }
80
81 /* In order to cache as much as possible while unwinding the local process,
82 we gather a map of the process before starting. If the cache is missing
83 a map, or a map exists but doesn't have the "expected_flags" set, then
84 check if the cache needs to be regenerated.
85 While regenerating the list, grab a write lock to avoid any readers using
86 the list while it's being modified. */
87 static int
rebuild_if_necessary(unw_word_t addr,int expected_flags)88 rebuild_if_necessary (unw_word_t addr, int expected_flags)
89 {
90 struct map_info *map;
91 struct map_info *new_list;
92 int ret_value = -1;
93 intrmask_t saved_mask;
94
95 new_list = map_create_list (UNW_MAP_CREATE_LOCAL, getpid());
96 map = map_find_from_addr (new_list, addr);
97 if (map && (expected_flags == 0 || (map->flags & expected_flags)))
98 {
99 /* Get a write lock on local_map_list since it's going to be modified. */
100 lock_rdwr_wr_acquire (&local_rdwr_lock, saved_mask);
101
102 /* Just in case another thread rebuilt the map, check to see if the
103 ip with expected_flags is in local_map_list. If not, the assumption
104 is that new_list is newer than local_map_list because the map only
105 gets new maps with new permissions. If this is not true, then it
106 would be necessary to regenerate the list one more time. */
107 ret_value = 0;
108 map = map_find_from_addr (local_map_list, addr);
109 if (!map || (expected_flags != 0 && !(map->flags & expected_flags)))
110 {
111 /* Move any cached items to the new list. */
112 move_cached_elf_data (local_map_list, new_list);
113 map = local_map_list;
114 local_map_list = new_list;
115 new_list = map;
116 }
117
118 lock_rdwr_release (&local_rdwr_lock, saved_mask);
119 }
120
121 map_destroy_list (new_list);
122
123 return ret_value;
124 }
125
126 static int
is_flag_set(unw_word_t addr,int flag)127 is_flag_set (unw_word_t addr, int flag)
128 {
129 struct map_info *map;
130 int ret = 0;
131 intrmask_t saved_mask;
132
133 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
134 map = map_find_from_addr (local_map_list, addr);
135 if (map != NULL)
136 {
137 if (map->flags & MAP_FLAGS_DEVICE_MEM)
138 {
139 lock_rdwr_release (&local_rdwr_lock, saved_mask);
140 return 0;
141 }
142 ret = map->flags & flag;
143 }
144 lock_rdwr_release (&local_rdwr_lock, saved_mask);
145
146 if (!ret && rebuild_if_necessary (addr, flag) == 0)
147 {
148 return 1;
149 }
150 return ret;
151 }
152
153 PROTECTED int
map_local_is_readable(unw_word_t addr)154 map_local_is_readable (unw_word_t addr)
155 {
156 return is_flag_set (addr, PROT_READ);
157 }
158
159 PROTECTED int
map_local_is_writable(unw_word_t addr)160 map_local_is_writable (unw_word_t addr)
161 {
162 return is_flag_set (addr, PROT_WRITE);
163 }
164
165 PROTECTED int
local_get_elf_image(unw_addr_space_t as,struct elf_image * ei,unw_word_t ip,unsigned long * segbase,unsigned long * mapoff,char ** path,void * as_arg)166 local_get_elf_image (unw_addr_space_t as, struct elf_image *ei, unw_word_t ip,
167 unsigned long *segbase, unsigned long *mapoff, char **path, void *as_arg)
168 {
169 struct map_info *map;
170 intrmask_t saved_mask;
171 int return_value = -UNW_ENOINFO;
172
173 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
174 map = map_find_from_addr (local_map_list, ip);
175 if (!map)
176 {
177 lock_rdwr_release (&local_rdwr_lock, saved_mask);
178 if (rebuild_if_necessary (ip, 0) < 0)
179 return -UNW_ENOINFO;
180
181 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
182 map = map_find_from_addr (local_map_list, ip);
183 }
184
185 if (map && elf_map_cached_image (as, as_arg, map, ip))
186 {
187 *ei = map->ei;
188 *segbase = map->start;
189 if (ei->mapped)
190 *mapoff = map->offset;
191 else
192 /* Always use zero as the map offset for in memory maps. The
193 * dlopen of a shared library from an APK will result in a
194 * non-zero offset so it won't match the elf data and cause
195 * unwinds to fail. Currently, only in memory unwinds of an APK
196 * are possible, so only modify this path.
197 */
198 *mapoff = 0;
199 if (path != NULL)
200 {
201 if (map->path)
202 *path = strdup(map->path);
203 else
204 *path = NULL;
205 }
206 return_value = 0;
207 }
208 lock_rdwr_release (&local_rdwr_lock, saved_mask);
209
210 return return_value;
211 }
212
213 PROTECTED char *
map_local_get_image_name(unw_word_t ip)214 map_local_get_image_name (unw_word_t ip)
215 {
216 struct map_info *map;
217 intrmask_t saved_mask;
218 char *image_name = NULL;
219
220 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
221 map = map_find_from_addr (local_map_list, ip);
222 if (!map)
223 {
224 lock_rdwr_release (&local_rdwr_lock, saved_mask);
225 if (rebuild_if_necessary (ip, 0) < 0)
226 return NULL;
227
228 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
229 map = map_find_from_addr (local_map_list, ip);
230 }
231 if (map)
232 image_name = strdup (map->path);
233 lock_rdwr_release (&local_rdwr_lock, saved_mask);
234
235 return image_name;
236 }
237