Skip to content

Commit

Permalink
feat: add a tflite::hexdump() for printing raw memory
Browse files Browse the repository at this point in the history
Add tflite::hexdump() for printing raw memory to output streams. Copy
the output format of Python's hexdump module.
  • Loading branch information
rkuester committed Sep 26, 2024
1 parent bf2ba11 commit 8115591
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 0 deletions.
26 changes: 26 additions & 0 deletions tensorflow/lite/micro/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 = [
Expand Down
103 changes: 103 additions & 0 deletions tensorflow/lite/micro/hexdump.cc
Original file line number Diff line number Diff line change
@@ -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 <algorithm>
#include <cctype>

#include "tensorflow/lite/micro/debug_log.h"
#include "tensorflow/lite/micro/static_vector.h"

namespace {

tflite::Span<char> output(const tflite::Span<char>& 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<char> 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<char> tflite::hexdump(const tflite::Span<const std::byte> region,
const tflite::Span<char> out) {
tflite::Span<char> 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<char, per_line> 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<int>(region[byte_nr++]);
buffer = output(buffer, " %02X", as_int);

// buffer an ascii printable value
char c{'.'};
if (std::isprint(as_int)) {
c = static_cast<char>(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<const std::byte> region) {
hexdump(region, {nullptr, 0});
}
35 changes: 35 additions & 0 deletions tensorflow/lite/micro/hexdump.h
Original file line number Diff line number Diff line change
@@ -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 <cstddef>

#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<const std::byte> 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<char> hexdump(Span<const std::byte> region, Span<char> buffer);

} // end namespace tflite

#endif // TENSORFLOW_LITE_MICRO_HEXDUMP_H_
58 changes: 58 additions & 0 deletions tensorflow/lite/micro/hexdump_test.cc
Original file line number Diff line number Diff line change
@@ -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 <array>

#include "tensorflow/lite/micro/span.h"
#include "tensorflow/lite/micro/testing/micro_test.h"

constexpr tflite::Span<const char> input{
"This is an input string for testing."};

const tflite::Span<const std::byte> region{
reinterpret_cast<const std::byte*>(input.data()), input.size()};

// clang-format off
constexpr tflite::Span<const char> 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<const char> 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<char, expected.size() + 10> buffer;

tflite::Span<char> 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

0 comments on commit 8115591

Please sign in to comment.