traced_probes: Parse printk formats ftrace fields

Parse printf_formats and use the resulting address to
string map to translate char* ftrace fields.

Change-Id: I449119abbafc00dca528f87602f73411198616f2
diff --git a/Android.bp b/Android.bp
index 1756f7b..5f2867e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -7510,6 +7510,7 @@
     "src/traced/probes/ftrace/ftrace_data_source.cc",
     "src/traced/probes/ftrace/ftrace_procfs.cc",
     "src/traced/probes/ftrace/ftrace_stats.cc",
+    "src/traced/probes/ftrace/printk_formats_parser.cc",
     "src/traced/probes/ftrace/proto_translation_table.cc",
   ],
 }
@@ -7667,6 +7668,7 @@
     "src/traced/probes/ftrace/ftrace_config_unittest.cc",
     "src/traced/probes/ftrace/ftrace_controller_unittest.cc",
     "src/traced/probes/ftrace/ftrace_procfs_unittest.cc",
+    "src/traced/probes/ftrace/printk_formats_parser_unittest.cc",
     "src/traced/probes/ftrace/proto_translation_table_unittest.cc",
   ],
 }
diff --git a/BUILD b/BUILD
index 0b6c279..86382c2 100644
--- a/BUILD
+++ b/BUILD
@@ -1277,6 +1277,8 @@
         "src/traced/probes/ftrace/ftrace_procfs.h",
         "src/traced/probes/ftrace/ftrace_stats.cc",
         "src/traced/probes/ftrace/ftrace_stats.h",
+        "src/traced/probes/ftrace/printk_formats_parser.cc",
+        "src/traced/probes/ftrace/printk_formats_parser.h",
         "src/traced/probes/ftrace/proto_translation_table.cc",
         "src/traced/probes/ftrace/proto_translation_table.h",
     ],
diff --git a/include/perfetto/base/flat_set.h b/include/perfetto/base/flat_set.h
index 068ad3c..9390537 100644
--- a/include/perfetto/base/flat_set.h
+++ b/include/perfetto/base/flat_set.h
@@ -48,7 +48,7 @@
 
   FlatSet() = default;
 
-  // Mainly for tests. Deliberately not marked as "expicit".
+  // Mainly for tests. Deliberately not marked as "explicit".
   FlatSet(std::initializer_list<T> initial) : entries_(initial) {
     std::sort(entries_.begin(), entries_.end());
     entries_.erase(std::unique(entries_.begin(), entries_.end()),
diff --git a/src/traced/probes/ftrace/BUILD.gn b/src/traced/probes/ftrace/BUILD.gn
index 90d1e4b..90cef6a 100644
--- a/src/traced/probes/ftrace/BUILD.gn
+++ b/src/traced/probes/ftrace/BUILD.gn
@@ -67,6 +67,7 @@
     "ftrace_config_unittest.cc",
     "ftrace_controller_unittest.cc",
     "ftrace_procfs_unittest.cc",
+    "printk_formats_parser_unittest.cc",
     "proto_translation_table_unittest.cc",
   ]
 }
@@ -145,6 +146,8 @@
     "ftrace_procfs.h",
     "ftrace_stats.cc",
     "ftrace_stats.h",
+    "printk_formats_parser.cc",
+    "printk_formats_parser.h",
     "proto_translation_table.cc",
     "proto_translation_table.h",
   ]
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index 75f34ac..312685b 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -19,6 +19,7 @@
 #include <dirent.h>
 #include <signal.h>
 
+#include <algorithm>
 #include <utility>
 
 #include "perfetto/base/build_config.h"
@@ -620,7 +621,7 @@
 
   bool success = true;
   for (const Field& field : table->common_fields())
-    success &= ParseField(field, start, end, message, metadata);
+    success &= ParseField(field, start, end, table, message, metadata);
 
   protozero::Message* nested =
       message->BeginNestedMessage<protozero::Message>(info.proto_field_id);
