• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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