From 811559143fa39acce897c86f712955b73ecc69f5 Mon Sep 17 00:00:00 2001 From: Ryan Kuester Date: Sun, 30 Jun 2024 21:28:16 -0500 Subject: [PATCH] feat: add a tflite::hexdump() for printing raw memory Add tflite::hexdump() for printing raw memory to output streams. Copy the output format of Python's hexdump module. --- tensorflow/lite/micro/BUILD | 26 +++++++ tensorflow/lite/micro/hexdump.cc | 103 ++++++++++++++++++++++++++ tensorflow/lite/micro/hexdump.h | 35 +++++++++ tensorflow/lite/micro/hexdump_test.cc | 58 +++++++++++++++ 4 files changed, 222 insertions(+) create mode 100644 tensorflow/lite/micro/hexdump.cc create mode 100644 tensorflow/lite/micro/hexdump.h create mode 100644 tensorflow/lite/micro/hexdump_test.cc diff --git a/tensorflow/lite/micro/BUILD b/tensorflow/lite/micro/BUILD index 1753465425d..5dd10b514b1 100644 --- a/tensorflow/lite/micro/BUILD +++ b/tensorflow/lite/micro/BUILD @@ -381,6 +381,20 @@ cc_library( ], ) +cc_library( + name = "hexdump", + srcs = [ + "hexdump.cc", + ], + hdrs = [ + "hexdump.h", + ], + deps = [ + ":span", + ":static_vector", + ], +) + cc_library( name = "recording_allocators", srcs = [ @@ -556,6 +570,18 @@ cc_test( ], ) +cc_test( + name = "hexdump_test", + size = "small", + srcs = [ + "hexdump_test.cc", + ], + deps = [ + ":hexdump", + "//tensorflow/lite/micro/testing:micro_test", + ], +) + cc_test( name = "memory_helpers_test", srcs = [ diff --git a/tensorflow/lite/micro/hexdump.cc b/tensorflow/lite/micro/hexdump.cc new file mode 100644 index 00000000000..fd0f6f7c84e --- /dev/null +++ b/tensorflow/lite/micro/hexdump.cc @@ -0,0 +1,103 @@ +// Copyright 2024 The TensorFlow Authors. All Rights Reserved. +// +// 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 "tensorflow/lite/micro/hexdump.h" + +#include +#include + +#include "tensorflow/lite/micro/debug_log.h" +#include "tensorflow/lite/micro/static_vector.h" + +namespace { + +tflite::Span output(const tflite::Span& buf, const char* format, + ...) { + // Writes formatted output, printf-style, to either a buffer or DebugLog. + // Writes to DebugLog if the buffer data pointer is null. Does not exceed + // the size of the buffer. Returns the unused remainder of the buffer, or a + // buffer with a null data pointer in the case of printing to DebugLog. + + tflite::Span result{nullptr, 0}; + + va_list args; + va_start(args, format); + + if (buf.data() == nullptr) { + DebugLog(format, args); + result = {nullptr, 0}; + } else { + size_t len = DebugVsnprintf(buf.data(), buf.size(), format, args); + // Returns the number of characters that would have been written if + // there were enough room, so cap it at the size of the buffer in order to + // know how much was actually written. + size_t consumed = std::min(len, buf.size()); + result = {buf.data() + consumed, buf.size() - consumed}; + } + + va_end(args); + return result; +} + +} // end anonymous namespace + +tflite::Span tflite::hexdump(const tflite::Span region, + const tflite::Span out) { + tflite::Span buffer{out}; + std::size_t byte_nr = 0; + constexpr int per_line = 16; + const int lines = (region.size() + per_line - 1) / per_line; // round up + + for (int line = 0; line < lines; ++line) { + tflite::StaticVector ascii; + + // print address + buffer = output(buffer, "%08X:", line); + + for (int pos = 0; pos < per_line; ++pos) { + if (byte_nr < region.size()) { + // print byte + int as_int = static_cast(region[byte_nr++]); + buffer = output(buffer, " %02X", as_int); + + // buffer an ascii printable value + char c{'.'}; + if (std::isprint(as_int)) { + c = static_cast(as_int); + } + ascii.push_back(c); + } else { + buffer = output(buffer, " "); + } + + // print extra space in middle of the line + if (pos == per_line / 2 - 1) { + buffer = output(buffer, " "); + } + } + + // print the ascii value + buffer = output(buffer, " "); + for (const auto& c : ascii) { + buffer = output(buffer, "%c", c); + } + buffer = output(buffer, "%c", '\n'); + } + + return {out.data(), out.size() - buffer.size()}; +} + +void tflite::hexdump(const tflite::Span region) { + hexdump(region, {nullptr, 0}); +} diff --git a/tensorflow/lite/micro/hexdump.h b/tensorflow/lite/micro/hexdump.h new file mode 100644 index 00000000000..0bdfcc47c05 --- /dev/null +++ b/tensorflow/lite/micro/hexdump.h @@ -0,0 +1,35 @@ +// Copyright 2024 The TensorFlow Authors. All Rights Reserved. +// +// 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 TENSORFLOW_LITE_MICRO_HEXDUMP_H_ +#define TENSORFLOW_LITE_MICRO_HEXDUMP_H_ + +#include + +#include "tensorflow/lite/micro/span.h" + +namespace tflite { + +// Displays the contents of a memory region, formatted in hexadecimal and ASCII +// in a style matching Python's hexdump module, using DebugLog(). +void hexdump(Span region); + +// Writes the contents of a memory region, formatted in hexadecimal and ASCII +// in a style matching Python's hexdump module, to a buffer. Returns the portion +// of the buffer written. +Span hexdump(Span region, Span buffer); + +} // end namespace tflite + +#endif // TENSORFLOW_LITE_MICRO_HEXDUMP_H_ diff --git a/tensorflow/lite/micro/hexdump_test.cc b/tensorflow/lite/micro/hexdump_test.cc new file mode 100644 index 00000000000..89d3a0404c5 --- /dev/null +++ b/tensorflow/lite/micro/hexdump_test.cc @@ -0,0 +1,58 @@ +// Copyright 2024 The TensorFlow Authors. All Rights Reserved. +// +// 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 "tensorflow/lite/micro/hexdump.h" + +#include + +#include "tensorflow/lite/micro/span.h" +#include "tensorflow/lite/micro/testing/micro_test.h" + +constexpr tflite::Span input{ + "This is an input string for testing."}; + +const tflite::Span region{ + reinterpret_cast(input.data()), input.size()}; + +// clang-format off +constexpr tflite::Span expected{ + "00000000: 54 68 69 73 20 69 73 20 61 6E 20 69 6E 70 75 74 This is an input\n" + "00000001: 20 73 74 72 69 6E 67 20 66 6F 72 20 74 65 73 74 string for test\n" + "00000002: 69 6E 67 2E 00 ing..\n"}; +// clang-format on + +// String literals have null terminators, but don't expect a null terminator +// in the hexdump output. +constexpr tflite::Span expected_no_null{expected.data(), + expected.size() - 1}; + +TF_LITE_MICRO_TESTS_BEGIN + +TF_LITE_MICRO_TEST(TestOutputToBuffer) { + // Allocate a buffer with an arbitrary amount of extra room so the test has + // the possibility of failing if hexdump mishandles the extra space. + std::array buffer; + + tflite::Span output = tflite::hexdump(region, buffer); + TF_LITE_MICRO_EXPECT(output == expected_no_null); +} + +TF_LITE_MICRO_TEST(TestOutputToDebugLog) { + // There's no easy way to verify DebugLog output; however, test it anyhow to + // catch an outright crash, and so the output appears in the log should + // someone wish to examine it. + tflite::hexdump(region); +} + +TF_LITE_MICRO_TESTS_END