|  | 
| 13 | 13 | #include <sys/mman.h> | 
| 14 | 14 | #include <unistd.h> | 
| 15 | 15 | #include <cstddef> | 
|  | 16 | +#include <fstream> | 
|  | 17 | +#include <mutex> | 
|  | 18 | +#include <sstream> | 
| 16 | 19 | 
 | 
| 17 | 20 | #include "xenia/base/math.h" | 
| 18 | 21 | #include "xenia/base/platform.h" | 
| @@ -79,42 +82,142 @@ uint32_t ToPosixProtectFlags(PageAccess access) { | 
| 79 | 82 |   } | 
| 80 | 83 | } | 
| 81 | 84 | 
 | 
|  | 85 | +PageAccess ToXeniaProtectFlags(const char* protection) { | 
|  | 86 | +  if (protection[0] == 'r' && protection[1] == 'w' && protection[2] == 'x') { | 
|  | 87 | +    return PageAccess::kExecuteReadWrite; | 
|  | 88 | +  } | 
|  | 89 | +  if (protection[0] == 'r' && protection[1] == '-' && protection[2] == 'x') { | 
|  | 90 | +    return PageAccess::kExecuteReadOnly; | 
|  | 91 | +  } | 
|  | 92 | +  if (protection[0] == 'r' && protection[1] == 'w' && protection[2] == '-') { | 
|  | 93 | +    return PageAccess::kReadWrite; | 
|  | 94 | +  } | 
|  | 95 | +  if (protection[0] == 'r' && protection[1] == '-' && protection[2] == '-') { | 
|  | 96 | +    return PageAccess::kReadOnly; | 
|  | 97 | +  } | 
|  | 98 | +  return PageAccess::kNoAccess; | 
|  | 99 | +} | 
|  | 100 | + | 
| 82 | 101 | bool IsWritableExecutableMemorySupported() { return true; } | 
| 83 | 102 | 
 | 
|  | 103 | +struct MappedFileRange { | 
|  | 104 | +  uintptr_t region_begin; | 
|  | 105 | +  uintptr_t region_end; | 
|  | 106 | +}; | 
|  | 107 | + | 
|  | 108 | +std::vector<MappedFileRange> mapped_file_ranges; | 
|  | 109 | +std::mutex g_mapped_file_ranges_mutex; | 
|  | 110 | + | 
| 84 | 111 | void* AllocFixed(void* base_address, size_t length, | 
| 85 | 112 |                  AllocationType allocation_type, PageAccess access) { | 
| 86 | 113 |   // mmap does not support reserve / commit, so ignore allocation_type. | 
| 87 | 114 |   uint32_t prot = ToPosixProtectFlags(access); | 
| 88 |  | -  int flags = 0; | 
|  | 115 | +  int flags = MAP_PRIVATE | MAP_ANONYMOUS; | 
|  | 116 | + | 
| 89 | 117 |   if (base_address != nullptr) { | 
| 90 |  | -    flags = MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS; | 
| 91 |  | -  } else { | 
| 92 |  | -    flags = MAP_PRIVATE | MAP_ANONYMOUS; | 
| 93 |  | -  } | 
| 94 |  | -  void* result = mmap(base_address, length, prot, | 
| 95 |  | -                      MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0); | 
| 96 |  | -  if (result == MAP_FAILED) { | 
| 97 |  | -    return nullptr; | 
| 98 |  | -  } else { | 
|  | 118 | +    if (allocation_type == AllocationType::kCommit) { | 
|  | 119 | +      if (Protect(base_address, length, access)) { | 
|  | 120 | +        return base_address; | 
|  | 121 | +      } | 
|  | 122 | +      return nullptr; | 
|  | 123 | +    } | 
|  | 124 | +    flags |= MAP_FIXED_NOREPLACE; | 
|  | 125 | +  } | 
|  | 126 | + | 
|  | 127 | +  void* result = mmap(base_address, length, prot, flags, -1, 0); | 
|  | 128 | + | 
|  | 129 | +  if (result != MAP_FAILED) { | 
| 99 | 130 |     return result; | 
| 100 | 131 |   } | 
|  | 132 | +  return nullptr; | 
| 101 | 133 | } | 
| 102 | 134 | 
 | 
