1 /*
2 * Copyright (C) 2022 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/trace_processor/util/protozero_to_text.h"
18 #include <optional>
19
20 #include "perfetto/ext/base/string_utils.h"
21 #include "perfetto/ext/base/string_view.h"
22 #include "perfetto/protozero/proto_decoder.h"
23 #include "perfetto/protozero/proto_utils.h"
24 #include "protos/perfetto/common/descriptor.pbzero.h"
25 #include "src/trace_processor/util/descriptors.h"
26
27 // This is the highest level that this protozero to text supports.
28 #include "src/trace_processor/importers/proto/track_event.descriptor.h"
29
30 namespace perfetto {
31 namespace trace_processor {
32 namespace protozero_to_text {
33
34 namespace {
35
36 using protozero::proto_utils::ProtoWireType;
37 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
38
39 // This function matches the implementation of TextFormatEscaper.escapeBytes
40 // from the Java protobuf library.
QuoteAndEscapeTextProtoString(base::StringView raw)41 std::string QuoteAndEscapeTextProtoString(base::StringView raw) {
42 std::string ret;
43 for (char c : raw) {
44 switch (c) {
45 case '\a':
46 ret += "\\a";
47 break;
48 case '\b':
49 ret += "\\b";
50 break;
51 case '\f':
52 ret += "\\f";
53 break;
54 case '\n':
55 ret += "\\n";
56 break;
57 case '\r':
58 ret += "\\r";
59 break;
60 case '\t':
61 ret += "\\t";
62 break;
63 case '\v':
64 ret += "\\v";
65 break;
66 case '\\':
67 ret += "\\\\";
68 break;
69 case '\'':
70 ret += "\\\'";
71 break;
72 case '"':
73 ret += "\\\"";
74 break;
75 default:
76 // Only ASCII characters between 0x20 (space) and 0x7e (tilde) are
77 // printable; other byte values are escaped with 3-character octal
78 // codes.
79 if (c >= 0x20 && c <= 0x7e) {
80 ret += c;
81 } else {
82 ret += '\\';
83
84 // Cast to unsigned char to make the right shift unsigned as well.
85 unsigned char uc = static_cast<unsigned char>(c);
86 ret += ('0' + ((uc >> 6) & 3));
87 ret += ('0' + ((uc >> 3) & 7));
88 ret += ('0' + (uc & 7));
89 }
90 break;
91 }
92 }
93 return '"' + ret + '"';
94 }
95
96 // Append |to_add| which is something string like to |out|.
97 template <typename T>
StrAppend(std::string * out,const T & to_add)98 void StrAppend(std::string* out, const T& to_add) {
99 out->append(to_add);
100 }
101
102 template <typename T, typename... strings>
StrAppend(std::string * out,const T & first,strings...values)103 void StrAppend(std::string* out, const T& first, strings... values) {
104 StrAppend(out, first);
105 StrAppend(out, values...);
106 }
107
IncreaseIndents(std::string * out)108 void IncreaseIndents(std::string* out) {
109 StrAppend(out, " ");
110 }
111
DecreaseIndents(std::string * out)112 void DecreaseIndents(std::string* out) {
113 PERFETTO_DCHECK(out->size() >= 2);
114 out->erase(out->size() - 2);
115 }
116
PrintUnknownVarIntField(uint32_t id,int64_t value,std::string * out)117 void PrintUnknownVarIntField(uint32_t id, int64_t value, std::string* out) {
118 StrAppend(out, std::to_string(id), ": ", std::to_string(value));
119 }
120
PrintEnumField(const FieldDescriptor & fd,const DescriptorPool & pool,uint32_t id,int32_t enum_value,std::string * out)121 void PrintEnumField(const FieldDescriptor& fd,
122 const DescriptorPool& pool,
123 uint32_t id,
124 int32_t enum_value,
125 std::string* out) {
126 auto opt_enum_descriptor_idx =
127 pool.FindDescriptorIdx(fd.resolved_type_name());
128 if (!opt_enum_descriptor_idx) {
129 PrintUnknownVarIntField(id, enum_value, out);
130 return;
131 }
132 auto opt_enum_string =
133 pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(enum_value);
134 // If the enum value is unknown, treat it like a completely unknown field.
135 if (!opt_enum_string) {
136 PrintUnknownVarIntField(id, enum_value, out);
137 return;
138 }
139 StrAppend(out, fd.name(), ": ", *opt_enum_string);
140 }
141
FormattedFieldDescriptorName(const FieldDescriptor & field_descriptor)142 std::string FormattedFieldDescriptorName(
143 const FieldDescriptor& field_descriptor) {
144 if (field_descriptor.is_extension()) {
145 // Libprotobuf formatter always formats extension field names as fully
146 // qualified names.
147 // TODO(b/197625974): Assuming for now all our extensions will belong to the
148 // perfetto.protos package. Update this if we ever want to support extendees
149 // in different package.
150 return "[perfetto.protos." + field_descriptor.name() + "]";
151 } else {
152 return field_descriptor.name();
153 }
154 }
155
PrintVarIntField(const FieldDescriptor * fd,const protozero::Field & field,const DescriptorPool & pool,std::string * out)156 void PrintVarIntField(const FieldDescriptor* fd,
157 const protozero::Field& field,
158 const DescriptorPool& pool,
159 std::string* out) {
160 uint32_t type = fd ? fd->type() : 0;
161 switch (type) {
162 case FieldDescriptorProto::TYPE_INT32:
163 StrAppend(out, fd->name(), ": ", std::to_string(field.as_int32()));
164 return;
165 case FieldDescriptorProto::TYPE_SINT32:
166 StrAppend(out, fd->name(), ": ", std::to_string(field.as_sint32()));
167 return;
168 case FieldDescriptorProto::TYPE_UINT32:
169 StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint32()));
170 return;
171 case FieldDescriptorProto::TYPE_INT64:
172 StrAppend(out, fd->name(), ": ", std::to_string(field.as_int64()));
173 return;
174 case FieldDescriptorProto::TYPE_SINT64:
175 StrAppend(out, fd->name(), ": ", std::to_string(field.as_sint64()));
176 return;
177 case FieldDescriptorProto::TYPE_UINT64:
178 StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint64()));
179 return;
180 case FieldDescriptorProto::TYPE_BOOL:
181 StrAppend(out, fd->name(), ": ", field.as_bool() ? "true" : "false");
182 return;
183 case FieldDescriptorProto::TYPE_ENUM:
184 PrintEnumField(*fd, pool, field.id(), field.as_int32(), out);
185 return;
186 case 0:
187 default:
188 PrintUnknownVarIntField(field.id(), field.as_int64(), out);
189 return;
190 }
191 }
192
PrintFixed32Field(const FieldDescriptor * fd,const protozero::Field & field,std::string * out)193 void PrintFixed32Field(const FieldDescriptor* fd,
194 const protozero::Field& field,
195 std::string* out) {
196 uint32_t type = fd ? fd->type() : 0;
197 switch (type) {
198 case FieldDescriptorProto::TYPE_SFIXED32:
199 StrAppend(out, fd->name(), ": ", std::to_string(field.as_int32()));
200 break;
201 case FieldDescriptorProto::TYPE_FIXED32:
202 StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint32()));
203 break;
204 case FieldDescriptorProto::TYPE_FLOAT:
205 StrAppend(out, fd->name(), ": ", std::to_string(field.as_float()));
206 break;
207 case 0:
208 default:
209 base::StackString<12> padded_hex("0x%08" PRIx32, field.as_uint32());
210 StrAppend(out, std::to_string(field.id()), ": ", padded_hex.c_str());
211 break;
212 }
213 }
214
PrintFixed64Field(const FieldDescriptor * fd,const protozero::Field & field,std::string * out)215 void PrintFixed64Field(const FieldDescriptor* fd,
216 const protozero::Field& field,
217 std::string* out) {
218 uint32_t type = fd ? fd->type() : 0;
219 switch (type) {
220 case FieldDescriptorProto::TYPE_SFIXED64:
221 StrAppend(out, fd->name(), ": ", std::to_string(field.as_int64()));
222 break;
223 case FieldDescriptorProto::TYPE_FIXED64:
224 StrAppend(out, fd->name(), ": ", std::to_string(field.as_uint64()));
225 break;
226 case FieldDescriptorProto::TYPE_DOUBLE:
227 StrAppend(out, fd->name(), ": ", std::to_string(field.as_double()));
228 break;
229 case 0:
230 default:
231 base::StackString<20> padded_hex("0x%016" PRIx64, field.as_uint64());
232 StrAppend(out, std::to_string(field.id()), ": ", padded_hex.c_str());
233 break;
234 }
235 }
236
237 void ProtozeroToTextInternal(const std::string& type,
238 protozero::ConstBytes protobytes,
239 NewLinesMode new_lines_mode,
240 const DescriptorPool& pool,
241 std::string* indents,
242 std::string* output);
243
244 template <protozero::proto_utils::ProtoWireType wire_type, typename T>
PrintPackedField(const FieldDescriptor & fd,const protozero::Field & field,NewLinesMode new_lines_mode,const std::string & indents,const DescriptorPool & pool,std::string * out)245 void PrintPackedField(const FieldDescriptor& fd,
246 const protozero::Field& field,
247 NewLinesMode new_lines_mode,
248 const std::string& indents,
249 const DescriptorPool& pool,
250 std::string* out) {
251 const bool include_new_lines = new_lines_mode == kIncludeNewLines;
252 bool err = false;
253 bool first_output = true;
254 for (protozero::PackedRepeatedFieldIterator<wire_type, T> it(
255 field.data(), field.size(), &err);
256 it; it++) {
257 T value = *it;
258 if (!first_output) {
259 if (include_new_lines) {
260 StrAppend(out, "\n", indents);
261 } else {
262 StrAppend(out, " ");
263 }
264 }
265 std::string serialized_value;
266 if (fd.type() == FieldDescriptorProto::TYPE_ENUM) {
267 PrintEnumField(fd, pool, field.id(), static_cast<int32_t>(value), out);
268 } else {
269 StrAppend(out, fd.name(), ": ", std::to_string(value));
270 }
271 first_output = false;
272 }
273
274 if (err) {
275 if (!first_output) {
276 if (include_new_lines) {
277 StrAppend(out, "\n", indents);
278 } else {
279 StrAppend(out, " ");
280 }
281 }
282 StrAppend(out, "# Packed decoding failure for field ", fd.name(), "\n");
283 }
284 }
285
PrintLengthDelimitedField(const FieldDescriptor * fd,const protozero::Field & field,NewLinesMode new_lines_mode,std::string * indents,const DescriptorPool & pool,std::string * out)286 void PrintLengthDelimitedField(const FieldDescriptor* fd,
287 const protozero::Field& field,
288 NewLinesMode new_lines_mode,
289 std::string* indents,
290 const DescriptorPool& pool,
291 std::string* out) {
292 const bool include_new_lines = new_lines_mode == kIncludeNewLines;
293 uint32_t type = fd ? fd->type() : 0;
294 switch (type) {
295 case FieldDescriptorProto::TYPE_BYTES:
296 case FieldDescriptorProto::TYPE_STRING: {
297 std::string value = QuoteAndEscapeTextProtoString(field.as_string());
298 StrAppend(out, fd->name(), ": ", value);
299 return;
300 }
301 case FieldDescriptorProto::TYPE_MESSAGE:
302 StrAppend(out, FormattedFieldDescriptorName(*fd), " {");
303 if (include_new_lines) {
304 IncreaseIndents(indents);
305 }
306 ProtozeroToTextInternal(fd->resolved_type_name(), field.as_bytes(),
307 new_lines_mode, pool, indents, out);
308 if (include_new_lines) {
309 DecreaseIndents(indents);
310 StrAppend(out, "\n", *indents, "}");
311 } else {
312 StrAppend(out, " }");
313 }
314 return;
315 case FieldDescriptorProto::TYPE_DOUBLE:
316 PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64, double>(
317 *fd, field, new_lines_mode, *indents, pool, out);
318 return;
319 case FieldDescriptorProto::TYPE_FLOAT:
320 PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32, float>(
321 *fd, field, new_lines_mode, *indents, pool, out);
322 return;
323 case FieldDescriptorProto::TYPE_INT64:
324 PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int64_t>(
325 *fd, field, new_lines_mode, *indents, pool, out);
326 return;
327 case FieldDescriptorProto::TYPE_UINT64:
328 PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt,
329 uint64_t>(*fd, field, new_lines_mode, *indents, pool,
330 out);
331 return;
332 case FieldDescriptorProto::TYPE_INT32:
333 PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int32_t>(
334 *fd, field, new_lines_mode, *indents, pool, out);
335 return;
336 case FieldDescriptorProto::TYPE_FIXED64:
337 PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64,
338 uint64_t>(*fd, field, new_lines_mode, *indents, pool,
339 out);
340 return;
341 case FieldDescriptorProto::TYPE_FIXED32:
342 PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32,
343 uint32_t>(*fd, field, new_lines_mode, *indents, pool,
344 out);
345 return;
346 case FieldDescriptorProto::TYPE_UINT32:
347 PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt,
348 uint32_t>(*fd, field, new_lines_mode, *indents, pool,
349 out);
350 return;
351 case FieldDescriptorProto::TYPE_SFIXED32:
352 PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed32,
353 int32_t>(*fd, field, new_lines_mode, *indents, pool,
354 out);
355 return;
356 case FieldDescriptorProto::TYPE_SFIXED64:
357 PrintPackedField<protozero::proto_utils::ProtoWireType::kFixed64,
358 int64_t>(*fd, field, new_lines_mode, *indents, pool,
359 out);
360 return;
361 case FieldDescriptorProto::TYPE_ENUM:
362 PrintPackedField<protozero::proto_utils::ProtoWireType::kVarInt, int32_t>(
363 *fd, field, new_lines_mode, *indents, pool, out);
364 return;
365 // Our protoc plugin cannot generate code for packed repeated fields with
366 // these types. Output a comment and then fall back to the raw field_id:
367 // string representation.
368 case FieldDescriptorProto::TYPE_BOOL:
369 case FieldDescriptorProto::TYPE_SINT32:
370 case FieldDescriptorProto::TYPE_SINT64:
371 StrAppend(out, "# Packed type ", std::to_string(type),
372 " not supported. Printing raw string.", "\n", *indents);
373 break;
374 case 0:
375 default:
376 break;
377 }
378 std::string value = QuoteAndEscapeTextProtoString(field.as_string());
379 StrAppend(out, std::to_string(field.id()), ": ", value);
380 }
381
382 // Recursive case function, Will parse |protobytes| assuming it is a proto of
383 // |type| and will use |pool| to look up the |type|. All output will be placed
384 // in |output|, using |new_lines_mode| to separate fields. When called for
385 // |indents| will be increased by 2 spaces to improve readability.
ProtozeroToTextInternal(const std::string & type,protozero::ConstBytes protobytes,NewLinesMode new_lines_mode,const DescriptorPool & pool,std::string * indents,std::string * output)386 void ProtozeroToTextInternal(const std::string& type,
387 protozero::ConstBytes protobytes,
388 NewLinesMode new_lines_mode,
389 const DescriptorPool& pool,
390 std::string* indents,
391 std::string* output) {
392 std::optional<uint32_t> opt_proto_desc_idx = pool.FindDescriptorIdx(type);
393 const ProtoDescriptor* opt_proto_descriptor =
394 opt_proto_desc_idx ? &pool.descriptors()[*opt_proto_desc_idx] : nullptr;
395 const bool include_new_lines = new_lines_mode == kIncludeNewLines;
396
397 protozero::ProtoDecoder decoder(protobytes.data, protobytes.size);
398 for (auto field = decoder.ReadField(); field.valid();
399 field = decoder.ReadField()) {
400 if (!output->empty()) {
401 if (include_new_lines) {
402 StrAppend(output, "\n", *indents);
403 } else {
404 StrAppend(output, " ", *indents);
405 }
406 } else {
407 StrAppend(output, *indents);
408 }
409 auto* opt_field_descriptor =
410 opt_proto_descriptor ? opt_proto_descriptor->FindFieldByTag(field.id())
411 : nullptr;
412 switch (field.type()) {
413 case ProtoWireType::kVarInt:
414 PrintVarIntField(opt_field_descriptor, field, pool, output);
415 break;
416 case ProtoWireType::kLengthDelimited:
417 PrintLengthDelimitedField(opt_field_descriptor, field, new_lines_mode,
418 indents, pool, output);
419 break;
420 case ProtoWireType::kFixed32:
421 PrintFixed32Field(opt_field_descriptor, field, output);
422 break;
423 case ProtoWireType::kFixed64:
424 PrintFixed64Field(opt_field_descriptor, field, output);
425 break;
426 }
427 }
428 if (decoder.bytes_left() != 0) {
429 if (!output->empty()) {
430 if (include_new_lines) {
431 StrAppend(output, "\n", *indents);
432 } else {
433 StrAppend(output, " ", *indents);
434 }
435 }
436 StrAppend(
437 output, "# Extra bytes: ",
438 QuoteAndEscapeTextProtoString(base::StringView(
439 reinterpret_cast<const char*>(decoder.end() - decoder.bytes_left()),
440 decoder.bytes_left())),
441 "\n");
442 }
443 }
444
445 } // namespace
446
ProtozeroToText(const DescriptorPool & pool,const std::string & type,protozero::ConstBytes protobytes,NewLinesMode new_lines_mode,uint32_t initial_indent_depth)447 std::string ProtozeroToText(const DescriptorPool& pool,
448 const std::string& type,
449 protozero::ConstBytes protobytes,
450 NewLinesMode new_lines_mode,
451 uint32_t initial_indent_depth) {
452 std::string indent = std::string(2 * initial_indent_depth, ' ');
453 std::string final_result;
454 ProtozeroToTextInternal(type, protobytes, new_lines_mode, pool, &indent,
455 &final_result);
456 return final_result;
457 }
458
DebugTrackEventProtozeroToText(const std::string & type,protozero::ConstBytes protobytes)459 std::string DebugTrackEventProtozeroToText(const std::string& type,
460 protozero::ConstBytes protobytes) {
461 DescriptorPool pool;
462 auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
463 kTrackEventDescriptor.size());
464 PERFETTO_DCHECK(status.ok());
465 return ProtozeroToText(pool, type, protobytes, kIncludeNewLines);
466 }
467
ShortDebugTrackEventProtozeroToText(const std::string & type,protozero::ConstBytes protobytes)468 std::string ShortDebugTrackEventProtozeroToText(
469 const std::string& type,
470 protozero::ConstBytes protobytes) {
471 DescriptorPool pool;
472 auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
473 kTrackEventDescriptor.size());
474 PERFETTO_DCHECK(status.ok());
475 return ProtozeroToText(pool, type, protobytes, kSkipNewLines);
476 }
477
ProtozeroEnumToText(const std::string & type,int32_t enum_value)478 std::string ProtozeroEnumToText(const std::string& type, int32_t enum_value) {
479 DescriptorPool pool;
480 auto status = pool.AddFromFileDescriptorSet(kTrackEventDescriptor.data(),
481 kTrackEventDescriptor.size());
482 PERFETTO_DCHECK(status.ok());
483 auto opt_enum_descriptor_idx = pool.FindDescriptorIdx(type);
484 if (!opt_enum_descriptor_idx) {
485 // Fall back to the integer representation of the field.
486 return std::to_string(enum_value);
487 }
488 auto opt_enum_string =
489 pool.descriptors()[*opt_enum_descriptor_idx].FindEnumString(enum_value);
490 if (!opt_enum_string) {
491 // Fall back to the integer representation of the field.
492 return std::to_string(enum_value);
493 }
494 return *opt_enum_string;
495 }
496
ProtozeroToText(const DescriptorPool & pool,const std::string & type,const std::vector<uint8_t> & protobytes,NewLinesMode new_lines_mode)497 std::string ProtozeroToText(const DescriptorPool& pool,
498 const std::string& type,
499 const std::vector<uint8_t>& protobytes,
500 NewLinesMode new_lines_mode) {
501 return ProtozeroToText(
502 pool, type, protozero::ConstBytes{protobytes.data(), protobytes.size()},
503 new_lines_mode);
504 }
505
506 } // namespace protozero_to_text
507 } // namespace trace_processor
508 } // namespace perfetto
509