1use std::marker::PhantomData;
2use std::sync::Arc;
3
4use debugid::DebugId;
5use macho_unwind_info::UnwindInfo;
6use object::macho::{self, LinkeditDataCommand, MachHeader32, MachHeader64};
7use object::read::macho::{
8 FatArch, LoadCommandIterator, MachHeader, MachOFatFile32, MachOFatFile64,
9};
10use object::read::{File, Object, ObjectSection};
11use object::{Endianness, FileKind, ReadRef};
12use uuid::Uuid;
13use yoke::Yoke;
14use yoke_derive::Yokeable;
15
16use crate::binary_image::{BinaryImage, BinaryImageInner};
17use crate::debugid_util::debug_id_for_object;
18use crate::dwarf::Addr2lineContextData;
19use crate::error::Error;
20use crate::shared::{
21 FileAndPathHelper, FileContents, FileContentsWrapper, FileLocation, MultiArchDisambiguator,
22 RangeReadRef,
23};
24use crate::symbol_map::SymbolMap;
25use crate::symbol_map_object::{
26 ObjectSymbolMap, ObjectSymbolMapInnerWrapper, ObjectSymbolMapOuter,
27};
28
29fn macho_arch_name_for_cpu_type(cputype: u32, cpusubtype: u32) -> Option<&'static str> {
33 use object::macho::*;
34 let s = match (cputype, cpusubtype) {
35 (CPU_TYPE_X86, _) => "i386",
36 (CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_H) => "x86_64h",
37 (CPU_TYPE_X86_64, _) => "x86_64",
38 (CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64E) => "arm64e",
39 (CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_V8) => "arm64v8",
40 (CPU_TYPE_ARM64, _) => "arm64",
41 (CPU_TYPE_ARM64_32, CPU_SUBTYPE_ARM64_32_V8) => "arm64_32v8",
42 (CPU_TYPE_ARM64_32, _) => "arm64_32",
43 (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V5TEJ) => "armv5",
44 (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6) => "armv6",
45 (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V6M) => "armv6m",
46 (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7) => "armv7",
47 (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7F) => "armv7f",
48 (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S) => "armv7s",
49 (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7K) => "armv7k",
50 (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7M) => "armv7m",
51 (CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7EM) => "armv7em",
52 (CPU_TYPE_ARM, _) => "arm",
53 (CPU_TYPE_POWERPC, CPU_SUBTYPE_POWERPC_ALL) => "ppc",
54 (CPU_TYPE_POWERPC64, CPU_SUBTYPE_POWERPC_ALL) => "ppc64",
55 _ => return None,
56 };
57 Some(s)
58}
59
60pub fn get_fat_archive_member(
65 file_contents: &FileContentsWrapper<impl FileContents>,
66 archive_kind: FileKind,
67 disambiguator: Option<MultiArchDisambiguator>,
68) -> Result<FatArchiveMember, Error> {
69 let mut members = get_fat_archive_members(file_contents, archive_kind)?;
70
71 if members.is_empty() {
72 return Err(Error::EmptyFatArchive);
73 }
74
75 if members.len() == 1 && disambiguator.is_none() {
76 return Ok(members.remove(0));
77 }
78
79 let disambiguator = match disambiguator {
80 Some(disambiguator) => disambiguator,
81 None => return Err(Error::NoDisambiguatorForFatArchive(members)),
82 };
83
84 match members
85 .iter()
86 .enumerate()
87 .filter_map(|(i, m)| {
88 m.match_score_for_disambiguator(&disambiguator)
89 .map(|score| (i, score))
90 })
91 .min_by_key(|(_i, score)| *score)
92 {
93 Some((i, _score)) => Ok(members.remove(i)),
94 None => Err(Error::NoMatchMultiArch(members)),
95 }
96}
97
98pub fn get_fat_archive_members_impl<FC: FileContents, FA: FatArch>(
99 file_contents: &FileContentsWrapper<FC>,
100 arches: &[FA],
101) -> Result<Vec<FatArchiveMember>, Error> {
102 let mut members = Vec::new();
103
104 for fat_arch in arches {
105 let (cputype, cpusubtype) = (fat_arch.cputype(), fat_arch.cpusubtype());
106 let arch = macho_arch_name_for_cpu_type(cputype, cpusubtype).map(ToString::to_string);
107 let (start, size) = fat_arch.file_range();
108 let file =
109 File::parse(file_contents.range(start, size)).map_err(Error::MachOHeaderParseError)?;
110 let uuid = file.mach_uuid().ok().flatten().map(Uuid::from_bytes);
111 members.push(FatArchiveMember {
112 offset_and_size: (start, size),
113 cputype,
114 cpusubtype,
115 arch,
116 uuid,
117 });
118 }
119
120 Ok(members)
121}
122
123#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
124pub struct FatArchiveMember {
125 pub offset_and_size: (u64, u64),
126 pub cputype: u32,
127 pub cpusubtype: u32,
128 pub arch: Option<String>,
129 pub uuid: Option<Uuid>,
130}
131
132impl FatArchiveMember {
133 pub fn match_score_for_disambiguator(
136 &self,
137 disambiguator: &MultiArchDisambiguator,
138 ) -> Option<usize> {
139 match disambiguator {
140 MultiArchDisambiguator::Arch(expected_arch) => {
141 if self.arch.as_deref() == Some(expected_arch) {
142 Some(0)
143 } else {
144 None
145 }
146 }
147 MultiArchDisambiguator::BestMatch(expected_archs) => {
148 if let Some(arch) = self.arch.as_deref() {
149 expected_archs.iter().position(|ea| ea == arch)
150 } else {
151 None
152 }
153 }
154 MultiArchDisambiguator::BestMatchForNative => {
155 if let Some(arch) = self.arch.as_deref() {
156 #[cfg(target_arch = "x86_64")]
157 match arch {
158 "x86_64h" => Some(0),
159 "x86_64" => Some(1),
160 _ => None,
161 }
162 #[cfg(target_arch = "aarch64")]
163 match arch {
164 "arm64e" => Some(0),
165 "arm64" => Some(1),
166 _ => None,
167 }
168 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
169 {
170 let _ = arch;
171 None
172 }
173 } else {
174 None
175 }
176 }
177 MultiArchDisambiguator::DebugId(expected_debug_id) => {
178 if self.uuid.map(DebugId::from_uuid) == Some(*expected_debug_id) {
179 Some(0)
180 } else {
181 None
182 }
183 }
184 }
185 }
186}
187
188pub fn get_fat_archive_members(
189 file_contents: &FileContentsWrapper<impl FileContents>,
190 archive_kind: FileKind,
191) -> Result<Vec<FatArchiveMember>, Error> {
192 if archive_kind == FileKind::MachOFat64 {
193 let fat_file = MachOFatFile64::parse(file_contents)
194 .map_err(|e| Error::ObjectParseError(archive_kind, e))?;
195 get_fat_archive_members_impl(file_contents, fat_file.arches())
196 } else {
197 let fat_file = MachOFatFile32::parse(file_contents)
198 .map_err(|e| Error::ObjectParseError(archive_kind, e))?;
199 get_fat_archive_members_impl(file_contents, fat_file.arches())
200 }
201}
202
203struct DyldCacheLoader<'a, H>
204where
205 H: FileAndPathHelper,
206{
207 helper: &'a H,
208 dyld_cache_path: &'a H::FL,
209}
210
211impl<'a, H, F> DyldCacheLoader<'a, H>
212where
213 H: FileAndPathHelper<F = F>,
214{
215 pub fn new(helper: &'a H, dyld_cache_path: &'a H::FL) -> Self {
216 Self {
217 helper,
218 dyld_cache_path,
219 }
220 }
221
222 pub async fn load_cache(&self) -> Result<F, Error> {
223 self.helper
224 .load_file(self.dyld_cache_path.clone())
225 .await
226 .map_err(|e| Error::HelperErrorDuringOpenFile(self.dyld_cache_path.to_string(), e))
227 }
228
229 pub async fn load_subcache(&self, suffix: &str) -> Result<F, Error> {
230 let subcache_location = self
231 .dyld_cache_path
232 .location_for_dyld_subcache(suffix)
233 .ok_or(Error::FileLocationRefusedSubcacheLocation)?;
234 self.helper
235 .load_file(subcache_location)
236 .await
237 .map_err(|e| Error::HelperErrorDuringOpenFile(self.dyld_cache_path.to_string(), e))
238 }
239}
240
241async fn load_file_data_for_dyld_cache<H, F>(
242 dyld_cache_path: H::FL,
243 dylib_path: String,
244 helper: &H,
245) -> Result<DyldCacheFileData<F>, Error>
246where
247 H: FileAndPathHelper<F = F>,
248 F: FileContents + 'static,
249{
250 let dcl = DyldCacheLoader::new(helper, &dyld_cache_path);
251 let root_contents = dcl.load_cache().await?;
252 let root_contents = FileContentsWrapper::new(root_contents);
253
254 let mut subcache_contents = Vec::new();
255 for subcache_index in 1.. {
256 let suffix = format!(".{subcache_index}");
258 let suffix2 = format!(".{subcache_index:02}");
259 let subcache = match dcl.load_subcache(&suffix).await {
260 Ok(subcache) => subcache,
261 Err(_) => match dcl.load_subcache(&suffix2).await {
262 Ok(subcache) => subcache,
263 Err(_) => break,
264 },
265 };
266 subcache_contents.push(FileContentsWrapper::new(subcache));
267 }
268 if let Ok(subcache) = dcl.load_subcache(".symbols").await {
269 subcache_contents.push(FileContentsWrapper::new(subcache));
270 };
271
272 Ok(DyldCacheFileData::new(
273 root_contents,
274 subcache_contents,
275 dylib_path,
276 ))
277}
278
279pub async fn load_symbol_map_for_dyld_cache<H>(
280 dyld_cache_path: H::FL,
281 dylib_path: String,
282 helper: &H,
283) -> Result<SymbolMap<H>, Error>
284where
285 H: FileAndPathHelper,
286{
287 let owner = load_file_data_for_dyld_cache(dyld_cache_path.clone(), dylib_path, helper).await?;
288 let owner = FileDataAndObject::new(Box::new(owner))?;
289 let symbol_map = ObjectSymbolMap::new(owner)?;
290 Ok(SymbolMap::new_plain(dyld_cache_path, Box::new(symbol_map)))
291}
292
293pub struct DyldCacheFileData<T>
294where
295 T: FileContents + 'static,
296{
297 root_file_data: FileContentsWrapper<T>,
298 subcache_file_data: Vec<FileContentsWrapper<T>>,
299 dylib_path: String,
300}
301
302type FileContentsRange<'data, T> = RangeReadRef<'data, &'data FileContentsWrapper<T>>;
303
304#[derive(Yokeable)]
305pub struct ObjectAndMachOData<'data, T: FileContents + 'static> {
306 object: File<'data, FileContentsRange<'data, T>>,
307 macho_data: MachOData<'data, FileContentsRange<'data, T>>,
308 addr2line_context: Addr2lineContextData,
309}
310
311impl<'data, T: FileContents + 'static> ObjectAndMachOData<'data, T> {
312 pub fn new(
313 object: File<'data, FileContentsRange<'data, T>>,
314 macho_data: MachOData<'data, FileContentsRange<'data, T>>,
315 ) -> Self {
316 Self {
317 object,
318 macho_data,
319 addr2line_context: Addr2lineContextData::new(),
320 }
321 }
322
323 pub fn into_parts(
324 self,
325 ) -> (
326 File<'data, FileContentsRange<'data, T>>,
327 MachOData<'data, FileContentsRange<'data, T>>,
328 ) {
329 (self.object, self.macho_data)
330 }
331}
332
333trait MakeMachObject<T: FileContents + 'static> {
334 fn file_data(&self) -> RangeReadRef<'_, &'_ FileContentsWrapper<T>>;
335 fn make_dependent_object(&self) -> Result<ObjectAndMachOData<'_, T>, Error>;
336}
337
338impl<T: FileContents + 'static> DyldCacheFileData<T> {
339 pub fn new(
340 root_file_data: FileContentsWrapper<T>,
341 subcache_file_data: Vec<FileContentsWrapper<T>>,
342 dylib_path: String,
343 ) -> Self {
344 Self {
345 root_file_data,
346 subcache_file_data,
347 dylib_path,
348 }
349 }
350
351 pub fn make_object(&self) -> Result<ObjectAndMachOData<'_, T>, Error> {
352 let rootcache_range = self.root_file_data.full_range();
353 let subcache_ranges: Vec<_> = self
354 .subcache_file_data
355 .iter()
356 .map(FileContentsWrapper::full_range)
357 .collect();
358 let cache = object::read::macho::DyldCache::<Endianness, _>::parse(
359 rootcache_range,
360 &subcache_ranges,
361 )
362 .map_err(Error::DyldCacheParseError)?;
363
364 let image = match cache
365 .images()
366 .find(|image| image.path() == Ok(&self.dylib_path))
367 {
368 Some(image) => image,
369 None => return Err(Error::NoMatchingDyldCacheImagePath(self.dylib_path.clone())),
370 };
371 let object = image.parse_object().map_err(Error::MachOHeaderParseError)?;
372 let (data, header_offset) = image
373 .image_data_and_offset()
374 .map_err(Error::MachOHeaderParseError)?;
375 let macho_data = MachOData::new(data, header_offset, object.is_64());
376 Ok(ObjectAndMachOData::new(object, macho_data))
377 }
378}
379
380impl<T: FileContents + 'static> MakeMachObject<T> for DyldCacheFileData<T> {
381 fn file_data(&self) -> RangeReadRef<'_, &'_ FileContentsWrapper<T>> {
382 self.root_file_data.full_range()
383 }
384 fn make_dependent_object(&self) -> Result<ObjectAndMachOData<'_, T>, Error> {
385 self.make_object()
386 }
387}
388
389struct FileDataAndObject<T: FileContents + 'static>(
390 Yoke<ObjectAndMachOData<'static, T>, Box<dyn MakeMachObject<T> + Send + Sync>>,
391);
392
393impl<T: FileContents + 'static> FileDataAndObject<T> {
394 pub fn new(data: Box<dyn MakeMachObject<T> + Send + Sync>) -> Result<Self, Error> {
395 let owner_and_object = Yoke::try_attach_to_cart(data, |data| data.make_dependent_object())?;
396 Ok(Self(owner_and_object))
397 }
398}
399
400impl<T: FileContents + 'static> ObjectSymbolMapOuter<T> for FileDataAndObject<T> {
401 fn make_symbol_map_inner(&self) -> Result<ObjectSymbolMapInnerWrapper<'_, T>, Error> {
402 let ObjectAndMachOData {
403 object,
404 macho_data,
405 addr2line_context,
406 } = self.0.get();
407 let (function_starts, function_ends) = compute_function_addresses_macho(macho_data, object);
408 let debug_id = debug_id_for_object(object)
409 .ok_or(Error::InvalidInputError("debug ID cannot be read"))?;
410 let symbol_map = ObjectSymbolMapInnerWrapper::new(
411 object,
412 addr2line_context
413 .make_context(macho_data.data, object, None, None)
414 .ok(),
415 None,
416 debug_id,
417 function_starts.as_deref(),
418 function_ends.as_deref(),
419 &(),
420 );
421
422 Ok(symbol_map)
423 }
424}
425
426pub fn get_symbol_map_for_macho<H: FileAndPathHelper>(
427 debug_file_location: H::FL,
428 file_contents: FileContentsWrapper<H::F>,
429 helper: Arc<H>,
430) -> Result<SymbolMap<H>, Error> {
431 let owner = FileDataAndObject::new(Box::new(MachSymbolMapData(file_contents)))?;
432 let symbol_map = ObjectSymbolMap::new(owner)?;
433 Ok(SymbolMap::new_with_external_file_support(
434 debug_file_location,
435 Box::new(symbol_map),
436 helper,
437 ))
438}
439
440pub fn get_symbol_map_for_fat_archive_member<H: FileAndPathHelper>(
441 debug_file_location: H::FL,
442 file_contents: FileContentsWrapper<H::F>,
443 member: FatArchiveMember,
444 helper: Arc<H>,
445) -> Result<SymbolMap<H>, Error> {
446 let (start_offset, range_size) = member.offset_and_size;
447 let owner =
448 MachOFatArchiveMemberData::new(file_contents, start_offset, range_size, member.arch);
449 let owner = FileDataAndObject::new(Box::new(owner))?;
450 let symbol_map = ObjectSymbolMap::new(owner)?;
451 Ok(SymbolMap::new_with_external_file_support(
452 debug_file_location,
453 Box::new(symbol_map),
454 helper,
455 ))
456}
457
458struct MachSymbolMapData<T: FileContents>(FileContentsWrapper<T>);
459
460impl<T: FileContents + 'static> MakeMachObject<T> for MachSymbolMapData<T> {
461 fn file_data(&self) -> RangeReadRef<'_, &'_ FileContentsWrapper<T>> {
462 self.0.full_range()
463 }
464
465 fn make_dependent_object(&self) -> Result<ObjectAndMachOData<'_, T>, Error> {
466 let file_data = self.file_data();
467 let object = File::parse(file_data).map_err(Error::MachOHeaderParseError)?;
468 let macho_data = MachOData::new(file_data, 0, object.is_64());
469 Ok(ObjectAndMachOData::new(object, macho_data))
470 }
471}
472
473pub struct MachOFatArchiveMemberData<T: FileContents> {
474 file_data: FileContentsWrapper<T>,
475 start_offset: u64,
476 range_size: u64,
477 arch: Option<String>,
478}
479
480impl<T: FileContents> MachOFatArchiveMemberData<T> {
481 pub fn new(
482 file_data: FileContentsWrapper<T>,
483 start_offset: u64,
484 range_size: u64,
485 arch: Option<String>,
486 ) -> Self {
487 Self {
488 file_data,
489 start_offset,
490 range_size,
491 arch,
492 }
493 }
494
495 pub fn data(&self) -> RangeReadRef<&'_ FileContentsWrapper<T>> {
496 let file_contents_ref = &self.file_data;
497 file_contents_ref.range(self.start_offset, self.range_size)
498 }
499
500 pub fn arch(&self) -> Option<String> {
501 self.arch.clone()
502 }
503}
504
505impl<T: FileContents + 'static> MakeMachObject<T> for MachOFatArchiveMemberData<T> {
506 fn file_data(&self) -> RangeReadRef<'_, &'_ FileContentsWrapper<T>> {
507 self.data()
508 }
509
510 fn make_dependent_object(&self) -> Result<ObjectAndMachOData<'_, T>, Error> {
511 let object = File::parse(self.file_data()).map_err(Error::MachOHeaderParseError)?;
512 let macho_data = MachOData::new(self.file_data(), 0, object.is_64());
513 Ok(ObjectAndMachOData::new(object, macho_data))
514 }
515}
516
517pub async fn load_binary_from_dyld_cache<F, H>(
518 dyld_cache_path: H::FL,
519 dylib_path: String,
520 helper: &H,
521) -> Result<BinaryImage<F>, Error>
522where
523 F: FileContents + 'static,
524 H: FileAndPathHelper<F = F>,
525{
526 let file_data =
527 load_file_data_for_dyld_cache(dyld_cache_path, dylib_path.clone(), helper).await?;
528 let inner = BinaryImageInner::MemberOfDyldSharedCache(file_data);
529 let name = match dylib_path.rfind('/') {
530 Some(index) => dylib_path[index + 1..].to_owned(),
531 None => dylib_path.to_owned(),
532 };
533 let image = BinaryImage::new(inner, Some(name), Some(dylib_path))?;
534 Ok(image)
535}
536
537fn compute_function_addresses_macho<'data, O, R>(
538 macho_data: &MachOData<'data, R>,
539 object_file: &O,
540) -> (Option<Vec<u32>>, Option<Vec<u32>>)
541where
542 O: object::Object<'data>,
543 R: ReadRef<'data>,
544{
545 let mut function_starts = macho_data.get_function_starts().ok().flatten();
547
548 if let Some(unwind_info) = object_file
550 .section_by_name_bytes(b"__unwind_info")
551 .and_then(|s| s.data().ok())
552 .and_then(|d| UnwindInfo::parse(d).ok())
553 {
554 let function_starts = function_starts.get_or_insert_with(Vec::new);
555 let mut iter = unwind_info.functions();
556 while let Ok(Some(function)) = iter.next() {
557 function_starts.push(function.start_address);
558 }
559 }
560
561 (function_starts, None)
562}
563
564#[derive(Clone, Copy)]
565pub struct MachOData<'data, R: ReadRef<'data>> {
566 data: R,
567 header_offset: u64,
568 is_64: bool,
569 _phantom: PhantomData<&'data ()>,
570}
571
572impl<'data, R: ReadRef<'data>> MachOData<'data, R> {
573 pub fn new(data: R, header_offset: u64, is_64: bool) -> Self {
574 Self {
575 data,
576 header_offset,
577 is_64,
578 _phantom: PhantomData,
579 }
580 }
581
582 pub fn get_function_starts(&self) -> Result<Option<Vec<u32>>, Error> {
592 let data = self
593 .function_start_data()
594 .map_err(Error::MachOHeaderParseError)?;
595 let data = if let Some(data) = data {
596 data
597 } else {
598 return Ok(None);
599 };
600 let mut function_starts = Vec::new();
601 let mut prev_address = 0;
602 let mut bytes = data;
603 while let Some((delta, rest)) = read_uleb128(bytes) {
604 if delta == 0 {
605 break;
606 }
607 bytes = rest;
608 let address = prev_address + delta;
609 function_starts.push(address as u32);
610 prev_address = address;
611 }
612
613 Ok(Some(function_starts))
614 }
615
616 pub fn get_arch(&self) -> Option<&'static str> {
617 if self.is_64 {
618 self.get_arch_impl::<MachHeader64<Endianness>>()
619 } else {
620 self.get_arch_impl::<MachHeader32<Endianness>>()
621 }
622 }
623
624 fn get_arch_impl<M: MachHeader>(&self) -> Option<&'static str> {
625 let header = M::parse(self.data, self.header_offset).ok()?;
626 let endian = header.endian().ok()?;
627 macho_arch_name_for_cpu_type(header.cputype(endian), header.cpusubtype(endian))
628 }
629
630 fn load_command_iter<M: MachHeader>(
631 &self,
632 ) -> object::read::Result<(M::Endian, LoadCommandIterator<M::Endian>)> {
633 let header = M::parse(self.data, self.header_offset)?;
634 let endian = header.endian()?;
635 let load_commands = header.load_commands(endian, self.data, self.header_offset)?;
636 Ok((endian, load_commands))
637 }
638
639 fn function_start_data(&self) -> object::read::Result<Option<&'data [u8]>> {
640 let (endian, mut commands) = if self.is_64 {
641 self.load_command_iter::<MachHeader64<Endianness>>()?
642 } else {
643 self.load_command_iter::<MachHeader32<Endianness>>()?
644 };
645 while let Ok(Some(command)) = commands.next() {
646 if command.cmd() == macho::LC_FUNCTION_STARTS {
647 let command: &LinkeditDataCommand<_> = command.data()?;
648 let dataoff: u64 = command.dataoff.get(endian).into();
649 let datasize: u64 = command.datasize.get(endian).into();
650 let data = self.data.read_bytes_at(dataoff, datasize).ok();
651 return Ok(data);
652 }
653 }
654 Ok(None)
655 }
656}
657
658fn read_uleb128(mut bytes: &[u8]) -> Option<(u64, &[u8])> {
659 const CONTINUATION_BIT: u8 = 1 << 7;
660
661 let mut result = 0;
662 let mut shift = 0;
663
664 while !bytes.is_empty() {
665 let byte = bytes[0];
666 bytes = &bytes[1..];
667 if shift == 63 && byte != 0x00 && byte != 0x01 {
668 return None;
669 }
670
671 let low_bits = u64::from(byte & !CONTINUATION_BIT);
672 result |= low_bits << shift;
673
674 if byte & CONTINUATION_BIT == 0 {
675 return Some((result, bytes));
676 }
677
678 shift += 7;
679 }
680 None
681}