@@ -635,11 +636,11 @@
       // TODO(taylori): Avoid outputting field names every time.
       generic_field->AppendString(GenericFtraceEvent::Field::kNameFieldNumber,
                                   field.ftrace_name);
-      success &= ParseField(field, start, end, generic_field, metadata);
+      success &= ParseField(field, start, end, table, generic_field, metadata);
     }
   } else {  // Parse all other events.
     for (const Field& field : info.fields) {
-      success &= ParseField(field, start, end, nested, metadata);
+      success &= ParseField(field, start, end, table, nested, metadata);
     }
   }
 
@@ -666,6 +667,7 @@
 bool CpuReader::ParseField(const Field& field,
                            const uint8_t* start,
                            const uint8_t* end,
+                           const ProtoTranslationTable* table,
                            protozero::Message* message,
                            FtraceMetadata* metadata) {
   PERFETTO_DCHECK(start + field.ftrace_offset + field.ftrace_size <= end);
@@ -709,10 +711,22 @@
                             field_id, message);
     case kCStringToString:
       // TODO(hjd): Kernel-dive to check this how size:0 char fields work.
-      return ReadIntoString(field_start, end, field.proto_field_id, message);
-    case kStringPtrToString:
-      // TODO(hjd): Figure out how to read these.
+      return ReadIntoString(field_start, end, field_id, message);
+    case kStringPtrToString: {
+      uint64_t n = 0;
+      // The ftrace field may be 8 or 4 bytes and we need to copy it into the
+      // bottom of n. In the unlikely case where the field is >8 bytes we
+      // should avoid making things worse by corrupting the stack but we
+      // don't need to handle it correctly.
+      size_t size = std::min<size_t>(field.ftrace_size, sizeof(n));
+      memcpy(base::AssumeLittleEndian(&n),
+             reinterpret_cast<const void*>(field_start), size);
+      // Look up the adddress in the printk format map and write it into the
+      // proto.
+      base::StringView name = table->LookupTraceString(n);
+      message->AppendBytes(field_id, name.begin(), name.size());
       return true;
+    }
     case kDataLocToString:
       return ReadDataLoc(start, field_start, end, field, message);
     case kBoolToUint32:
diff --git a/src/traced/probes/ftrace/cpu_reader.h b/src/traced/probes/ftrace/cpu_reader.h
index 1ad5127..ba0f9fd 100644
--- a/src/traced/probes/ftrace/cpu_reader.h
+++ b/src/traced/probes/ftrace/cpu_reader.h
@@ -208,6 +208,7 @@
   static bool ParseField(const Field& field,
                          const uint8_t* start,
                          const uint8_t* end,
+                         const ProtoTranslationTable* table,
                          protozero::Message* message,
                          FtraceMetadata* metadata);
 
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 5182c79..688dd7c 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -37,6 +37,7 @@
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/power.gen.h"
 #include "protos/perfetto/trace/ftrace/sched.gen.h"
 #include "protos/perfetto/trace/trace_packet.gen.h"
 #include "src/traced/probes/ftrace/test/test_messages.gen.h"
@@ -997,10 +998,21 @@
     }
 
     {
+      // char* -> string
+      event->fields.emplace_back(Field{});
+      Field* field = &event->fields.back();
+      field->ftrace_offset = 56;
+      field->ftrace_size = 8;
+      field->ftrace_type = kFtraceStringPtr;
+      field->proto_field_id = 503;
+      field->proto_field_type = ProtoSchemaType::kString;
+    }
+
+    {
       // dataloc -> string
       event->fields.emplace_back(Field{});
       Field* field = &event->fields.back();
-      field->ftrace_offset = 57;
+      field->ftrace_offset = 65;
       field->ftrace_size = 4;
       field->ftrace_type = kFtraceDataLoc;
       field->proto_field_id = 502;
@@ -1011,7 +1023,7 @@
       // char -> string
       event->fields.emplace_back(Field{});
       Field* field = &event->fields.back();
-      field->ftrace_offset = 61;
+      field->ftrace_offset = 69;
       field->ftrace_size = 0;
       field->ftrace_type = kFtraceCString;
       field->proto_field_id = 501;
@@ -1024,10 +1036,12 @@
     }
   }
 
