| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "src/kallsyms/kernel_symbol_map.h" |
| |
| #include <cinttypes> |
| #include <random> |
| #include <unordered_map> |
| |
| #include "perfetto/ext/base/file_utils.h" |
| #include "perfetto/ext/base/string_view.h" |
| #include "perfetto/ext/base/temp_file.h" |
| |
| #include "test/gtest_and_gmock.h" |
| |
| namespace perfetto { |
| namespace { |
| |
| using TokenId = KernelSymbolMap::TokenTable::TokenId; |
| |
| std::string Lookup(KernelSymbolMap::TokenTable& tokens, TokenId id) { |
| base::StringView sv = tokens.Lookup(id); |
| std::string str; |
| str.reserve(sv.size() + 1); |
| for (const char c : sv) |
| str.push_back(c & 0x7f); |
| return str; |
| } |
| |
| TEST(KernelSymbolMapTest, TokenTable) { |
| KernelSymbolMap::TokenTable tokens; |
| ASSERT_EQ(tokens.Add("a"), 1u); |
| ASSERT_EQ(tokens.Add("bb"), 2u); |
| ASSERT_EQ(tokens.Add("ccc"), 3u); |
| ASSERT_EQ(tokens.Add("foobar"), 4u); |
| ASSERT_EQ(tokens.Add("foobaz"), 5u); |
| ASSERT_EQ(tokens.Add("_"), 6u); |
| ASSERT_EQ(Lookup(tokens, 0), ""); |
| ASSERT_EQ(Lookup(tokens, 1), "a"); |
| ASSERT_EQ(Lookup(tokens, 2), "bb"); |
| ASSERT_EQ(Lookup(tokens, 3), "ccc"); |
| ASSERT_EQ(Lookup(tokens, 4), "foobar"); |
| ASSERT_EQ(Lookup(tokens, 5), "foobaz"); |
| ASSERT_EQ(Lookup(tokens, 6), "_"); |
| ASSERT_EQ(Lookup(tokens, 0), ""); |
| ASSERT_EQ(Lookup(tokens, 42), "<error>"); |
| } |
| |
| TEST(KernelSymbolMapTest, ManyTokens) { |
| KernelSymbolMap::TokenTable tokens; |
| std::unordered_map<TokenId, std::string> tok_map; |
| static std::minstd_rand rng(0); |
| for (int rep = 0; rep < 10000; rep++) { |
| static const size_t kNameMax = 128; |
| std::string sym_name; |
| sym_name.reserve(kNameMax); |
| size_t len = 1 + (rng() % kNameMax); |
| for (size_t j = 0; j < len; j++) |
| sym_name.push_back(rng() & 0x7f); |
| TokenId id = tokens.Add(sym_name); |
| ASSERT_EQ(tok_map.count(id), 0u); |
| tok_map[id] = sym_name; |
| } |
| for (const auto& kv : tok_map) { |
| ASSERT_EQ(Lookup(tokens, kv.first), kv.second); |
| } |
| } |
| |
| TEST(KernelSymbolMapTest, EdgeCases) { |
| base::TempFile tmp = base::TempFile::Create(); |
| static const char kContents[] = R"(ffffff8f73e2fa10 t one |
| ffffff8f73e2fa20 t two_ |
| ffffff8f73e2fa30 t _three [module_name_ignored] |
| ffffff8f73e2fa40 x ignore |
| ffffff8f73e2fa40 t _fo_ur_ |
| ffffff8f73e2fa50 t _five__.cfi |
| ffffff8f73e2fa60 t __si__x__ |
| ffffff8f73e2fa70 t ___se___v_e__n___ |
| ffffff8f73e2fa80 t _eight_omg_this_name_is_so_loooooooooooooooooooooooooooooooooooong_should_be_truncated_exactly_here_because_this_is_the_128_char_TRUNCATED_HERE |
| ffffff8f73e2fa90 t NiNe |
| )"; |
| base::WriteAll(tmp.fd(), kContents, sizeof(kContents)); |
| base::FlushFile(tmp.fd()); |
| |
| KernelSymbolMap kallsyms; |
| EXPECT_EQ(kallsyms.Lookup(0x42), ""); |
| |
| kallsyms.Parse(tmp.path().c_str()); |
| EXPECT_EQ(kallsyms.num_syms(), 9u); |
| |
| // Test first exact lookups. |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa10ULL), "one"); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa20ULL), "two_"); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa30LL), "_three"); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa40ULL), "_fo_ur_"); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa50ULL), "_five__"); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa60ULL), "__si__x__"); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa70ULL), "___se___v_e__n___"); |
| EXPECT_EQ( |
| kallsyms.Lookup(0xffffff8f73e2fa80ULL), |
| "_eight_omg_this_name_is_so_loooooooooooooooooooooooooooooooooooong_" |
| "should_be_truncated_exactly_here_because_this_is_the_128_char"); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa90ULL), "NiNe"); |
| |
| // Now check bound searches. |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa00ULL), ""); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa11ULL), "one"); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa19ULL), "one"); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa71ULL), "___se___v_e__n___"); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa91ULL), "NiNe"); |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8f73e2fa99ULL), "NiNe"); |
| |
| // This is too far from the last symbol and should fail. |
| EXPECT_EQ(kallsyms.Lookup(0xffffff8fffffffffULL), ""); |
| } |
| |
| TEST(KernelSymbolMapTest, GoldenTest) { |
| std::string fake_kallsyms; |
| fake_kallsyms.reserve(8 * 1024 * 1024); |
| static std::minstd_rand rng(0); |
| static const size_t kNameMax = 64; |
| std::map<uint64_t, std::string> symbols; |
| for (int rep = 0; rep < 1000; rep++) { |
| uint64_t addr = static_cast<uint64_t>(rng()); |
| if (symbols.count(addr)) |
| continue; |
| static const char kCharset[] = "_abcdef"; |
| char sym_name[kNameMax + 1]; // Deliberately not initialized. |
| size_t sym_name_len = 1 + (rng() % kNameMax); |
| for (size_t i = 0; i < sym_name_len; i++) |
| sym_name[i] = kCharset[rng() % strlen(kCharset)]; |
| sym_name[sym_name_len] = '\0'; |
| char line[kNameMax + 40]; |
| sprintf(line, "%" PRIx64 " t %s\n", addr, sym_name); |
| symbols[addr] = sym_name; |
| fake_kallsyms += line; |
| } |
| base::TempFile tmp = base::TempFile::Create(); |
| base::WriteAll(tmp.fd(), fake_kallsyms.data(), fake_kallsyms.size()); |
| base::FlushFile(tmp.fd()); |
| |
| KernelSymbolMap kallsyms; |
| kallsyms.Parse(tmp.path().c_str()); |
| ASSERT_EQ(kallsyms.num_syms(), symbols.size()); |
| for (const auto& kv : symbols) { |
| ASSERT_EQ(kallsyms.Lookup(kv.first), kv.second); |
| } |
| } |
| |
| } // namespace |
| } // namespace perfetto |