blob: c501732e02bcd2bf5620e4e3755554d0d20911f8 [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
33HIDDEN void
34map_local_init (void)
35{
36 lock_rdwr_init (&local_rdwr_lock);
37}
38
39static void
40move_cached_elf_data (struct map_info *old_list, struct map_info *new_list)
41{
42 while (old_list)
43 {
44 if (old_list->ei.image == NULL)
45 {
46 old_list = old_list->next;
47 continue;
48 }
49 /* Both lists are in order, so it's not necessary to scan through
50 from the beginning of new_list each time looking for a match to
51 the current map. As we progress, simply start from the last element
52 in new_list we checked. */
53 while (new_list && old_list->start <= new_list->start)
54 {
55 if (old_list->start == new_list->start
56 && old_list->end == new_list->end)
57 {
58 /* No need to do any lock, the entire local_map_list is locked
59 at this point. */
60 new_list->ei.size = old_list->ei.size;
61 new_list->ei.image = old_list->ei.image;
62 old_list->ei.size = 0;
63 old_list->ei.image = NULL;
64 /* Don't bother breaking out of the loop, the next while check
65 is guaranteed to fail, causing us to break out of the loop
66 after advancing to the next map element. */
67 }
68 new_list = new_list->next;
69 }
70 old_list = old_list->next;
71 }
72}
73
74/* In order to cache as much as possible while unwinding the local process,
75 we gather a map of the process before starting. If the cache is missing
76 a map, or a map exists but doesn't have the "expected_flags" set, then
77 check if the cache needs to be regenerated.
78 While regenerating the list, grab a write lock to avoid any readers using
79 the list while it's being modified. */
80static int
81rebuild_if_necessary (unw_word_t addr, int expected_flags)
82{
83 struct map_info *map;
84 struct map_info *new_list;
85 int ret_value = -1;
86 intrmask_t saved_mask;
87
88 new_list = map_create_list (getpid());
89 map = map_find_from_addr (new_list, addr);
90 if (map && (expected_flags == 0 || (map->flags & expected_flags)))
91 {
92 /* Get a write lock on local_map_list since it's going to be modified. */
93 lock_rdwr_wr_acquire (&local_rdwr_lock, saved_mask);
94
95 /* Just in case another thread rebuilt the map, check to see if the
96 ip with expected_flags is in local_map_list. If not, the assumption
97 is that new_list is newer than local_map_list because the map only
98 gets new maps with new permissions. If this is not true, then it
99 would be necessary to regenerate the list one more time. */
100 ret_value = 0;
101 map = map_find_from_addr (local_map_list, addr);
102 if (!map || (expected_flags != 0 && !(map->flags & expected_flags)))
103 {
104 /* Move any cached items to the new list. */
105 move_cached_elf_data (local_map_list, new_list);
106 map = local_map_list;
107 local_map_list = new_list;
108 new_list = map;
109 }
110
111 lock_rdwr_release (&local_rdwr_lock, saved_mask);
112 }
113
114 map_destroy_list (new_list);
115
116 return ret_value;
117}
118
119static int
120is_flag_set (unw_word_t addr, int flag)
121{
122 struct map_info *map;
123 int ret = 0;
124 intrmask_t saved_mask;
125
126 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
127 map = map_find_from_addr (local_map_list, addr);
128 if (map != NULL)
129 ret = map->flags & flag;
130 lock_rdwr_release (&local_rdwr_lock, saved_mask);
131
132 if (!ret && rebuild_if_necessary (addr, flag) == 0)
133 {
134 return 1;
135 }
136 return ret;
137}
138
139PROTECTED int
140map_local_is_readable (unw_word_t addr)
141{
142 return is_flag_set (addr, PROT_READ);
143}
144
145PROTECTED int
146map_local_is_writable (unw_word_t addr)
147{
148 return is_flag_set (addr, PROT_WRITE);
149}
150
151PROTECTED int
152local_get_elf_image (struct elf_image *ei, unw_word_t ip,
153 unsigned long *segbase, unsigned long *mapoff, char **path)
154{
155 struct map_info *map;
156 intrmask_t saved_mask;
Christopher Ferris0fa05b02014-04-09 12:25:23 -0700157 int return_value = -UNW_ENOINFO;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800158
159 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
160 map = map_find_from_addr (local_map_list, ip);
161 if (!map)
162 {
163 lock_rdwr_release (&local_rdwr_lock, saved_mask);
164 if (rebuild_if_necessary (ip, 0) < 0)
165 return -UNW_ENOINFO;
166
167 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
168 map = map_find_from_addr (local_map_list, ip);
169 }
170
Christopher Ferris0fa05b02014-04-09 12:25:23 -0700171 if (map && elf_map_cached_image (map, ip) == 0)
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800172 {
173 *ei = map->ei;
174 *segbase = map->start;
175 *mapoff = map->offset;
176 if (path != NULL)
177 {
178 if (map->path)
179 *path = strdup(map->path);
180 else
181 *path = NULL;
182 }
Christopher Ferris0fa05b02014-04-09 12:25:23 -0700183 return_value = 0;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800184 }
185 lock_rdwr_release (&local_rdwr_lock, saved_mask);
186
Christopher Ferris0fa05b02014-04-09 12:25:23 -0700187 return return_value;
Christopher Ferrisf4a8df52014-03-07 19:40:06 -0800188}
Christopher Ferrisbb754472014-04-09 18:21:28 -0700189
190PROTECTED char *
191map_local_get_image_name (unw_word_t ip)
192{
193 struct map_info *map;
194 intrmask_t saved_mask;
195 char *image_name = NULL;
196
197 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
198 map = map_find_from_addr (local_map_list, ip);
199 if (!map)
200 {
201 lock_rdwr_release (&local_rdwr_lock, saved_mask);
202 if (rebuild_if_necessary (ip, 0) < 0)
203 return NULL;
204
205 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
206 map = map_find_from_addr (local_map_list, ip);
207 }
208 if (map)
209 image_name = strdup (map->path);
210 lock_rdwr_release (&local_rdwr_lock, saved_mask);
211
212 return image_name;
213}