+  PrintkMap printk_formats;
+  printk_formats.insert(0xffffff8504f51b23, "my_printk_format_string");
   ProtoTranslationTable table(
       &ftrace_, events, std::move(common_fields),
       ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
-      InvalidCompactSchedEventFormatForTesting());
+      InvalidCompactSchedEventFormatForTesting(), printk_formats);
 
   FakeEventProvider provider(base::kPageSize);
 
@@ -1054,6 +1068,7 @@
   writer.Write<int64_t>(k64BitKernelBlockDeviceId);  // Dev id 64
   writer.Write<int64_t>(99u);                        // Inode 64
   writer.WriteFixedString(16, "Hello");
+  writer.Write<uint64_t>(0xffffff8504f51b23ULL);  // char* (printk formats)
   writer.Write<uint8_t>(0);  // Deliberately mis-aligning.
   writer.Write<uint32_t>(40 | 6 << 16);
   writer.WriteFixedString(300, "Goodbye");
@@ -1084,6 +1099,7 @@
   EXPECT_EQ(event->all_fields().field_char_16(), "Hello");
   EXPECT_EQ(event->all_fields().field_char(), "Goodbye");
   EXPECT_EQ(event->all_fields().field_data_loc(), "Hello");
+  EXPECT_EQ(event->all_fields().field_char_star(), "my_printk_format_string");
   EXPECT_THAT(metadata.pids, Contains(97));
   EXPECT_EQ(metadata.inode_and_device.size(), 2U);
   EXPECT_THAT(metadata.inode_and_device,
@@ -1648,6 +1664,95 @@
 // clang-format off
 // # tracer: nop
 // #
+// # entries-in-buffer/entries-written: 18/18   #P:8
+// #
+// #                              _-----=> irqs-off
+// #                             / _----=> need-resched
+// #                            | / _---=> hardirq/softirq
+// #                            || / _--=> preempt-depth
+// #                            ||| /     delay
+// #           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
+// #              | |       |   ||||       |         |
+//            <...>-9290  [000] ....  1352.654573: suspend_resume: sync_filesystems[0] end
+//            <...>-9290  [000] ....  1352.665366: suspend_resume: freeze_processes[0] begin
+//            <...>-9290  [000] ....  1352.699711: suspend_resume: freeze_processes[0] end
+//            <...>-9290  [000] ....  1352.699718: suspend_resume: suspend_enter[1] end
+//            <...>-9290  [000] ....  1352.699723: suspend_resume: dpm_prepare[2] begin
+//            <...>-9290  [000] ....  1352.703470: suspend_resume: dpm_prepare[2] end
+//            <...>-9290  [000] ....  1352.703477: suspend_resume: dpm_suspend[2] begin
+//            <...>-9290  [000] ....  1352.720107: suspend_resume: dpm_resume[16] end
+//            <...>-9290  [000] ....  1352.720113: suspend_resume: dpm_complete[16] begin
+//            <...>-9290  [000] .n..  1352.724540: suspend_resume: dpm_complete[16] end
+//            <...>-9290  [000] ....  1352.724567: suspend_resume: resume_console[1] begin
+//            <...>-9290  [000] ....  1352.724570: suspend_resume: resume_console[1] end
+//            <...>-9290  [000] ....  1352.724574: suspend_resume: thaw_processes[0] begin
+static ExamplePage g_suspend_resume {
+    "synthetic",
+    R"(00000000: edba 155a 3201 0000 7401 0000 0000 0000  ...Z2...t.......
+00000010: 7e58 22cd 1201 0000 0600 0000 ac00 0000  ~X".............
+00000020: 4a24 0000 5a7a f504 85ff ffff 0000 0000  J$..Zz..........
+00000030: 0017 0000 c621 9614 ac00 0000 4a24 0000  .....!......J$..
+00000040: 1c7a f504 85ff ffff 0000 0000 0100 0000  .z..............
+00000050: e6f1 8141 ac00 0000 4a24 0000 1c7a f504  ...A....J$...z..
+00000060: 85ff ffff 0000 0000 0000 0000 8682 0300  ................
+00000070: ac00 0000 4a24 0000 4c7a f504 85ff ffff  ....J$..Lz......
+00000080: 0100 0000 0063 755f 0657 0200 ac00 0000  .....cu_.W......
+00000090: 4a24 0000 8ad5 0105 85ff ffff 0200 0000  J$..............
+000000a0: 0100 0000 06b5 2507 ac00 0000 4a24 0000  ......%.....J$..
+000000b0: 8ad5 0105 85ff ffff 0200 0000 0000 0000  ................
+000000c0: 460d 0300 ac00 0000 4a24 0000 51d5 0105  F.......J$..Q...
+000000d0: 85ff ffff 0200 0000 0117 0000 c63e b81f  .............>..
+000000e0: ac00 0000 4a24 0000 7fd5 0105 85ff ffff  ....J$..........
+000000f0: 1000 0000 0010 0b00 a6f9 0200 ac00 0000  ................
+00000100: 4a24 0000 96d5 0105 85ff ffff 1000 0000  J$..............
+00000110: 01c0 1f00 a6dd 7108 ac00 0400 4a24 0000  ......q.....J$..
+00000120: 96d5 0105 85ff ffff 1000 0000 0000 0000  ................
+00000130: c6f1 0c00 ac00 0000 4a24 0000 3d7a f504  ........J$..=z..
+00000140: 85ff ffff 0100 0000 01ea 24d5 a66c 0100  ..........$..l..
+00000150: ac00 0000 4a24 0000 3d7a f504 85ff ffff  ....J$..=z......
+00000160: 0100 0000 0000 0001 6636 0200 ac00 0000  ........f6......
+00000170: 4a24 0000 d178 f504 85ff ffff 0000 0000  J$...x..........
+00000180: 0100 0000 0000 0000 0000 0000 0000 0000  ................
+00000190: 0000 0000 0000 0000 0000 0000 0000 0000  ................
+)"};
+
+TEST(CpuReaderTest, ParseSuspendResume) {
+  const ExamplePage* test_case = &g_suspend_resume;
+
+  BundleProvider bundle_provider(base::kPageSize);
+  ProtoTranslationTable* table = GetTable(test_case->name);
+  auto page = PageFromXxd(test_case->data);
+
+  FtraceDataSourceConfig ds_config = EmptyConfig();
+  ds_config.event_filter.AddEnabledEvent(
+      table->EventToFtraceId(GroupAndName("power", "suspend_resume")));
+
+  FtraceMetadata metadata{};
+  CompactSchedBuffer compact_buffer;
+  const uint8_t* parse_pos = page.get();
+  base::Optional<CpuReader::PageHeader> page_header =
+      CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
+  ASSERT_TRUE(page_header.has_value());
+
+  CpuReader::ParsePagePayload(
+      parse_pos, &page_header.value(), table, &ds_config, &compact_buffer,
+      bundle_provider.writer(), &metadata);
+  auto bundle = bundle_provider.ParseProto();
+  ASSERT_TRUE(bundle);
+  ASSERT_EQ(bundle->event().size(), 13u);
+  EXPECT_EQ(bundle->event()[0].suspend_resume().action(), "sync_filesystems");
+  EXPECT_EQ(bundle->event()[1].suspend_resume().action(), "freeze_processes");
+  EXPECT_EQ(bundle->event()[2].suspend_resume().action(), "freeze_processes");
+  EXPECT_EQ(bundle->event()[3].suspend_resume().action(), "suspend_enter");
+  // dpm_prepare deliberately missing from:
+  // src/traced/probes/ftrace/test/data/synthetic/printk_formats to ensure we
+  // handle that case correctly.
+  EXPECT_EQ(bundle->event()[4].suspend_resume().action(), "");
+}
+
+// clang-format off
+// # tracer: nop
+// #
 // # entries-in-buffer/entries-written: 1041/238740   #P:8
 // #
 // #                              _-----=> irqs-off
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
index d9b5911..c3602aa 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
@@ -84,7 +84,8 @@
                               events,
                               common_fields,
                               ftrace_page_header_spec,
-                              compact_sched_format) {}
+                              compact_sched_format,
+                              PrintkMap()) {}
   MOCK_METHOD1(GetOrCreateEvent, Event*(const GroupAndName& group_and_name));
   MOCK_CONST_METHOD1(GetEvent,
                      const Event*(const GroupAndName& group_and_name));
