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
AddGroupingByte(size_t byte_index,FormattedHexDumper::Flags & flags,StringBuilder & builder)47 void AddGroupingByte(size_t byte_index,
48 FormattedHexDumper::Flags& flags,
49 StringBuilder& builder) {
50 // Never add grouping when it is disabled.
51 if (flags.group_every == 0) {
52 return;
53 }
54 // If this byte isn't at the end of a group, don't add a space.
55 if ((byte_index + 1) % flags.group_every != 0) {
56 return;
57 }
58 // If this byte is the last byte in a line, don't add a grouping byte
59 // (prevents trailing spaces).
60 if (byte_index + 1 == flags.bytes_per_line) {
61 return;
62 }
63
64 builder << ' ';
65 }
66
67 } // namespace
68
DumpAddr(span<char> dest,uintptr_t addr)69 Status DumpAddr(span<char> dest, uintptr_t addr) {
70 if (dest.data() == nullptr) {
71 return Status::InvalidArgument();
72 }
73 // Include null terminator.
74 if (dest.size() < kHexAddrStringSize + 1) {
75 return Status::ResourceExhausted();
76 }
77 dest[0] = '0';
78 dest[1] = 'x';
79
80 return IntToHexString(addr, dest.subspan(2), sizeof(uintptr_t) * 2).status();
81 }
82
PrintFormatHeader()83 Status FormattedHexDumper::PrintFormatHeader() {
84 StringBuilder builder(dest_);
85
86 if (flags.prefix_mode != AddressMode::kDisabled) {
87 std::string_view header(flags.prefix_mode == AddressMode::kOffset
88 ? kOffsetHeader
89 : kAddressHeader);
90 // Pad to align to address width.
91 size_t padding = 0;
92 if (flags.prefix_mode == AddressMode::kOffset) {
93 size_t offs_width =
94 HexDigitCount(source_data_.size_bytes() + current_offset_);
95 padding = std::max(offs_width, kMinOffsetChars);
96 } else {
97 padding = kHexAddrStringSize;
98 }
99
100 padding += kAddressSeparator.length();
101 padding -= header.size();
102
103 builder << header;
104 builder.append(padding, ' ');
105 }
106
107 // Print offsets.
108 for (size_t i = 0; i < static_cast<size_t>(flags.bytes_per_line); ++i) {
109 // Early loop termination for when bytes_remaining <
110 // bytes_per_line.
111 if (flags.group_every != 0 &&
112 i % static_cast<uint8_t>(flags.group_every) == 0) {
113 builder << std::byte(i);
114 } else {
115 builder.append(2, ' ');
116 }
117 AddGroupingByte(i, flags, builder);
118 }
119
120 if (flags.show_ascii) {
121 builder << kSectionSeparator;
122 builder << kAsciiHeader;
123 }
124
125 return builder.status();
126 }
127
DumpLine()128 Status FormattedHexDumper::DumpLine() {
129 if (source_data_.empty()) {
130 return Status::ResourceExhausted();
131 }
132
133 if (!ValidateBufferSize().ok() || dest_.data() == nullptr) {
134 return Status::FailedPrecondition();
135 }
136
137 if (dest_[0] == 0 && flags.show_header) {
138 // First line, print out dump format header.
139 return PrintFormatHeader();
140 }
141
142 StringBuilder builder(dest_);
143 // Dump address/offset prefix.
144 // TODO(amontanez): This block can be much nicer if StringBuilder exposed an
145 // easy way to control zero padding for hex address.
146 if (flags.prefix_mode != AddressMode::kDisabled) {
147 uintptr_t val;
148 size_t significant;
149 if (flags.prefix_mode == AddressMode::kAbsolute) {
150 val = reinterpret_cast<uintptr_t>(source_data_.data());
151 builder << "0x";
152 significant = HexDigitCount(val);
153 builder.append(sizeof(uintptr_t) * 2 - significant, '0');
154 } else {
155 val = current_offset_;
156 significant = HexDigitCount(val);
157 if (significant < kMinOffsetChars) {
158 builder.append(kMinOffsetChars - significant, '0');
159 }
160 }
161 if (val != 0) {
162 builder << reinterpret_cast<void*>(val);
163 } else {
164 builder.append(significant, '0');
165 }
166 builder << kAddressSeparator;
167 }
168
169 size_t bytes_in_line = std::min(source_data_.size_bytes(),
170 static_cast<size_t>(flags.bytes_per_line));
171 // Convert raw bytes to hex characters.
172 for (size_t i = 0; i < bytes_in_line; ++i) {
173 // Early loop termination for when bytes_remaining <
174 // bytes_per_line.
175 builder << source_data_[i];
176 AddGroupingByte(i, flags, builder);
177 }
178 // Add padding spaces to ensure lines are aligned.
179 if (flags.show_ascii) {
180 for (size_t i = bytes_in_line;
181 i < static_cast<size_t>(flags.bytes_per_line);
182 ++i) {
183 builder.append(2, ' ');
184 AddGroupingByte(i, flags, builder);
185 }
186 }
187
188 // Interpret bytes as characters.
189 if (flags.show_ascii) {
190 builder << kSectionSeparator;
191 for (size_t i = 0; i < bytes_in_line; ++i) {
192 builder << PrintableChar(source_data_[i]);
193 }
194 }
195
196 source_data_ = source_data_.subspan(bytes_in_line);
197 current_offset_ += bytes_in_line;
198 return builder.status();
199 }
200
SetLineBuffer(span<char> dest)201 Status FormattedHexDumper::SetLineBuffer(span<char> dest) {
202 if (dest.data() == nullptr || dest.size_bytes() == 0) {
203 return Status::InvalidArgument();
204 }
205 dest_ = dest;
206 return ValidateBufferSize().ok() ? OkStatus() : Status::ResourceExhausted();
207 }
208
BeginDump(ConstByteSpan data)209 Status FormattedHexDumper::BeginDump(ConstByteSpan data) {
210 current_offset_ = 0;
211 source_data_ = data;
212 if (data.data() == nullptr) {
213 return Status::InvalidArgument();
214 }
215 if (dest_.data() != nullptr && dest_.size_bytes() > 0) {
216 dest_[0] = 0;
217 }
218 return ValidateBufferSize().ok() ? OkStatus() : Status::FailedPrecondition();
219 }
220
ValidateBufferSize()221 Status FormattedHexDumper::ValidateBufferSize() {
222 // Minimum size is number of bytes per line as hex pairs plus the null
223 // terminator.
224 size_t required_size = flags.bytes_per_line * 2 + 1;
225 if (flags.show_ascii) {
226 required_size += kSectionSeparator.length() + flags.bytes_per_line;
227 }
228 if (flags.prefix_mode == AddressMode::kAbsolute) {
229 required_size += kHexAddrStringSize;
230 required_size += kAddressSeparator.length();
231 } else if (flags.prefix_mode == AddressMode::kOffset) {
232 required_size +=
233 HexDigitCount(std::max(source_data_.size_bytes(), kMinOffsetChars));
234 required_size += kAddressSeparator.length();
235 }
236 if (flags.group_every != 0) {
237 required_size += (flags.bytes_per_line - 1) / flags.group_every;
238 }
239
240 if (dest_.size_bytes() < required_size) {
241 return Status::ResourceExhausted();
242 }
243
244 return OkStatus();
245 }
246
247 } // namespace pw::dump
248