1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef SRC_KALLSYMS_KERNEL_SYMBOL_MAP_H_ 18 #define SRC_KALLSYMS_KERNEL_SYMBOL_MAP_H_ 19 20 #include <stdint.h> 21 22 #include <array> 23 #include <forward_list> 24 #include <string> 25 #include <vector> 26 27 #include "perfetto/ext/base/scoped_file.h" 28 29 namespace perfetto { 30 31 namespace base { 32 class StringView; 33 } 34 35 // A parser and memory-efficient container for /proc/kallsyms. 36 // It can store a full kernel symbol table in ~1.2MB of memory and perform fast 37 // lookups using logarithmic binary searches + bounded linear scans. 38 // 39 // /proc/kallsyms is a ~10 MB text file that contains the map of kernel symbols, 40 // as follows: 41 // ffffff8f77682f8c t el0_sync_invalid 42 // ffffff8f77683060 t el0_irq_invalid 43 // ... 44 // In a typipcal Android kernel, it consists of 213K lines. Out of these, only 45 // 116K are interesting for the sake of symbolizing kernel functions, the rest 46 // are .rodata (variables), weak or other useless symbols. 47 // Still, even keeping around 116K pointers would require 116K * 8 ~= 1 MB of 48 // memory, without accounting for any strings for the symbols names. 49 // The SUM(str.len) for the 116K symbol names adds up to 2.7 MB (without 50 // counting their addresses). 51 // However consider the following: 52 // - Symbol addresses are mostly contiguous. Modulo the initial KASLR loading 53 // address, most symbols are few hundreds bytes apart from each other. 54 // - Symbol names are made of tokens that are quite frequent (token: the result 55 // of name.split('_')). If we tokenize the 2.7 MB of strings, the resulting 56 // SUM(distinct_token.len) goes down 2.7MB -> 146 KB. This is because tokens 57 // like "get", "set" or "event" show up thousands of times. 58 // - Symbol names are ASCII strings using only 7 out of 8 bits. 59 // 60 // In the light of this, the in-memory architecture of this data structure is 61 // as follows: 62 // We keep two tables around: (1) a token table and (2) a symbol table. Both 63 // table are a flat byte vector with some sparse lookaside index to make lookups 64 // faster and avoid full linear scans. 65 // 66 // Token table 67 // ----------- 68 // The token table is a flat char buffer. Tokens are variable size (>0). Each 69 // token is identified by its ordinality, so token id 3 is the 3rd token in 70 // the table. All tokens are concatenated together. 71 // Given the ASCII encoding, the MSB is used as a terminator. So instead of 72 // wasting an extra NUL byte for each string, the last char of each token has 73 // the MSB set. 74 // Furthermore, a lookaside index stores the offset of tokens (i.e. Token N 75 // starts at offset O in the buffer) to allow fast lookups. In order to avoid 76 // wasting too much memory, the index is sparse and track the offsets of only 77 // one every kTokenIndexSamplinig tokens. 78 // When looking up a token ID N, the table seeks at the offset of the closest 79 // token <= N, and then scans linearly the next (at most kTokenIndexSamplinig) 80 // tokens, counting the MSBs found, until the right token id is found. 81 // buf: set*get*kernel*load*fpsimd*return*wrapper*el0*skip*sync*neon*bit*aes 82 // ^ ^ ^ 83 // | | | 84 // index: 0@0 4@15 8@21 85 86 // Symbol table 87 // ------------ 88 // The symbol table is a flat char buffer that stores for each symbol: its 89 // address + the list of token indexes in the token table. The main caveats are 90 // that: 91 // - Symbol addresses are delta encoded (delta from prev symbol's addr). 92 // - Both delta addresses and token indexes are var-int encoded. 93 // - The LSB of token indexes is used as EOF marker (i.e. the next varint is 94 // the delta-addr for the next symbol). This time the LSB is used because of 95 // the varint encoding. 96 // At parsing time symbols are ordered by address and tokens are sorted by 97 // frequency, so that the top used 64 tokens can be represented with 1 byte. 98 // (Rationale for 64: 1 byte = 8 bits. The MSB bit of each byte is used for the 99 // varint encoding, the LSB bit of each number is used as end-of-tokens marker. 100 // There are 6 bits left -> 64 indexes can be represented using one byte). 101 // In summary the symbol table looks as follows: 102 // 103 // Base address: 0xbeef0000 104 // Symbol buffer: 105 // 0 1|0 4|0 6|1 // 0xbeef0000: 1,4,6 -> get_fpsimd_wrapper 106 // 8 7|0 3|1 // 0xbeef0008: 7,3 -> el0_load 107 // ... 108 // Like in the case of the token table, a lookaside index keeps track of the 109 // offset of one every kSymIndexSamplinig addresses. 110 // The Lookup(ADDR) function operates as follows: 111 // 1. Performs a logarithmic binary search in the symbols index, finding the 112 // offset of the closest addres <= ADDR. 113 // 2. Skip over at most kSymIndexSamplinig until the symbol is found. 114 // 3. For each token index, lookup the corresponding token string and 115 // concatenate them to build the symbol name. 116 117 class KernelSymbolMap { 118 public: 119 // The two constants below are changeable only for the benchmark use. 120 // Trades off size of the root |index_| vs worst-case linear scans size. 121 // A higher number makes the index more sparse. 122 static size_t kSymIndexSampling; 123 124 // Trades off size of the TokenTable |index_| vs worst-case linear scans size. 125 static size_t kTokenIndexSampling; 126 127 // Parses a kallsyms file. Returns the number of valid symbols decoded. 128 // Does not take ownership of the fd. 129 size_t Parse(int fd); 130 131 // Looks up the closest symbol (i.e. the one with the highest address <= 132 // |addr|) from its absolute 64-bit address. 133 // Returns an empty string if the symbol is not found (which can happen only 134 // if the passed |addr| is < min(addr)). 135 std::string Lookup(uint64_t addr); 136 137 // Returns the numberr of valid symbols decoded. num_syms()138 size_t num_syms() const { return num_syms_; } 139 140 // Returns the size in bytes used by the adddress table (without counting 141 // the tokens). addr_bytes()142 size_t addr_bytes() const { return buf_.size() + index_.size() * 8; } 143 144 // Returns the total memory usage in bytes. size_bytes()145 size_t size_bytes() const { return addr_bytes() + tokens_.size_bytes(); } 146 147 // Token table. 148 class TokenTable { 149 public: 150 using TokenId = uint32_t; 151 TokenTable(); 152 ~TokenTable(); 153 TokenId Add(const std::string&); 154 base::StringView Lookup(TokenId); size_bytes()155 size_t size_bytes() const { return buf_.size() + index_.size() * 4; } 156 shrink_to_fit()157 void shrink_to_fit() { 158 buf_.shrink_to_fit(); 159 index_.shrink_to_fit(); 160 } 161 162 private: 163 TokenId num_tokens_ = 0; 164 165 std::vector<char> buf_; // Token buffer. 166 167 // The value i-th in the vector contains the offset (within |buf_|) of the 168 // (i * kTokenIndexSamplinig)-th token. 169 std::vector<uint32_t> index_; 170 }; 171 172 private: 173 TokenTable tokens_; // Token table. 174 175 uint64_t base_addr_ = 0; // Address of the first symbol (after sorting). 176 size_t num_syms_ = 0; // Number of valid symbols stored. 177 std::vector<uint8_t> buf_; // Symbol buffer. 178 179 // The key is (address - base_addr_), the value is the byte offset in |buf_| 180 // where the symbol entry starts (i.e. the start of the varint that tells the 181 // delta from the previous symbol). 182 std::vector<std::pair<uint32_t /*rel_addr*/, uint32_t /*offset*/>> index_; 183 }; 184 185 } // namespace perfetto 186 187 #endif // SRC_KALLSYMS_KERNEL_SYMBOL_MAP_H_ 188