| 103 | 135 | bool DeallocFixed(void* base_address, size_t length, | 
| 104 | 136 |                   DeallocationType deallocation_type) { | 
| 105 |  | -  return munmap(base_address, length) == 0; | 
|  | 137 | +  const auto region_begin = reinterpret_cast<uintptr_t>(base_address); | 
|  | 138 | +  const uintptr_t region_end = | 
|  | 139 | +      reinterpret_cast<uintptr_t>(base_address) + length; | 
|  | 140 | + | 
|  | 141 | +  std::lock_guard guard(g_mapped_file_ranges_mutex); | 
|  | 142 | +  for (const auto& mapped_range : mapped_file_ranges) { | 
|  | 143 | +    if (region_begin >= mapped_range.region_begin && | 
|  | 144 | +        region_end <= mapped_range.region_end) { | 
|  | 145 | +      switch (deallocation_type) { | 
|  | 146 | +        case DeallocationType::kDecommit: | 
|  | 147 | +          return Protect(base_address, length, PageAccess::kNoAccess); | 
|  | 148 | +        case DeallocationType::kRelease: | 
|  | 149 | +          assert_always("Error: Tried to release mapped memory!"); | 
|  | 150 | +        default: | 
|  | 151 | +          assert_unhandled_case(deallocation_type); | 
|  | 152 | +      } | 
|  | 153 | +    } | 
|  | 154 | +  } | 
|  | 155 | + | 
|  | 156 | +  switch (deallocation_type) { | 
|  | 157 | +    case DeallocationType::kDecommit: | 
|  | 158 | +      return Protect(base_address, length, PageAccess::kNoAccess); | 
|  | 159 | +    case DeallocationType::kRelease: | 
|  | 160 | +      return munmap(base_address, length) == 0; | 
|  | 161 | +    default: | 
|  | 162 | +      assert_unhandled_case(deallocation_type); | 
|  | 163 | +  } | 
| 106 | 164 | } | 
| 107 | 165 | 
 | 
| 108 | 166 | bool Protect(void* base_address, size_t length, PageAccess access, | 
| 109 | 167 |              PageAccess* out_old_access) { | 
| 110 |  | -  // Linux does not have a syscall to query memory permissions. | 
| 111 |  | -  assert_null(out_old_access); | 
|  | 168 | +  if (out_old_access) { | 
|  | 169 | +    size_t length_copy = length; | 
|  | 170 | +    QueryProtect(base_address, length_copy, *out_old_access); | 
|  | 171 | +  } | 
| 112 | 172 | 
 | 
| 113 | 173 |   uint32_t prot = ToPosixProtectFlags(access); | 
| 114 | 174 |   return mprotect(base_address, length, prot) == 0; | 
| 115 | 175 | } | 
| 116 | 176 | 
 | 
| 117 | 177 | bool QueryProtect(void* base_address, size_t& length, PageAccess& access_out) { | 
|  | 178 | +  // No generic POSIX solution exists. The Linux solution should work on all | 
|  | 179 | +  // Linux kernel based OS, including Android. | 
|  | 180 | +  std::ifstream memory_maps; | 
|  | 181 | +  memory_maps.open("/proc/self/maps", std::ios_base::in); | 
|  | 182 | +  std::string maps_entry_string; | 
|  | 183 | + | 
|  | 184 | +  while (std::getline(memory_maps, maps_entry_string)) { | 
|  | 185 | +    std::stringstream entry_stream(maps_entry_string); | 
|  | 186 | +    uintptr_t map_region_begin, map_region_end; | 
|  | 187 | +    char separator, protection[4]; | 
|  | 188 | + | 
|  | 189 | +    entry_stream >> std::hex >> map_region_begin >> separator >> | 
|  | 190 | +        map_region_end >> protection; | 
|  | 191 | + | 
|  | 192 | +    if (map_region_begin <= reinterpret_cast<uintptr_t>(base_address) && | 
|  | 193 | +        map_region_end > reinterpret_cast<uintptr_t>(base_address)) { | 
|  | 194 | +      length = map_region_end - reinterpret_cast<uintptr_t>(base_address); | 
|  | 195 | + | 
|  | 196 | +      access_out = ToXeniaProtectFlags(protection); | 
|  | 197 | + | 
|  | 198 | +      // Look at the next consecutive mappings | 
|  | 199 | +      while (std::getline(memory_maps, maps_entry_string)) { | 
|  | 200 | +        std::stringstream next_entry_stream(maps_entry_string); | 
|  | 201 | +        uintptr_t next_map_region_begin, next_map_region_end; | 
|  | 202 | +        char next_protection[4]; | 
|  | 203 | + | 
|  | 204 | +        next_entry_stream >> std::hex >> next_map_region_begin >> separator >> | 
|  | 205 | +            next_map_region_end >> next_protection; | 
|  | 206 | +        if (map_region_end == next_map_region_begin && | 
|  | 207 | +            access_out == ToXeniaProtectFlags(next_protection)) { | 
|  | 208 | +          length = | 
|  | 209 | +              next_map_region_end - reinterpret_cast<uintptr_t>(base_address); | 
|  | 210 | +          continue; | 
|  | 211 | +        } | 
|  | 212 | +        break; | 
|  | 213 | +      } | 
|  | 214 | + | 
|  | 215 | +      memory_maps.close(); | 
|  | 216 | +      return true; | 
|  | 217 | +    } | 
|  | 218 | +  } | 
|  | 219 | + | 
|  | 220 | +  memory_maps.close(); | 
| 118 | 221 |   return false; | 
| 119 | 222 | } | 
| 120 | 223 | 
 | 