@@ -170,7 +171,7 @@
     return std::unique_ptr<ProtoTranslationTable>(new ProtoTranslationTable(
         &table_procfs_, events, std::move(common_fields),
         ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
-        compact_format));
+        compact_format, PrintkMap()));
   }
 
   NiceMock<MockFtraceProcfs> table_procfs_;
diff --git a/src/traced/probes/ftrace/ftrace_controller_unittest.cc b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
index dca24c8..9c38286 100644
--- a/src/traced/probes/ftrace/ftrace_controller_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
@@ -87,7 +87,7 @@
   return std::unique_ptr<Table>(
       new Table(ftrace, events, std::move(common_fields),
                 ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
-                InvalidCompactSchedEventFormatForTesting()));
+                InvalidCompactSchedEventFormatForTesting(), PrintkMap()));
 }
 
 std::unique_ptr<FtraceConfigMuxer> FakeModel(FtraceProcfs* ftrace,
diff --git a/src/traced/probes/ftrace/ftrace_procfs.cc b/src/traced/probes/ftrace/ftrace_procfs.cc
index 7a206da..5628cb7 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.cc
+++ b/src/traced/probes/ftrace/ftrace_procfs.cc
@@ -109,6 +109,11 @@
   return ReadFileIntoString(path);
 }
 
