Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a tflite::hexdump() for printing raw memory #2659

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading