| // Copyright 2026 The Fuchsia Authors | |
| // | |
| // Use of this source code is governed by a MIT-style | |
| // license that can be found in the LICENSE file or at | |
| // https://opensource.org/licenses/MIT | |
| #include "phys/runtime/self.h" | |
| #include <lib/elfldltl/diagnostics.h> | |
| #include <lib/elfldltl/self.h> | |
| #include <zircon/assert.h> | |
| #include <fbl/no_destructor.h> | |
| #include <ktl/byte.h> | |
| #include <ktl/string_view.h> | |
| #include <ktl/variant.h> | |
| #include <phys/elf/image.h> | |
| #include <ktl/enforce.h> | |
| // These are defined by the phys.ld linker script. | |
| extern "C" ktl::byte __executable_start[], _edata[]; | |
| extern "C" const ktl::byte __start_note_gnu_build_id[]; | |
| extern "C" const ktl::byte __stop_note_gnu_build_id[]; | |
| namespace { | |
| using LoadInfo = ElfImage::LoadInfo; | |
| using size_type = ElfImage::Elf::size_type; | |
| // This is function-local so it can be non-constinit. It's never destroyed. | |
| ElfImage& MutableSelf() { | |
| static fbl::NoDestructor<ElfImage> gSelf; | |
| return *gSelf; | |
| } | |
| auto PanicDiagnostics(ktl::string_view name) { | |
| return elfldltl::Diagnostics{ | |
| elfldltl::PrintfDiagnosticsReport(__zx_panic, name, ": "), | |
| elfldltl::DiagnosticsPanicFlags(), | |
| }; | |
| } | |
| void PruneSegment(LoadInfo::ZeroFillSegment& segment, size_type memsz) { | |
| segment = LoadInfo::ZeroFillSegment{segment.vaddr(), memsz}; | |
| } | |
| void PruneSegment(LoadInfo::DataWithZeroFillSegment& segment, size_type memsz) { | |
| ZX_DEBUG_ASSERT(memsz >= segment.filesz()); | |
| segment = LoadInfo::DataWithZeroFillSegment{ | |
| segment.offset(), | |
| segment.vaddr(), | |
| memsz, | |
| segment.filesz(), | |
| }; | |
| } | |
| void PruneSegment(auto& segment, size_type memsz) { | |
| // The final segment being pruned should only ever be either | |
| // DataWithZeroFillSegment or pure ZeroFillSegment. | |
| ZX_PANIC("impossible segment type for PruneFromSelf!"); | |
| } | |
| } // namespace | |
| void InitSelf(const char* name) { | |
| using Phdr = ElfImage::Elf::Phdr; | |
| auto memory = elfldltl::Self<>::Memory(__executable_start, _end); | |
| auto bias = elfldltl::Self<>::LoadBias(); | |
| Phdr load_segment = { | |
| .type = elfldltl::ElfPhdrType::kLoad, | |
| .vaddr = memory.base(), | |
| .filesz = _edata - __executable_start, | |
| .memsz = memory.image().size_bytes(), | |
| }; | |
| load_segment.flags = Phdr::kRead | Phdr::kWrite | Phdr::kExecute; | |
| MutableSelf().InitSelf(name, memory, bias, load_segment, | |
| {__start_note_gnu_build_id, __stop_note_gnu_build_id}); | |
| } | |
| const ElfImage& Self() { return MutableSelf(); } | |
| void PruneFromSelf(uint64_t start, uint64_t end) { | |
| ElfImage& self = MutableSelf(); | |
| // Modify the ZeroFillSegment or DataWithZeroFillSegment to prune the range. | |
| auto adjust_memsz = [start, end, &self](auto& segment) { | |
| [[maybe_unused]] auto abs = segment.vaddr() + self.load_bias(); | |
| ZX_DEBUG_ASSERT(start >= abs); | |
| ZX_DEBUG_ASSERT(end >= start); | |
| ZX_DEBUG_ASSERT(end - abs <= segment.memsz()); | |
| auto rel_end = static_cast<size_type>(start - self.load_bias()); | |
| size_type new_memsz = rel_end - segment.vaddr(); | |
| ZX_DEBUG_ASSERT(new_memsz >= segment.filesz()); | |
| PruneSegment(segment, new_memsz); | |
| }; | |
| // Adjusting the last segment in LoadInfo is actually done by removing the | |
| // existing segment and then adding back its replacement. | |
| auto& load_info = self.load_info(); | |
| auto segment = load_info.RemoveLastSegment(); | |
| ktl::visit(adjust_memsz, segment); | |
| auto diag = PanicDiagnostics(self.name()); | |
| load_info.AddSegment(diag, segment); | |
| } |