You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
260 lines
8.1 KiB
260 lines
8.1 KiB
// Copyright 2020 The Pigweed Authors
|
|
//
|
|
// 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
|
|
//
|
|
// https://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 "pw_hex_dump/hex_dump.h"
|
|
|
|
#include <cctype>
|
|
#include <cstddef>
|
|
#include <string_view>
|
|
|
|
#include "pw_status/status_with_size.h"
|
|
#include "pw_string/string_builder.h"
|
|
#include "pw_string/type_to_string.h"
|
|
|
|
using pw::string::HexDigitCount;
|
|
using pw::string::IntToHexString;
|
|
|
|
namespace pw::dump {
|
|
namespace {
|
|
|
|
constexpr const std::string_view kAddressSeparator(": ");
|
|
constexpr const std::string_view kSectionSeparator(" ");
|
|
constexpr const std::string_view kAddressHeader("Address");
|
|
constexpr const std::string_view kOffsetHeader("Offs.");
|
|
constexpr const std::string_view kAsciiHeader("Text");
|
|
|
|
// Minimum number of hex characters to use when displaying dump offset.
|
|
constexpr const size_t kMinOffsetChars = 4;
|
|
|
|
char PrintableChar(std::byte b) {
|
|
if (std::isprint(std::to_integer<char>(b)) == 0) {
|
|
return '.';
|
|
}
|
|
return std::to_integer<char>(b);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Status DumpAddr(std::span<char> dest, uintptr_t addr) {
|
|
if (dest.data() == nullptr) {
|
|
return Status::InvalidArgument();
|
|
}
|
|
// Include null terminator.
|
|
if (dest.size() < kHexAddrStringSize + 1) {
|
|
return Status::ResourceExhausted();
|
|
}
|
|
dest[0] = '0';
|
|
dest[1] = 'x';
|
|
|
|
return IntToHexString(addr, dest.subspan(2), sizeof(uintptr_t) * 2).status();
|
|
}
|
|
|
|
Status FormattedHexDumper::PrintFormatHeader() {
|
|
StringBuilder builder(dest_);
|
|
|
|
if (flags.prefix_mode != AddressMode::kDisabled) {
|
|
std::string_view header(flags.prefix_mode == AddressMode::kOffset
|
|
? kOffsetHeader
|
|
: kAddressHeader);
|
|
// Pad to align to address width.
|
|
size_t padding = 0;
|
|
if (flags.prefix_mode == AddressMode::kOffset) {
|
|
size_t offs_width =
|
|
HexDigitCount(source_data_.size_bytes() + current_offset_);
|
|
padding = std::max(offs_width, kMinOffsetChars);
|
|
} else {
|
|
padding = kHexAddrStringSize;
|
|
}
|
|
|
|
padding += kAddressSeparator.length();
|
|
padding -= header.size();
|
|
|
|
builder << header;
|
|
builder.append(padding, ' ');
|
|
}
|
|
|
|
// Print offsets.
|
|
for (size_t i = 0; i < static_cast<size_t>(flags.bytes_per_line); ++i) {
|
|
// Early loop termination for when bytes_remaining <
|
|
// bytes_per_line.
|
|
if (flags.group_every != 0 &&
|
|
i % static_cast<uint8_t>(flags.group_every) == 0) {
|
|
uint8_t c = static_cast<uint8_t>(i);
|
|
if (c >> 4 == 0) {
|
|
builder << ' ';
|
|
} else {
|
|
builder << std::byte(c >> 4);
|
|
}
|
|
builder << std::byte(c & 0xF);
|
|
} else {
|
|
builder.append(2, ' ');
|
|
}
|
|
if (flags.group_every != 0 && (i + 1) % flags.group_every == 0) {
|
|
builder << ' ';
|
|
}
|
|
}
|
|
|
|
// Removes extraneous space from end when bytes_per_line is divisible by
|
|
// group_every. kSectionSeparator includes a space, so it's unnecessary.
|
|
// Ommitting the space from the section separator actually makes for more
|
|
// workarounds and code duplication, so this is better.
|
|
if (flags.group_every != 0 && flags.bytes_per_line % flags.group_every == 0) {
|
|
builder.pop_back();
|
|
}
|
|
|
|
if (flags.show_ascii) {
|
|
builder << kSectionSeparator;
|
|
builder << kAsciiHeader;
|
|
}
|
|
|
|
return builder.status();
|
|
}
|
|
|
|
Status FormattedHexDumper::DumpLine() {
|
|
if (source_data_.empty()) {
|
|
return Status::ResourceExhausted();
|
|
}
|
|
|
|
if (!ValidateBufferSize().ok() || dest_.data() == nullptr) {
|
|
return Status::FailedPrecondition();
|
|
}
|
|
|
|
if (dest_[0] == 0 && flags.show_header) {
|
|
// First line, print out dump format header.
|
|
return PrintFormatHeader();
|
|
}
|
|
|
|
StringBuilder builder(dest_);
|
|
// Dump address/offset prefix.
|
|
// TODO(amontanez): This block can be much nicer if StringBuilder exposed an
|
|
// easy way to control zero padding for hex address.
|
|
if (flags.prefix_mode != AddressMode::kDisabled) {
|
|
uintptr_t val;
|
|
if (flags.prefix_mode == AddressMode::kAbsolute) {
|
|
val = reinterpret_cast<uintptr_t>(source_data_.data());
|
|
builder << "0x";
|
|
uint8_t significant = HexDigitCount(val);
|
|
builder.append(sizeof(uintptr_t) * 2 - significant, '0');
|
|
} else {
|
|
val = current_offset_;
|
|
size_t significant =
|
|
HexDigitCount(source_data_.size_bytes() + current_offset_);
|
|
if (significant < kMinOffsetChars) {
|
|
builder.append(kMinOffsetChars - significant, '0');
|
|
}
|
|
}
|
|
if (val != 0) {
|
|
builder << reinterpret_cast<void*>(val);
|
|
} else {
|
|
builder.append(2, '0');
|
|
}
|
|
builder << kAddressSeparator;
|
|
}
|
|
|
|
size_t bytes_in_line = std::min(source_data_.size_bytes(),
|
|
static_cast<size_t>(flags.bytes_per_line));
|
|
// Convert raw bytes to hex characters.
|
|
for (size_t i = 0; i < bytes_in_line; ++i) {
|
|
// Early loop termination for when bytes_remaining <
|
|
// bytes_per_line.
|
|
uint8_t c = std::to_integer<uint8_t>(source_data_[i]);
|
|
// TODO(amontanez): Maybe StringBuilder can be augmented to support full-
|
|
// width bytes? (`04` instead of `4`, for example)
|
|
builder << std::byte(c >> 4);
|
|
builder << std::byte(c & 0xF);
|
|
if (flags.group_every != 0 && (i + 1) % flags.group_every == 0) {
|
|
builder << ' ';
|
|
}
|
|
}
|
|
// Add padding spaces to ensure lines are aligned.
|
|
if (flags.show_ascii) {
|
|
for (size_t i = bytes_in_line;
|
|
i < static_cast<size_t>(flags.bytes_per_line);
|
|
++i) {
|
|
builder.append(2, ' ');
|
|
if (flags.group_every != 0 && (i + 1) % flags.group_every == 0) {
|
|
builder << ' ';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Removes extraneous space from end when bytes_per_line is divisible by
|
|
// group_every. kSectionSeparator includes a space, so it's unnecessary.
|
|
// Ommitting the space from the section separator actually makes for more
|
|
// workarounds and code duplication, so this is better.
|
|
if (flags.group_every != 0 && flags.bytes_per_line % flags.group_every == 0) {
|
|
builder.pop_back();
|
|
}
|
|
|
|
// Interpret bytes as characters.
|
|
if (flags.show_ascii) {
|
|
builder << kSectionSeparator;
|
|
for (size_t i = 0; i < bytes_in_line; ++i) {
|
|
builder << PrintableChar(source_data_[i]);
|
|
}
|
|
}
|
|
|
|
source_data_ = source_data_.subspan(bytes_in_line);
|
|
current_offset_ += bytes_in_line;
|
|
return builder.status();
|
|
}
|
|
|
|
Status FormattedHexDumper::SetLineBuffer(std::span<char> dest) {
|
|
if (dest.data() == nullptr || dest.size_bytes() == 0) {
|
|
return Status::InvalidArgument();
|
|
}
|
|
dest_ = dest;
|
|
return ValidateBufferSize().ok() ? OkStatus() : Status::ResourceExhausted();
|
|
}
|
|
|
|
Status FormattedHexDumper::BeginDump(ConstByteSpan data) {
|
|
current_offset_ = 0;
|
|
source_data_ = data;
|
|
if (data.data() == nullptr) {
|
|
return Status::InvalidArgument();
|
|
}
|
|
if (dest_.data() != nullptr && dest_.size_bytes() > 0) {
|
|
dest_[0] = 0;
|
|
}
|
|
return ValidateBufferSize().ok() ? OkStatus() : Status::FailedPrecondition();
|
|
}
|
|
|
|
Status FormattedHexDumper::ValidateBufferSize() {
|
|
// Minimum size is number of bytes per line as hex pairs plus the null
|
|
// terminator.
|
|
size_t required_size = flags.bytes_per_line * 2 + 1;
|
|
if (flags.show_ascii) {
|
|
required_size += kSectionSeparator.length() + flags.bytes_per_line;
|
|
}
|
|
if (flags.prefix_mode == AddressMode::kAbsolute) {
|
|
required_size += kHexAddrStringSize;
|
|
required_size += kAddressSeparator.length();
|
|
} else if (flags.prefix_mode == AddressMode::kOffset) {
|
|
required_size +=
|
|
HexDigitCount(std::max(source_data_.size_bytes(), kMinOffsetChars));
|
|
required_size += kAddressSeparator.length();
|
|
}
|
|
if (flags.group_every != 0) {
|
|
required_size += (flags.bytes_per_line - 1) / flags.group_every;
|
|
}
|
|
|
|
if (dest_.size_bytes() < required_size) {
|
|
return Status::ResourceExhausted();
|
|
}
|
|
|
|
return OkStatus();
|
|
}
|
|
|
|
} // namespace pw::dump
|