+std::string FtraceProcfs::ReadPrintkFormats() const {
+  std::string path = root_ + "printk_formats";
+  return ReadFileIntoString(path);
+}
+
 std::vector<std::string> FtraceProcfs::ReadEnabledEvents() {
   std::string path = root_ + "set_event";
   std::string s = ReadFileIntoString(path);
diff --git a/src/traced/probes/ftrace/ftrace_procfs.h b/src/traced/probes/ftrace/ftrace_procfs.h
index 3d8186f..f2ea712 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.h
+++ b/src/traced/probes/ftrace/ftrace_procfs.h
@@ -50,6 +50,9 @@
 
   virtual std::string ReadPageHeaderFormat() const;
 
+  // Read the printk formats file.
+  std::string ReadPrintkFormats() const;
+
   // Read the "/per_cpu/cpuXX/stats" file for the given |cpu|.
   std::string ReadCpuStats(size_t cpu) const;
 
diff --git a/src/traced/probes/ftrace/printk_formats_parser.cc b/src/traced/probes/ftrace/printk_formats_parser.cc
new file mode 100644
index 0000000..274e981
--- /dev/null
+++ b/src/traced/probes/ftrace/printk_formats_parser.cc
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 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/traced/probes/ftrace/printk_formats_parser.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_splitter.h"
+#include "perfetto/ext/base/string_utils.h"
+
+namespace perfetto {
+
+PrintkMap ParsePrintkFormats(const std::string& format) {
+  PrintkMap mapping;
+  for (base::StringSplitter lines(format, '\n'); lines.Next();) {
+    // Lines have the format:
+    // 0xdeadbeef : "not alive cow"
+    // and may be duplicated.
+    std::string line(lines.cur_token());
+
+    auto index = line.find(':');
+    if (index == std::string::npos)
+      continue;
+    std::string raw_address = line.substr(0, index);
+    std::string name = line.substr(index);
+
+    // Remove colon, space and surrounding quotes:
+    raw_address = base::StripSuffix(raw_address, " ");
+    name = base::StripPrefix(name, ":");
+    name = base::StripPrefix(name, " ");
+    name = base::StripPrefix(name, "\"");
+    name = base::StripSuffix(name, "\"");
+
+    if (name.empty())
+      continue;
+
+    base::Optional<uint64_t> address = base::StringToUInt64(raw_address, 16);
+    if (address && address.value() != 0)
+      mapping.insert(address.value(), name);
+  }
+  return mapping;
+}
+
+}  // namespace perfetto
diff --git a/src/traced/probes/ftrace/printk_formats_parser.h b/src/traced/probes/ftrace/printk_formats_parser.h
new file mode 100644
index 0000000..df7e556
--- /dev/null
+++ b/src/traced/probes/ftrace/printk_formats_parser.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#ifndef SRC_TRACED_PROBES_FTRACE_PRINTK_FORMATS_PARSER_H_
+#define SRC_TRACED_PROBES_FTRACE_PRINTK_FORMATS_PARSER_H_
+
+#include <string>
+
+#include "perfetto/base/flat_set.h"
+#include "perfetto/ext/base/string_view.h"
+
+namespace perfetto {
+
+struct PrintkEntry {
+  uint64_t address;
+  std::string name;
+
+  PrintkEntry(uint64_t _address) : PrintkEntry(_address, "") {}
+
+  PrintkEntry(uint64_t _address, std::string _name)
+      : address(_address), name(_name) {}
+
+  bool operator<(const PrintkEntry& other) const {
+    return address < other.address;
+  }
+
+  bool operator==(const PrintkEntry& other) const {
+    return address == other.address;
+  }
+};
+
+class PrintkMap {
+ public:
+  void insert(uint64_t address, std::string name) {
+    set_.insert(PrintkEntry(address, name));
+  }
+
+  base::StringView at(uint64_t address) const {
+    auto it = set_.find(address);
+    if (it == set_.end()) {
+      return base::StringView();
+    }
+    return base::StringView(it->name);
+  }
+
+  size_t size() const { return set_.size(); }
+
+  size_t empty() const { return set_.empty(); }
+
+  base::FlatSet<PrintkEntry> set_;
+};
+
+PrintkMap ParsePrintkFormats(const std::string& format);
+
+}  // namespace perfetto
+
+#endif  // SRC_TRACED_PROBES_FTRACE_PRINTK_FORMATS_PARSER_H_
diff --git a/src/traced/probes/ftrace/printk_formats_parser_unittest.cc b/src/traced/probes/ftrace/printk_formats_parser_unittest.cc
new file mode 100644
index 0000000..59d282d
--- /dev/null
+++ b/src/traced/probes/ftrace/printk_formats_parser_unittest.cc
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 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/traced/probes/ftrace/printk_formats_parser.h"
+
+#include "test/gtest_and_gmock.h"
+
+using ::testing::Contains;
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::Key;
+using ::testing::Not;
+using ::testing::Pair;
+
+namespace perfetto {
+namespace {
+
+TEST(PrintkFormatParserTest, AllZeros) {
+  std::string format = R"(0x0 : "Rescheduling interrupts"
+0x0 : "Function call interrupts"
+0x0 : "CPU stop interrupts"
+0x0 : "Timer broadcast interrupts"
+0x0 : "IRQ work interrupts"
+0x0 : "CPU wakeup interrupts"
+0x0 : "CPU backtrace"
+0x0 : "rcu_sched"
+0x0 : "rcu_bh"
+0x0 : "rcu_preempt"
+)";
+
+  PrintkMap result = ParsePrintkFormats(format);
+  EXPECT_THAT(result, IsEmpty());
+}
+
+TEST(PrintkFormatParserTest, VariousAddresses) {
+  std::string format = R"(0x1 : "First line"
+0x1 : "First line"
+0x2 : "Unfortunate: colon"
+0x3 : ""
+0xffffff92349439b8 : "Large address"
+0x9 : "Last line")";
+
+  PrintkMap result = ParsePrintkFormats(format);
+  EXPECT_THAT(result.at(1), Eq("First line"));
+  EXPECT_THAT(result.at(2), Eq("Unfortunate: colon"));
+  EXPECT_THAT(result.at(18446743602145278392ULL), Eq("Large address"));
+  EXPECT_THAT(result.at(9), Eq("Last line"));
+  EXPECT_THAT(result.at(3), Eq(""));
+}
+
+TEST(PrintkFormatParserTest, RobustToRubbish) {
+  std::string format = R"(
+: leading colon
+trailing colon:
+multiple colons: : : : :
+Empty line:
+
+Just colon:
+:
+: "No address"
+No name:
+0x1 :
+0xbadhexaddress : "Bad hex address"
+0x2 : No quotes
+0x3:"No gap"
+"Wrong way round" : 0x4
+)";
+
+  PrintkMap result = ParsePrintkFormats(format);
+  EXPECT_THAT(result.at(2), Eq("No quotes"));
+  EXPECT_THAT(result.at(3), Eq("No gap"));
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/traced/probes/ftrace/proto_translation_table.cc b/src/traced/probes/ftrace/proto_translation_table.cc
index 0ff3f27..9a69745 100644
--- a/src/traced/probes/ftrace/proto_translation_table.cc
+++ b/src/traced/probes/ftrace/proto_translation_table.cc
@@ -463,9 +463,12 @@
   // about their format hold for this kernel.
   CompactSchedEventFormat compact_sched = ValidateFormatForCompactSched(events);
 
-  auto table = std::unique_ptr<ProtoTranslationTable>(
-      new ProtoTranslationTable(ftrace_procfs, events, std::move(common_fields),
-                                header_spec, compact_sched));
+  std::string text = ftrace_procfs->ReadPrintkFormats();
+  PrintkMap printk_formats = ParsePrintkFormats(text);
+
+  auto table = std::unique_ptr<ProtoTranslationTable>(new ProtoTranslationTable(
+      ftrace_procfs, events, std::move(common_fields), header_spec,
+      compact_sched, std::move(printk_formats)));
   return table;
 }
 
