| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "crazy_linker_proc_maps.h" |
| |
| #include <inttypes.h> |
| #include <limits.h> |
| |
| #include "elf_traits.h" |
| #include "crazy_linker_debug.h" |
| #include "crazy_linker_line_reader.h" |
| #include "crazy_linker_util.h" |
| #include "crazy_linker_system.h" |
| |
| namespace crazy { |
| |
| namespace { |
| |
| // Decompose the components of a /proc/$PID/maps file into multiple |
| // components. |line| should be the address of a zero-terminated line |
| // of input. On success, returns true and sets |*entry|, false otherwise. |
| // |
| // IMPORTANT: On success, |entry->path| will point into the input line, |
| // the caller will have to copy the string into a different location if |
| // it needs to persist it. |
| bool ParseProcMapsLine(const char* line, |
| const char* line_end, |
| ProcMaps::Entry* entry) { |
| // Example input lines on a 64-bit system, one cannot assume that |
| // everything is properly sized. |
| // |
| // 00400000-0040b000 r-xp 00000000 08:01 6570708 |
| // /bin/cat |
| // 0060a000-0060b000 r--p 0000a000 08:01 6570708 |
| // /bin/cat |
| // 0060b000-0060c000 rw-p 0000b000 08:01 6570708 |
| // /bin/cat |
| // 01dd0000-01df1000 rw-p 00000000 00:00 0 |
| // [heap] |
| // 7f4b8d4d7000-7f4b8e22a000 r--p 00000000 08:01 38666648 |
| // /usr/lib/locale/locale-archive |
| // 7f4b8e22a000-7f4b8e3df000 r-xp 00000000 08:01 28836281 |
| // /lib/x86_64-linux-gnu/libc-2.15.so |
| // 7f4b8e3df000-7f4b8e5de000 ---p 001b5000 08:01 28836281 |
| // /lib/x86_64-linux-gnu/libc-2.15.so |
| // 7f4b8e5de000-7f4b8e5e2000 r--p 001b4000 08:01 28836281 |
| // /lib/x86_64-linux-gnu/libc-2.15.so |
| // 7f4b8e5e2000-7f4b8e5e4000 rw-p 001b8000 08:01 28836281 |
| // /lib/x86_64-linux-gnu/libc-2.15.so |
| const char* p = line; |
| for (int token = 0; token < 7; ++token) { |
| char separator = (token == 0) ? '-' : ' '; |
| // skip leading token separators first. |
| while (p < line_end && *p == separator) |
| p++; |
| |
| // find start and end of current token, and compute start of |
| // next search. The result of memchr(_,_,0) is undefined, treated as |
| // not-found. |
| const char* tok_start = p; |
| const size_t range = line_end - p; |
| const char* tok_end; |
| if (range > 0) |
| tok_end = static_cast<const char*>(memchr(p, separator, range)); |
| else |
| tok_end = NULL; |
| if (!tok_end) { |
| tok_end = line_end; |
| p = line_end; |
| } else { |
| p = tok_end + 1; |
| } |
| |
| if (tok_end == tok_start) { |
| if (token == 6) { |
| // empty token can happen for index 6, when there is no path |
| // element on the line. This corresponds to anonymous memory |
| // mapped segments. |
| entry->path = NULL; |
| entry->path_len = 0; |
| break; |
| } |
| return false; |
| } |
| |
| switch (token) { |
| case 0: // vma_start |
| entry->vma_start = static_cast<size_t>(strtoumax(tok_start, NULL, 16)); |
| break; |
| |
| case 1: // vma_end |
| entry->vma_end = static_cast<size_t>(strtoumax(tok_start, NULL, 16)); |
| break; |
| |
| case 2: // protection bits |
| { |
| int flags = 0; |
| for (const char* t = tok_start; t < tok_end; ++t) { |
| if (*t == 'r') |
| flags |= PROT_READ; |
| if (*t == 'w') |
| flags |= PROT_WRITE; |
| if (*t == 'x') |
| flags |= PROT_EXEC; |
| } |
| entry->prot_flags = flags; |
| } break; |
| |
| case 3: // page offset |
| entry->load_offset = |
| static_cast<size_t>(strtoumax(tok_start, NULL, 16)) * PAGE_SIZE; |
| break; |
| |
| case 6: // path |
| // Get rid of trailing newlines, if any. |
| while (tok_end > tok_start && tok_end[-1] == '\n') |
| tok_end--; |
| entry->path = tok_start; |
| entry->path_len = tok_end - tok_start; |
| break; |
| |
| default: // ignore all other tokens. |
| ; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| ProcMaps::ProcMaps() { |
| static const char kFilePath[] = "/proc/self/maps"; |
| // NOTE: On Android, /proc/self/maps can easily be more than 10 kiB due |
| // to the large number of shared libraries and memory mappings loaded or |
| // created by the framework. Use a large capacity to reduce the number |
| // of dynamic allocations during parsing. |
| const size_t kCapacity = 16000; |
| LineReader reader(kFilePath, kCapacity); |
| while (reader.GetNextLine()) { |
| Entry entry = {}; |
| if (!ParseProcMapsLine(reader.line(), reader.line() + reader.length(), |
| &entry)) { |
| // Ignore broken lines. |
| continue; |
| } |
| |
| // Reallocate path. |
| const char* old_path = entry.path; |
| if (old_path) { |
| char* new_path = static_cast<char*>(::malloc(entry.path_len + 1)); |
| ::memcpy(new_path, old_path, entry.path_len); |
| new_path[entry.path_len] = '\0'; |
| entry.path = const_cast<const char*>(new_path); |
| } |
| |
| entries_.PushBack(entry); |
| } |
| } |
| |
| ProcMaps::~ProcMaps() { |
| for (ProcMaps::Entry& entry : entries_) { |
| ::free(const_cast<char*>(entry.path)); |
| } |
| entries_.Resize(0); |
| } |
| |
| const ProcMaps::Entry* ProcMaps::FindEntryForAddress(void* address) const { |
| size_t vma_addr = reinterpret_cast<size_t>(address); |
| for (const Entry& entry : entries_) { |
| if (entry.vma_start <= vma_addr && vma_addr < entry.vma_end) |
| return &entry; |
| } |
| return nullptr; |
| } |
| |
| const ProcMaps::Entry* ProcMaps::FindEntryForFile(const char* file_name) const { |
| size_t file_name_len = strlen(file_name); |
| bool is_base_name = (strchr(file_name, '/') == NULL); |
| for (const Entry& entry : entries_) { |
| // Skip vDSO et al. |
| if (entry.path_len == 0 || entry.path[0] == '[') |
| continue; |
| |
| const char* entry_name = entry.path; |
| size_t entry_len = entry.path_len; |
| |
| if (is_base_name) { |
| const char* p = reinterpret_cast<const char*>( |
| ::memrchr(entry.path, '/', entry.path_len)); |
| if (p) { |
| entry_name = p + 1; |
| entry_len = entry.path_len - (p - entry.path) - 1; |
| } |
| } |
| |
| if (file_name_len == entry_len && |
| !memcmp(file_name, entry_name, entry_len)) { |
| return &entry; |
| } |
| } |
| return nullptr; |
| } |
| |
| bool FindElfBinaryForAddress(void* address, |
| uintptr_t* load_address, |
| char* path_buffer, |
| size_t path_buffer_len) { |
| ProcMaps self_maps; |
| |
| uintptr_t addr = reinterpret_cast<uintptr_t>(address); |
| |
| const ProcMaps::Entry* entry = self_maps.FindEntryForAddress(address); |
| if (!entry) { |
| return false; |
| } |
| *load_address = entry->vma_start; |
| if (!entry->path) { |
| LOG("Could not find ELF binary path!?"); |
| return false; |
| } |
| if (entry->path_len >= path_buffer_len) { |
| LOG("ELF binary path too long: '%.*s'", entry->path_len, entry->path); |
| return false; |
| } |
| ::memcpy(path_buffer, entry->path, entry->path_len); |
| path_buffer[entry->path_len] = '\0'; |
| return true; |
| } |
| |
| bool FindLoadAddressForFile(const char* file_name, |
| uintptr_t* load_address, |
| uintptr_t* load_offset) { |
| ProcMaps self_maps; |
| const ProcMaps::Entry* entry = self_maps.FindEntryForFile(file_name); |
| if (!entry) { |
| return false; |
| } |
| *load_address = entry->vma_start; |
| *load_offset = entry->load_offset; |
| return true; |
| } |
| |
| } // namespace crazy |