1/* 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
33static 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
41HIDDEN void
42map_local_init (void)
43{
44  pthread_once (&local_rdwr_lock_init, map_local_init_once);
45}
46
47static void
48move_cached_elf_data (struct map_info *old_list, struct map_info *new_list)
49{
50  while (old_list)
51    {
52      if (old_list->ei.image == NULL)
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.size = old_list->ei.size;
69              new_list->ei.image = old_list->ei.image;
70              old_list->ei.size = 0;
71              old_list->ei.image = NULL;
72              /* Don't bother breaking out of the loop, the next while check
73                 is guaranteed to fail, causing us to break out of the loop
74                 after advancing to the next map element. */
75            }
76          new_list = new_list->next;
77        }
78      old_list = old_list->next;
79    }
80}
81
82/* In order to cache as much as possible while unwinding the local process,
83   we gather a map of the process before starting. If the cache is missing
84   a map, or a map exists but doesn't have the "expected_flags" set, then
85   check if the cache needs to be regenerated.
86   While regenerating the list, grab a write lock to avoid any readers using
87   the list while it's being modified. */
88static int
89rebuild_if_necessary (unw_word_t addr, int expected_flags)
90{
91  struct map_info *map;
92  struct map_info *new_list;
93  int ret_value = -1;
94  intrmask_t saved_mask;
95
96  new_list = map_create_list (getpid());
97  map = map_find_from_addr (new_list, addr);
98  if (map && (expected_flags == 0 || (map->flags & expected_flags)))
99    {
100      /* Get a write lock on local_map_list since it's going to be modified. */
101      lock_rdwr_wr_acquire (&local_rdwr_lock, saved_mask);
102
103      /* Just in case another thread rebuilt the map, check to see if the
104         ip with expected_flags is in local_map_list. If not, the assumption
105         is that new_list is newer than local_map_list because the map only
106         gets new maps with new permissions. If this is not true, then it
107         would be necessary to regenerate the list one more time. */
108      ret_value = 0;
109      map = map_find_from_addr (local_map_list, addr);
110      if (!map || (expected_flags != 0 && !(map->flags & expected_flags)))
111        {
112          /* Move any cached items to the new list. */
113          move_cached_elf_data (local_map_list, new_list);
114          map = local_map_list;
115          local_map_list = new_list;
116          new_list = map;
117        }
118
119      lock_rdwr_release (&local_rdwr_lock, saved_mask);
120    }
121
122  map_destroy_list (new_list);
123
124  return ret_value;
125}
126
127static int
128is_flag_set (unw_word_t addr, int flag)
129{
130  struct map_info *map;
131  int ret = 0;
132  intrmask_t saved_mask;
133
134  lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
135  map = map_find_from_addr (local_map_list, addr);
136  if (map != NULL)
137    {
138      if (map->flags & MAP_FLAGS_DEVICE_MEM)
139        {
140          lock_rdwr_release (&local_rdwr_lock, saved_mask);
141          return 0;
142        }
143      ret = map->flags & flag;
144    }
145  lock_rdwr_release (&local_rdwr_lock, saved_mask);
146
147  if (!ret && rebuild_if_necessary (addr, flag) == 0)
148    {
149      return 1;
150    }
151  return ret;
152}
153
154PROTECTED int
155map_local_is_readable (unw_word_t addr)
156{
157  return is_flag_set (addr, PROT_READ);
158}
159
160PROTECTED int
161map_local_is_writable (unw_word_t addr)
162{
163  return is_flag_set (addr, PROT_WRITE);
164}
165
166PROTECTED int
167local_get_elf_image (struct elf_image *ei, unw_word_t ip,
168                     unsigned long *segbase, unsigned long *mapoff, char **path)
169{
170  struct map_info *map;
171  intrmask_t saved_mask;
172  int return_value = -UNW_ENOINFO;
173
174  lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
175  map = map_find_from_addr (local_map_list, ip);
176  if (!map)
177    {
178      lock_rdwr_release (&local_rdwr_lock, saved_mask);
179      if (rebuild_if_necessary (ip, 0) < 0)
180        return -UNW_ENOINFO;
181
182      lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
183      map = map_find_from_addr (local_map_list, ip);
184    }
185
186  if (map && elf_map_cached_image (map, ip) == 0)
187    {
188      *ei = map->ei;
189      *segbase = map->start;
190      *mapoff = map->offset;
191      if (path != NULL)
192        {
193          if (map->path)
194            *path = strdup(map->path);
195          else
196            *path = NULL;
197        }
198      return_value = 0;
199    }
200  lock_rdwr_release (&local_rdwr_lock, saved_mask);
201
202  return return_value;
203}
204
205PROTECTED char *
206map_local_get_image_name (unw_word_t ip)
207{
208  struct map_info *map;
209  intrmask_t saved_mask;
210  char *image_name = NULL;
211
212  lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
213  map = map_find_from_addr (local_map_list, ip);
214  if (!map)
215    {
216      lock_rdwr_release (&local_rdwr_lock, saved_mask);
217      if (rebuild_if_necessary (ip, 0) < 0)
218        return NULL;
219
220      lock_rdwr_rd_acquire (&local_rdwr_lock, saved_mask);
221      map = map_find_from_addr (local_map_list, ip);
222    }
223  if (map)
224    image_name = strdup (map->path);
225  lock_rdwr_release (&local_rdwr_lock, saved_mask);
226
227  return image_name;
228}
229