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
PrintableChar(std::byte b)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
DumpAddr(std::span<char> dest,uintptr_t addr)49 Status DumpAddr(std::span<char> dest, uintptr_t addr) {
50 if (dest.data() == nullptr) {
51 return Status::InvalidArgument();
52 }
53 // Include null terminator.
54 if (dest.size() < kHexAddrStringSize + 1) {
55 return Status::ResourceExhausted();
56 }
57 dest[0] = '0';
58 dest[1] = 'x';
59
60 return IntToHexString(addr, dest.subspan(2), sizeof(uintptr_t) * 2).status();
61 }
62
PrintFormatHeader()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
DumpLine()124 Status FormattedHexDumper::DumpLine() {
125 if (source_data_.empty()) {
126 return Status::ResourceExhausted();
127 }
128
129 if (!ValidateBufferSize().ok() || dest_.data() == nullptr) {
130 return Status::FailedPrecondition();
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
SetLineBuffer(std::span<char> dest)213 Status FormattedHexDumper::SetLineBuffer(std::span<char> dest) {
214 if (dest.data() == nullptr || dest.size_bytes() == 0) {
215 return Status::InvalidArgument();
216 }
217 dest_ = dest;
218 return ValidateBufferSize().ok() ? OkStatus() : Status::ResourceExhausted();
219 }
220
BeginDump(ConstByteSpan data)221 Status FormattedHexDumper::BeginDump(ConstByteSpan data) {
222 current_offset_ = 0;
223 source_data_ = data;
224 if (data.data() == nullptr) {
225 return Status::InvalidArgument();
226 }
227 if (dest_.data() != nullptr && dest_.size_bytes() > 0) {
228 dest_[0] = 0;
229 }
230 return ValidateBufferSize().ok() ? OkStatus() : Status::FailedPrecondition();
231 }
232
ValidateBufferSize()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) {
253 return Status::ResourceExhausted();
254 }
255
256 return OkStatus();
257 }
258
259 } // namespace pw::dump
260