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/traced/probes/ftrace/kallsyms/kernel_symbol_map.h"
18
19 #include "perfetto/base/logging.h"
20 #include "perfetto/ext/base/metatrace.h"
21 #include "perfetto/ext/base/paged_memory.h"
22 #include "perfetto/ext/base/scoped_file.h"
23 #include "perfetto/ext/base/string_view.h"
24 #include "perfetto/ext/base/utils.h"
25 #include "perfetto/protozero/proto_utils.h"
26
27 #include <inttypes.h>
28 #include <stdio.h>
29
30 #include <algorithm>
31 #include <functional>
32 #include <map>
33 #include <utility>
34
35 namespace perfetto {
36
37 // On a Pixel 3 this gives an avg. lookup time of 600 ns and a memory usage
38 // of 1.1 MB for 65k symbols. See go/kallsyms-parser-bench.
39 size_t KernelSymbolMap::kSymIndexSampling = 16;
40 size_t KernelSymbolMap::kTokenIndexSampling = 4;
41
42 namespace {
43
44 using TokenId = KernelSymbolMap::TokenTable::TokenId;
45 constexpr size_t kSymNameMaxLen = 128;
46
47 // Reads a kallsyms file in blocks of 4 pages each and decode its lines using
48 // a simple FSM. Calls the passed lambda for each valid symbol.
49 // It skips undefined symbols and other useless stuff.
50 template <typename Lambda /* void(uint64_t, const char*) */>
ForEachSym(const std::string & kallsyms_path,Lambda fn)51 void ForEachSym(const std::string& kallsyms_path, Lambda fn) {
52 base::ScopedFile fd = base::OpenFile(kallsyms_path.c_str(), O_RDONLY);
53 if (!fd) {
54 PERFETTO_PLOG("Cannot open %s", kallsyms_path.c_str());
55 return;
56 }
57
58 // /proc/kallsyms looks as follows:
59 // 0000000000026a80 A bpf_trace_sds
60 //
61 // ffffffffc03a6000 T cpufreq_gov_powersave_init<TAB> [cpufreq_powersave]
62 // ffffffffc035d000 T cpufreq_gov_userspace_init<TAB> [cpufreq_userspace]
63 //
64 // We parse it with a state machine that has four states, one for each column.
65 // We don't care about the part in the square brackets and ignore everything
66 // after the symbol name.
67
68 static constexpr size_t kBufSize = 16 * 1024;
69 base::PagedMemory buffer = base::PagedMemory::Allocate(kBufSize);
70 enum { kSymAddr, kSymType, kSymName, kEatRestOfLine } state = kSymAddr;
71 uint64_t sym_addr = 0;
72 char sym_type = '\0';
73 char sym_name[kSymNameMaxLen + 1];
74 size_t sym_name_len = 0;
75 for (;;) {
76 char* buf = static_cast<char*>(buffer.Get());
77 auto rsize = PERFETTO_EINTR(read(*fd, buf, kBufSize));
78 if (rsize < 0) {
79 PERFETTO_PLOG("read(%s) failed", kallsyms_path.c_str());
80 return;
81 }
82 if (rsize == 0)
83 return; // EOF
84 for (size_t i = 0; i < static_cast<size_t>(rsize); i++) {
85 char c = buf[i];
86 const bool is_space = c == ' ' || c == '\t';
87 switch (state) {
88 case kSymAddr:
89 if (c >= '0' && c <= '9') {
90 sym_addr = (sym_addr << 4) | static_cast<uint8_t>(c - '0');
91 } else if (c >= 'a' && c <= 'f') {
92 sym_addr = (sym_addr << 4) | static_cast<uint8_t>(c - 'a' + 10);
93 } else if (is_space) {
94 state = kSymType;
95 } else if (c == '\0') {
96 return;
97 } else {
98 PERFETTO_ELOG("kallsyms parser error: chr 0x%x @ off=%zu", c, i);
99 return;
100 }
101 break;
102
103 case kSymType:
104 if (is_space)
105 break; // Eat leading spaces.
106 sym_type = c;
107 state = kSymName;
108 sym_name_len = 0;
109 break;
110
111 case kSymName:
112 if (is_space && sym_name_len == 0)
113 break; // Eat leading spaces.
114 if (c && c != '\n' && !is_space && sym_name_len < kSymNameMaxLen) {
115 sym_name[sym_name_len++] = c;
116 break;
117 }
118 sym_name[sym_name_len] = '\0';
119 fn(sym_addr, sym_type, sym_name);
120 sym_addr = 0;
121 sym_type = '\0';
122 state = c == '\n' ? kSymAddr : kEatRestOfLine;
123 break;
124
125 case kEatRestOfLine:
126 if (c == '\n')
127 state = kSymAddr;
128 break;
129 } // switch(state)
130 } // for (char in buf)
131 } // for (read chunk)
132 }
133
134 // Splits a symbol name into tokens using '_' as a separator, calling the passed
135 // lambda for each token. It splits tokens in a way that allows the original
136 // string to be rebuilt as-is by re-joining using a '_' between each token.
137 // For instance:
138 // _fo_a_b -> ["", fo, a, b]
139 // __fo_a_b -> [_, fo, a, b]
140 // __fo_a_b_ -> [_, fo, a, b, ""]
141 // __fo_a_b____ -> [_, fo, a, b, ___]
142 template <typename Lambda /* void(base::StringView) */>
Tokenize(const char * name,Lambda fn)143 void Tokenize(const char* name, Lambda fn) {
144 const char* tok_start = name;
145 bool is_start_of_token = true;
146 bool tok_is_sep = false;
147 for (const char* ptr = name;; ptr++) {
148 const char c = *ptr;
149 if (is_start_of_token) {
150 tok_is_sep = *tok_start == '_'; // Deals with tokens made of '_'s.
151 is_start_of_token = false;
152 }
153 // Scan until either the end of string or the next character (which is a '_'
154 // in nominal cases, or anything != '_' for tokens made by 1+ '_').
155 if (c == '\0' || (!tok_is_sep && c == '_') || (tok_is_sep && c != '_')) {
156 size_t tok_len = static_cast<size_t>(ptr - tok_start);
157 if (tok_is_sep && c != '\0')
158 --tok_len;
159 fn(base::StringView(tok_start, tok_len));
160 if (c == '\0')
161 return;
162 tok_start = tok_is_sep ? ptr : ptr + 1;
163 is_start_of_token = true;
164 }
165 }
166 }
167
168 } // namespace
169
TokenTable()170 KernelSymbolMap::TokenTable::TokenTable() {
171 // Insert a null token as id 0. We can't just add "" because the empty string
172 // is special-cased and doesn't insert an actual token. So we push a string of
173 // size one that contains only the null character instead.
174 char null_tok = 0;
175 Add(std::string(&null_tok, 1));
176 }
177
178 KernelSymbolMap::TokenTable::~TokenTable() = default;
179
180 // Adds a new token to the db. Does not dedupe identical token (with the
181 // exception of the empty string). The caller has to deal with that.
182 // Supports only ASCII characters in the range [1, 127].
183 // The last character of the token will have the MSB set.
Add(const std::string & token)184 TokenId KernelSymbolMap::TokenTable::Add(const std::string& token) {
185 const size_t token_size = token.size();
186 if (token_size == 0)
187 return 0;
188 TokenId id = num_tokens_++;
189
190 const size_t buf_size_before_insertion = buf_.size();
191 if (id % kTokenIndexSampling == 0)
192 index_.emplace_back(buf_size_before_insertion);
193
194 const size_t prev_size = buf_.size();
195 buf_.resize(prev_size + token_size);
196 char* tok_wptr = &buf_[prev_size];
197 for (size_t i = 0; i < token_size - 1; i++) {
198 PERFETTO_DCHECK((token.at(i) & 0x80) == 0); // |token| must be ASCII only.
199 *(tok_wptr++) = token.at(i) & 0x7f;
200 }
201 *(tok_wptr++) = token.at(token_size - 1) | 0x80;
202 PERFETTO_DCHECK(tok_wptr == &buf_[buf_.size()]);
203 return id;
204 }
205
206 // NOTE: the caller need to mask the returned chars with 0x7f. The last char of
207 // the StringView will have its MSB set (it's used as a EOF char internally).
Lookup(TokenId id)208 base::StringView KernelSymbolMap::TokenTable::Lookup(TokenId id) {
209 if (id == 0)
210 return base::StringView();
211 if (id > num_tokens_)
212 return base::StringView("<error>");
213 // We don't know precisely where the id-th token starts in the buffer. We
214 // store only one position every kTokenIndexSampling. From there, the token
215 // can be found with a linear scan of at most kTokenIndexSampling steps.
216 size_t index_off = id / kTokenIndexSampling;
217 PERFETTO_DCHECK(index_off < index_.size());
218 TokenId cur_id = static_cast<TokenId>(index_off * kTokenIndexSampling);
219 uint32_t begin = index_[index_off];
220 PERFETTO_DCHECK(begin == 0 || buf_[begin - 1] & 0x80);
221 const size_t buf_size = buf_.size();
222 for (uint32_t off = begin; off < buf_size; ++off) {
223 // Advance |off| until the end of the token (which has the MSB set).
224 if ((buf_[off] & 0x80) == 0)
225 continue;
226 if (cur_id == id)
227 return base::StringView(&buf_[begin], off - begin + 1);
228 ++cur_id;
229 begin = off + 1;
230 }
231 return base::StringView();
232 }
233
Parse(const std::string & kallsyms_path)234 size_t KernelSymbolMap::Parse(const std::string& kallsyms_path) {
235 PERFETTO_METATRACE_SCOPED(TAG_FTRACE, KALLSYMS_PARSE);
236 using SymAddr = uint64_t;
237
238 struct TokenInfo {
239 uint32_t count = 0;
240 TokenId id = 0;
241 };
242
243 // Note if changing the container: the code below relies on stable iterators.
244 using TokenMap = std::map<std::string, TokenInfo>;
245 using TokenMapPtr = TokenMap::value_type*;
246 TokenMap tokens;
247
248 // Keep the (ordered) list of tokens for each symbol.
249 std::multimap<SymAddr, TokenMapPtr> symbols;
250
251 ForEachSym(kallsyms_path, [&](SymAddr addr, char type, const char* name) {
252 if (addr == 0 || (type != 't' && type != 'T') || name[0] == '$') {
253 return;
254 }
255
256 // Split each symbol name in tokens, using '_' as a separator (so that
257 // "foo_bar" -> ["foo", "bar"]). For each token hash:
258 // 1. Keep track of the frequency of each token.
259 // 2. Keep track of the list of token hashes for each symbol.
260 Tokenize(name, [&tokens, &symbols, addr](base::StringView token) {
261 // Strip the .cfi part if present.
262 if (token.substr(token.size() - 4) == ".cfi")
263 token = token.substr(0, token.size() - 4);
264 auto it_and_ins = tokens.emplace(token.ToStdString(), TokenInfo{});
265 it_and_ins.first->second.count++;
266 symbols.emplace(addr, &*it_and_ins.first);
267 });
268 });
269
270 // At this point we have broken down each symbol into a set of token hashes.
271 // Now generate the token ids, putting high freq tokens first, so they use
272 // only one byte to varint encode.
273
274 // This block limits the lifetime of |tokens_by_freq|.
275 {
276 std::vector<TokenMapPtr> tokens_by_freq;
277 tokens_by_freq.resize(tokens.size());
278 size_t tok_idx = 0;
279 for (auto& kv : tokens)
280 tokens_by_freq[tok_idx++] = &kv;
281
282 auto comparer = [](TokenMapPtr a, TokenMapPtr b) {
283 PERFETTO_DCHECK(a && b);
284 return b->second.count < a->second.count;
285 };
286 std::sort(tokens_by_freq.begin(), tokens_by_freq.end(), comparer);
287 for (TokenMapPtr tinfo : tokens_by_freq) {
288 tinfo->second.id = tokens_.Add(tinfo->first);
289 }
290 }
291 tokens_.shrink_to_fit();
292
293 buf_.resize(2 * 1024 * 1024); // Based on real-word observations.
294 base_addr_ = symbols.empty() ? 0 : symbols.begin()->first;
295 SymAddr prev_sym_addr = base_addr_;
296 uint8_t* wptr = buf_.data();
297
298 for (auto it = symbols.begin(); it != symbols.end();) {
299 const SymAddr sym_addr = it->first;
300
301 // Find the iterator to the first token of the next symbol (or the end).
302 auto sym_start = it;
303 auto sym_end = it;
304 while (sym_end != symbols.end() && sym_end->first == sym_addr)
305 ++sym_end;
306
307 // The range [sym_start, sym_end) has all the tokens for the current symbol.
308 uint32_t size_before = static_cast<uint32_t>(wptr - buf_.data());
309
310 // Make sure there is enough headroom to write the symbol.
311 if (buf_.size() - size_before < 1024) {
312 buf_.resize(buf_.size() + 32768);
313 wptr = buf_.data() + size_before;
314 }
315
316 uint32_t sym_rel_addr = static_cast<uint32_t>(sym_addr - base_addr_);
317 const size_t sym_num = num_syms_++;
318 if (sym_num % kSymIndexSampling == 0)
319 index_.emplace_back(std::make_pair(sym_rel_addr, size_before));
320 PERFETTO_DCHECK(sym_addr >= prev_sym_addr);
321 uint32_t delta = static_cast<uint32_t>(sym_addr - prev_sym_addr);
322 wptr = protozero::proto_utils::WriteVarInt(delta, wptr);
323 // Append all the token ids.
324 for (it = sym_start; it != sym_end;) {
325 PERFETTO_DCHECK(it->first == sym_addr);
326 TokenId token_id = it->second->second.id << 1;
327 ++it;
328 token_id |= (it == sym_end) ? 1 : 0; // Last one has LSB set to 1.
329 wptr = protozero::proto_utils::WriteVarInt(token_id, wptr);
330 }
331 prev_sym_addr = sym_addr;
332 } // for (symbols)
333
334 buf_.resize(static_cast<size_t>(wptr - buf_.data()));
335 buf_.shrink_to_fit();
336
337 PERFETTO_DLOG(
338 "Loaded %zu kalllsyms entries. Mem usage: %zu B (addresses) + %zu B "
339 "(tokens), total: %zu B",
340 num_syms_, addr_bytes(), tokens_.size_bytes(), size_bytes());
341
342 return num_syms_;
343 }
344
Lookup(uint64_t sym_addr)345 std::string KernelSymbolMap::Lookup(uint64_t sym_addr) {
346 if (index_.empty() || sym_addr < base_addr_)
347 return "";
348
349 // First find the highest symbol address <= sym_addr.
350 // Start with a binary search using the sparse index.
351
352 const uint32_t sym_rel_addr = static_cast<uint32_t>(sym_addr - base_addr_);
353 auto it = std::upper_bound(index_.cbegin(), index_.cend(),
354 std::make_pair(sym_rel_addr, 0u));
355 if (it != index_.cbegin())
356 --it;
357
358 // Then continue with a linear scan (of at most kSymIndexSampling steps).
359 uint32_t addr = it->first;
360 uint32_t off = it->second;
361 const uint8_t* rdptr = &buf_[off];
362 const uint8_t* const buf_end = &buf_[buf_.size()];
363 bool parsing_addr = true;
364 const uint8_t* next_rdptr = nullptr;
365 for (bool is_first_addr = true;; is_first_addr = false) {
366 uint64_t v = 0;
367 const auto* prev_rdptr = rdptr;
368 rdptr = protozero::proto_utils::ParseVarInt(rdptr, buf_end, &v);
369 if (rdptr == prev_rdptr)
370 break;
371 if (parsing_addr) {
372 addr += is_first_addr ? 0 : static_cast<uint32_t>(v);
373 parsing_addr = false;
374 if (addr > sym_rel_addr)
375 break;
376 next_rdptr = rdptr;
377 } else {
378 // This is a token. Wait for the EOF maker.
379 parsing_addr = (v & 1) == 1;
380 }
381 }
382
383 if (!next_rdptr)
384 return "";
385
386 // The address has been found. Now rejoin the tokens to form the symbol name.
387
388 rdptr = next_rdptr;
389 std::string sym_name;
390 sym_name.reserve(kSymNameMaxLen);
391 for (bool eof = false, is_first_token = true; !eof; is_first_token = false) {
392 uint64_t v = 0;
393 const auto* old = rdptr;
394 rdptr = protozero::proto_utils::ParseVarInt(rdptr, buf_end, &v);
395 if (rdptr == old)
396 break;
397 eof = v & 1;
398 base::StringView token = tokens_.Lookup(static_cast<TokenId>(v >> 1));
399 if (!is_first_token)
400 sym_name.push_back('_');
401 for (size_t i = 0; i < token.size(); i++)
402 sym_name.push_back(token.at(i) & 0x7f);
403 }
404 return sym_name;
405 }
406
407 } // namespace perfetto
408