[vm] Handle Windows unwinding records better in the Mach-O loader.

The Mach-O writer puts the unwinding records in a __unwind_info
section, so just find the appropriate section and use its contents
instead of forcing it to be at the end of the segment's memory space.

Create an overload of UnwindingRecordsPlatform::RegisterExecutableMemory
that takes a pointer to the start of the unwinding records for use by
the Mach-O loader.

Add __unwind_info for any executable segments that are not the text
segment as well.

Instead of testing host/target windows plus 64-bit arch, use the
defines provided by platform/unwinding_records.h in the Mach-O
writer and the ELF and Mach-O loaders.

TEST=ci on Windows trybots

Issue: https://github.com/dart-lang/sdk/issues/60307
Change-Id: Iee53c6725a681f44ac98ed9c9aecf1b75853ece5
Cq-Include-Trybots: luci.dart.try:vm-aot-linux-debug-x64-try,vm-mac-release-arm64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-dwarf-linux-product-x64-try,vm-linux-debug-x64-try,vm-mac-debug-arm64-try,vm-gcc-linux-try,vm-aot-win-release-arm64-try,vm-aot-win-release-x64-try,vm-win-release-x64-try,vm-win-release-arm64-try,vm-aot-win-debug-arm64-try,vm-win-debug-arm64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/432960
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
Tess Strickland
2025-06-11 04:43:24 -07:00
parent b67482d6bc
commit 14889cc06d
6 changed files with 137 additions and 59 deletions
+3 -3
View File
@@ -104,7 +104,7 @@ class LoadedElf {
const dart::elf::Symbol* dynamic_symbol_table_ = nullptr;
uword dynamic_symbol_count_ = 0;
#if defined(DART_HOST_OS_WINDOWS) && defined(ARCH_IS_64_BIT)
#if defined(UNWINDING_RECORDS_WINDOWS_HOST)
// Dynamic table for looking up unwinding exceptions info.
// Initialized by LoadSegments as we load executable segment.
MallocGrowableArray<void*> dynamic_runtime_function_tables_;
@@ -157,7 +157,7 @@ bool LoadedElf::Load() {
}
LoadedElf::~LoadedElf() {
#if defined(DART_HOST_OS_WINDOWS) && defined(ARCH_IS_64_BIT)
#if defined(UNWINDING_RECORDS_WINDOWS_HOST)
for (intptr_t i = 0; i < dynamic_runtime_function_tables_.length(); i++) {
UnwindingRecordsPlatform::UnregisterDynamicTable(
dynamic_runtime_function_tables_[i]);
@@ -326,7 +326,7 @@ bool LoadedElf::LoadSegments() {
CHECK_ERROR(memory != nullptr, "Could not map segment.");
CHECK_ERROR(memory->address() == memory_start,
"Mapping not at requested address.");
#if defined(DART_HOST_OS_WINDOWS) && defined(ARCH_IS_64_BIT)
#if defined(UNWINDING_RECORDS_WINDOWS_HOST)
// For executable pages register unwinding information that should be
// present on the page.
if (map_type == File::kReadExecute) {
+72 -11
View File
@@ -25,7 +25,7 @@ namespace bin {
namespace mach_o {
class LoadCommandIterator {
class LoadCommandIterator : public ValueObject {
public:
LoadCommandIterator(const void* start, size_t size)
: end_(reinterpret_cast<const void*>(reinterpret_cast<uword>(start) +
@@ -48,6 +48,29 @@ class LoadCommandIterator {
private:
const void* end_;
const void* current_;
DISALLOW_COPY_AND_ASSIGN(LoadCommandIterator);
};
// For MachO structs of constant size that are contiguous in memory.
template <typename T>
class MachOStructIterator : public ValueObject {
public:
MachOStructIterator(const T* start, size_t size_in_bytes)
: start_(start),
end_(reinterpret_cast<const T*>(reinterpret_cast<uword>(start) +
size_in_bytes)) {
ASSERT_EQUAL(0, size_in_bytes % sizeof(T));
}
const T* begin() const { return start_; }
const T* end() const { return end_; }
private:
const T* start_;
const T* end_;
DISALLOW_COPY_AND_ASSIGN(MachOStructIterator);
};
/// A loader for a subset of Mach-O which may be used to load objects produced
@@ -118,7 +141,7 @@ class LoadedMachODylib {
uword external_symbol_count_ = 0;
std::unique_ptr<MappedMemory> external_symbols_mapping_;
#if defined(DART_HOST_OS_WINDOWS) && defined(ARCH_IS_64_BIT)
#if defined(UNWINDING_RECORDS_WINDOWS_HOST)
// Dynamic table for looking up unwinding exceptions info.
// Initialized by LoadSegments as we load executable segment.
MallocGrowableArray<void*> dynamic_runtime_function_tables_;
@@ -169,7 +192,7 @@ bool LoadedMachODylib::Load() {
}
LoadedMachODylib::~LoadedMachODylib() {
#if defined(DART_HOST_OS_WINDOWS) && defined(ARCH_IS_64_BIT)
#if defined(UNWINDING_RECORDS_WINDOWS_HOST)
for (intptr_t i = 0; i < dynamic_runtime_function_tables_.length(); i++) {
UnwindingRecordsPlatform::UnregisterDynamicTable(
dynamic_runtime_function_tables_[i]);
@@ -272,6 +295,9 @@ bool LoadedMachODylib::LoadSegments() {
auto* const current = it.current();
uint64_t memory_offset, memory_size, file_offset, file_size;
dart::mach_o::vm_prot_t initprot;
#if defined(UNWINDING_RECORDS_WINDOWS_HOST)
uint64_t records_address = 0, records_size = 0;
#endif
if (current->cmd == dart::mach_o::LC_SEGMENT) {
auto* const segment =
reinterpret_cast<const dart::mach_o::segment_command*>(current);
@@ -280,6 +306,21 @@ bool LoadedMachODylib::LoadSegments() {
file_offset = segment->fileoff;
file_size = segment->filesize;
initprot = segment->initprot;
#if defined(UNWINDING_RECORDS_WINDOWS_HOST)
if ((initprot & dart::mach_o::VM_PROT_EXECUTE) != 0) {
auto* const start =
reinterpret_cast<const dart::mach_o::section*>(segment + 1);
const size_t size_in_bytes = segment->cmdsize - sizeof(*segment);
MachOStructIterator sections(start, size_in_bytes);
for (const auto& section : sections) {
if (strcmp(section.sectname, dart::mach_o::SECT_UNWIND_INFO) == 0) {
records_address = section.addr;
records_size = section.size;
break;
}
}
}
#endif
} else if (current->cmd == dart::mach_o::LC_SEGMENT_64) {
auto* const segment_64 =
reinterpret_cast<const dart::mach_o::segment_command_64*>(current);
@@ -288,6 +329,22 @@ bool LoadedMachODylib::LoadSegments() {
file_offset = segment_64->fileoff;
file_size = segment_64->filesize;
initprot = segment_64->initprot;
#if defined(UNWINDING_RECORDS_WINDOWS_HOST)
if ((initprot & dart::mach_o::VM_PROT_EXECUTE) != 0) {
auto* const start =
reinterpret_cast<const dart::mach_o::section_64*>(segment_64 + 1);
const size_t size_in_bytes =
segment_64->cmdsize - sizeof(*segment_64);
MachOStructIterator sections(start, size_in_bytes);
for (const auto& section : sections) {
if (strcmp(section.sectname, dart::mach_o::SECT_UNWIND_INFO) == 0) {
records_address = section.addr;
records_size = section.size;
break;
}
}
}
#endif
} else {
it.Advance();
continue;
@@ -344,18 +401,22 @@ bool LoadedMachODylib::LoadSegments() {
CHECK_ERROR(memory != nullptr, "Could not map segment.");
CHECK_ERROR(memory->address() == memory_start,
"Mapping not at requested address.");
#if defined(DART_HOST_OS_WINDOWS) && defined(ARCH_IS_64_BIT)
#if defined(UNWINDING_RECORDS_WINDOWS_HOST)
// For executable pages register unwinding information that should be
// present on the page.
if (map_type == File::kReadExecute) {
// RegisterExecutableMemory checks the end of the memory space, so
// if there are zerofill sections or the like in this segment, then
// the offset of the unwinding records is incorrectly calculated.
CHECK_ERROR(memory_size == file_size,
"Executable segment contains zerofill sections.");
CHECK_ERROR(records_address != 0,
"No __unwind_info section found in segment");
CHECK_ERROR(records_size == UnwindingRecordsPlatform::SizeInBytes(),
"__unwind_info section does not contain expected "
"unwinding records");
void* ptable = nullptr;
UnwindingRecordsPlatform::RegisterExecutableMemory(memory->address(),
length, &ptable);
void* start = memory->address();
void* records_start = reinterpret_cast<void*>(
reinterpret_cast<uword>(memory->address()) + adjustment +
(records_address - memory_offset));
UnwindingRecordsPlatform::RegisterExecutableMemory(
start, length, records_start, &ptable);
dynamic_runtime_function_tables_.Add(ptable);
}
#else
+6
View File
@@ -25,6 +25,12 @@ void UnwindingRecordsPlatform::RegisterExecutableMemory(
intptr_t size,
void** pp_dynamic_table) {}
void UnwindingRecordsPlatform::RegisterExecutableMemory(
void* start,
intptr_t size,
void* records_start,
void** pp_dynamic_table) {}
void UnwindingRecordsPlatform::UnregisterDynamicTable(void* p_dynamic_table) {}
#endif // !defined(UNWINDING_RECORDS_WINDOWS_HOST)
+4
View File
@@ -17,6 +17,10 @@ class UnwindingRecordsPlatform : public AllStatic {
static void RegisterExecutableMemory(void* start,
intptr_t size,
void** pp_dynamic_table);
static void RegisterExecutableMemory(void* start,
intptr_t size,
void* records_start,
void** pp_dynamic_table);
static void UnregisterDynamicTable(void* p_dynamic_table);
};
+9 -1
View File
@@ -35,8 +35,16 @@ void UnwindingRecordsPlatform::RegisterExecutableMemory(
void** pp_dynamic_table) {
intptr_t unwinding_record_offset = size - kReservedUnwindingRecordsSizeBytes;
uint8_t* record_ptr = static_cast<uint8_t*>(start) + unwinding_record_offset;
RegisterExecutableMemory(start, size, record_ptr, pp_dynamic_table);
}
void UnwindingRecordsPlatform::RegisterExecutableMemory(
void* start,
intptr_t size,
void* records_start,
void** pp_dynamic_table) {
CodeRangeUnwindingRecord* record =
reinterpret_cast<CodeRangeUnwindingRecord*>(record_ptr);
reinterpret_cast<CodeRangeUnwindingRecord*>(records_start);
RELEASE_ASSERT(record->magic == kUnwindingRecordMagic);
uword start_num = reinterpret_cast<intptr_t>(start);
uword end_num = start_num + size;
+43 -44
View File
@@ -744,6 +744,10 @@ class MachOSegment : public MachOCommand {
bool HasContents() const override { return next_contents_index_ > 0; }
bool IsAllocated() const override { return true; }
bool HasZerofillSections() const {
return next_contents_index_ != contents_.length();
}
uint32_t cmdsize() const override {
uword size = sizeof(SegmentCommandType);
// The header information for sections is nested within the
@@ -2075,7 +2079,6 @@ void MachOHeader::CreateBSS() {
}
void MachOHeader::GenerateUnwindingInformation() {
ASSERT(text_segment_ != nullptr);
#if !defined(TARGET_ARCH_IA32)
// Unwinding information is added to the text segment in Mach-O files.
// Thus, we need the size of the unwinding information even for debugging
@@ -2089,26 +2092,18 @@ void MachOHeader::GenerateUnwindingInformation() {
const bool use_zerofill = type_ == SnapshotType::DebugInfo;
auto const section_type =
use_zerofill ? mach_o::S_ZEROFILL : mach_o::S_REGULAR;
#if defined(DEBUG)
for (auto* const c : text_segment_->contents()) {
// The header always has contents, but the restriction on zerofill
// sections coming after content-containing sections only matters with
// respect to sections, not other contents.
if (c->IsMachOHeader()) continue;
// RegisterExecutablePages looks at the end of the loaded segment for
// the unwinding information, which means the unwinding info needs to be
// the last section, but any sections without file contents are ordered
// after any sections with file contents in a segment.
ASSERT_EQUAL(use_zerofill, !c->HasContents());
}
#endif
#if defined(DART_TARGET_OS_MACOS)
// TODO(dartbug.com/60307): Add compact unwind information.
USE(section_type);
#else
ASSERT(text_segment_ != nullptr);
if (auto* const text_section =
text_segment_->FindSection(mach_o::SECT_TEXT)) {
ASSERT(use_zerofill || !text_segment_->HasZerofillSections());
// Not idempotent.
ASSERT(text_segment_->FindSection(mach_o::SECT_EH_FRAME) == nullptr);
// For the __eh_frame section, the easiest way to determine the size is to
// generate the contents and just discard them if using zerofill.
GrowableArray<Dwarf::FrameDescriptionEntry> fdes(zone_, 0);
@@ -2121,7 +2116,6 @@ void MachOHeader::GenerateUnwindingInformation() {
DwarfSharedObjectStream dwarf_stream(zone_, &stream);
Dwarf::WriteCallFrameInformationRecords(&dwarf_stream, fdes);
ASSERT(!text_segment_->FindSection(mach_o::SECT_EH_FRAME));
auto* const eh_frame = new (zone())
MachOSection(zone(), mach_o::SECT_EH_FRAME, section_type,
mach_o::S_NO_ATTRIBUTES, /*has_contents=*/!use_zerofill,
@@ -2133,36 +2127,41 @@ void MachOHeader::GenerateUnwindingInformation() {
}
#endif // defined(DART_TARGET_OS_MACOS)
#if defined(DART_TARGET_OS_WINDOWS) && defined(TARGET_ARCH_IS_64_BIT)
// Append Windows unwinding instructions as another section at the end of
// the text segment.
auto* const unwinding_records = new (zone()) MachOSection(
zone(), mach_o::SECT_UNWIND_INFO, section_type, mach_o::S_NO_ATTRIBUTES,
/*has_contents=*/!use_zerofill, compiler::target::kWordSize);
const intptr_t records_size = UnwindingRecordsPlatform::SizeInBytes();
// The memory space of the text segment is padded to the alignment size, so
// we need to make sure the resulting data has initial padding so that the
// records are at the end of the segment without extra padding.
const intptr_t section_start = Utils::RoundUp(
text_segment_->UnpaddedMemorySize(), unwinding_records->Alignment());
const intptr_t section_size =
Utils::RoundUp(section_start + records_size, text_segment_->Alignment()) -
section_start;
const uint8_t* bytes = nullptr;
if (!use_zerofill) {
ZoneWriteStream stream(zone(), /*initial_size=*/section_size);
uint8_t* unwinding_instructions = zone()->Alloc<uint8_t>(records_size);
intptr_t records_start = section_size - records_size;
stream.SetPosition(records_start);
stream.WriteBytes(UnwindingRecords::GenerateRecordsInto(
records_start, unwinding_instructions),
records_size);
ASSERT_EQUAL(section_size, stream.Position());
bytes = stream.buffer();
#if defined(UNWINDING_RECORDS_WINDOWS_PRECOMPILER)
// Append Windows unwinding instructions as a __unwind_info section at
// the end of any executable segments.
for (auto* const command : commands_) {
if (auto* const segment = command->AsMachOSegment()) {
if (segment->IsExecutable()) {
ASSERT(use_zerofill || !segment->HasZerofillSections());
// Not idempotent.
ASSERT(segment->FindSection(mach_o::SECT_UNWIND_INFO) == nullptr);
auto* const unwinding_records = new (zone()) MachOSection(
zone(), mach_o::SECT_UNWIND_INFO, section_type,
mach_o::S_NO_ATTRIBUTES,
/*has_contents=*/!use_zerofill, compiler::target::kWordSize);
const intptr_t records_size = UnwindingRecordsPlatform::SizeInBytes();
const intptr_t section_start = Utils::RoundUp(
segment->UnpaddedMemorySize(), unwinding_records->Alignment());
const uint8_t* bytes = nullptr;
if (!use_zerofill) {
ZoneWriteStream stream(zone(), /*initial_size=*/records_size);
uint8_t* unwinding_instructions =
zone()->Alloc<uint8_t>(records_size);
stream.WriteBytes(UnwindingRecords::GenerateRecordsInto(
section_start, unwinding_instructions),
records_size);
ASSERT_EQUAL(records_size, stream.Position());
bytes = stream.buffer();
}
unwinding_records->AddPortion(bytes, records_size);
segment->AddContents(unwinding_records);
ASSERT_EQUAL(section_start + records_size,
segment->UnpaddedMemorySize());
}
}
}
unwinding_records->AddPortion(bytes, section_size);
text_segment_->AddContents(unwinding_records);
ASSERT_EQUAL(section_start + section_size, text_segment_->MemorySize());
#endif // defined(DART_TARGET_OS_WINDOWS) && defined(TARGET_ARCH_IS_64_BIT)
#endif // !defined(TARGET_ARCH_IA32)
}