@@ -474,13 +477,15 @@
     const std::vector<Event>& events,
     std::vector<Field> common_fields,
     FtracePageHeaderSpec ftrace_page_header_spec,
-    CompactSchedEventFormat compact_sched_format)
+    CompactSchedEventFormat compact_sched_format,
+    PrintkMap printk_formats)
     : ftrace_procfs_(ftrace_procfs),
       events_(BuildEventsDeque(events)),
       largest_id_(events_.size() - 1),
       common_fields_(std::move(common_fields)),
       ftrace_page_header_spec_(ftrace_page_header_spec),
-      compact_sched_format_(compact_sched_format) {
+      compact_sched_format_(compact_sched_format),
+      printk_formats_(printk_formats) {
   for (const Event& event : events) {
     group_and_name_to_event_[GroupAndName(event.group, event.name)] =
         &events_.at(event.ftrace_event_id);
diff --git a/src/traced/probes/ftrace/proto_translation_table.h b/src/traced/probes/ftrace/proto_translation_table.h
index 2cf0926..59b28bc 100644
--- a/src/traced/probes/ftrace/proto_translation_table.h
+++ b/src/traced/probes/ftrace/proto_translation_table.h
@@ -31,6 +31,7 @@
 #include "src/traced/probes/ftrace/compact_sched.h"
 #include "src/traced/probes/ftrace/event_info.h"
 #include "src/traced/probes/ftrace/format_parser.h"
+#include "src/traced/probes/ftrace/printk_formats_parser.h"
 
 namespace perfetto {
 
@@ -99,7 +100,8 @@
                         const std::vector<Event>& events,
                         std::vector<Field> common_fields,
                         FtracePageHeaderSpec ftrace_page_header_spec,
-                        CompactSchedEventFormat compact_sched_format);
+                        CompactSchedEventFormat compact_sched_format,
+                        PrintkMap printk_formats);
 
   size_t largest_id() const { return largest_id_; }
 
@@ -164,6 +166,10 @@
     return compact_sched_format_;
   }
 
+  base::StringView LookupTraceString(uint64_t address) const {
+    return printk_formats_.at(address);
+  }
+
  private:
   ProtoTranslationTable(const ProtoTranslationTable&) = delete;
   ProtoTranslationTable& operator=(const ProtoTranslationTable&) = delete;
@@ -184,6 +190,7 @@
   FtracePageHeaderSpec ftrace_page_header_spec_{};
   std::set<std::string> interned_strings_;
   CompactSchedEventFormat compact_sched_format_;
+  PrintkMap printk_formats_;
 };
 
 // Class for efficient 'is event with id x enabled?' checks.
