simpleperf: Support parsing dynamic string field of tracepoint events.

Also disable hardware counter testing for cf_x86_64 targets.

Bug: 165708389
Test: run simpleperf_unit_test
Change-Id: Ie2f5c4c96239077d5d55023c8f2d0ffd9b838653
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index c996832..554d4ea 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -47,7 +47,8 @@
   uint64_t vaddr_in_file;
 };
 
-using ExtractFieldFn = std::function<std::string(const TracingField&, const char*)>;
+using ExtractFieldFn =
+    std::function<std::string(const TracingField&, const PerfSampleRawType&)>;
 
 struct EventInfo {
   size_t tp_data_size = 0;
@@ -55,22 +56,43 @@
   std::vector<ExtractFieldFn> extract_field_functions;
 };
 
-std::string ExtractStringField(const TracingField& field, const char* data) {
+std::string ExtractStringField(const TracingField& field, const PerfSampleRawType& data) {
   std::string s;
   // data points to a char [field.elem_count] array. It is not guaranteed to be ended
   // with '\0'. So need to copy from data like strncpy.
-  for (size_t i = 0; i < field.elem_count && data[i] != '\0'; i++) {
-    s.push_back(data[i]);
+  size_t max_len = std::min(data.size - field.offset, field.elem_count);
+  const char* p = data.data + field.offset;
+  for (size_t i = 0; i < max_len && *p != '\0'; i++) {
+    s.push_back(*p++);
+  }
+  return s;
+}
+
+std::string ExtractDynamicStringField(const TracingField& field, const PerfSampleRawType& data) {
+  std::string s;
+  const char* p = data.data + field.offset;
+  if (field.elem_size != 4 || field.offset + field.elem_size > data.size) {
+    return s;
+  }
+  uint32_t location;
+  MoveFromBinaryFormat(location, p);
+  // Parse location: (max_len << 16) | off.
+  uint32_t offset = location & 0xffff;
+  uint32_t max_len = location >> 16;
+  if (offset + max_len <= data.size) {
+    p = data.data + offset;
+    for (size_t i = 0; i < max_len && *p != '\0'; i++) {
+      s.push_back(*p++);
+    }
   }
   return s;
 }
 
 template <typename T, typename UT = typename std::make_unsigned<T>::type>
-std::string ExtractIntField(const TracingField& field, const char* data) {
+std::string ExtractIntFieldFromPointer(const TracingField& field, const char* p) {
   static_assert(std::is_signed<T>::value);
-
   T value;
-  MoveFromBinaryFormat(value, data);
+  MoveFromBinaryFormat(value, p);
 
   if (field.is_signed) {
     return android::base::StringPrintf("%" PRId64, static_cast<int64_t>(value));
@@ -79,33 +101,52 @@
 }
 
 template <typename T>
-std::string ExtractIntArrayField(const TracingField& field, const char* data) {
+std::string ExtractIntField(const TracingField& field, const PerfSampleRawType& data) {
+  if (field.offset + sizeof(T) > data.size) {
+    return "";
+  }
+  return ExtractIntFieldFromPointer<T>(field, data.data + field.offset);
+}
+
+template <typename T>
+std::string ExtractIntArrayField(const TracingField& field, const PerfSampleRawType& data) {
+  if (field.offset + field.elem_size * field.elem_count > data.size) {
+    return "";
+  }
   std::string s;
+  const char* p = data.data + field.offset;
   for (size_t i = 0; i < field.elem_count; i++) {
     if (i != 0) {
       s.push_back(' ');
     }
-    s += ExtractIntField<T>(field, data);
-    data += field.elem_size;
+    ExtractIntFieldFromPointer<T>(field, p);
+    p += field.elem_size;
   }
   return s;
 }
 
-std::string ExtractUnknownField(const TracingField& field, const char* data) {
+std::string ExtractUnknownField(const TracingField& field, const PerfSampleRawType& data) {
   size_t total = field.elem_size * field.elem_count;
+  if (field.offset + total > data.size) {
+    return "";
+  }
   uint32_t value;
   std::string s;
+  const char* p = data.data + field.offset;
   for (size_t i = 0; i + sizeof(value) <= total; i += sizeof(value)) {
     if (i != 0) {
       s.push_back(' ');
     }
-    MoveFromBinaryFormat(value, data);
+    MoveFromBinaryFormat(value, p);
     s += android::base::StringPrintf("0x%08x", value);
   }
   return s;
 }
 
 ExtractFieldFn GetExtractFieldFunction(const TracingField& field) {
+  if (field.is_dynamic) {
+    return ExtractDynamicStringField;
+  }
   if (field.elem_count > 1 && field.elem_size == 1) {
     // Probably the field is a string.
     // Don't use field.is_signed, which has different values on x86 and arm.
@@ -336,13 +377,11 @@
     size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(&sr);
     auto& event = events_[attr_index];
     if (event.tp_data_size > 0 && sr.raw_data.size >= event.tp_data_size) {
-      const char* p = sr.raw_data.data;
       PrintIndented(1, "tracepoint fields:\n");
       for (size_t i = 0; i < event.tp_fields.size(); i++) {
         auto& field = event.tp_fields[i];
-        std::string s = event.extract_field_functions[i](field, p);
+        std::string s = event.extract_field_functions[i](field, sr.raw_data);
         PrintIndented(2, "%s: %s\n", field.name.c_str(), s.c_str());
-        p += field.elem_count * field.elem_size;
       }
     }
   }
diff --git a/simpleperf/cmd_dumprecord_test.cpp b/simpleperf/cmd_dumprecord_test.cpp
index dc33e71..7309424 100644
--- a/simpleperf/cmd_dumprecord_test.cpp
+++ b/simpleperf/cmd_dumprecord_test.cpp
@@ -53,6 +53,13 @@
   ASSERT_TRUE(DumpCmd()->Run({GetTestData("perf_with_tracepoint_event.data")}));
   std::string data = capture.Finish();
   ASSERT_NE(data.find("prev_comm: sleep"), std::string::npos);
+
+  // dump dynamic field of tracepoint events.
+  ASSERT_TRUE(capture.Start());
+  ASSERT_TRUE(DumpCmd()->Run({GetTestData("perf_with_tracepoint_event_dynamic_field.data")}));
+  data = capture.Finish();
+  ASSERT_NE(data.find("name: /sys/kernel/debug/tracing/events/kprobes/myopen/format"),
+            std::string::npos);
 }
 
 TEST(cmd_dump, etm_data) {
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index 097f94b..3a327a3 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -277,7 +277,9 @@
 #if defined(__ANDROID__)
   std::string prop_value = android::base::GetProperty("ro.build.flavor", "");
   if (android::base::StartsWith(prop_value, "cf_x86_phone") ||
-      android::base::StartsWith(prop_value, "aosp_cf_x86_phone")) {
+      android::base::StartsWith(prop_value, "aosp_cf_x86_phone") ||
+      android::base::StartsWith(prop_value, "cf_x86_64_phone") ||
+      android::base::StartsWith(prop_value, "aosp_cf_x86_64_phone")) {
     return true;
   }
 #endif
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
index c73f5a0..c627afc 100644
--- a/simpleperf/report_lib_interface.cpp
+++ b/simpleperf/report_lib_interface.cpp
@@ -29,6 +29,8 @@
 #include "tracing.h"
 #include "utils.h"
 
+using namespace simpleperf;
+
 class ReportLib;
 
 extern "C" {
diff --git a/simpleperf/testdata/perf_with_tracepoint_event_dynamic_field.data b/simpleperf/testdata/perf_with_tracepoint_event_dynamic_field.data
new file mode 100644
index 0000000..24e3d90
--- /dev/null
+++ b/simpleperf/testdata/perf_with_tracepoint_event_dynamic_field.data
Binary files differ
diff --git a/simpleperf/tracing.cpp b/simpleperf/tracing.cpp
index 303704d..c5a4558 100644
--- a/simpleperf/tracing.cpp
+++ b/simpleperf/tracing.cpp
@@ -21,6 +21,7 @@
 
 #include <map>
 #include <optional>
+#include <regex>
 #include <string>
 #include <vector>
 
@@ -34,6 +35,20 @@
 #include "perf_event.h"
 #include "utils.h"
 
+using android::base::Split;
+using android::base::StartsWith;
+
+template <>
+void MoveFromBinaryFormat(std::string& data, const char*& p) {
+  data.clear();
+  while (*p != '\0') {
+    data.push_back(*p++);
+  }
+  p++;
+}
+
+namespace simpleperf {
+
 const char TRACING_INFO_MAGIC[10] = {23,  8,   68,  't', 'r',
                                      'a', 'c', 'i', 'n', 'g'};
 
@@ -52,15 +67,6 @@
   data.insert(data.end(), s.c_str(), s.c_str() + s.size() + 1);
 }
 
-template <>
-void MoveFromBinaryFormat(std::string& data, const char*& p) {
-  data.clear();
-  while (*p != '\0') {
-    data.push_back(*p++);
-  }
-  p++;
-}
-
 static void AppendFile(std::vector<char>& data, const std::string& file,
                        uint32_t file_size_bytes = 8) {
   if (file_size_bytes == 8) {
@@ -281,55 +287,62 @@
 // Parse lines like: field:char comm[16]; offset:8; size:16;  signed:1;
 static TracingField ParseTracingField(const std::string& s) {
   TracingField field;
-  size_t start = 0;
   std::string name;
   std::string value;
-  for (size_t i = 0; i < s.size(); ++i) {
-    if (!isspace(s[i]) && (i == 0 || isspace(s[i - 1]))) {
-      start = i;
-    } else if (s[i] == ':') {
-      name = s.substr(start, i - start);
-      start = i + 1;
-    } else if (s[i] == ';') {
-      value = s.substr(start, i - start);
-      if (name == "field") {
-        // Parse value with brackets like "comm[16]", or just a field name.
-        size_t left_bracket_pos = value.find('[');
-        if (left_bracket_pos == std::string::npos) {
-          field.name = value;
-          field.elem_count = 1;
-        } else {
-          field.name = value.substr(0, left_bracket_pos);
-          field.elem_count = 1;
-          size_t right_bracket_pos = value.find(']', left_bracket_pos);
-          if (right_bracket_pos != std::string::npos) {
-            size_t len = right_bracket_pos - left_bracket_pos - 1;
-            size_t elem_count;
-            // Array size may not be a number, like field:u32 rates[IEEE80211_NUM_BANDS].
-            if (android::base::ParseUint(value.substr(left_bracket_pos + 1, len), &elem_count)) {
-              field.elem_count = elem_count;
-            }
+  std::regex re(R"((\w+):(.+?);)");
+
+  std::sregex_iterator match_it(s.begin(), s.end(), re);
+  std::sregex_iterator match_end;
+  while (match_it != match_end) {
+    std::smatch match = *match_it++;
+    std::string name = match.str(1);
+    std::string value = match.str(2);
+
+    if (name == "field") {
+      std::string last_value_part = Split(value, " \t").back();
+
+      if (StartsWith(value, "__data_loc char[]")) {
+        // Parse value like "__data_loc char[] name".
+        field.name = last_value_part;
+        field.elem_count = 1;
+        field.is_dynamic = true;
+      } else if (auto left_bracket_pos = last_value_part.find('[');
+                 left_bracket_pos != std::string::npos) {
+        // Parse value with brackets like "char comm[16]".
+        field.name = last_value_part.substr(0, left_bracket_pos);
+        field.elem_count = 1;
+        if (size_t right_bracket_pos = last_value_part.find(']', left_bracket_pos);
+            right_bracket_pos != std::string::npos) {
+          size_t len = right_bracket_pos - left_bracket_pos - 1;
+          size_t elem_count;
+          // Array size may not be a number, like field:u32 rates[IEEE80211_NUM_BANDS].
+          if (android::base::ParseUint(last_value_part.substr(left_bracket_pos + 1, len),
+                                       &elem_count)) {
+            field.elem_count = elem_count;
           }
         }
-      } else if (name == "offset") {
-        field.offset =
-            static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
-      } else if (name == "size") {
-        size_t size = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
-        CHECK_EQ(size % field.elem_count, 0u);
-        field.elem_size = size / field.elem_count;
-      } else if (name == "signed") {
-        int is_signed = static_cast<int>(strtoull(value.c_str(), nullptr, 10));
-        field.is_signed = (is_signed == 1);
+      } else {
+        // Parse value like "int common_pid".
+        field.name = last_value_part;
+        field.elem_count = 1;
       }
+    } else if (name == "offset") {
+      field.offset = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
+    } else if (name == "size") {
+      size_t size = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
+      CHECK_EQ(size % field.elem_count, 0u);
+      field.elem_size = size / field.elem_count;
+    } else if (name == "signed") {
+      int is_signed = static_cast<int>(strtoull(value.c_str(), nullptr, 10));
+      field.is_signed = (is_signed == 1);
     }
   }
   return field;
 }
 
-static TracingFormat ParseTracingFormat(const std::string& data) {
+TracingFormat ParseTracingFormat(const std::string& data) {
   TracingFormat format;
-  std::vector<std::string> strs = android::base::Split(data, "\n");
+  std::vector<std::string> strs = Split(data, "\n");
   FormatParsingState state = FormatParsingState::READ_NAME;
   for (const auto& s : strs) {
     if (state == FormatParsingState::READ_NAME) {
@@ -608,7 +621,7 @@
 }
 
 std::optional<FieldNameSet> GetFieldNamesForTracepointEvent(const EventType& event) {
-  std::vector<std::string> strs = android::base::Split(event.name, ":");
+  std::vector<std::string> strs = Split(event.name, ":");
   if (strs.size() != 2) {
     return {};
   }
@@ -623,3 +636,5 @@
   }
   return names;
 }
+
+}  // namespace simpleperf
diff --git a/simpleperf/tracing.h b/simpleperf/tracing.h
index f8c88ad..60d99dd 100644
--- a/simpleperf/tracing.h
+++ b/simpleperf/tracing.h
@@ -26,12 +26,21 @@
 #include "event_type.h"
 #include "utils.h"
 
+namespace simpleperf {
+
 struct TracingField {
   std::string name;
-  size_t offset;
-  size_t elem_size;
-  size_t elem_count;
-  bool is_signed;
+  size_t offset = 0;
+  size_t elem_size = 0;
+  size_t elem_count = 1;
+  bool is_signed = false;
+  bool is_dynamic = false;
+
+  bool operator==(const TracingField& other) const {
+    return name == other.name && offset == other.offset && elem_size == other.elem_size &&
+           elem_count == other.elem_count && is_signed == other.is_signed &&
+           is_dynamic == other.is_dynamic;
+  }
 };
 
 struct TracingFieldPlace {
@@ -113,5 +122,8 @@
 std::optional<std::string> AdjustTracepointFilter(const std::string& filter, bool use_quote,
                                                   FieldNameSet* used_fields);
 std::optional<FieldNameSet> GetFieldNamesForTracepointEvent(const EventType& event);
+TracingFormat ParseTracingFormat(const std::string& data);
+
+}  // namespace simpleperf
 
 #endif  // SIMPLE_PERF_TRACING_H_
diff --git a/simpleperf/tracing_test.cpp b/simpleperf/tracing_test.cpp
index 26cbeeb..698fd44 100644
--- a/simpleperf/tracing_test.cpp
+++ b/simpleperf/tracing_test.cpp
@@ -20,6 +20,8 @@
 
 #include <android-base/strings.h>
 
+using namespace simpleperf;
+
 static void CheckAdjustFilter(const std::string& filter, bool use_quote,
                               const std::string& adjusted_filter,
                               const std::string used_field_str) {
@@ -55,3 +57,46 @@
   // unknown operator
   ASSERT_FALSE(AdjustTracepointFilter("pid ^ 3", true, &used_fields).has_value());
 }
+
+namespace simpleperf {
+std::ostream& operator<<(std::ostream& os, const TracingField& field){
+  os << "field (" << field.name << ", off " << field.offset << ", elem size " << field.elem_size
+     << ", elem_count " << field.elem_count << ", is_signed " << field.is_signed << ", is_dynamic "
+     << field.is_dynamic << ")";
+  return os;
+}
+}  // namespace simpleperf
+
+TEST(tracing, ParseTracingFormat) {
+  std::string data =
+      "name: sched_wakeup_new\n"
+      "ID: 94\n"
+      "format:\n"
+      "\tfield:unsigned short common_type;	offset:0;	size:2;	signed:0;\n"
+      "\tfield:unsigned char common_flags;	offset:2;	size:1;	signed:0;\n"
+      "\tfield:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;\n"
+      "\tfield:int common_pid;	offset:4;	size:4;	signed:1;\n"
+      "\n"
+      "\tfield:char comm[16];	offset:8;	size:16;	signed:1;\n"
+      "\tfield:__data_loc char[] name;	offset:24;	size:4;	signed:1;\n";
+  TracingFormat format = ParseTracingFormat(data);
+  ASSERT_EQ(format.name, "sched_wakeup_new");
+  ASSERT_EQ(format.id, 94);
+  ASSERT_EQ(format.fields.size(), 6);
+  ASSERT_EQ(format.fields[0], TracingField({.name = "common_type", .offset = 0, .elem_size = 2}));
+  ASSERT_EQ(format.fields[1], TracingField({.name = "common_flags", .offset = 2, .elem_size = 1}));
+  ASSERT_EQ(format.fields[2],
+            TracingField({.name = "common_preempt_count", .offset = 3, .elem_size = 1}));
+  ASSERT_EQ(format.fields[3],
+            TracingField({.name = "common_pid", .offset = 4, .elem_size = 4, .is_signed = true}));
+  ASSERT_EQ(
+      format.fields[4],
+      TracingField(
+          {.name = "comm", .offset = 8, .elem_size = 1, .elem_count = 16, .is_signed = true}));
+  ASSERT_EQ(format.fields[5], TracingField({.name = "name",
+                                            .offset = 24,
+                                            .elem_size = 4,
+                                            .elem_count = 1,
+                                            .is_signed = true,
+                                            .is_dynamic = true}));
+}