| @@ -184,12 +287,41 @@ void CloseFileMappingHandle(FileMappingHandle handle, | 
| 184 | 287 | void* MapFileView(FileMappingHandle handle, void* base_address, size_t length, | 
| 185 | 288 |                   PageAccess access, size_t file_offset) { | 
| 186 | 289 |   uint32_t prot = ToPosixProtectFlags(access); | 
| 187 |  | -  return mmap64(base_address, length, prot, MAP_PRIVATE | MAP_ANONYMOUS, handle, | 
| 188 |  | -                file_offset); | 
|  | 290 | + | 
|  | 291 | +  int flags = MAP_SHARED; | 
|  | 292 | +  if (base_address != nullptr) { | 
|  | 293 | +    flags |= MAP_FIXED_NOREPLACE; | 
|  | 294 | +  } | 
|  | 295 | + | 
|  | 296 | +  void* result = mmap(base_address, length, prot, flags, handle, file_offset); | 
|  | 297 | + | 
|  | 298 | +  if (result != MAP_FAILED) { | 
|  | 299 | +    std::lock_guard guard(g_mapped_file_ranges_mutex); | 
|  | 300 | +    mapped_file_ranges.push_back( | 
|  | 301 | +        {reinterpret_cast<uintptr_t>(result), | 
|  | 302 | +         reinterpret_cast<uintptr_t>(result) + length}); | 
|  | 303 | +    return result; | 
|  | 304 | +  } | 
|  | 305 | + | 
|  | 306 | +  return nullptr; | 
| 189 | 307 | } | 
| 190 | 308 | 
 | 
| 191 | 309 | bool UnmapFileView(FileMappingHandle handle, void* base_address, | 
| 192 | 310 |                    size_t length) { | 
|  | 311 | +  std::lock_guard guard(g_mapped_file_ranges_mutex); | 
|  | 312 | +  for (auto mapped_range = mapped_file_ranges.begin(); | 
|  | 313 | +       mapped_range != mapped_file_ranges.end();) { | 
|  | 314 | +    if (mapped_range->region_begin == | 
|  | 315 | +            reinterpret_cast<uintptr_t>(base_address) && | 
|  | 316 | +        mapped_range->region_end == | 
|  | 317 | +            reinterpret_cast<uintptr_t>(base_address) + length) { | 
|  | 318 | +      mapped_file_ranges.erase(mapped_range); | 
|  | 319 | +      return munmap(base_address, length) == 0; | 
|  | 320 | +    } | 
|  | 321 | +    ++mapped_range; | 
|  | 322 | +  } | 
|  | 323 | +  // TODO: Implement partial file unmapping. | 
|  | 324 | +  assert_always("Error: Partial unmapping of files not yet supported."); | 
| 193 | 325 |   return munmap(base_address, length) == 0; | 
| 194 | 326 | } | 
| 195 | 327 | 
 | 
|  | 
0 commit comments