diff --git a/src/traced/probes/ftrace/proto_translation_table_unittest.cc b/src/traced/probes/ftrace/proto_translation_table_unittest.cc
index 1ca013a..0210f71 100644
--- a/src/traced/probes/ftrace/proto_translation_table_unittest.cc
+++ b/src/traced/probes/ftrace/proto_translation_table_unittest.cc
@@ -397,7 +397,7 @@
   ProtoTranslationTable table(
       &ftrace, events, std::move(common_fields),
       ProtoTranslationTable::DefaultPageHeaderSpecForTesting(),
-      InvalidCompactSchedEventFormatForTesting());
+      InvalidCompactSchedEventFormatForTesting(), PrintkMap());
 
   EXPECT_EQ(table.largest_id(), 100ul);
   EXPECT_EQ(table.EventToFtraceId(GroupAndName("group_one", "foo")), 1ul);
diff --git a/src/traced/probes/ftrace/test/data/synthetic/available_events b/src/traced/probes/ftrace/test/data/synthetic/available_events
index 1267a9f..b20b698 100644
--- a/src/traced/probes/ftrace/test/data/synthetic/available_events
+++ b/src/traced/probes/ftrace/test/data/synthetic/available_events
@@ -10,3 +10,4 @@
 fastrpc:fastrpc_dma_stat
 dpu:tracing_mark_write
 g2d:tracing_mark_write
