1 // Copyright 2020 The Pigweed Authors
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
7 // https://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
15 #include "pw_hex_dump/hex_dump.h"
19 #include <string_view>
21 #include "pw_status/status_with_size.h"
22 #include "pw_string/string_builder.h"
23 #include "pw_string/type_to_string.h"
25 using pw::string::HexDigitCount;
26 using pw::string::IntToHexString;
31 constexpr const std::string_view kAddressSeparator(": ");
32 constexpr const std::string_view kSectionSeparator(" ");
33 constexpr const std::string_view kAddressHeader("Address");
34 constexpr const std::string_view kOffsetHeader("Offs.");
35 constexpr const std::string_view kAsciiHeader("Text");
37 // Minimum number of hex characters to use when displaying dump offset.
38 constexpr const size_t kMinOffsetChars = 4;
40 char PrintableChar(std::byte b) {
41 if (std::isprint(std::to_integer<char>(b)) == 0) {
44 return std::to_integer<char>(b);
49 Status DumpAddr(std::span<char> dest, uintptr_t addr) {
50 if (dest.data() == nullptr) {
51 return Status::InvalidArgument();
53 // Include null terminator.
54 if (dest.size() < kHexAddrStringSize + 1) {
55 return Status::ResourceExhausted();
60 return IntToHexString(addr, dest.subspan(2), sizeof(uintptr_t) * 2).status();
63 Status FormattedHexDumper::PrintFormatHeader() {
64 StringBuilder builder(dest_);
66 if (flags.prefix_mode != AddressMode::kDisabled) {
67 std::string_view header(flags.prefix_mode == AddressMode::kOffset
70 // Pad to align to address width.
72 if (flags.prefix_mode == AddressMode::kOffset) {
74 HexDigitCount(source_data_.size_bytes() + current_offset_);
75 padding = std::max(offs_width, kMinOffsetChars);
77 padding = kHexAddrStringSize;
80 padding += kAddressSeparator.length();
81 padding -= header.size();
84 builder.append(padding, ' ');
88 for (size_t i = 0; i < static_cast<size_t>(flags.bytes_per_line); ++i) {
89 // Early loop termination for when bytes_remaining <
91 if (flags.group_every != 0 &&
92 i % static_cast<uint8_t>(flags.group_every) == 0) {
93 uint8_t c = static_cast<uint8_t>(i);
97 builder << std::byte(c >> 4);
99 builder << std::byte(c & 0xF);
101 builder.append(2, ' ');
103 if (flags.group_every != 0 && (i + 1) % flags.group_every == 0) {
108 // Removes extraneous space from end when bytes_per_line is divisible by
109 // group_every. kSectionSeparator includes a space, so it's unnecessary.
110 // Ommitting the space from the section separator actually makes for more
111 // workarounds and code duplication, so this is better.
112 if (flags.group_every != 0 && flags.bytes_per_line % flags.group_every == 0) {
116 if (flags.show_ascii) {
117 builder << kSectionSeparator;
118 builder << kAsciiHeader;
121 return builder.status();
124 Status FormattedHexDumper::DumpLine() {
125 if (source_data_.empty()) {
126 return Status::ResourceExhausted();
129 if (!ValidateBufferSize().ok() || dest_.data() == nullptr) {
130 return Status::FailedPrecondition();
133 if (dest_[0] == 0 && flags.show_header) {
134 // First line, print out dump format header.
135 return PrintFormatHeader();
138 StringBuilder builder(dest_);
139 // Dump address/offset prefix.
140 // TODO(amontanez): This block can be much nicer if StringBuilder exposed an
141 // easy way to control zero padding for hex address.
142 if (flags.prefix_mode != AddressMode::kDisabled) {
144 if (flags.prefix_mode == AddressMode::kAbsolute) {
145 val = reinterpret_cast<uintptr_t>(source_data_.data());
147 uint8_t significant = HexDigitCount(val);
148 builder.append(sizeof(uintptr_t) * 2 - significant, '0');
150 val = current_offset_;
152 HexDigitCount(source_data_.size_bytes() + current_offset_);
153 if (significant < kMinOffsetChars) {
154 builder.append(kMinOffsetChars - significant, '0');
158 builder << reinterpret_cast<void*>(val);
160 builder.append(2, '0');
162 builder << kAddressSeparator;
165 size_t bytes_in_line = std::min(source_data_.size_bytes(),
166 static_cast<size_t>(flags.bytes_per_line));
167 // Convert raw bytes to hex characters.
168 for (size_t i = 0; i < bytes_in_line; ++i) {
169 // Early loop termination for when bytes_remaining <
171 uint8_t c = std::to_integer<uint8_t>(source_data_[i]);
172 // TODO(amontanez): Maybe StringBuilder can be augmented to support full-
173 // width bytes? (`04` instead of `4`, for example)
174 builder << std::byte(c >> 4);
175 builder << std::byte(c & 0xF);
176 if (flags.group_every != 0 && (i + 1) % flags.group_every == 0) {
180 // Add padding spaces to ensure lines are aligned.
181 if (flags.show_ascii) {
182 for (size_t i = bytes_in_line;
183 i < static_cast<size_t>(flags.bytes_per_line);
185 builder.append(2, ' ');
186 if (flags.group_every != 0 && (i + 1) % flags.group_every == 0) {
192 // Removes extraneous space from end when bytes_per_line is divisible by
193 // group_every. kSectionSeparator includes a space, so it's unnecessary.
194 // Ommitting the space from the section separator actually makes for more
195 // workarounds and code duplication, so this is better.
196 if (flags.group_every != 0 && flags.bytes_per_line % flags.group_every == 0) {
200 // Interpret bytes as characters.
201 if (flags.show_ascii) {
202 builder << kSectionSeparator;
203 for (size_t i = 0; i < bytes_in_line; ++i) {
204 builder << PrintableChar(source_data_[i]);
208 source_data_ = source_data_.subspan(bytes_in_line);
209 current_offset_ += bytes_in_line;
210 return builder.status();
213 Status FormattedHexDumper::SetLineBuffer(std::span<char> dest) {
214 if (dest.data() == nullptr || dest.size_bytes() == 0) {
215 return Status::InvalidArgument();
218 return ValidateBufferSize().ok() ? OkStatus() : Status::ResourceExhausted();
221 Status FormattedHexDumper::BeginDump(ConstByteSpan data) {
224 if (data.data() == nullptr) {
225 return Status::InvalidArgument();
227 if (dest_.data() != nullptr && dest_.size_bytes() > 0) {
230 return ValidateBufferSize().ok() ? OkStatus() : Status::FailedPrecondition();
233 Status FormattedHexDumper::ValidateBufferSize() {
234 // Minimum size is number of bytes per line as hex pairs plus the null
236 size_t required_size = flags.bytes_per_line * 2 + 1;
237 if (flags.show_ascii) {
238 required_size += kSectionSeparator.length() + flags.bytes_per_line;
240 if (flags.prefix_mode == AddressMode::kAbsolute) {
241 required_size += kHexAddrStringSize;
242 required_size += kAddressSeparator.length();
243 } else if (flags.prefix_mode == AddressMode::kOffset) {
245 HexDigitCount(std::max(source_data_.size_bytes(), kMinOffsetChars));
246 required_size += kAddressSeparator.length();
248 if (flags.group_every != 0) {
249 required_size += (flags.bytes_per_line - 1) / flags.group_every;
252 if (dest_.size_bytes() < required_size) {
253 return Status::ResourceExhausted();
259 } // namespace pw::dump