blob: c11cb7bed469646fa61284d733fb5cc494b62117 [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;
69 /* If it was mapped before, make sure to mark it unmapped now. */
70 old_list->ei.mapped = false;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -080071 /* 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. */
87static int
88rebuild_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 (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
126static int
127is_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)
Christopher Ferrisf360ccc2014-09-10 16:26:12 -0700136 {
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 }
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800144 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
153PROTECTED int
154map_local_is_readable (unw_word_t addr)
155{
156 return is_flag_set (addr, PROT_READ);
157}
158
159PROTECTED int
160map_local_is_writable (unw_word_t addr)
161{
162 return is_flag_set (addr, PROT_WRITE);
163}
164
165PROTECTED int
Christopher Ferriscdf0d032015-05-04 11:01:39 -0700166local_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)
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800168{
169 struct map_info *map;
170 intrmask_t saved_mask;
Christopher Ferris0fa05b02014-04-09 12:25:23 -0700171 int return_value = -UNW_ENOINFO;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800172
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
Christopher Ferriscdf0d032015-05-04 11:01:39 -0700185 if (map && elf_map_cached_image (as, as_arg, map, ip))
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800186 {
187 *ei = map->ei;
188 *segbase = map->start;
189 *mapoff = map->offset;
190 if (path != NULL)
191 {
192 if (map->path)
193 *path = strdup(map->path);
194 else
195 *path = NULL;
196 }
Christopher Ferris0fa05b02014-04-09 12:25:23 -0700197 return_value = 0;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800198 }
199 lock_rdwr_release (&local_rdwr_lock, saved_mask);
200
Christopher Ferris0fa05b02014-04-09 12:25:23 -0700201 return return_value;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800202}
Christopher Ferrisbb754472014-04-09 18:21:28 -0700203
204PROTECTED char *
205map_local_get_image_name (unw_word_t ip)
206{
207 struct map_info *map;
208 intrmask_t saved_mask;
209 char *image_name = NULL;
210
211 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
212 map = map_find_from_addr (local_map_list, ip);
213 if (!map)
214 {
215 lock_rdwr_release (&local_rdwr_lock, saved_mask);
216 if (rebuild_if_necessary (ip, 0) < 0)
217 return NULL;
218
219 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
220 map = map_find_from_addr (local_map_list, ip);
221 }
222 if (map)
223 image_name = strdup (map->path);
224 lock_rdwr_release (&local_rdwr_lock, saved_mask);
225
226 return image_name;
227}