+power:suspend_resume
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/power/suspend_resume/format b/src/traced/probes/ftrace/test/data/synthetic/events/power/suspend_resume/format
new file mode 100644
index 0000000..aabfb0b
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/power/suspend_resume/format
@@ -0,0 +1,13 @@
+name: suspend_resume
+ID: 172
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:const char * action;	offset:8;	size:8;	signed:0;
+	field:int val;	offset:16;	size:4;	signed:1;
+	field:bool start;	offset:20;	size:1;	signed:0;
+
+print fmt: "%s[%u] %s", REC->action, (unsigned int)REC->val, (REC->start)?"begin":"end"
diff --git a/src/traced/probes/ftrace/test/data/synthetic/printk_formats b/src/traced/probes/ftrace/test/data/synthetic/printk_formats
new file mode 100644
index 0000000..5f0f8c6
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/printk_formats
@@ -0,0 +1,5 @@
+0xffffff850501d52e : "Entries can be duplicated"
+0xffffff8504f57a1c : "freeze_processes"
+0xffffff8504f57a1c : "freeze_processes"
+0xffffff8504f57a5a : "sync_filesystems"
+0xffffff8504f57a4c : "suspend_enter"
diff --git a/src/traced/probes/ftrace/test/test_messages.proto b/src/traced/probes/ftrace/test/test_messages.proto
index 1e6ece4..3d584d9 100644
--- a/src/traced/probes/ftrace/test/test_messages.proto
+++ b/src/traced/probes/ftrace/test/test_messages.proto
@@ -32,4 +32,5 @@
   optional string field_char_16 = 500;
   optional string field_char = 501;
   optional string field_data_loc = 502;
+  optional string field_char_star = 503;
 }