• 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 #include "src/kallsyms/kernel_symbol_map.h"
18 
19 #include "perfetto/base/build_config.h"
20 #include "perfetto/base/logging.h"
21 #include "perfetto/ext/base/file_utils.h"
22 #include "perfetto/ext/base/metatrace.h"
23 #include "perfetto/ext/base/paged_memory.h"
24 #include "perfetto/ext/base/scoped_file.h"
25 #include "perfetto/ext/base/string_view.h"
26 #include "perfetto/ext/base/utils.h"
27 #include "perfetto/protozero/proto_utils.h"
28 
29 #include <stdio.h>
30 #include <unistd.h>
31 
32 #include <algorithm>
33 #include <cinttypes>
34 #include <functional>
35 #include <map>
36 #include <unordered_map>
37 #include <utility>
38 
39 namespace perfetto {
40 
41 // On a Pixel 3 this gives an avg. lookup time of 600 ns and a memory usage
42 // of 1.1 MB for 65k symbols. See go/kallsyms-parser-bench.
43 size_t KernelSymbolMap::kSymIndexSampling = 16;
44 size_t KernelSymbolMap::kTokenIndexSampling = 4;
45 
46 namespace {
47 
48 using TokenId = KernelSymbolMap::TokenTable::TokenId;
49 // TODO(rsavitski): the ToT kernel can supposedly contain symbols >255 bytes in
50 // length (in particular due to Rust). Consider bumping this.
51 // https://github.com/torvalds/linux/commit/73bbb94
52 constexpr size_t kSymNameMaxLen = 128;
53 constexpr size_t kSymMaxSizeBytes = 1024 * 1024;
54 
55 // Reads a kallsyms file and decodes its lines using a simple FSM. Calls the
56 // passed lambda for each valid symbol. It skips undefined symbols and other
57 // useless stuff.
58 template <typename Lambda /* void(uint64_t, char, base::StringView) */>
ForEachSym(int fd,Lambda fn)59 void ForEachSym(int fd, Lambda fn) {
60   // /proc/kallsyms looks as follows:
61   // 0000000000026a80 A bpf_trace_sds
62   //
63   // ffffffffc03a6000 T cpufreq_gov_powersave_init<TAB> [cpufreq_powersave]
64   // ffffffffc035d000 T cpufreq_gov_userspace_init<TAB> [cpufreq_userspace]
65   //
66   // We parse it with a state machine that has four states, one for each column.
67   // We don't care about the part in the square brackets and ignore everything
68   // after the symbol name.
69 
70   static constexpr size_t kBufSize = 16 * 1024;
71   base::PagedMemory buffer = base::PagedMemory::Allocate(kBufSize);
72   enum { kSymAddr, kSymType, kSymName, kEatRestOfLine } state = kSymAddr;
73   off_t rd_offset = 0;
74   uint64_t sym_addr = 0;
75   char sym_type = '\0';
76   char sym_name[kSymNameMaxLen + 1];
77   size_t sym_name_len = 0;
78   for (;;) {
79     char* buf = static_cast<char*>(buffer.Get());
80     // Use pread because on android we might be sharing an open file across
81     // processes. Even if they should be mutually excluded, not relying on a
82     // seek position is simpler to reason about.
83     ssize_t rsize = PERFETTO_EINTR(pread(fd, buf, kBufSize, rd_offset));
84     rd_offset += rsize;
85     if (rsize < 0) {
86       PERFETTO_PLOG("pread(kallsyms) failed");
87       return;
88     }
89     if (rsize == 0)
90       return;  // EOF
91     for (size_t i = 0; i < static_cast<size_t>(rsize); i++) {
92       char c = buf[i];
93       const bool is_space = c == ' ' || c == '\t';
94       switch (state) {
95         case kSymAddr:
96           if (c >= '0' && c <= '9') {
97             sym_addr = (sym_addr << 4) | static_cast<uint8_t>(c - '0');
98           } else if (c >= 'a' && c <= 'f') {
99             sym_addr = (sym_addr << 4) | static_cast<uint8_t>(c - 'a' + 10);
100           } else if (is_space) {
101             state = kSymType;
102           } else if (c == '\0') {
103             return;
104           } else {
105             PERFETTO_ELOG("kallsyms parser error: chr 0x%x @ off=%zu", c, i);
106             return;
107           }
108           break;
109 
110         case kSymType:
111           if (is_space)
112             break;  // Eat leading spaces.
113           sym_type = c;
114           state = kSymName;
115           sym_name_len = 0;
116           break;
117 
118         case kSymName:
119           if (is_space && sym_name_len == 0)
120             break;  // Eat leading spaces.
121           if (c && c != '\n' && !is_space && sym_name_len < kSymNameMaxLen) {
122             sym_name[sym_name_len++] = c;
123             break;
124           }
125           fn(sym_addr, sym_type, base::StringView(sym_name, sym_name_len));
126           sym_addr = 0;
127           sym_type = '\0';
128           state = c == '\n' ? kSymAddr : kEatRestOfLine;
129           break;
130 
131         case kEatRestOfLine:
132           if (c == '\n')
133             state = kSymAddr;
134           break;
135       }  // switch(state)
136     }    // for (char in buf)
137   }      // for (read chunk)
138 }
139 
140 // Splits a symbol name into tokens using '_' as a separator, calling the passed
141 // lambda for each token. It splits tokens in a way that allows the original
142 // string to be rebuilt as-is by re-joining using a '_' between each token.
143 // For instance:
144 // _fo_a_b      ->  ["", fo, a, b]
145 // __fo_a_b     ->  [_, fo, a, b]
146 // __fo_a_b_    ->  [_, fo, a, b, ""]
147 // __fo_a_b____ ->  [_, fo, a, b, ___]
148 template <typename Lambda /* void(base::StringView) */>
Tokenize(const base::StringView name,Lambda fn)149 void Tokenize(const base::StringView name, Lambda fn) {
150   size_t tok_start = 0;
151   bool tok_is_sep = !name.empty() && name.at(tok_start) == '_';
152   for (size_t i = 0; i < name.size(); i++) {
153     char c = name.at(i);
154     // Scan until either the end of string or the next character (which is a
155     // '_' in nominal cases, or anything != '_' for tokens made by 1+ '_').
156     if (!tok_is_sep && c == '_') {
157       fn(name.substr(tok_start, i - tok_start));
158       tok_start = i + 1;
159       if (tok_start < name.size()) {
160         tok_is_sep = name.at(tok_start) == '_';
161       }
162     } else if (tok_is_sep && c != '_') {
163       fn(name.substr(tok_start, i - tok_start - 1));
164       tok_start = i;
165       tok_is_sep = false;
166     }
167   }
168   fn(name.substr(tok_start));  // last token
169 }
170 
171 }  // namespace
172 
TokenTable()173 KernelSymbolMap::TokenTable::TokenTable() {
174   // Insert a null token as id 0. We can't just add "" because the empty string
175   // is special-cased and doesn't insert an actual token. So we push a string of
176   // size one that contains only the null character instead.
177   char null_tok = 0;
178   Add(std::string(&null_tok, 1));
179 }
180 
181 KernelSymbolMap::TokenTable::~TokenTable() = default;
182 
183 // Adds a new token to the db. Does not dedupe identical token (with the
184 // exception of the empty string). The caller has to deal with that.
185 // Supports only ASCII characters in the range [1, 127].
186 // The last character of the token will have the MSB set.
Add(const std::string & token)187 TokenId KernelSymbolMap::TokenTable::Add(const std::string& token) {
188   const size_t token_size = token.size();
189   if (token_size == 0)
190     return 0;
191   TokenId id = num_tokens_++;
192 
193   const size_t buf_size_before_insertion = buf_.size();
194   if (id % kTokenIndexSampling == 0)
195     index_.emplace_back(buf_size_before_insertion);
196 
197   const size_t prev_size = buf_.size();
198   buf_.resize(prev_size + token_size);
199   char* tok_wptr = &buf_[prev_size];
200   for (size_t i = 0; i < token_size - 1; i++) {
201     PERFETTO_DCHECK((token.at(i) & 0x80) == 0);  // |token| must be ASCII only.
202     *(tok_wptr++) = token.at(i) & 0x7f;
203   }
204   *(tok_wptr++) = static_cast<char>(token.at(token_size - 1) | 0x80);
205   PERFETTO_DCHECK(tok_wptr == buf_.data() + buf_.size());
206   return id;
207 }
208 
209 // NOTE: the caller need to mask the returned chars with 0x7f. The last char of
210 // the StringView will have its MSB set (it's used as a EOF char internally).
Lookup(TokenId id)211 base::StringView KernelSymbolMap::TokenTable::Lookup(TokenId id) {
212   if (id == 0)
213     return base::StringView();
214   if (id > num_tokens_)
215     return base::StringView("<error>");
216   // We don't know precisely where the id-th token starts in the buffer. We
217   // store only one position every kTokenIndexSampling. From there, the token
218   // can be found with a linear scan of at most kTokenIndexSampling steps.
219   size_t index_off = id / kTokenIndexSampling;
220   PERFETTO_DCHECK(index_off < index_.size());
221   TokenId cur_id = static_cast<TokenId>(index_off * kTokenIndexSampling);
222   uint32_t begin = index_[index_off];
223   PERFETTO_DCHECK(begin == 0 || buf_[begin - 1] & 0x80);
224   const size_t buf_size = buf_.size();
225   for (uint32_t off = begin; off < buf_size; ++off) {
226     // Advance |off| until the end of the token (which has the MSB set).
227     if ((buf_[off] & 0x80) == 0)
228       continue;
229     if (cur_id == id)
230       return base::StringView(&buf_[begin], off - begin + 1);
231     ++cur_id;
232     begin = off + 1;
233   }
234   return base::StringView();
235 }
236 
Parse(int fd)237 size_t KernelSymbolMap::Parse(int fd) {
238   PERFETTO_METATRACE_SCOPED(TAG_PRODUCER, KALLSYMS_PARSE);
239   using SymAddr = uint64_t;
240 
241   struct TokenInfo {
242     uint32_t count = 0;
243     TokenId id = 0;
244   };
245 
246   // Note if changing the container: the code below doesn't rely on stable
247   // iterators, but relies on stable pointers.
248   using TokenMap = std::unordered_map<std::string, TokenInfo>;
249   using TokenMapPtr = TokenMap::value_type*;
250   TokenMap tokens;
251 
252   // Keep the (ordered) list of tokens for each symbol.
253   struct SymAddrAndTokenPtr {
254     SymAddr addr;
255     TokenMapPtr token_map_entry;
256 
257     bool operator<(const SymAddrAndTokenPtr& other) const {
258       return addr < other.addr;
259     }
260   };
261   std::vector<SymAddrAndTokenPtr> symbol_tokens;
262 
263   // Based on `cat /proc/kallsyms | egrep "\b[tT]\b" | wc -l`.
264   symbol_tokens.reserve(128 * 1024);
265 
266   if (fd < 0) {
267     PERFETTO_ELOG("Invalid kallsyms fd");
268     return 0;
269   }
270 
271   ForEachSym(fd, [&](SymAddr addr, char type, base::StringView name) {
272     // Special cases:
273     //
274     // Skip arm mapping symbols such as $x, $x.123, $d, $d.123. They exist to
275     // delineate interleaved data and text for certain tools, and do not
276     // identify real functions. Should be fine to ignore on non-arm platforms
277     // since '$' isn't a valid C identifier and therefore unlikely to mark a
278     // real function.
279     //
280     // Strip .cfi/.cfi_jt suffixes if the kernel is built with clang control
281     // flow integrity checks (where for "my_func" there will be a
282     // "my_func.cfi_jt"). These can account for a third of the total symbols
283     // after the above filters, and tracing users want to see the unadorned
284     // name anyway. Normally we'd record the full string here and remove the
285     // suffix during trace ingestion, but it makes a nontrivial impact on the
286     // size of the in-memory token table since we tokenize only on underscore
287     // boundaries.
288     if (addr == 0 || (type != 't' && type != 'T') || name.at(0) == '$') {
289       return;
290     }
291     const base::StringView cfi = ".cfi";
292     const base::StringView cfi_jt = ".cfi_jt";
293     if (name.EndsWith(cfi)) {
294       name = name.substr(0, name.size() - cfi.size());
295     } else if (name.EndsWith(cfi_jt)) {
296       name = name.substr(0, name.size() - cfi_jt.size());
297     }
298 
299     // Split each symbol name in tokens, using '_' as a separator (so that
300     // "foo_bar" -> ["foo", "bar"]). For each token hash:
301     // 1. Keep track of the frequency of each token.
302     // 2. Keep track of the list of token hashes for each symbol.
303     Tokenize(name, [&tokens, &symbol_tokens, addr](base::StringView token) {
304       auto it_and_ins = tokens.emplace(token.ToStdString(), TokenInfo{});
305       it_and_ins.first->second.count++;
306       symbol_tokens.emplace_back(SymAddrAndTokenPtr{addr, &*it_and_ins.first});
307     });
308   });
309 
310   symbol_tokens.shrink_to_fit();
311 
312   // For each symbol address, T entries are inserted into |symbol_tokens|, one
313   // for each token. These symbols are added in arbitrary address (as seen in
314   // /proc/kallsyms). Here we want to sort symbols by addresses, but at the same
315   // time preserve the order of tokens within each address.
316   // For instance, if kallsyms has: {0x41: connect_socket, 0x42: write_file}:
317   // Before sort: [(0x42, write), (0x42, file), (0x41, connect), (0x41, socket)]
318   // After sort: [(0x41, connect), (0x41, socket), (0x42, write), (0x42, file)]
319   std::stable_sort(symbol_tokens.begin(), symbol_tokens.end());
320 
321   // At this point we have broken down each symbol into a set of token hashes.
322   // Now generate the token ids, putting high freq tokens first, so they use
323   // only one byte to varint encode.
324 
325   // This block limits the lifetime of |tokens_by_freq|.
326   {
327     std::vector<TokenMapPtr> tokens_by_freq;
328     tokens_by_freq.resize(tokens.size());
329     size_t tok_idx = 0;
330     for (auto& kv : tokens)
331       tokens_by_freq[tok_idx++] = &kv;
332 
333     auto comparer = [](TokenMapPtr a, TokenMapPtr b) {
334       PERFETTO_DCHECK(a && b);
335       return b->second.count < a->second.count;
336     };
337     std::sort(tokens_by_freq.begin(), tokens_by_freq.end(), comparer);
338     for (TokenMapPtr tinfo : tokens_by_freq) {
339       tinfo->second.id = tokens_.Add(tinfo->first);
340     }
341   }
342   tokens_.shrink_to_fit();
343 
344   buf_.resize(2 * 1024 * 1024);  // Based on real-word observations.
345   base_addr_ = symbol_tokens.empty() ? 0 : symbol_tokens.begin()->addr;
346   SymAddr prev_sym_addr = base_addr_;
347   uint8_t* wptr = buf_.data();
348 
349   for (auto it = symbol_tokens.begin(); it != symbol_tokens.end();) {
350     const SymAddr sym_addr = it->addr;
351 
352     // Find the iterator to the first token of the next symbol (or the end).
353     auto sym_start = it;
354     auto sym_end = it;
355     while (sym_end != symbol_tokens.end() && sym_end->addr == sym_addr)
356       ++sym_end;
357 
358     // The range [sym_start, sym_end) has all the tokens for the current symbol.
359     uint32_t size_before = static_cast<uint32_t>(wptr - buf_.data());
360 
361     // Make sure there is enough headroom to write the symbol.
362     if (buf_.size() - size_before < 1024) {
363       buf_.resize(buf_.size() + 32768);
364       wptr = buf_.data() + size_before;
365     }
366 
367     uint32_t sym_rel_addr = static_cast<uint32_t>(sym_addr - base_addr_);
368     const size_t sym_num = num_syms_++;
369     if (sym_num % kSymIndexSampling == 0)
370       index_.emplace_back(std::make_pair(sym_rel_addr, size_before));
371     PERFETTO_DCHECK(sym_addr >= prev_sym_addr);
372     uint32_t delta = static_cast<uint32_t>(sym_addr - prev_sym_addr);
373     wptr = protozero::proto_utils::WriteVarInt(delta, wptr);
374     // Append all the token ids.
375     for (it = sym_start; it != sym_end;) {
376       PERFETTO_DCHECK(it->addr == sym_addr);
377       TokenMapPtr const token_map_entry = it->token_map_entry;
378       const TokenInfo& token_info = token_map_entry->second;
379       TokenId token_id = token_info.id << 1;
380       ++it;
381       token_id |= (it == sym_end) ? 1 : 0;  // Last one has LSB set to 1.
382       wptr = protozero::proto_utils::WriteVarInt(token_id, wptr);
383     }
384     prev_sym_addr = sym_addr;
385   }  // for (symbols)
386 
387   buf_.resize(static_cast<size_t>(wptr - buf_.data()));
388   buf_.shrink_to_fit();
389   base::MaybeReleaseAllocatorMemToOS();  // For Scudo, b/170217718.
390 
391   if (num_syms_ == 0) {
392     PERFETTO_ELOG(
393         "Failed to parse kallsyms. Kernel functions will not be symbolized. On "
394         "Linux this requires either running traced_probes as root or manually "
395         "lowering /proc/sys/kernel/kptr_restrict");
396   } else {
397     PERFETTO_DLOG(
398         "Loaded %zu kalllsyms entries. Mem usage: %zu B (addresses) + %zu B "
399         "(tokens), total: %zu B",
400         num_syms_, addr_bytes(), tokens_.size_bytes(), size_bytes());
401   }
402 
403   return num_syms_;
404 }
405 
Lookup(uint64_t sym_addr)406 std::string KernelSymbolMap::Lookup(uint64_t sym_addr) {
407   if (index_.empty() || sym_addr < base_addr_)
408     return "";
409 
410   // First find the highest symbol address <= sym_addr.
411   // Start with a binary search using the sparse index.
412 
413   const uint32_t sym_rel_addr = static_cast<uint32_t>(sym_addr - base_addr_);
414   auto it = std::upper_bound(index_.cbegin(), index_.cend(),
415                              std::make_pair(sym_rel_addr, 0u));
416   if (it != index_.cbegin())
417     --it;
418 
419   // Then continue with a linear scan (of at most kSymIndexSampling steps).
420   uint32_t addr = it->first;
421   uint32_t off = it->second;
422   const uint8_t* rdptr = &buf_[off];
423   const uint8_t* const buf_end = buf_.data() + buf_.size();
424   bool parsing_addr = true;
425   const uint8_t* next_rdptr = nullptr;
426   uint64_t sym_start_addr = 0;
427   for (bool is_first_addr = true;; is_first_addr = false) {
428     uint64_t v = 0;
429     const auto* prev_rdptr = rdptr;
430     rdptr = protozero::proto_utils::ParseVarInt(rdptr, buf_end, &v);
431     if (rdptr == prev_rdptr)
432       break;
433     if (parsing_addr) {
434       addr += is_first_addr ? 0 : static_cast<uint32_t>(v);
435       parsing_addr = false;
436       if (addr > sym_rel_addr)
437         break;
438       next_rdptr = rdptr;
439       sym_start_addr = addr;
440     } else {
441       // This is a token. Wait for the EOF maker.
442       parsing_addr = (v & 1) == 1;
443     }
444   }
445 
446   if (!next_rdptr)
447     return "";
448 
449   PERFETTO_DCHECK(sym_rel_addr >= sym_start_addr);
450 
451   // If this address is too far from the start of the symbol, this is likely
452   // a pointer to something else (e.g. some vmalloc struct) and we just picked
453   // the very last symbol for a loader region.
454   if (sym_rel_addr - sym_start_addr > kSymMaxSizeBytes)
455     return "";
456 
457   // The address has been found. Now rejoin the tokens to form the symbol name.
458 
459   rdptr = next_rdptr;
460   std::string sym_name;
461   sym_name.reserve(kSymNameMaxLen);
462   for (bool eof = false, is_first_token = true; !eof; is_first_token = false) {
463     uint64_t v = 0;
464     const auto* old = rdptr;
465     rdptr = protozero::proto_utils::ParseVarInt(rdptr, buf_end, &v);
466     if (rdptr == old)
467       break;
468     eof = v & 1;
469     base::StringView token = tokens_.Lookup(static_cast<TokenId>(v >> 1));
470     if (!is_first_token)
471       sym_name.push_back('_');
472     for (size_t i = 0; i < token.size(); i++)
473       sym_name.push_back(token.at(i) & 0x7f);
474   }
475   return sym_name;
476 }
477 
478 }  // namespace perfetto
479