Armando Montanez | 593d0d5 | 2020-07-08 19:55:01 -0700 | [diff] [blame] | 1 | // Copyright 2020 The Pigweed Authors |
| 2 | // |
| 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 |
| 5 | // the License at |
| 6 | // |
| 7 | // https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 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 |
| 13 | // the License. |
| 14 | |
| 15 | #include "pw_hex_dump/hex_dump.h" |
| 16 | |
| 17 | #include <cctype> |
| 18 | #include <cstddef> |
| 19 | #include <string_view> |
| 20 | |
| 21 | #include "pw_status/status_with_size.h" |
| 22 | #include "pw_string/string_builder.h" |
| 23 | #include "pw_string/type_to_string.h" |
| 24 | |
| 25 | using pw::string::HexDigitCount; |
| 26 | using pw::string::IntToHexString; |
| 27 | |
| 28 | namespace pw::dump { |
| 29 | namespace { |
| 30 | |
| 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"); |
| 36 | |
| 37 | // Minimum number of hex characters to use when displaying dump offset. |
| 38 | constexpr const size_t kMinOffsetChars = 4; |
| 39 | |
| 40 | char PrintableChar(std::byte b) { |
| 41 | if (std::isprint(std::to_integer<char>(b)) == 0) { |
| 42 | return '.'; |
| 43 | } |
| 44 | return std::to_integer<char>(b); |
| 45 | } |
| 46 | |
| 47 | } // namespace |
| 48 | |
| 49 | Status DumpAddr(std::span<char> dest, uintptr_t addr) { |
| 50 | if (dest.data() == nullptr) { |
Wyatt Hepler | d78f7c6 | 2020-09-28 14:27:32 -0700 | [diff] [blame] | 51 | return Status::InvalidArgument(); |
Armando Montanez | 593d0d5 | 2020-07-08 19:55:01 -0700 | [diff] [blame] | 52 | } |
| 53 | // Include null terminator. |
| 54 | if (dest.size() < kHexAddrStringSize + 1) { |
Wyatt Hepler | d78f7c6 | 2020-09-28 14:27:32 -0700 | [diff] [blame] | 55 | return Status::ResourceExhausted(); |
Armando Montanez | 593d0d5 | 2020-07-08 19:55:01 -0700 | [diff] [blame] | 56 | } |
| 57 | dest[0] = '0'; |
| 58 | dest[1] = 'x'; |
| 59 | |
| 60 | return IntToHexString(addr, dest.subspan(2), sizeof(uintptr_t) * 2).status(); |
| 61 | } |
| 62 | |
| 63 | Status FormattedHexDumper::PrintFormatHeader() { |
| 64 | StringBuilder builder(dest_); |
| 65 | |
| 66 | if (flags.prefix_mode != AddressMode::kDisabled) { |
| 67 | std::string_view header(flags.prefix_mode == AddressMode::kOffset |
| 68 | ? kOffsetHeader |
| 69 | : kAddressHeader); |
| 70 | // Pad to align to address width. |
| 71 | size_t padding = 0; |
| 72 | if (flags.prefix_mode == AddressMode::kOffset) { |
| 73 | size_t offs_width = |
| 74 | HexDigitCount(source_data_.size_bytes() + current_offset_); |
| 75 | padding = std::max(offs_width, kMinOffsetChars); |
| 76 | } else { |
| 77 | padding = kHexAddrStringSize; |
| 78 | } |
| 79 | |
| 80 | padding += kAddressSeparator.length(); |
| 81 | padding -= header.size(); |
| 82 | |
| 83 | builder << header; |
| 84 | builder.append(padding, ' '); |
| 85 | } |
| 86 | |
| 87 | // Print offsets. |
| 88 | for (size_t i = 0; i < static_cast<size_t>(flags.bytes_per_line); ++i) { |
| 89 | // Early loop termination for when bytes_remaining < |
| 90 | // bytes_per_line. |
| 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); |
| 94 | if (c >> 4 == 0) { |
| 95 | builder << ' '; |
| 96 | } else { |
| 97 | builder << std::byte(c >> 4); |
| 98 | } |
| 99 | builder << std::byte(c & 0xF); |
| 100 | } else { |
| 101 | builder.append(2, ' '); |
| 102 | } |
| 103 | if (flags.group_every != 0 && (i + 1) % flags.group_every == 0) { |
| 104 | builder << ' '; |
| 105 | } |
| 106 | } |
| 107 | |
| 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) { |
| 113 | builder.pop_back(); |
| 114 | } |
| 115 | |
| 116 | if (flags.show_ascii) { |
| 117 | builder << kSectionSeparator; |
| 118 | builder << kAsciiHeader; |
| 119 | } |
| 120 | |
| 121 | return builder.status(); |
| 122 | } |
| 123 | |
| 124 | Status FormattedHexDumper::DumpLine() { |
| 125 | if (source_data_.empty()) { |
Wyatt Hepler | d78f7c6 | 2020-09-28 14:27:32 -0700 | [diff] [blame] | 126 | return Status::ResourceExhausted(); |
Armando Montanez | 593d0d5 | 2020-07-08 19:55:01 -0700 | [diff] [blame] | 127 | } |
| 128 | |
| 129 | if (!ValidateBufferSize().ok() || dest_.data() == nullptr) { |
Wyatt Hepler | d78f7c6 | 2020-09-28 14:27:32 -0700 | [diff] [blame] | 130 | return Status::FailedPrecondition(); |
Armando Montanez | 593d0d5 | 2020-07-08 19:55:01 -0700 | [diff] [blame] | 131 | } |
| 132 | |
| 133 | if (dest_[0] == 0 && flags.show_header) { |
| 134 | // First line, print out dump format header. |
| 135 | return PrintFormatHeader(); |
| 136 | } |
| 137 | |
| 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) { |
| 143 | uintptr_t val; |
| 144 | if (flags.prefix_mode == AddressMode::kAbsolute) { |
| 145 | val = reinterpret_cast<uintptr_t>(source_data_.data()); |
| 146 | builder << "0x"; |
| 147 | uint8_t significant = HexDigitCount(val); |
| 148 | builder.append(sizeof(uintptr_t) * 2 - significant, '0'); |
| 149 | } else { |
| 150 | val = current_offset_; |
| 151 | size_t significant = |
| 152 | HexDigitCount(source_data_.size_bytes() + current_offset_); |
| 153 | if (significant < kMinOffsetChars) { |
| 154 | builder.append(kMinOffsetChars - significant, '0'); |
| 155 | } |
| 156 | } |
| 157 | if (val != 0) { |
| 158 | builder << reinterpret_cast<void*>(val); |
| 159 | } else { |
| 160 | builder.append(2, '0'); |
| 161 | } |
| 162 | builder << kAddressSeparator; |
| 163 | } |
| 164 | |
| 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 < |
| 170 | // bytes_per_line. |
| 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) { |
| 177 | builder << ' '; |
| 178 | } |
| 179 | } |
| 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); |
| 184 | ++i) { |
| 185 | builder.append(2, ' '); |
| 186 | if (flags.group_every != 0 && (i + 1) % flags.group_every == 0) { |
| 187 | builder << ' '; |
| 188 | } |
| 189 | } |
| 190 | } |
| 191 | |
| 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) { |
| 197 | builder.pop_back(); |
| 198 | } |
| 199 | |
| 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]); |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | source_data_ = source_data_.subspan(bytes_in_line); |
| 209 | current_offset_ += bytes_in_line; |
| 210 | return builder.status(); |
| 211 | } |
| 212 | |
| 213 | Status FormattedHexDumper::SetLineBuffer(std::span<char> dest) { |
| 214 | if (dest.data() == nullptr || dest.size_bytes() == 0) { |
Wyatt Hepler | d78f7c6 | 2020-09-28 14:27:32 -0700 | [diff] [blame] | 215 | return Status::InvalidArgument(); |
Armando Montanez | 593d0d5 | 2020-07-08 19:55:01 -0700 | [diff] [blame] | 216 | } |
| 217 | dest_ = dest; |
Wyatt Hepler | 1b3da3a | 2021-01-07 13:26:57 -0800 | [diff] [blame] | 218 | return ValidateBufferSize().ok() ? OkStatus() : Status::ResourceExhausted(); |
Armando Montanez | 593d0d5 | 2020-07-08 19:55:01 -0700 | [diff] [blame] | 219 | } |
| 220 | |
| 221 | Status FormattedHexDumper::BeginDump(ConstByteSpan data) { |
| 222 | current_offset_ = 0; |
| 223 | source_data_ = data; |
| 224 | if (data.data() == nullptr) { |
Wyatt Hepler | d78f7c6 | 2020-09-28 14:27:32 -0700 | [diff] [blame] | 225 | return Status::InvalidArgument(); |
Armando Montanez | 593d0d5 | 2020-07-08 19:55:01 -0700 | [diff] [blame] | 226 | } |
| 227 | if (dest_.data() != nullptr && dest_.size_bytes() > 0) { |
| 228 | dest_[0] = 0; |
| 229 | } |
Wyatt Hepler | 1b3da3a | 2021-01-07 13:26:57 -0800 | [diff] [blame] | 230 | return ValidateBufferSize().ok() ? OkStatus() : Status::FailedPrecondition(); |
Armando Montanez | 593d0d5 | 2020-07-08 19:55:01 -0700 | [diff] [blame] | 231 | } |
| 232 | |
| 233 | Status FormattedHexDumper::ValidateBufferSize() { |
| 234 | // Minimum size is number of bytes per line as hex pairs plus the null |
| 235 | // terminator. |
| 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; |
| 239 | } |
| 240 | if (flags.prefix_mode == AddressMode::kAbsolute) { |
| 241 | required_size += kHexAddrStringSize; |
| 242 | required_size += kAddressSeparator.length(); |
| 243 | } else if (flags.prefix_mode == AddressMode::kOffset) { |
| 244 | required_size += |
| 245 | HexDigitCount(std::max(source_data_.size_bytes(), kMinOffsetChars)); |
| 246 | required_size += kAddressSeparator.length(); |
| 247 | } |
| 248 | if (flags.group_every != 0) { |
| 249 | required_size += (flags.bytes_per_line - 1) / flags.group_every; |
| 250 | } |
| 251 | |
| 252 | if (dest_.size_bytes() < required_size) { |
Wyatt Hepler | d78f7c6 | 2020-09-28 14:27:32 -0700 | [diff] [blame] | 253 | return Status::ResourceExhausted(); |
Armando Montanez | 593d0d5 | 2020-07-08 19:55:01 -0700 | [diff] [blame] | 254 | } |
| 255 | |
Wyatt Hepler | 1b3da3a | 2021-01-07 13:26:57 -0800 | [diff] [blame] | 256 | return OkStatus(); |
Armando Montanez | 593d0d5 | 2020-07-08 19:55:01 -0700 | [diff] [blame] | 257 | } |
| 258 | |
| 259 | } // namespace pw::dump |