• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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