diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp index 658919608179a..7b4465966727d 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp @@ -67,7 +67,8 @@ bool UnixNativeCodeManager::VirtualUnwind(MethodInfo* pMethodInfo, REGDISPLAY* p { UnixNativeMethodInfo * pNativeMethodInfo = (UnixNativeMethodInfo *)pMethodInfo; - return UnwindHelpers::StepFrame(pRegisterSet, pNativeMethodInfo->start_ip, pNativeMethodInfo->format, pNativeMethodInfo->unwind_info); + return UnwindHelpers::StepFrame( + pRegisterSet, pNativeMethodInfo->start_ip, pNativeMethodInfo->format, pNativeMethodInfo->unwind_info); } bool UnixNativeCodeManager::FindMethodInfo(PTR_VOID ControlPC, @@ -258,14 +259,14 @@ uintptr_t UnixNativeCodeManager::GetConservativeUpperBoundForOutgoingArgs(Method uint8_t unwindBlockFlags = *p++; - if ((unwindBlockFlags & UBF_FUNC_HAS_ASSOCIATED_DATA) != 0) - p += sizeof(int32_t); - if ((unwindBlockFlags & UBF_FUNC_REVERSE_PINVOKE) != 0) { // Reverse PInvoke transition should be on the main function body only assert(pNativeMethodInfo->pMainLSDA == pNativeMethodInfo->pLSDA); + if ((unwindBlockFlags & UBF_FUNC_HAS_ASSOCIATED_DATA) != 0) + p += sizeof(int32_t); + if ((unwindBlockFlags & UBF_FUNC_HAS_EHINFO) != 0) p += sizeof(int32_t); @@ -320,14 +321,14 @@ bool UnixNativeCodeManager::UnwindStackFrame(MethodInfo * pMethodInfo, uint8_t unwindBlockFlags = *p++; - if ((unwindBlockFlags & UBF_FUNC_HAS_ASSOCIATED_DATA) != 0) - p += sizeof(int32_t); - if ((unwindBlockFlags & UBF_FUNC_REVERSE_PINVOKE) != 0) { // Reverse PInvoke transition should be on the main function body only assert(pNativeMethodInfo->pMainLSDA == pNativeMethodInfo->pLSDA); + if ((unwindBlockFlags & UBF_FUNC_HAS_ASSOCIATED_DATA) != 0) + p += sizeof(int32_t); + if ((unwindBlockFlags & UBF_FUNC_HAS_EHINFO) != 0) p += sizeof(int32_t); @@ -377,7 +378,81 @@ bool UnixNativeCodeManager::IsUnwindable(PTR_VOID pvAddress) #endif // VirtualUnwind can't unwind epilogues. - return TrailingEpilogueInstructionsCount(pMethodInfo, pvAddress) == 0; + return TrailingEpilogueInstructionsCount(pMethodInfo, pvAddress) == 0 && IsInProlog(pMethodInfo, pvAddress) != 1; +} + +// checks for known prolog instructions generated by ILC and returns +// 1 - in prolog +// 0 - not in prolog, +// -1 - unknown. +int UnixNativeCodeManager::IsInProlog(MethodInfo * pMethodInfo, PTR_VOID pvAddress) +{ +#if defined(TARGET_ARM64) + +// post/pre + + +// stp with signed offset +// x010 1001 00xx xxxx xxxx xxxx xxxx xxxx +#define STP_BITS1 0x29000000 +#define STP_MASK1 0x7FC00000 + +// stp with pre/post/no offset +// x010 100x x0xx xxxx xxxx xxxx xxxx xxxx +#define STP_BITS2 0x28000000 +#define STP_MASK2 0x7E400000 + +// add fp, sp, x +// mov fp, sp +// 1001 0001 0xxx xxxx xxxx xx11 1111 1101 +#define ADD_FP_SP_BITS 0x910003FD +#define ADD_FP_SP_MASK 0xFF8003FF + +#define STP_RT2_RT_MASK 0x7C1F +#define STP_RT2_RT_FP_LR 0x781D +#define STP_RN_MASK 0x3E0 +#define STP_RN_SP 0x3E0 +#define STP_RN_FP 0x3A0 + + UnixNativeMethodInfo * pNativeMethodInfo = (UnixNativeMethodInfo *)pMethodInfo; + ASSERT(pNativeMethodInfo != NULL); + + uint32_t* start = (uint32_t*)pNativeMethodInfo->pMethodStartAddress; + bool savedFpLr = false; + bool establishedFp = false; + + for (uint32_t* pInstr = (uint32_t*)start; pInstr < pvAddress && !(savedFpLr && establishedFp); pInstr++) + { + uint32_t instr = *pInstr; + + if (((instr & STP_MASK1) == STP_BITS1 || (instr & STP_MASK2) == STP_BITS2) && + ((instr & STP_RN_MASK) == STP_RN_SP || (instr & STP_RN_MASK) == STP_RN_FP)) + { + // SP/FP-relative store of pair of registers + savedFpLr |= (instr & STP_RT2_RT_MASK) == STP_RT2_RT_FP_LR; + } + else if ((instr & ADD_FP_SP_MASK) == ADD_FP_SP_BITS) + { + establishedFp = true; + } + else + { + // JIT generates other patterns into the prolog that we currently don't + // recognize (saving unpaired register, stack pointer adjustments). We + // don't need to recognize these patterns unless a compact unwinding code + // is generated for them in ILC. + // https://github.com/dotnet/runtime/issues/76371 + return -1; + } + } + + return savedFpLr && establishedFp ? 0 : 1; + +#else + + return -1; + +#endif } // when stopped in an epilogue, returns the count of remaining stack-consuming instructions @@ -682,9 +757,6 @@ bool UnixNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn uint8_t unwindBlockFlags = *p++; - if ((unwindBlockFlags & UBF_FUNC_HAS_ASSOCIATED_DATA) != 0) - p += sizeof(int32_t); - // Check whether this is a funclet if ((unwindBlockFlags & UBF_FUNC_KIND_MASK) != UBF_FUNC_KIND_ROOT) return false; @@ -694,6 +766,9 @@ bool UnixNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn if ((unwindBlockFlags & UBF_FUNC_REVERSE_PINVOKE) != 0) return false; + if ((unwindBlockFlags & UBF_FUNC_HAS_ASSOCIATED_DATA) != 0) + p += sizeof(int32_t); + if ((unwindBlockFlags & UBF_FUNC_HAS_EHINFO) != 0) p += sizeof(int32_t); @@ -718,6 +793,20 @@ bool UnixNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn return true; } +#if defined(TARGET_APPLE) && defined(TARGET_ARM64) + // If we are inside a prolog without a saved frame then we cannot safely unwind. + // + // Some known frame layouts use compact unwind encoding which cannot handle unwinding + // inside prolog or epilog, so don't even try that. These known sequences must be + // recognized by IsInProlog. Any other instruction sequence, known or unknown, falls + // through to the platform unwinder which should have DWARF information about the + // frame. + if (IsInProlog(pMethodInfo, (PTR_VOID)pRegisterSet->IP) == 1) + { + return false; + } +#endif + ASSERT(IsUnwindable((PTR_VOID)pRegisterSet->IP)); // Unwind the current method context to the caller's context to get its stack pointer @@ -919,6 +1008,10 @@ PTR_VOID UnixNativeCodeManager::GetAssociatedData(PTR_VOID ControlPC) PTR_UInt8 p = methodInfo.pLSDA; uint8_t unwindBlockFlags = *p++; + + if ((unwindBlockFlags & UBF_FUNC_KIND_MASK) != UBF_FUNC_KIND_ROOT) + p += sizeof(uint32_t); + if ((unwindBlockFlags & UBF_FUNC_HAS_ASSOCIATED_DATA) == 0) return NULL; diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.h b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.h index 5998572edeb98..9c129cc1fd3f3 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.h +++ b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.h @@ -63,6 +63,8 @@ class UnixNativeCodeManager : public ICodeManager bool IsUnwindable(PTR_VOID pvAddress); + int IsInProlog(MethodInfo * pMethodInfo, PTR_VOID pvAddress); + int TrailingEpilogueInstructionsCount(MethodInfo * pMethodInfo, PTR_VOID pvAddress); bool GetReturnAddressHijackInfo(MethodInfo * pMethodInfo, diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs index 9e7241048ff99..bef41e3d7ba35 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs @@ -49,6 +49,8 @@ public class ObjectWriter : IDisposable, ITypesDebugInfoWriter private HashSet _offsetToCfiStart = new HashSet(); // Code offsets that ends a frame private HashSet _offsetToCfiEnd = new HashSet(); + // Code offsets to compact unwind encoding + private Dictionary _offsetToCfiCompactEncoding = new Dictionary(); // Used to assert whether frames are not overlapped. private bool _frameOpened; @@ -248,6 +250,14 @@ public void EmitCFICode(int nativeOffset, byte[] blob) EmitCFICode(_nativeObjectWriter, nativeOffset, blob); } + [DllImport(NativeObjectWriterFileName)] + private static extern void EmitCFICompactUnwindEncoding(IntPtr objWriter, uint encoding); + public void EmitCFICompactUnwindEncoding(uint encoding) + { + Debug.Assert(_frameOpened); + EmitCFICompactUnwindEncoding(_nativeObjectWriter, encoding); + } + [DllImport(NativeObjectWriterFileName)] private static extern void EmitDebugFileInfo(IntPtr objWriter, int fileId, string fileName); public void EmitDebugFileInfo(int fileId, string fileName) @@ -574,12 +584,49 @@ public void PublishUnwindInfo(ObjectNode node) } } + // This represents the following DWARF code: + // DW_CFA_advance_loc: 4 + // DW_CFA_def_cfa_offset: +16 + // DW_CFA_offset: W29 -16 + // DW_CFA_offset: W30 -8 + // DW_CFA_advance_loc: 4 + // DW_CFA_def_cfa_register: W29 + // which is generated for the following frame prolog/epilog: + // stp fp, lr, [sp, #-10]! + // mov fp, sp + // ... + // ldp fp, lr, [sp], #0x10 + // ret + private static ReadOnlySpan DwarfArm64EmptyFrame => new byte[] + { + 0x04, 0x00, 0xFF, 0xFF, 0x10, 0x00, 0x00, 0x00, + 0x04, 0x02, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x02, 0x1E, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x08, 0x01, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + private bool TryGetCompactUnwindEncoding(byte[] blob, out uint encoding) + { + if (_targetPlatform.Architecture == TargetArchitecture.ARM64) + { + if (blob.AsSpan().SequenceEqual(DwarfArm64EmptyFrame)) + { + // Frame-based encoding, no saved registers + encoding = 0x04000000; + return true; + } + } + encoding = 0; + return false; + } + public void BuildCFIMap(NodeFactory factory, ObjectNode node) { _offsetToCfis.Clear(); _offsetToCfiStart.Clear(); _offsetToCfiEnd.Clear(); _offsetToCfiLsdaBlobName.Clear(); + _offsetToCfiCompactEncoding.Clear(); _frameOpened = false; INodeWithCodeInfo nodeWithCodeInfo = node as INodeWithCodeInfo; @@ -609,6 +656,7 @@ public void BuildCFIMap(NodeFactory factory, ObjectNode node) int end = frameInfo.EndOffset; int len = frameInfo.BlobData.Length; byte[] blob = frameInfo.BlobData; + bool emitDwarf = true; ObjectNodeSection lsdaSection = LsdaSection; if (ShouldShareSymbol(node)) @@ -621,6 +669,13 @@ public void BuildCFIMap(NodeFactory factory, ObjectNode node) byte[] blobSymbolName = _sb.ToUtf8String().UnderlyingArray; EmitSymbolDef(blobSymbolName); + if (_targetPlatform.IsOSXLike && + TryGetCompactUnwindEncoding(blob, out uint compactEncoding)) + { + _offsetToCfiCompactEncoding[start] = compactEncoding; + emitDwarf = false; + } + FrameInfoFlags flags = frameInfo.Flags; flags |= ehInfo != null ? FrameInfoFlags.HasEHInfo : 0; flags |= associatedDataNode != null ? FrameInfoFlags.HasAssociatedData : 0; @@ -662,21 +717,25 @@ public void BuildCFIMap(NodeFactory factory, ObjectNode node) _byteInterruptionOffsets[start] = true; _byteInterruptionOffsets[end] = true; _offsetToCfiLsdaBlobName.Add(start, blobSymbolName); - for (int j = 0; j < len; j += CfiCodeSize) + + if (emitDwarf) { - // The first byte of CFI_CODE is offset from the range the frame covers. - // Compute code offset from the root method. - int codeOffset = blob[j] + start; - List cfis; - if (!_offsetToCfis.TryGetValue(codeOffset, out cfis)) + for (int j = 0; j < len; j += CfiCodeSize) { - cfis = new List(); - _offsetToCfis.Add(codeOffset, cfis); - _byteInterruptionOffsets[codeOffset] = true; + // The first byte of CFI_CODE is offset from the range the frame covers. + // Compute code offset from the root method. + int codeOffset = blob[j] + start; + List cfis; + if (!_offsetToCfis.TryGetValue(codeOffset, out cfis)) + { + cfis = new List(); + _offsetToCfis.Add(codeOffset, cfis); + _byteInterruptionOffsets[codeOffset] = true; + } + byte[] cfi = new byte[CfiCodeSize]; + Array.Copy(blob, j, cfi, 0, CfiCodeSize); + cfis.Add(cfi); } - byte[] cfi = new byte[CfiCodeSize]; - Array.Copy(blob, j, cfi, 0, CfiCodeSize); - cfis.Add(cfi); } } @@ -726,6 +785,11 @@ public void EmitCFICodes(int offset) // prefix to `_fram`. "_fram"u8.CopyTo(blobSymbolName); EmitSymbolDef(blobSymbolName); + + if (_offsetToCfiCompactEncoding.TryGetValue(offset, out uint compactEncoding)) + { + EmitCFICompactUnwindEncoding(compactEncoding); + } } }