1 /* libunwind - a platform-independent unwind library 2 Copyright (C) 2014 The Android Open Source Project 3 4 This file is part of libunwind. 5 6 Permission is hereby granted, free of charge, to any person obtaining 7 a copy of this software and associated documentation files (the 8 "Software"), to deal in the Software without restriction, including 9 without limitation the rights to use, copy, modify, merge, publish, 10 distribute, sublicense, and/or sell copies of the Software, and to 11 permit persons to whom the Software is furnished to do so, subject to 12 the following conditions: 13 14 The above copyright notice and this permission notice shall be 15 included in all copies or substantial portions of the Software. 16 17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 WITH 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. */ 30 extern struct map_info *local_map_list; 31 extern lock_rdwr_var (local_rdwr_lock); 32 33 static pthread_once_t local_rdwr_lock_init = PTHREAD_ONCE_INIT; 34 35 static void 36 map_local_init_once (void) 37 { 38 lock_rdwr_init (&local_rdwr_lock); 39 } 40 41 HIDDEN void 42 map_local_init (void) 43 { 44 pthread_once (&local_rdwr_lock_init, map_local_init_once); 45 } 46 47 static void 48 move_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. */ 88 static int 89 rebuild_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 127 static int 128 is_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 154 PROTECTED int 155 map_local_is_readable (unw_word_t addr) 156 { 157 return is_flag_set (addr, PROT_READ); 158 } 159 160 PROTECTED int 161 map_local_is_writable (unw_word_t addr) 162 { 163 return is_flag_set (addr, PROT_WRITE); 164 } 165 166 PROTECTED int 167 local_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 205 PROTECTED char * 206 map_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