blob: dc6f17591064aa3ef9c6c71eac84293821b9c819 [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;
157
158 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
159 map = map_find_from_addr (local_map_list, ip);
160 if (!map)
161 {
162 lock_rdwr_release (&local_rdwr_lock, saved_mask);
163 if (rebuild_if_necessary (ip, 0) < 0)
164 return -UNW_ENOINFO;
165
166 lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
167 map = map_find_from_addr (local_map_list, ip);
168 }
169
170 if (map && elf_map_cached_image (map, ip) < 0)
171 map = NULL;
172 else
173 {
174 *ei = map->ei;
175 *segbase = map->start;
176 *mapoff = map->offset;
177 if (path != NULL)
178 {
179 if (map->path)
180 *path = strdup(map->path);
181 else
182 *path = NULL;
183 }
184 }
185 lock_rdwr_release (&local_rdwr_lock, saved_mask);
186
187 return 0;
188}