EHABIStackWalk.cpp (17449B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* 8 * This is an implementation of stack unwinding according to a subset 9 * of the ARM Exception Handling ABI, as described in: 10 * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf 11 * 12 * This handles only the ARM-defined "personality routines" (chapter 13 * 9), and don't track the value of FP registers, because profiling 14 * needs only chain of PC/SP values. 15 * 16 * Because the exception handling info may not be accurate for all 17 * possible places where an async signal could occur (e.g., in a 18 * prologue or epilogue), this bounds-checks all stack accesses. 19 * 20 * This file uses "struct" for structures in the exception tables and 21 * "class" otherwise. We should avoid violating the C++11 22 * standard-layout rules in the former. 23 */ 24 25 #include "EHABIStackWalk.h" 26 27 #include "platform.h" 28 29 #include "mozilla/Atomics.h" 30 #include "mozilla/BaseProfiler.h" 31 #include "mozilla/DebugOnly.h" 32 #include "mozilla/EndianUtils.h" 33 #include "mozilla/SharedLibraries.h" 34 35 #include <algorithm> 36 #include <elf.h> 37 #include <stdint.h> 38 #include <vector> 39 #include <string> 40 41 #ifndef PT_ARM_EXIDX 42 # define PT_ARM_EXIDX 0x70000001 43 #endif 44 45 namespace mozilla { 46 namespace baseprofiler { 47 48 struct PRel31 { 49 uint32_t mBits; 50 bool topBit() const { return mBits & 0x80000000; } 51 uint32_t value() const { return mBits & 0x7fffffff; } 52 int32_t offset() const { return (static_cast<int32_t>(mBits) << 1) >> 1; } 53 const void* compute() const { 54 return reinterpret_cast<const char*>(this) + offset(); 55 } 56 57 private: 58 PRel31(const PRel31& copied) = delete; 59 PRel31() = delete; 60 }; 61 62 struct EHEntry { 63 PRel31 startPC; 64 PRel31 exidx; 65 66 private: 67 EHEntry(const EHEntry& copied) = delete; 68 EHEntry() = delete; 69 }; 70 71 class EHState { 72 // Note that any core register can be used as a "frame pointer" to 73 // influence the unwinding process, so this must track all of them. 74 uint32_t mRegs[16]; 75 76 public: 77 bool unwind(const EHEntry* aEntry, const void* stackBase); 78 uint32_t& operator[](int i) { return mRegs[i]; } 79 const uint32_t& operator[](int i) const { return mRegs[i]; } 80 explicit EHState(const mcontext_t&); 81 }; 82 83 enum { R_SP = 13, R_LR = 14, R_PC = 15 }; 84 85 class EHTable { 86 uint32_t mStartPC; 87 uint32_t mEndPC; 88 uint32_t mBaseAddress; 89 const EHEntry* mEntriesBegin; 90 const EHEntry* mEntriesEnd; 91 std::string mName; 92 93 public: 94 EHTable(const void* aELF, size_t aSize, const std::string& aName); 95 const EHEntry* lookup(uint32_t aPC) const; 96 bool isValid() const { return mEntriesEnd != mEntriesBegin; } 97 const std::string& name() const { return mName; } 98 uint32_t startPC() const { return mStartPC; } 99 uint32_t endPC() const { return mEndPC; } 100 uint32_t baseAddress() const { return mBaseAddress; } 101 }; 102 103 class EHAddrSpace { 104 std::vector<uint32_t> mStarts; 105 std::vector<EHTable> mTables; 106 static Atomic<const EHAddrSpace*> sCurrent; 107 108 public: 109 explicit EHAddrSpace(const std::vector<EHTable>& aTables); 110 const EHTable* lookup(uint32_t aPC) const; 111 static void Update(); 112 static const EHAddrSpace* Get(); 113 }; 114 115 void EHABIStackWalkInit() { EHAddrSpace::Update(); } 116 117 size_t EHABIStackWalk(const mcontext_t& aContext, void* stackBase, void** aSPs, 118 void** aPCs, const size_t aNumFrames) { 119 const EHAddrSpace* space = EHAddrSpace::Get(); 120 EHState state(aContext); 121 size_t count = 0; 122 123 while (count < aNumFrames) { 124 uint32_t pc = state[R_PC], sp = state[R_SP]; 125 aPCs[count] = reinterpret_cast<void*>(pc); 126 aSPs[count] = reinterpret_cast<void*>(sp); 127 count++; 128 129 if (!space) break; 130 // TODO: cache these lookups. Binary-searching libxul is 131 // expensive (possibly more expensive than doing the actual 132 // unwind), and even a small cache should help. 133 const EHTable* table = space->lookup(pc); 134 if (!table) break; 135 const EHEntry* entry = table->lookup(pc); 136 if (!entry) break; 137 if (!state.unwind(entry, stackBase)) break; 138 } 139 140 return count; 141 } 142 143 class EHInterp { 144 public: 145 // Note that stackLimit is exclusive and stackBase is inclusive 146 // (i.e, stackLimit < SP <= stackBase), following the convention 147 // set by the AAPCS spec. 148 EHInterp(EHState& aState, const EHEntry* aEntry, uint32_t aStackLimit, 149 uint32_t aStackBase) 150 : mState(aState), 151 mStackLimit(aStackLimit), 152 mStackBase(aStackBase), 153 mNextWord(0), 154 mWordsLeft(0), 155 mFailed(false) { 156 const PRel31& exidx = aEntry->exidx; 157 uint32_t firstWord; 158 159 if (exidx.mBits == 1) { // EXIDX_CANTUNWIND 160 mFailed = true; 161 return; 162 } 163 if (exidx.topBit()) { 164 firstWord = exidx.mBits; 165 } else { 166 mNextWord = reinterpret_cast<const uint32_t*>(exidx.compute()); 167 firstWord = *mNextWord++; 168 } 169 170 switch (firstWord >> 24) { 171 case 0x80: // short 172 mWord = firstWord << 8; 173 mBytesLeft = 3; 174 break; 175 case 0x81: 176 case 0x82: // long; catch descriptor size ignored 177 mWord = firstWord << 16; 178 mBytesLeft = 2; 179 mWordsLeft = (firstWord >> 16) & 0xff; 180 break; 181 default: 182 // unknown personality 183 mFailed = true; 184 } 185 } 186 187 bool unwind(); 188 189 private: 190 // TODO: GCC has been observed not CSEing repeated reads of 191 // mState[R_SP] with writes to mFailed between them, suggesting that 192 // it hasn't determined that they can't alias and is thus missing 193 // optimization opportunities. So, we may want to flatten EHState 194 // into this class; this may also make the code simpler. 195 EHState& mState; 196 uint32_t mStackLimit; 197 uint32_t mStackBase; 198 const uint32_t* mNextWord; 199 uint32_t mWord; 200 uint8_t mWordsLeft; 201 uint8_t mBytesLeft; 202 bool mFailed; 203 204 enum { 205 I_ADDSP = 0x00, // 0sxxxxxx (subtract if s) 206 M_ADDSP = 0x80, 207 I_POPMASK = 0x80, // 1000iiii iiiiiiii (if any i set) 208 M_POPMASK = 0xf0, 209 I_MOVSP = 0x90, // 1001nnnn 210 M_MOVSP = 0xf0, 211 I_POPN = 0xa0, // 1010lnnn 212 M_POPN = 0xf0, 213 I_FINISH = 0xb0, // 10110000 214 I_POPLO = 0xb1, // 10110001 0000iiii (if any i set) 215 I_ADDSPBIG = 0xb2, // 10110010 uleb128 216 I_POPFDX = 0xb3, // 10110011 sssscccc 217 I_POPFDX8 = 0xb8, // 10111nnn 218 M_POPFDX8 = 0xf8, 219 // "Intel Wireless MMX" extensions omitted. 220 I_POPFDD = 0xc8, // 1100100h sssscccc 221 M_POPFDD = 0xfe, 222 I_POPFDD8 = 0xd0, // 11010nnn 223 M_POPFDD8 = 0xf8 224 }; 225 226 uint8_t next() { 227 if (mBytesLeft == 0) { 228 if (mWordsLeft == 0) { 229 return I_FINISH; 230 } 231 mWordsLeft--; 232 mWord = *mNextWord++; 233 mBytesLeft = 4; 234 } 235 mBytesLeft--; 236 mWord = (mWord << 8) | (mWord >> 24); // rotate 237 return mWord; 238 } 239 240 uint32_t& vSP() { return mState[R_SP]; } 241 uint32_t* ptrSP() { return reinterpret_cast<uint32_t*>(vSP()); } 242 243 void checkStackBase() { 244 if (vSP() > mStackBase) mFailed = true; 245 } 246 void checkStackLimit() { 247 if (vSP() <= mStackLimit) mFailed = true; 248 } 249 void checkStackAlign() { 250 if ((vSP() & 3) != 0) mFailed = true; 251 } 252 void checkStack() { 253 checkStackBase(); 254 checkStackLimit(); 255 checkStackAlign(); 256 } 257 258 void popRange(uint8_t first, uint8_t last, uint16_t mask) { 259 bool hasSP = false; 260 uint32_t tmpSP; 261 if (mask == 0) mFailed = true; 262 for (uint8_t r = first; r <= last; ++r) { 263 if (mask & 1) { 264 if (r == R_SP) { 265 hasSP = true; 266 tmpSP = *ptrSP(); 267 } else 268 mState[r] = *ptrSP(); 269 vSP() += 4; 270 checkStackBase(); 271 if (mFailed) return; 272 } 273 mask >>= 1; 274 } 275 if (hasSP) { 276 vSP() = tmpSP; 277 checkStack(); 278 } 279 } 280 }; 281 282 bool EHState::unwind(const EHEntry* aEntry, const void* stackBasePtr) { 283 // The unwinding program cannot set SP to less than the initial value. 284 uint32_t stackLimit = mRegs[R_SP] - 4; 285 uint32_t stackBase = reinterpret_cast<uint32_t>(stackBasePtr); 286 EHInterp interp(*this, aEntry, stackLimit, stackBase); 287 return interp.unwind(); 288 } 289 290 bool EHInterp::unwind() { 291 mState[R_PC] = 0; 292 checkStack(); 293 while (!mFailed) { 294 uint8_t insn = next(); 295 #if DEBUG_EHABI_UNWIND 296 LOG("unwind insn = %02x", (unsigned)insn); 297 #endif 298 // Try to put the common cases first. 299 300 // 00xxxxxx: vsp = vsp + (xxxxxx << 2) + 4 301 // 01xxxxxx: vsp = vsp - (xxxxxx << 2) - 4 302 if ((insn & M_ADDSP) == I_ADDSP) { 303 uint32_t offset = ((insn & 0x3f) << 2) + 4; 304 if (insn & 0x40) { 305 vSP() -= offset; 306 checkStackLimit(); 307 } else { 308 vSP() += offset; 309 checkStackBase(); 310 } 311 continue; 312 } 313 314 // 10100nnn: Pop r4-r[4+nnn] 315 // 10101nnn: Pop r4-r[4+nnn], r14 316 if ((insn & M_POPN) == I_POPN) { 317 uint8_t n = (insn & 0x07) + 1; 318 bool lr = insn & 0x08; 319 uint32_t* ptr = ptrSP(); 320 vSP() += (n + (lr ? 1 : 0)) * 4; 321 checkStackBase(); 322 for (uint8_t r = 4; r < 4 + n; ++r) mState[r] = *ptr++; 323 if (lr) mState[R_LR] = *ptr++; 324 continue; 325 } 326 327 // 1011000: Finish 328 if (insn == I_FINISH) { 329 if (mState[R_PC] == 0) { 330 mState[R_PC] = mState[R_LR]; 331 // Non-standard change (bug 916106): Prevent the caller from 332 // re-using LR. Since the caller is by definition not a leaf 333 // routine, it will have to restore LR from somewhere to 334 // return to its own caller, so we can safely zero it here. 335 // This makes a difference only if an error in unwinding 336 // (e.g., caused by starting from within a prologue/epilogue) 337 // causes us to load a pointer to a leaf routine as LR; if we 338 // don't do something, we'll go into an infinite loop of 339 // "returning" to that same function. 340 mState[R_LR] = 0; 341 } 342 return true; 343 } 344 345 // 1001nnnn: Set vsp = r[nnnn] 346 if ((insn & M_MOVSP) == I_MOVSP) { 347 vSP() = mState[insn & 0x0f]; 348 checkStack(); 349 continue; 350 } 351 352 // 11001000 sssscccc: Pop VFP regs D[16+ssss]-D[16+ssss+cccc] (as FLDMFDD) 353 // 11001001 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDD) 354 if ((insn & M_POPFDD) == I_POPFDD) { 355 uint8_t n = (next() & 0x0f) + 1; 356 // Note: if the 16+ssss+cccc > 31, the encoding is reserved. 357 // As the space is currently unused, we don't try to check. 358 vSP() += 8 * n; 359 checkStackBase(); 360 continue; 361 } 362 363 // 11010nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDD) 364 if ((insn & M_POPFDD8) == I_POPFDD8) { 365 uint8_t n = (insn & 0x07) + 1; 366 vSP() += 8 * n; 367 checkStackBase(); 368 continue; 369 } 370 371 // 10110010 uleb128: vsp = vsp + 0x204 + (uleb128 << 2) 372 if (insn == I_ADDSPBIG) { 373 uint32_t acc = 0; 374 uint8_t shift = 0; 375 uint8_t byte; 376 do { 377 if (shift >= 32) return false; 378 byte = next(); 379 acc |= (byte & 0x7f) << shift; 380 shift += 7; 381 } while (byte & 0x80); 382 uint32_t offset = 0x204 + (acc << 2); 383 // The calculations above could have overflowed. 384 // But the one we care about is this: 385 if (vSP() + offset < vSP()) mFailed = true; 386 vSP() += offset; 387 // ...so that this is the only other check needed: 388 checkStackBase(); 389 continue; 390 } 391 392 // 1000iiii iiiiiiii (i not all 0): Pop under masks {r15-r12}, {r11-r4} 393 if ((insn & M_POPMASK) == I_POPMASK) { 394 popRange(4, 15, ((insn & 0x0f) << 8) | next()); 395 continue; 396 } 397 398 // 1011001 0000iiii (i not all 0): Pop under mask {r3-r0} 399 if (insn == I_POPLO) { 400 popRange(0, 3, next() & 0x0f); 401 continue; 402 } 403 404 // 10110011 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDX) 405 if (insn == I_POPFDX) { 406 uint8_t n = (next() & 0x0f) + 1; 407 vSP() += 8 * n + 4; 408 checkStackBase(); 409 continue; 410 } 411 412 // 10111nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDX) 413 if ((insn & M_POPFDX8) == I_POPFDX8) { 414 uint8_t n = (insn & 0x07) + 1; 415 vSP() += 8 * n + 4; 416 checkStackBase(); 417 continue; 418 } 419 420 // unhandled instruction 421 #ifdef DEBUG_EHABI_UNWIND 422 LOG("Unhandled EHABI instruction 0x%02x", insn); 423 #endif 424 mFailed = true; 425 } 426 return false; 427 } 428 429 bool operator<(const EHTable& lhs, const EHTable& rhs) { 430 return lhs.startPC() < rhs.startPC(); 431 } 432 433 // Async signal unsafe. 434 EHAddrSpace::EHAddrSpace(const std::vector<EHTable>& aTables) 435 : mTables(aTables) { 436 std::sort(mTables.begin(), mTables.end()); 437 DebugOnly<uint32_t> lastEnd = 0; 438 for (std::vector<EHTable>::iterator i = mTables.begin(); i != mTables.end(); 439 ++i) { 440 MOZ_ASSERT(i->startPC() >= lastEnd); 441 mStarts.push_back(i->startPC()); 442 lastEnd = i->endPC(); 443 } 444 } 445 446 const EHTable* EHAddrSpace::lookup(uint32_t aPC) const { 447 ptrdiff_t i = (std::upper_bound(mStarts.begin(), mStarts.end(), aPC) - 448 mStarts.begin()) - 449 1; 450 451 if (i < 0 || aPC >= mTables[i].endPC()) return 0; 452 return &mTables[i]; 453 } 454 455 const EHEntry* EHTable::lookup(uint32_t aPC) const { 456 MOZ_ASSERT(aPC >= mStartPC); 457 if (aPC >= mEndPC) return nullptr; 458 459 const EHEntry* begin = mEntriesBegin; 460 const EHEntry* end = mEntriesEnd; 461 MOZ_ASSERT(begin < end); 462 if (aPC < reinterpret_cast<uint32_t>(begin->startPC.compute())) 463 return nullptr; 464 465 while (end - begin > 1) { 466 #ifdef EHABI_UNWIND_MORE_ASSERTS 467 if ((end - 1)->startPC.compute() < begin->startPC.compute()) { 468 MOZ_CRASH("unsorted exidx"); 469 } 470 #endif 471 const EHEntry* mid = begin + (end - begin) / 2; 472 if (aPC < reinterpret_cast<uint32_t>(mid->startPC.compute())) 473 end = mid; 474 else 475 begin = mid; 476 } 477 return begin; 478 } 479 480 #if MOZ_LITTLE_ENDIAN() 481 static const unsigned char hostEndian = ELFDATA2LSB; 482 #elif MOZ_BIG_ENDIAN() 483 static const unsigned char hostEndian = ELFDATA2MSB; 484 #else 485 # error "No endian?" 486 #endif 487 488 // Async signal unsafe: std::vector::reserve, std::string copy ctor. 489 EHTable::EHTable(const void* aELF, size_t aSize, const std::string& aName) 490 : mStartPC(~0), // largest uint32_t 491 mEndPC(0), 492 mEntriesBegin(nullptr), 493 mEntriesEnd(nullptr), 494 mName(aName) { 495 const uint32_t fileHeaderAddr = reinterpret_cast<uint32_t>(aELF); 496 497 if (aSize < sizeof(Elf32_Ehdr)) return; 498 499 const Elf32_Ehdr& file = *(reinterpret_cast<Elf32_Ehdr*>(fileHeaderAddr)); 500 if (memcmp(&file.e_ident[EI_MAG0], ELFMAG, SELFMAG) != 0 || 501 file.e_ident[EI_CLASS] != ELFCLASS32 || 502 file.e_ident[EI_DATA] != hostEndian || 503 file.e_ident[EI_VERSION] != EV_CURRENT || file.e_machine != EM_ARM || 504 file.e_version != EV_CURRENT) 505 // e_flags? 506 return; 507 508 MOZ_ASSERT(file.e_phoff + file.e_phnum * file.e_phentsize <= aSize); 509 const Elf32_Phdr *exidxHdr = 0, *zeroHdr = 0; 510 for (unsigned i = 0; i < file.e_phnum; ++i) { 511 const Elf32_Phdr& phdr = *(reinterpret_cast<Elf32_Phdr*>( 512 fileHeaderAddr + file.e_phoff + i * file.e_phentsize)); 513 if (phdr.p_type == PT_ARM_EXIDX) { 514 exidxHdr = &phdr; 515 } else if (phdr.p_type == PT_LOAD) { 516 if (phdr.p_offset == 0) { 517 zeroHdr = &phdr; 518 } 519 if (phdr.p_flags & PF_X) { 520 mStartPC = std::min(mStartPC, phdr.p_vaddr); 521 mEndPC = std::max(mEndPC, phdr.p_vaddr + phdr.p_memsz); 522 } 523 } 524 } 525 if (!exidxHdr) return; 526 if (!zeroHdr) return; 527 mBaseAddress = fileHeaderAddr - zeroHdr->p_vaddr; 528 mStartPC += mBaseAddress; 529 mEndPC += mBaseAddress; 530 mEntriesBegin = 531 reinterpret_cast<const EHEntry*>(mBaseAddress + exidxHdr->p_vaddr); 532 mEntriesEnd = reinterpret_cast<const EHEntry*>( 533 mBaseAddress + exidxHdr->p_vaddr + exidxHdr->p_memsz); 534 } 535 536 Atomic<const EHAddrSpace*> EHAddrSpace::sCurrent(nullptr); 537 538 // Async signal safe; can fail if Update() hasn't returned yet. 539 const EHAddrSpace* EHAddrSpace::Get() { return sCurrent; } 540 541 // Collect unwinding information from loaded objects. Calls after the 542 // first have no effect. Async signal unsafe. 543 void EHAddrSpace::Update() { 544 const EHAddrSpace* space = sCurrent; 545 if (space) return; 546 547 SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf(); 548 std::vector<EHTable> tables; 549 550 for (size_t i = 0; i < info.GetSize(); ++i) { 551 const SharedLibrary& lib = info.GetEntry(i); 552 // FIXME: This isn't correct if the start address isn't p_offset 0, because 553 // the start address will not point at the file header. But this is worked 554 // around by magic number checks in the EHTable constructor. 555 EHTable tab(reinterpret_cast<const void*>(lib.GetStart()), 556 lib.GetEnd() - lib.GetStart(), lib.GetDebugPath()); 557 if (tab.isValid()) tables.push_back(tab); 558 } 559 space = new EHAddrSpace(tables); 560 561 if (!sCurrent.compareExchange(nullptr, space)) { 562 delete space; 563 space = sCurrent; 564 } 565 } 566 567 EHState::EHState(const mcontext_t& context) { 568 #ifdef linux 569 mRegs[0] = context.arm_r0; 570 mRegs[1] = context.arm_r1; 571 mRegs[2] = context.arm_r2; 572 mRegs[3] = context.arm_r3; 573 mRegs[4] = context.arm_r4; 574 mRegs[5] = context.arm_r5; 575 mRegs[6] = context.arm_r6; 576 mRegs[7] = context.arm_r7; 577 mRegs[8] = context.arm_r8; 578 mRegs[9] = context.arm_r9; 579 mRegs[10] = context.arm_r10; 580 mRegs[11] = context.arm_fp; 581 mRegs[12] = context.arm_ip; 582 mRegs[13] = context.arm_sp; 583 mRegs[14] = context.arm_lr; 584 mRegs[15] = context.arm_pc; 585 #else 586 # error "Unhandled OS for ARM EHABI unwinding" 587 #endif 588 } 589 590 } // namespace baseprofiler 591 } // namespace mozilla