1 //===-- CodeGen/AsmPrinter/WinException.cpp - Dwarf Exception Impl ------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // This file contains support for writing Win64 exception info into asm files. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "WinException.h" 15 #include "llvm/ADT/SmallString.h" 16 #include "llvm/ADT/StringExtras.h" 17 #include "llvm/ADT/Twine.h" 18 #include "llvm/CodeGen/AsmPrinter.h" 19 #include "llvm/CodeGen/MachineFrameInfo.h" 20 #include "llvm/CodeGen/MachineFunction.h" 21 #include "llvm/CodeGen/MachineModuleInfo.h" 22 #include "llvm/CodeGen/WinEHFuncInfo.h" 23 #include "llvm/IR/DataLayout.h" 24 #include "llvm/IR/Mangler.h" 25 #include "llvm/IR/Module.h" 26 #include "llvm/MC/MCAsmInfo.h" 27 #include "llvm/MC/MCContext.h" 28 #include "llvm/MC/MCExpr.h" 29 #include "llvm/MC/MCSection.h" 30 #include "llvm/MC/MCStreamer.h" 31 #include "llvm/MC/MCSymbol.h" 32 #include "llvm/MC/MCWin64EH.h" 33 #include "llvm/Support/Dwarf.h" 34 #include "llvm/Support/ErrorHandling.h" 35 #include "llvm/Support/FormattedStream.h" 36 #include "llvm/Target/TargetFrameLowering.h" 37 #include "llvm/Target/TargetLoweringObjectFile.h" 38 #include "llvm/Target/TargetOptions.h" 39 #include "llvm/Target/TargetRegisterInfo.h" 40 using namespace llvm; 41 42 WinException::WinException(AsmPrinter *A) : EHStreamer(A) { 43 // MSVC's EH tables are always composed of 32-bit words. All known 64-bit 44 // platforms use an imagerel32 relocation to refer to symbols. 45 useImageRel32 = (A->getDataLayout().getPointerSizeInBits() == 64); 46 } 47 48 WinException::~WinException() {} 49 50 /// endModule - Emit all exception information that should come after the 51 /// content. 52 void WinException::endModule() { 53 } 54 55 void WinException::beginFunction(const MachineFunction *MF) { 56 shouldEmitMoves = shouldEmitPersonality = shouldEmitLSDA = false; 57 58 // If any landing pads survive, we need an EH table. 59 bool hasLandingPads = !MMI->getLandingPads().empty(); 60 61 const Function *F = MF->getFunction(); 62 const Function *ParentF = MMI->getWinEHParent(F); 63 64 shouldEmitMoves = Asm->needsSEHMoves(); 65 66 const TargetLoweringObjectFile &TLOF = Asm->getObjFileLowering(); 67 unsigned PerEncoding = TLOF.getPersonalityEncoding(); 68 const Function *Per = MMI->getPersonality(); 69 70 shouldEmitPersonality = hasLandingPads && 71 PerEncoding != dwarf::DW_EH_PE_omit && Per; 72 73 unsigned LSDAEncoding = TLOF.getLSDAEncoding(); 74 shouldEmitLSDA = shouldEmitPersonality && 75 LSDAEncoding != dwarf::DW_EH_PE_omit; 76 77 // If we're not using CFI, we don't want the CFI or the personality. Emit the 78 // LSDA if this is the parent function. 79 if (!Asm->MAI->usesWindowsCFI()) { 80 shouldEmitLSDA = (hasLandingPads && F == ParentF); 81 shouldEmitPersonality = false; 82 return; 83 } 84 85 // If this was an outlined handler, we need to define the label corresponding 86 // to the offset of the parent frame relative to the stack pointer after the 87 // prologue. 88 if (F != ParentF) { 89 WinEHFuncInfo &FuncInfo = MMI->getWinEHFuncInfo(ParentF); 90 auto I = FuncInfo.CatchHandlerParentFrameObjOffset.find(F); 91 if (I != FuncInfo.CatchHandlerParentFrameObjOffset.end()) { 92 MCSymbol *HandlerTypeParentFrameOffset = 93 Asm->OutContext.getOrCreateParentFrameOffsetSymbol( 94 GlobalValue::getRealLinkageName(F->getName())); 95 96 // Emit a symbol assignment. 97 Asm->OutStreamer->EmitAssignment( 98 HandlerTypeParentFrameOffset, 99 MCConstantExpr::create(I->second, Asm->OutContext)); 100 } 101 } 102 103 if (shouldEmitMoves || shouldEmitPersonality) 104 Asm->OutStreamer->EmitWinCFIStartProc(Asm->CurrentFnSym); 105 106 if (shouldEmitPersonality) { 107 const MCSymbol *PersHandlerSym = 108 TLOF.getCFIPersonalitySymbol(Per, *Asm->Mang, Asm->TM, MMI); 109 Asm->OutStreamer->EmitWinEHHandler(PersHandlerSym, true, true); 110 } 111 } 112 113 /// endFunction - Gather and emit post-function exception information. 114 /// 115 void WinException::endFunction(const MachineFunction *MF) { 116 if (!shouldEmitPersonality && !shouldEmitMoves && !shouldEmitLSDA) 117 return; 118 119 EHPersonality Per = MMI->getPersonalityType(); 120 121 // Get rid of any dead landing pads if we're not using a Windows EH scheme. In 122 // Windows EH schemes, the landing pad is not actually reachable. It only 123 // exists so that we can emit the right table data. 124 if (!isMSVCEHPersonality(Per)) 125 MMI->TidyLandingPads(); 126 127 if (shouldEmitPersonality || shouldEmitLSDA) { 128 Asm->OutStreamer->PushSection(); 129 130 if (shouldEmitMoves || shouldEmitPersonality) { 131 // Emit an UNWIND_INFO struct describing the prologue. 132 Asm->OutStreamer->EmitWinEHHandlerData(); 133 } else { 134 // Just switch sections to the right xdata section. This use of 135 // CurrentFnSym assumes that we only emit the LSDA when ending the parent 136 // function. 137 MCSection *XData = WinEH::UnwindEmitter::getXDataSection( 138 Asm->CurrentFnSym, Asm->OutContext); 139 Asm->OutStreamer->SwitchSection(XData); 140 } 141 142 // Emit the tables appropriate to the personality function in use. If we 143 // don't recognize the personality, assume it uses an Itanium-style LSDA. 144 if (Per == EHPersonality::MSVC_Win64SEH) 145 emitCSpecificHandlerTable(); 146 else if (Per == EHPersonality::MSVC_X86SEH) 147 emitCSpecificHandlerTable(); // FIXME 148 else if (Per == EHPersonality::MSVC_CXX) 149 emitCXXFrameHandler3Table(MF); 150 else 151 emitExceptionTable(); 152 153 Asm->OutStreamer->PopSection(); 154 } 155 156 if (shouldEmitMoves) 157 Asm->OutStreamer->EmitWinCFIEndProc(); 158 } 159 160 const MCExpr *WinException::create32bitRef(const MCSymbol *Value) { 161 if (!Value) 162 return MCConstantExpr::create(0, Asm->OutContext); 163 return MCSymbolRefExpr::create(Value, useImageRel32 164 ? MCSymbolRefExpr::VK_COFF_IMGREL32 165 : MCSymbolRefExpr::VK_None, 166 Asm->OutContext); 167 } 168 169 const MCExpr *WinException::create32bitRef(const GlobalValue *GV) { 170 if (!GV) 171 return MCConstantExpr::create(0, Asm->OutContext); 172 return create32bitRef(Asm->getSymbol(GV)); 173 } 174 175 /// Emit the language-specific data that __C_specific_handler expects. This 176 /// handler lives in the x64 Microsoft C runtime and allows catching or cleaning 177 /// up after faults with __try, __except, and __finally. The typeinfo values 178 /// are not really RTTI data, but pointers to filter functions that return an 179 /// integer (1, 0, or -1) indicating how to handle the exception. For __finally 180 /// blocks and other cleanups, the landing pad label is zero, and the filter 181 /// function is actually a cleanup handler with the same prototype. A catch-all 182 /// entry is modeled with a null filter function field and a non-zero landing 183 /// pad label. 184 /// 185 /// Possible filter function return values: 186 /// EXCEPTION_EXECUTE_HANDLER (1): 187 /// Jump to the landing pad label after cleanups. 188 /// EXCEPTION_CONTINUE_SEARCH (0): 189 /// Continue searching this table or continue unwinding. 190 /// EXCEPTION_CONTINUE_EXECUTION (-1): 191 /// Resume execution at the trapping PC. 192 /// 193 /// Inferred table structure: 194 /// struct Table { 195 /// int NumEntries; 196 /// struct Entry { 197 /// imagerel32 LabelStart; 198 /// imagerel32 LabelEnd; 199 /// imagerel32 FilterOrFinally; // One means catch-all. 200 /// imagerel32 LabelLPad; // Zero means __finally. 201 /// } Entries[NumEntries]; 202 /// }; 203 void WinException::emitCSpecificHandlerTable() { 204 const std::vector<LandingPadInfo> &PadInfos = MMI->getLandingPads(); 205 206 // Simplifying assumptions for first implementation: 207 // - Cleanups are not implemented. 208 // - Filters are not implemented. 209 210 // The Itanium LSDA table sorts similar landing pads together to simplify the 211 // actions table, but we don't need that. 212 SmallVector<const LandingPadInfo *, 64> LandingPads; 213 LandingPads.reserve(PadInfos.size()); 214 for (const auto &LP : PadInfos) 215 LandingPads.push_back(&LP); 216 217 // Compute label ranges for call sites as we would for the Itanium LSDA, but 218 // use an all zero action table because we aren't using these actions. 219 SmallVector<unsigned, 64> FirstActions; 220 FirstActions.resize(LandingPads.size()); 221 SmallVector<CallSiteEntry, 64> CallSites; 222 computeCallSiteTable(CallSites, LandingPads, FirstActions); 223 224 MCSymbol *EHFuncBeginSym = Asm->getFunctionBegin(); 225 MCSymbol *EHFuncEndSym = Asm->getFunctionEnd(); 226 227 // Emit the number of table entries. 228 unsigned NumEntries = 0; 229 for (const CallSiteEntry &CSE : CallSites) { 230 if (!CSE.LPad) 231 continue; // Ignore gaps. 232 NumEntries += CSE.LPad->SEHHandlers.size(); 233 } 234 Asm->OutStreamer->EmitIntValue(NumEntries, 4); 235 236 // If there are no actions, we don't need to iterate again. 237 if (NumEntries == 0) 238 return; 239 240 // Emit the four-label records for each call site entry. The table has to be 241 // sorted in layout order, and the call sites should already be sorted. 242 for (const CallSiteEntry &CSE : CallSites) { 243 // Ignore gaps. Unlike the Itanium model, unwinding through a frame without 244 // an EH table entry will propagate the exception rather than terminating 245 // the program. 246 if (!CSE.LPad) 247 continue; 248 const LandingPadInfo *LPad = CSE.LPad; 249 250 // Compute the label range. We may reuse the function begin and end labels 251 // rather than forming new ones. 252 const MCExpr *Begin = 253 create32bitRef(CSE.BeginLabel ? CSE.BeginLabel : EHFuncBeginSym); 254 const MCExpr *End; 255 if (CSE.EndLabel) { 256 // The interval is half-open, so we have to add one to include the return 257 // address of the last invoke in the range. 258 End = MCBinaryExpr::createAdd(create32bitRef(CSE.EndLabel), 259 MCConstantExpr::create(1, Asm->OutContext), 260 Asm->OutContext); 261 } else { 262 End = create32bitRef(EHFuncEndSym); 263 } 264 265 // Emit an entry for each action. 266 for (SEHHandler Handler : LPad->SEHHandlers) { 267 Asm->OutStreamer->EmitValue(Begin, 4); 268 Asm->OutStreamer->EmitValue(End, 4); 269 270 // Emit the filter or finally function pointer, if present. Otherwise, 271 // emit '1' to indicate a catch-all. 272 const Function *F = Handler.FilterOrFinally; 273 if (F) 274 Asm->OutStreamer->EmitValue(create32bitRef(Asm->getSymbol(F)), 4); 275 else 276 Asm->OutStreamer->EmitIntValue(1, 4); 277 278 // Emit the recovery address, if present. Otherwise, this must be a 279 // finally. 280 const BlockAddress *BA = Handler.RecoverBA; 281 if (BA) 282 Asm->OutStreamer->EmitValue( 283 create32bitRef(Asm->GetBlockAddressSymbol(BA)), 4); 284 else 285 Asm->OutStreamer->EmitIntValue(0, 4); 286 } 287 } 288 } 289 290 void WinException::emitCXXFrameHandler3Table(const MachineFunction *MF) { 291 const Function *F = MF->getFunction(); 292 const Function *ParentF = MMI->getWinEHParent(F); 293 auto &OS = *Asm->OutStreamer; 294 WinEHFuncInfo &FuncInfo = MMI->getWinEHFuncInfo(ParentF); 295 296 StringRef ParentLinkageName = 297 GlobalValue::getRealLinkageName(ParentF->getName()); 298 299 MCSymbol *FuncInfoXData = nullptr; 300 if (shouldEmitPersonality) { 301 FuncInfoXData = Asm->OutContext.getOrCreateSymbol( 302 Twine("$cppxdata$", ParentLinkageName)); 303 OS.EmitValue(create32bitRef(FuncInfoXData), 4); 304 305 extendIP2StateTable(MF, ParentF, FuncInfo); 306 307 // Defer emission until we've visited the parent function and all the catch 308 // handlers. Cleanups don't contribute to the ip2state table, so don't count 309 // them. 310 if (ParentF != F && !FuncInfo.CatchHandlerMaxState.count(F)) 311 return; 312 ++FuncInfo.NumIPToStateFuncsVisited; 313 if (FuncInfo.NumIPToStateFuncsVisited != FuncInfo.CatchHandlerMaxState.size()) 314 return; 315 } else { 316 FuncInfoXData = Asm->OutContext.getOrCreateLSDASymbol(ParentLinkageName); 317 } 318 319 MCSymbol *UnwindMapXData = nullptr; 320 MCSymbol *TryBlockMapXData = nullptr; 321 MCSymbol *IPToStateXData = nullptr; 322 if (!FuncInfo.UnwindMap.empty()) 323 UnwindMapXData = Asm->OutContext.getOrCreateSymbol( 324 Twine("$stateUnwindMap$", ParentLinkageName)); 325 if (!FuncInfo.TryBlockMap.empty()) 326 TryBlockMapXData = Asm->OutContext.getOrCreateSymbol( 327 Twine("$tryMap$", ParentLinkageName)); 328 if (!FuncInfo.IPToStateList.empty()) 329 IPToStateXData = Asm->OutContext.getOrCreateSymbol( 330 Twine("$ip2state$", ParentLinkageName)); 331 332 // FuncInfo { 333 // uint32_t MagicNumber 334 // int32_t MaxState; 335 // UnwindMapEntry *UnwindMap; 336 // uint32_t NumTryBlocks; 337 // TryBlockMapEntry *TryBlockMap; 338 // uint32_t IPMapEntries; // always 0 for x86 339 // IPToStateMapEntry *IPToStateMap; // always 0 for x86 340 // uint32_t UnwindHelp; // non-x86 only 341 // ESTypeList *ESTypeList; 342 // int32_t EHFlags; 343 // } 344 // EHFlags & 1 -> Synchronous exceptions only, no async exceptions. 345 // EHFlags & 2 -> ??? 346 // EHFlags & 4 -> The function is noexcept(true), unwinding can't continue. 347 OS.EmitLabel(FuncInfoXData); 348 OS.EmitIntValue(0x19930522, 4); // MagicNumber 349 OS.EmitIntValue(FuncInfo.UnwindMap.size(), 4); // MaxState 350 OS.EmitValue(create32bitRef(UnwindMapXData), 4); // UnwindMap 351 OS.EmitIntValue(FuncInfo.TryBlockMap.size(), 4); // NumTryBlocks 352 OS.EmitValue(create32bitRef(TryBlockMapXData), 4); // TryBlockMap 353 OS.EmitIntValue(FuncInfo.IPToStateList.size(), 4); // IPMapEntries 354 OS.EmitValue(create32bitRef(IPToStateXData), 4); // IPToStateMap 355 if (Asm->MAI->usesWindowsCFI()) 356 OS.EmitIntValue(FuncInfo.UnwindHelpFrameOffset, 4); // UnwindHelp 357 OS.EmitIntValue(0, 4); // ESTypeList 358 OS.EmitIntValue(1, 4); // EHFlags 359 360 // UnwindMapEntry { 361 // int32_t ToState; 362 // void (*Action)(); 363 // }; 364 if (UnwindMapXData) { 365 OS.EmitLabel(UnwindMapXData); 366 for (const WinEHUnwindMapEntry &UME : FuncInfo.UnwindMap) { 367 OS.EmitIntValue(UME.ToState, 4); // ToState 368 OS.EmitValue(create32bitRef(UME.Cleanup), 4); // Action 369 } 370 } 371 372 // TryBlockMap { 373 // int32_t TryLow; 374 // int32_t TryHigh; 375 // int32_t CatchHigh; 376 // int32_t NumCatches; 377 // HandlerType *HandlerArray; 378 // }; 379 if (TryBlockMapXData) { 380 OS.EmitLabel(TryBlockMapXData); 381 SmallVector<MCSymbol *, 1> HandlerMaps; 382 for (size_t I = 0, E = FuncInfo.TryBlockMap.size(); I != E; ++I) { 383 WinEHTryBlockMapEntry &TBME = FuncInfo.TryBlockMap[I]; 384 MCSymbol *HandlerMapXData = nullptr; 385 386 if (!TBME.HandlerArray.empty()) 387 HandlerMapXData = 388 Asm->OutContext.getOrCreateSymbol(Twine("$handlerMap$") 389 .concat(Twine(I)) 390 .concat("$") 391 .concat(ParentLinkageName)); 392 393 HandlerMaps.push_back(HandlerMapXData); 394 395 int CatchHigh = -1; 396 for (WinEHHandlerType &HT : TBME.HandlerArray) 397 CatchHigh = 398 std::max(CatchHigh, FuncInfo.CatchHandlerMaxState[HT.Handler]); 399 400 assert(TBME.TryLow <= TBME.TryHigh); 401 OS.EmitIntValue(TBME.TryLow, 4); // TryLow 402 OS.EmitIntValue(TBME.TryHigh, 4); // TryHigh 403 OS.EmitIntValue(CatchHigh, 4); // CatchHigh 404 OS.EmitIntValue(TBME.HandlerArray.size(), 4); // NumCatches 405 OS.EmitValue(create32bitRef(HandlerMapXData), 4); // HandlerArray 406 } 407 408 for (size_t I = 0, E = FuncInfo.TryBlockMap.size(); I != E; ++I) { 409 WinEHTryBlockMapEntry &TBME = FuncInfo.TryBlockMap[I]; 410 MCSymbol *HandlerMapXData = HandlerMaps[I]; 411 if (!HandlerMapXData) 412 continue; 413 // HandlerType { 414 // int32_t Adjectives; 415 // TypeDescriptor *Type; 416 // int32_t CatchObjOffset; 417 // void (*Handler)(); 418 // int32_t ParentFrameOffset; // x64 only 419 // }; 420 OS.EmitLabel(HandlerMapXData); 421 for (const WinEHHandlerType &HT : TBME.HandlerArray) { 422 // Get the frame escape label with the offset of the catch object. If 423 // the index is -1, then there is no catch object, and we should emit an 424 // offset of zero, indicating that no copy will occur. 425 const MCExpr *FrameAllocOffsetRef = nullptr; 426 if (HT.CatchObjRecoverIdx >= 0) { 427 MCSymbol *FrameAllocOffset = 428 Asm->OutContext.getOrCreateFrameAllocSymbol( 429 GlobalValue::getRealLinkageName(ParentF->getName()), 430 HT.CatchObjRecoverIdx); 431 FrameAllocOffsetRef = MCSymbolRefExpr::create( 432 FrameAllocOffset, MCSymbolRefExpr::VK_None, Asm->OutContext); 433 } else { 434 FrameAllocOffsetRef = MCConstantExpr::create(0, Asm->OutContext); 435 } 436 437 OS.EmitIntValue(HT.Adjectives, 4); // Adjectives 438 OS.EmitValue(create32bitRef(HT.TypeDescriptor), 4); // Type 439 OS.EmitValue(FrameAllocOffsetRef, 4); // CatchObjOffset 440 OS.EmitValue(create32bitRef(HT.Handler), 4); // Handler 441 442 if (shouldEmitPersonality) { 443 MCSymbol *ParentFrameOffset = 444 Asm->OutContext.getOrCreateParentFrameOffsetSymbol( 445 GlobalValue::getRealLinkageName(HT.Handler->getName())); 446 const MCSymbolRefExpr *ParentFrameOffsetRef = MCSymbolRefExpr::create( 447 ParentFrameOffset, MCSymbolRefExpr::VK_None, Asm->OutContext); 448 OS.EmitValue(ParentFrameOffsetRef, 4); // ParentFrameOffset 449 } 450 } 451 } 452 } 453 454 // IPToStateMapEntry { 455 // void *IP; 456 // int32_t State; 457 // }; 458 if (IPToStateXData) { 459 OS.EmitLabel(IPToStateXData); 460 for (auto &IPStatePair : FuncInfo.IPToStateList) { 461 OS.EmitValue(create32bitRef(IPStatePair.first), 4); // IP 462 OS.EmitIntValue(IPStatePair.second, 4); // State 463 } 464 } 465 } 466 467 void WinException::extendIP2StateTable(const MachineFunction *MF, 468 const Function *ParentF, 469 WinEHFuncInfo &FuncInfo) { 470 const Function *F = MF->getFunction(); 471 472 // The Itanium LSDA table sorts similar landing pads together to simplify the 473 // actions table, but we don't need that. 474 SmallVector<const LandingPadInfo *, 64> LandingPads; 475 const std::vector<LandingPadInfo> &PadInfos = MMI->getLandingPads(); 476 LandingPads.reserve(PadInfos.size()); 477 for (const auto &LP : PadInfos) 478 LandingPads.push_back(&LP); 479 480 RangeMapType PadMap; 481 computePadMap(LandingPads, PadMap); 482 483 // The end label of the previous invoke or nounwind try-range. 484 MCSymbol *LastLabel = Asm->getFunctionBegin(); 485 486 // Whether there is a potentially throwing instruction (currently this means 487 // an ordinary call) between the end of the previous try-range and now. 488 bool SawPotentiallyThrowing = false; 489 490 int LastEHState = -2; 491 492 // The parent function and the catch handlers contribute to the 'ip2state' 493 // table. 494 495 // Include ip2state entries for the beginning of the main function and 496 // for catch handler functions. 497 if (F == ParentF) { 498 FuncInfo.IPToStateList.push_back(std::make_pair(LastLabel, -1)); 499 LastEHState = -1; 500 } else if (FuncInfo.HandlerBaseState.count(F)) { 501 FuncInfo.IPToStateList.push_back( 502 std::make_pair(LastLabel, FuncInfo.HandlerBaseState[F])); 503 LastEHState = FuncInfo.HandlerBaseState[F]; 504 } 505 for (const auto &MBB : *MF) { 506 for (const auto &MI : MBB) { 507 if (!MI.isEHLabel()) { 508 if (MI.isCall()) 509 SawPotentiallyThrowing |= !callToNoUnwindFunction(&MI); 510 continue; 511 } 512 513 // End of the previous try-range? 514 MCSymbol *BeginLabel = MI.getOperand(0).getMCSymbol(); 515 if (BeginLabel == LastLabel) 516 SawPotentiallyThrowing = false; 517 518 // Beginning of a new try-range? 519 RangeMapType::const_iterator L = PadMap.find(BeginLabel); 520 if (L == PadMap.end()) 521 // Nope, it was just some random label. 522 continue; 523 524 const PadRange &P = L->second; 525 const LandingPadInfo *LandingPad = LandingPads[P.PadIndex]; 526 assert(BeginLabel == LandingPad->BeginLabels[P.RangeIndex] && 527 "Inconsistent landing pad map!"); 528 529 // FIXME: Should this be using FuncInfo.HandlerBaseState? 530 if (SawPotentiallyThrowing && LastEHState != -1) { 531 FuncInfo.IPToStateList.push_back(std::make_pair(LastLabel, -1)); 532 SawPotentiallyThrowing = false; 533 LastEHState = -1; 534 } 535 536 if (LandingPad->WinEHState != LastEHState) 537 FuncInfo.IPToStateList.push_back( 538 std::make_pair(BeginLabel, LandingPad->WinEHState)); 539 LastEHState = LandingPad->WinEHState; 540 LastLabel = LandingPad->EndLabels[P.RangeIndex]; 541 } 542 } 543 } 544