1use std::collections::{HashMap, HashSet};
2
3#[cfg(feature = "logging")]
4use log::info;
5use object::{build, elf};
6
7use super::{Error, Result, Rewriter};
8
9#[derive(Debug, Default)]
16#[non_exhaustive]
17pub struct ElfOptions {
18 pub add_dynamic_debug: bool,
22 pub delete_runpath: bool,
26 pub set_runpath: Option<Vec<u8>>,
30 pub add_runpath: Vec<Vec<u8>>,
34 pub use_runpath: bool,
38 pub use_rpath: bool,
42 pub delete_needed: HashSet<Vec<u8>>,
46 pub replace_needed: HashMap<Vec<u8>, Vec<u8>>,
50 pub add_needed: Vec<Vec<u8>>,
54 pub set_soname: Option<Vec<u8>>,
58 pub set_interpreter: Option<Vec<u8>>,
62}
63
64impl<'data> Rewriter<'data> {
65 pub fn elf_delete_symbols(&mut self, names: &HashSet<Vec<u8>>) {
67 for symbol in &mut self.builder.dynamic_symbols {
68 if names.contains(&*symbol.name) {
69 #[cfg(feature = "logging")]
70 info!("Deleting symbol {}", symbol.name);
71 symbol.delete = true;
72 self.modified = true;
73 }
74 }
75 }
76
77 pub fn elf_delete_dynamic_symbols(&mut self, names: &HashSet<Vec<u8>>) {
79 for symbol in &mut self.builder.symbols {
80 if names.contains(&*symbol.name) {
81 #[cfg(feature = "logging")]
82 info!("Deleting dynamic symbol {}", symbol.name);
83 symbol.delete = true;
84 self.modified = true;
85 }
86 }
87 }
88
89 pub fn elf_rename_symbols(&mut self, names: &HashMap<Vec<u8>, Vec<u8>>) {
93 for symbol in &mut self.builder.dynamic_symbols {
94 if let Some(name) = names.get(&*symbol.name) {
95 let name = name.clone().into();
96 #[cfg(feature = "logging")]
97 info!("Renaming symbol {} to {}", symbol.name, name);
98 symbol.name = name;
99 self.modified = true;
100 }
101 }
102 }
103
104 pub fn elf_rename_dynamic_symbols(&mut self, names: &HashMap<Vec<u8>, Vec<u8>>) {
108 for symbol in &mut self.builder.dynamic_symbols {
109 if let Some(name) = names.get(&*symbol.name) {
110 let name = name.clone().into();
111 #[cfg(feature = "logging")]
112 info!("Renaming dynamic symbol {} to {}", symbol.name, name);
113 symbol.name = name;
114 self.modified = true;
115 }
116 }
117 }
118
119 pub(crate) fn elf_delete_sections(&mut self, names: &HashSet<Vec<u8>>) {
120 for section in &mut self.builder.sections {
121 if names.contains(&*section.name) {
122 #[cfg(feature = "logging")]
123 info!("Deleting section {}", section.name);
124 section.delete = true;
126 self.modified = true;
127 }
128 }
129 }
130
131 pub(crate) fn elf_rename_sections(&mut self, names: &HashMap<Vec<u8>, Vec<u8>>) {
132 for section in &mut self.builder.sections {
133 if let Some(name) = names.get(&*section.name) {
134 let name = name.clone().into();
135 #[cfg(feature = "logging")]
136 info!("Renaming section {} to {}", section.name, name);
137 section.name = name;
138 self.modified = true;
139 }
140 }
141 }
142
143 pub(crate) fn elf_modify(&mut self, options: ElfOptions) -> Result<()> {
144 if options.add_dynamic_debug {
145 self.elf_add_dynamic_debug()?;
146 }
147 if options.delete_runpath {
148 self.elf_delete_runpath()?;
149 }
150 if let Some(path) = options.set_runpath {
151 self.elf_set_runpath(path)?;
152 }
153 if !options.add_runpath.is_empty() {
154 self.elf_add_runpath(&options.add_runpath)?;
155 }
156 if options.use_runpath {
157 self.elf_use_runpath()?;
158 }
159 if options.use_rpath {
160 self.elf_use_rpath()?;
161 }
162 if !options.delete_needed.is_empty() {
163 self.elf_delete_needed(&options.delete_needed)?;
164 }
165 if !options.replace_needed.is_empty() {
166 self.elf_replace_needed(&options.replace_needed)?;
167 }
168 if !options.add_needed.is_empty() {
169 self.elf_add_needed(&options.add_needed)?;
170 }
171 if let Some(name) = options.set_soname {
172 self.elf_set_soname(name)?;
173 }
174 if let Some(interpreter) = options.set_interpreter {
175 self.elf_set_interpreter(interpreter)?;
176 }
177 Ok(())
178 }
179
180 pub fn elf_add_dynamic_debug(&mut self) -> Result<()> {
182 let dynamic = self
183 .builder
184 .dynamic_data_mut()
185 .ok_or_else(|| Error::modify("No dynamic section found; can't add debug entry"))?;
186 if dynamic.iter().any(|entry| entry.tag() == elf::DT_DEBUG) {
187 return Ok(());
188 }
189
190 #[cfg(feature = "logging")]
191 info!("Adding DT_DEBUG entry");
192 dynamic.push(build::elf::Dynamic::Integer {
193 tag: elf::DT_DEBUG,
194 val: 0,
195 });
196 self.modified = true;
197 Ok(())
198 }
199
200 pub fn elf_runpath(&self) -> Option<&[u8]> {
202 let dynamic = self.builder.dynamic_data()?;
203 for entry in dynamic.iter() {
204 let build::elf::Dynamic::String { tag, val } = entry else {
205 continue;
206 };
207 if *tag != elf::DT_RPATH && *tag != elf::DT_RUNPATH {
208 continue;
209 }
210 return Some(val);
211 }
212 None
213 }
214
215 pub fn elf_delete_runpath(&mut self) -> Result<()> {
217 let dynamic = self
218 .builder
219 .dynamic_data_mut()
220 .ok_or_else(|| Error::modify("No dynamic section found; can't delete runpath"))?;
221 let mut modified = false;
222 dynamic.retain(|entry| {
223 let tag = entry.tag();
224 if tag != elf::DT_RPATH && tag != elf::DT_RUNPATH {
225 return true;
226 }
227
228 #[cfg(feature = "logging")]
229 info!(
230 "Deleting {} entry",
231 if tag == elf::DT_RPATH {
232 "DT_RPATH"
233 } else {
234 "DT_RUNPATH"
235 }
236 );
237 modified = true;
238 false
239 });
240 if modified {
241 self.modified = true;
242 }
243 Ok(())
244 }
245
246 pub fn elf_set_runpath(&mut self, runpath: Vec<u8>) -> Result<()> {
248 let dynamic = self
249 .builder
250 .dynamic_data_mut()
251 .ok_or_else(|| Error::modify("No dynamic section found; can't set runpath"))?;
252 let mut found = false;
253 for entry in dynamic.iter_mut() {
254 let build::elf::Dynamic::String { tag, val } = entry else {
255 continue;
256 };
257 if *tag != elf::DT_RPATH && *tag != elf::DT_RUNPATH {
258 continue;
259 }
260
261 *val = build::ByteString::from(runpath.clone());
262 #[cfg(feature = "logging")]
263 info!(
264 "Setting {} entry to {}",
265 if *tag == elf::DT_RPATH {
266 "DT_RPATH"
267 } else {
268 "DT_RUNPATH"
269 },
270 *val
271 );
272 found = true;
273 }
274 if !found {
275 let val = build::ByteString::from(runpath);
276 #[cfg(feature = "logging")]
277 info!("Adding DT_RUNPATH entry {}", val);
278 dynamic.push(build::elf::Dynamic::String {
279 tag: elf::DT_RUNPATH,
280 val,
281 });
282 }
283 self.modified = true;
284 Ok(())
285 }
286
287 pub fn elf_add_runpath(&mut self, runpaths: &[Vec<u8>]) -> Result<()> {
289 let dynamic = self
290 .builder
291 .dynamic_data_mut()
292 .ok_or_else(|| Error::modify("No dynamic section found; can't add runpath"))?;
293 let mut found = false;
294 for entry in dynamic.iter_mut() {
295 let build::elf::Dynamic::String { tag, val } = entry else {
296 continue;
297 };
298 if *tag != elf::DT_RPATH && *tag != elf::DT_RUNPATH {
299 continue;
300 }
301
302 for path in runpaths {
303 if !val.is_empty() {
304 val.to_mut().push(b':');
305 }
306 val.to_mut().extend_from_slice(path);
307 }
308 #[cfg(feature = "logging")]
309 info!(
310 "Changing {} entry to {}",
311 if *tag == elf::DT_RPATH {
312 "DT_RPATH"
313 } else {
314 "DT_RUNPATH"
315 },
316 val
317 );
318 found = true;
319 }
320 if !found {
321 let val = runpaths.join(&[b':'][..]).into();
322 #[cfg(feature = "logging")]
323 info!("Adding DT_RUNPATH entry {}", val);
324 dynamic.push(build::elf::Dynamic::String {
325 tag: elf::DT_RUNPATH,
326 val,
327 });
328 }
329 self.modified = true;
330 Ok(())
331 }
332
333 pub fn elf_use_runpath(&mut self) -> Result<()> {
335 let dynamic = self
336 .builder
337 .dynamic_data_mut()
338 .ok_or_else(|| Error::modify("No dynamic section found; can't change runpath"))?;
339 for entry in dynamic.iter_mut() {
340 let build::elf::Dynamic::String { tag, .. } = entry else {
341 continue;
342 };
343 if *tag != elf::DT_RPATH {
344 continue;
345 }
346
347 #[cfg(feature = "logging")]
348 info!("Changing DT_RPATH to DT_RUNPATH");
349 *tag = elf::DT_RUNPATH;
350 self.modified = true;
351 }
352 Ok(())
353 }
354
355 pub fn elf_use_rpath(&mut self) -> Result<()> {
357 let dynamic = self
358 .builder
359 .dynamic_data_mut()
360 .ok_or_else(|| Error::modify("No dynamic section found; can't change rpath"))?;
361 for entry in dynamic.iter_mut() {
362 let build::elf::Dynamic::String { tag, .. } = entry else {
363 continue;
364 };
365 if *tag != elf::DT_RUNPATH {
366 continue;
367 }
368
369 #[cfg(feature = "logging")]
370 info!("Changing DT_RUNPATH to DT_RPATH");
371 *tag = elf::DT_RPATH;
372 self.modified = true;
373 }
374 Ok(())
375 }
376
377 pub fn elf_needed(&self) -> impl Iterator<Item = &[u8]> {
379 let dynamic = self.builder.dynamic_data().unwrap_or(&[]);
380 dynamic.iter().filter_map(|entry| {
381 if let build::elf::Dynamic::String { tag, val } = entry {
382 if *tag == elf::DT_NEEDED {
383 return Some(val.as_slice());
384 }
385 }
386 None
387 })
388 }
389
390 pub fn elf_delete_needed(&mut self, names: &HashSet<Vec<u8>>) -> Result<()> {
392 let dynamic = self.builder.dynamic_data_mut().ok_or_else(|| {
393 Error::modify("No dynamic section found; can't delete needed library")
394 })?;
395 let mut modified = false;
396 dynamic.retain(|entry| {
397 let build::elf::Dynamic::String { tag, val } = entry else {
398 return true;
399 };
400 if *tag != elf::DT_NEEDED || !names.contains(val.as_slice()) {
401 return true;
402 }
403
404 #[cfg(feature = "logging")]
405 info!("Deleting DT_NEEDED entry {}", val);
406 modified = true;
407 false
408 });
409 if modified {
410 self.modified = true;
411 }
412 Ok(())
413 }
414
415 pub fn elf_replace_needed(&mut self, names: &HashMap<Vec<u8>, Vec<u8>>) -> Result<()> {
417 let dynamic = self.builder.dynamic_data_mut().ok_or_else(|| {
418 Error::modify("No dynamic section found; can't replace needed library")
419 })?;
420 for entry in dynamic.iter_mut() {
421 let build::elf::Dynamic::String { tag, val } = entry else {
422 continue;
423 };
424 if *tag != elf::DT_NEEDED {
425 continue;
426 }
427 let Some(name) = names.get(val.as_slice()) else {
428 continue;
429 };
430
431 let name = name.clone().into();
432 #[cfg(feature = "logging")]
433 info!("Replacing DT_NEEDED entry {} with {}", val, name);
434 *val = name;
435 self.modified = true;
436 }
437 Ok(())
438 }
439
440 pub fn elf_add_needed(&mut self, names: &[Vec<u8>]) -> Result<()> {
444 let dynamic = self
445 .builder
446 .dynamic_data_mut()
447 .ok_or_else(|| Error::modify("No dynamic section found; can't add needed library"))?;
448 let mut found = HashSet::new();
449 for entry in dynamic.iter() {
450 let build::elf::Dynamic::String { tag, val } = entry else {
451 continue;
452 };
453 if *tag != elf::DT_NEEDED {
454 continue;
455 }
456 found.insert(val.clone());
457 }
458 for name in names
459 .iter()
460 .rev()
461 .filter(|name| !found.contains(name.as_slice()))
462 {
463 let val = name.clone().into();
464 #[cfg(feature = "logging")]
465 info!("Adding DT_NEEDED entry {}", val);
466 dynamic.insert(
467 0,
468 build::elf::Dynamic::String {
469 tag: elf::DT_NEEDED,
470 val,
471 },
472 );
473 self.modified = true;
474 }
475 Ok(())
476 }
477
478 pub fn elf_soname(&self) -> Option<&[u8]> {
480 let id = self.builder.dynamic_section()?;
481 let section = self.builder.sections.get(id);
482 let build::elf::SectionData::Dynamic(dynamic) = §ion.data else {
483 return None;
484 };
485 for entry in dynamic.iter() {
486 let build::elf::Dynamic::String { tag, val } = entry else {
487 continue;
488 };
489 if *tag != elf::DT_SONAME {
490 continue;
491 }
492
493 return Some(val);
494 }
495 None
496 }
497
498 pub fn elf_set_soname(&mut self, soname: Vec<u8>) -> Result<()> {
500 let dynamic = self
501 .builder
502 .dynamic_data_mut()
503 .ok_or_else(|| Error::modify("No dynamic section found; can't set soname"))?;
504 let mut found = false;
505 for entry in dynamic.iter_mut() {
506 let build::elf::Dynamic::String { tag, val } = entry else {
507 continue;
508 };
509 if *tag != elf::DT_SONAME {
510 continue;
511 }
512
513 *val = soname.clone().into();
514 #[cfg(feature = "logging")]
515 info!("Setting DT_SONAME entry to {}", val);
516 found = true;
517 }
518 if !found {
519 let val = soname.into();
520 #[cfg(feature = "logging")]
521 info!("Adding DT_SONAME entry {}", val);
522 dynamic.push(build::elf::Dynamic::String {
523 tag: elf::DT_SONAME,
524 val,
525 });
526 }
527 self.modified = true;
528 Ok(())
529 }
530
531 pub fn elf_interpreter(&self) -> Option<&[u8]> {
533 self.builder.interp_data()
534 }
535
536 pub fn elf_set_interpreter(&mut self, mut interpreter: Vec<u8>) -> Result<()> {
540 let data = self
541 .builder
542 .interp_data_mut()
543 .ok_or_else(|| Error::modify("No interp section found; can't set interpreter"))?;
544 #[cfg(feature = "logging")]
545 info!(
546 "Setting interpreter to {}",
547 build::ByteString::from(interpreter.as_slice())
548 );
549 if !interpreter.is_empty() && interpreter.last() != Some(&0) {
550 interpreter.push(0);
551 }
552 *data = interpreter.into();
553 self.modified = true;
554 Ok(())
555 }
556
557 pub(crate) fn elf_finalize(&mut self) -> Result<()> {
558 if self.modified {
559 move_sections(&mut self.builder)?;
560 }
561 Ok(())
562 }
563}
564
565enum BlockKind {
566 FileHeader,
567 ProgramHeaders,
568 Segment,
569 Section(build::elf::SectionId),
570}
571
572struct Block<'a> {
573 #[allow(dead_code)]
574 name: build::ByteString<'a>,
575 kind: BlockKind,
576 address: u64,
577 size: u64,
578 move_priority: u8,
580}
581
582fn move_sections(builder: &mut build::elf::Builder) -> Result<()> {
587 builder.delete_orphans();
588 builder.delete_unused_versions();
589 builder.set_section_sizes();
590
591 let mut added_p_flags = Vec::new();
592 let mut added_segments = 0;
593
594 loop {
596 let mut move_sections = find_move_sections(builder, added_segments)?;
597 if move_sections.is_empty() {
598 return Ok(());
599 }
600
601 added_p_flags.clear();
603 for id in &move_sections {
604 let section = builder.sections.get_mut(*id);
605 section.sh_offset = 0;
607 let p_flags = section.p_flags();
609 if !added_p_flags.contains(&p_flags) {
610 added_p_flags.push(p_flags);
611 }
612 }
613
614 let mut split_segments = 0;
617 for segment in &mut builder.segments {
618 if segment.p_type == elf::PT_LOAD {
619 continue;
620 }
621 let mut any = false;
622 let mut all = true;
623 for id in &segment.sections {
624 if move_sections.contains(id) {
625 any = true;
626 } else {
627 all = false;
628 }
629 }
630 if !any || all {
631 continue;
632 }
633 split_segments += 1;
634 }
635
636 if added_segments < split_segments + added_p_flags.len() {
638 added_segments = split_segments + added_p_flags.len();
639 continue;
640 }
641
642 #[cfg(feature = "logging")]
643 info!(
644 "Moving {} sections, adding {} PT_LOAD segments, splitting {} segments",
645 move_sections.len(),
646 added_p_flags.len(),
647 split_segments,
648 );
649
650 move_sections.sort_by_key(|id| {
653 let section = builder.sections.get(*id);
654 (section.sh_addr, section.sh_size)
655 });
656 for p_flags in added_p_flags {
657 let segment = builder
659 .segments
660 .add_load_segment(p_flags, builder.load_align);
661 for id in &move_sections {
662 let section = builder.sections.get_mut(*id);
663 if p_flags == section.p_flags() {
664 segment.append_section(section);
665 #[cfg(feature = "logging")]
666 info!(
667 "Moved {} to offset {:x}, addr {:x}",
668 section.name, section.sh_offset, section.sh_addr
669 );
670 }
671 }
672 #[cfg(feature = "logging")]
673 info!(
674 "Added PT_LOAD segment with p_flags {:x}, offset {:x}, addr {:x}, size {:x}",
675 p_flags, segment.p_offset, segment.p_vaddr, segment.p_memsz,
676 );
677 }
678
679 let sections = &builder.sections;
681 let mut split_segments = Vec::new();
682 for segment in &mut builder.segments {
683 if segment.p_type == elf::PT_LOAD {
684 continue;
685 }
686
687 let mut any = false;
688 let mut all = true;
689 for id in &segment.sections {
690 if move_sections.contains(id) {
691 any = true;
692 } else {
693 all = false;
694 }
695 }
696 if !any {
697 continue;
698 }
699 if !all {
700 let mut split_sections = Vec::new();
704 segment.sections.retain(|id| {
705 if move_sections.contains(id) {
706 split_sections.push(*id);
707 false
708 } else {
709 true
710 }
711 });
712 split_segments.push((segment.id(), split_sections));
713 }
714
715 segment.recalculate_ranges(sections);
720 }
721
722 for (segment_id, split_sections) in split_segments {
724 let segment = builder.segments.copy(segment_id);
725 for id in split_sections {
726 let section = builder.sections.get_mut(id);
727 segment.append_section(section);
728 }
729 #[cfg(feature = "logging")]
730 info!(
731 "Split segment with type {:x}, offset {:x}, addr {:x}, size {:x}",
732 segment.p_type, segment.p_offset, segment.p_vaddr, segment.p_memsz,
733 );
734 }
735
736 let size = builder.program_headers_size() as u64;
738 for segment in &mut builder.segments {
739 if segment.p_type != elf::PT_PHDR {
740 continue;
741 }
742 segment.p_filesz = size;
743 segment.p_memsz = size;
744 }
745 return Ok(());
746 }
747}
748
749fn find_move_sections(
750 builder: &build::elf::Builder,
751 added_segments: usize,
752) -> Result<Vec<build::elf::SectionId>> {
753 use build::elf::SectionData;
754
755 let mut move_sections = Vec::new();
756 let mut blocks = Vec::new();
757 let file_header_size = builder.file_header_size() as u64;
758 let program_headers_size = (builder.program_headers_size()
759 + added_segments * builder.class().program_header_size())
760 as u64;
761 let interp = builder.interp_section();
762
763 if let Some(segment) = builder.segments.find_load_segment_from_offset(0) {
764 let address = segment.address_from_offset(0);
765 blocks.push(Block {
766 name: "file header".into(),
767 kind: BlockKind::FileHeader,
768 address,
769 size: file_header_size,
770 move_priority: 0,
771 });
772 }
773 if let Some(segment) = builder
774 .segments
775 .find_load_segment_from_offset(builder.header.e_phoff)
776 {
777 let address = segment.address_from_offset(builder.header.e_phoff);
778 blocks.push(Block {
779 name: "program headers".into(),
780 kind: BlockKind::ProgramHeaders,
781 address,
782 size: program_headers_size,
783 move_priority: 0,
784 });
785 }
786 for segment in &builder.segments {
787 if segment.p_type != elf::PT_LOAD {
788 continue;
789 }
790 blocks.push(Block {
793 name: "segment start".into(),
794 kind: BlockKind::Segment,
795 address: segment.p_vaddr,
796 size: 0,
797 move_priority: 0,
798 });
799 blocks.push(Block {
800 name: "segment end".into(),
801 kind: BlockKind::Segment,
802 address: segment.p_vaddr + segment.p_memsz,
803 size: 0,
804 move_priority: 0,
805 });
806 }
807 for section in &builder.sections {
808 if !section.is_alloc() {
809 continue;
810 }
811 if section.sh_offset == 0 {
812 move_sections.push(section.id());
815 continue;
816 }
817 if section.sh_type == elf::SHT_NOBITS && section.sh_flags & u64::from(elf::SHF_TLS) != 0 {
818 continue;
820 }
821 let move_priority = match §ion.data {
822 SectionData::Data(_) => {
825 if Some(section.id()) == interp {
826 1
827 } else {
828 0
829 }
830 }
831 SectionData::UninitializedData(_) | SectionData::Dynamic(_) => 0,
832 SectionData::DynamicRelocation(_) => 0,
834 SectionData::Relocation(_)
836 | SectionData::Note(_)
837 | SectionData::Attributes(_)
838 | SectionData::SectionString
839 | SectionData::Symbol
840 | SectionData::SymbolSectionIndex
841 | SectionData::String
842 | SectionData::DynamicSymbol
843 | SectionData::DynamicString
844 | SectionData::Hash
845 | SectionData::GnuHash
846 | SectionData::GnuVersym
847 | SectionData::GnuVerdef
848 | SectionData::GnuVerneed => 2,
849 };
850 blocks.push(Block {
851 name: (*section.name).into(),
852 kind: BlockKind::Section(section.id()),
853 address: section.sh_addr,
854 size: section.sh_size,
855 move_priority,
856 });
857 }
858 blocks.sort_by_key(|block| (block.address, block.size));
859
860 let mut i = 0;
862 while i + 1 < blocks.len() {
863 let end_address = blocks[i].address + blocks[i].size;
864 if end_address <= blocks[i + 1].address {
865 i += 1;
866 continue;
867 }
868 if blocks[i].move_priority >= blocks[i + 1].move_priority {
870 if blocks[i].move_priority == 0 {
871 #[cfg(feature = "logging")]
872 info!(
873 "Immovable {} (end address {:x}) overlaps immovable {} (start address {:x})",
874 blocks[i].name,
875 end_address,
876 blocks[i + 1].name,
877 blocks[i + 1].address,
878 );
879 return Err(Error::modify("Overlapping immovable sections"));
880 }
881 #[cfg(feature = "logging")]
882 info!(
883 "Need to move {} (end address {:x}) because of {} (start address {:x})",
884 blocks[i].name,
885 end_address,
886 blocks[i + 1].name,
887 blocks[i + 1].address,
888 );
889 if let BlockKind::Section(section) = blocks[i].kind {
890 move_sections.push(section);
891 blocks.remove(i);
892 } else {
893 unreachable!();
895 }
896 } else {
897 #[cfg(feature = "logging")]
898 info!(
899 "Need to move {} (start address {:x}) because of {} (end address {:x})",
900 blocks[i + 1].name,
901 blocks[i + 1].address,
902 blocks[i].name,
903 end_address,
904 );
905 if let BlockKind::Section(section) = blocks[i + 1].kind {
906 move_sections.push(section);
907 blocks.remove(i + 1);
908 } else {
909 unreachable!();
911 }
912 }
913 }
914 Ok(move_sections)
915}