blob: fd6ae3fabca113235fda2cce68e61019e32fd9a8 [file] [log] [blame]
Christopher Ferrisf4a8df52014-03-07 19:40:06 -08001/* libunwind - a platform-independent unwind library
2 Copyright (C) 2014 The Android Open Source Project
3
4This file is part of libunwind.
5
6Permission is hereby granted, free of charge, to any person obtaining
7a copy of this software and associated documentation files (the
8"Software"), to deal in the Software without restriction, including
9without limitation the rights to use, copy, modify, merge, publish,
10distribute, sublicense, and/or sell copies of the Software, and to
11permit persons to whom the Software is furnished to do so, subject to
12the following conditions:
13
14The above copyright notice and this permission notice shall be
15included in all copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23WITH 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. */
30extern struct map_info *local_map_list;
31extern lock_rdwr_var (local_rdwr_lock);
32
Christopher Ferris965cd692014-11-12 19:26:33 -080033static pthread_once_t local_rdwr_lock_init = PTHREAD_ONCE_INIT;
34
35static void
36map_local_init_once (void)
37{
38 lock_rdwr_init (&local_rdwr_lock);
39}
40
Christopher Ferrisf4a8df52014-03-07 19:40:06 -080041HIDDEN void
42map_local_init (void)
43{
Christopher Ferris965cd692014-11-12 19:26:33 -080044 pthread_once (&local_rdwr_lock_init, map_local_init_once);
Christopher Ferrisf4a8df52014-03-07 19:40:06 -080045}
46
47static void
48move_cached_elf_data (struct map_info *old_list, struct map_info *new_list)
49{
50 while (old_list)
51 {
Christopher Ferriscdf0d032015-05-04 11:01:39 -070052 if (!old_list->ei.valid)
Christopher Ferrisf4a8df52014-03-07 19:40:06 -080053 {
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. */
Christopher Ferriscdf0d032015-05-04 11:01:39 -070068 new_list->ei = old_list->ei;
Christopher Ferris2d1a6f72015-10-21 13:13:22 -070069 /* Adjust the map pointer in the elf image data if necessary. */
70 if (!new_list->ei.mapped)
71 new_list->ei.u.memory.map = new_list;
Christopher Ferriscdf0d032015-05-04 11:01:39 -070072 /* If it was mapped before, make sure to mark it unmapped now. */
73 old_list->ei.mapped = false;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -080074 /* Don't bother breaking out of the loop, the next while check
75 is guaranteed to fail, causing us to break out of the loop
76 after advancing to the next map element. */
77 }
78 new_list = new_list->next;
79 }
80 old_list = old_list->next;
81 }
82}
83
84/* In order to cache as much as possible while unwinding the local process,
85 we gather a map of the process before starting. If the cache is missing
86 a map, or a map exists but doesn't have the "expected_flags" set, then
87 check if the cache needs to be regenerated.
88 While regenerating the list, grab a write lock to avoid any readers using
89 the list while it's being modified. */
90static int
91rebuild_if_necessary (unw_word_t addr, int expected_flags)
92{
93 struct map_info *map;
94 struct map_info *new_list;
95 int ret_value = -1;
96 intrmask_t saved_mask;
97
Christopher Ferris2553e592015-06-05 17:23:36 -070098 new_list = map_create_list (UNW_MAP_CREATE_LOCAL, getpid());
Christopher Ferrisf4a8df52014-03-07 19:40:06 -080099 map = map_find_from_addr (new_list, addr);
100 if (map && (expected_flags == 0 || (map->flags & expected_flags)))
101 {
102 /* Get a write lock on local_map_list since it's going to be modified. */
103 lock_rdwr_wr_acquire (&local_rdwr_lock, saved_mask);
104
105 /* Just in case another thread rebuilt the map, check to see if the
106 ip with expected_flags is in local_map_list. If not, the assumption
107 is that new_list is newer than local_map_list because the map only
108 gets new maps with new permissions. If this is not true, then it
109 would be necessary to regenerate the list one more time. */
110 ret_value = 0;
111 map = map_find_from_addr (local_map_list, addr);
112 if (!map || (expected_flags != 0 && !(map->flags & expected_flags)))
113 {
114 /* Move any cached items to the new list. */
115 move_cached_elf_data (local_map_list, new_list);
116 map = local_map_list;
117 local_map_list = new_list;
118 new_list = map;
119 }
120
121 lock_rdwr_release (&local_rdwr_lock, saved_mask);
122 }
123
124 map_destroy_list (new_list);
125
126 return ret_value;
127}
128
129static int
130is_flag_set (unw_word_t addr, int flag)
131{
132 struct map_info *map;
133 int ret = 0;
134 intrmask_t saved_mask;
135
136 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
137 map = map_find_from_addr (local_map_list, addr);
138 if (map != NULL)
Christopher Ferrisf360ccc2014-09-10 16:26:12 -0700139 {
140 if (map->flags & MAP_FLAGS_DEVICE_MEM)
141 {
142 lock_rdwr_release (&local_rdwr_lock, saved_mask);
143 return 0;
144 }
145 ret = map->flags & flag;
146 }
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800147 lock_rdwr_release (&local_rdwr_lock, saved_mask);
148
149 if (!ret && rebuild_if_necessary (addr, flag) == 0)
150 {
151 return 1;
152 }
153 return ret;
154}
155
156PROTECTED int
157map_local_is_readable (unw_word_t addr)
158{
159 return is_flag_set (addr, PROT_READ);
160}
161
162PROTECTED int
163map_local_is_writable (unw_word_t addr)
164{
165 return is_flag_set (addr, PROT_WRITE);
166}
167
168PROTECTED int
Christopher Ferriscdf0d032015-05-04 11:01:39 -0700169local_get_elf_image (unw_addr_space_t as, struct elf_image *ei, unw_word_t ip,
170 unsigned long *segbase, unsigned long *mapoff, char **path, void *as_arg)
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800171{
172 struct map_info *map;
173 intrmask_t saved_mask;
Christopher Ferris0fa05b02014-04-09 12:25:23 -0700174 int return_value = -UNW_ENOINFO;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800175
176 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
177 map = map_find_from_addr (local_map_list, ip);
178 if (!map)
179 {
180 lock_rdwr_release (&local_rdwr_lock, saved_mask);
181 if (rebuild_if_necessary (ip, 0) < 0)
182 return -UNW_ENOINFO;
183
184 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
185 map = map_find_from_addr (local_map_list, ip);
186 }
187
Christopher Ferriscdf0d032015-05-04 11:01:39 -0700188 if (map && elf_map_cached_image (as, as_arg, map, ip))
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800189 {
190 *ei = map->ei;
191 *segbase = map->start;
Christopher Ferrisb025c422015-08-19 16:02:47 -0700192 if (ei->mapped)
193 *mapoff = map->offset;
194 else
195 /* Always use zero as the map offset for in memory maps. The
196 * dlopen of a shared library from an APK will result in a
197 * non-zero offset so it won't match the elf data and cause
198 * unwinds to fail. Currently, only in memory unwinds of an APK
199 * are possible, so only modify this path.
200 */
201 *mapoff = 0;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800202 if (path != NULL)
203 {
204 if (map->path)
205 *path = strdup(map->path);
206 else
207 *path = NULL;
208 }
Christopher Ferris0fa05b02014-04-09 12:25:23 -0700209 return_value = 0;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800210 }
211 lock_rdwr_release (&local_rdwr_lock, saved_mask);
212
Christopher Ferris0fa05b02014-04-09 12:25:23 -0700213 return return_value;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800214}
Christopher Ferrisbb754472014-04-09 18:21:28 -0700215
216PROTECTED char *
217map_local_get_image_name (unw_word_t ip)
218{
219 struct map_info *map;
220 intrmask_t saved_mask;
221 char *image_name = NULL;
222
223 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
224 map = map_find_from_addr (local_map_list, ip);
225 if (!map)
226 {
227 lock_rdwr_release (&local_rdwr_lock, saved_mask);
228 if (rebuild_if_necessary (ip, 0) < 0)
229 return NULL;
230
231 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
232 map = map_find_from_addr (local_map_list, ip);
233 }
234 if (map)
235 image_name = strdup (map->path);
236 lock_rdwr_release (&local_rdwr_lock, saved_mask);
237
238 return image_name;
239}