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/. */
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
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.
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.
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.
25 #include "EHABIStackWalk.h"
27 #include "SharedLibraries.h"
30 #include "mozilla/Atomics.h"
31 #include "mozilla/Attributes.h"
32 #include "mozilla/DebugOnly.h"
33 #include "mozilla/EndianUtils.h"
42 # define PT_ARM_EXIDX 0x70000001
49 bool topBit() const { return mBits
& 0x80000000; }
50 uint32_t value() const { return mBits
& 0x7fffffff; }
51 int32_t offset() const { return (static_cast<int32_t>(mBits
) << 1) >> 1; }
52 const void* compute() const {
53 return reinterpret_cast<const char*>(this) + offset();
57 PRel31(const PRel31
& copied
) = delete;
66 EHEntry(const EHEntry
& copied
) = delete;
71 // Note that any core register can be used as a "frame pointer" to
72 // influence the unwinding process, so this must track all of them.
76 bool unwind(const EHEntry
* aEntry
, const void* stackBase
);
77 uint32_t& operator[](int i
) { return mRegs
[i
]; }
78 const uint32_t& operator[](int i
) const { return mRegs
[i
]; }
79 explicit EHState(const mcontext_t
&);
82 enum { R_SP
= 13, R_LR
= 14, R_PC
= 15 };
87 uint32_t mBaseAddress
;
88 const EHEntry
* mEntriesBegin
;
89 const EHEntry
* mEntriesEnd
;
93 EHTable(const void* aELF
, size_t aSize
, const std::string
& aName
);
94 const EHEntry
* lookup(uint32_t aPC
) const;
95 bool isValid() const { return mEntriesEnd
!= mEntriesBegin
; }
96 const std::string
& name() const { return mName
; }
97 uint32_t startPC() const { return mStartPC
; }
98 uint32_t endPC() const { return mEndPC
; }
99 uint32_t baseAddress() const { return mBaseAddress
; }
103 std::vector
<uint32_t> mStarts
;
104 std::vector
<EHTable
> mTables
;
105 static mozilla::Atomic
<const EHAddrSpace
*> sCurrent
;
108 explicit EHAddrSpace(const std::vector
<EHTable
>& aTables
);
109 const EHTable
* lookup(uint32_t aPC
) const;
110 static void Update();
111 static const EHAddrSpace
* Get();
114 void EHABIStackWalkInit() { EHAddrSpace::Update(); }
116 size_t EHABIStackWalk(const mcontext_t
& aContext
, void* stackBase
, void** aSPs
,
117 void** aPCs
, const size_t aNumFrames
) {
118 const EHAddrSpace
* space
= EHAddrSpace::Get();
119 EHState
state(aContext
);
122 while (count
< aNumFrames
) {
123 uint32_t pc
= state
[R_PC
], sp
= state
[R_SP
];
125 // ARM instructions are always aligned to 2 or 4 bytes.
126 // The last bit of the pc / lr indicates ARM or Thumb mode.
127 // We're only interested in the instruction address, so we mask off that
129 constexpr uint32_t instrAddrMask
= ~1;
130 uint32_t instrAddress
= pc
& instrAddrMask
;
132 aPCs
[count
] = reinterpret_cast<void*>(instrAddress
);
133 aSPs
[count
] = reinterpret_cast<void*>(sp
);
137 // TODO: cache these lookups. Binary-searching libxul is
138 // expensive (possibly more expensive than doing the actual
139 // unwind), and even a small cache should help.
140 const EHTable
* table
= space
->lookup(pc
);
142 const EHEntry
* entry
= table
->lookup(pc
);
144 if (!state
.unwind(entry
, stackBase
)) break;
152 // Note that stackLimit is exclusive and stackBase is inclusive
153 // (i.e, stackLimit < SP <= stackBase), following the convention
154 // set by the AAPCS spec.
155 EHInterp(EHState
& aState
, const EHEntry
* aEntry
, uint32_t aStackLimit
,
158 mStackLimit(aStackLimit
),
159 mStackBase(aStackBase
),
163 const PRel31
& exidx
= aEntry
->exidx
;
166 if (exidx
.mBits
== 1) { // EXIDX_CANTUNWIND
170 if (exidx
.topBit()) {
171 firstWord
= exidx
.mBits
;
173 mNextWord
= reinterpret_cast<const uint32_t*>(exidx
.compute());
174 firstWord
= *mNextWord
++;
177 switch (firstWord
>> 24) {
179 mWord
= firstWord
<< 8;
183 case 0x82: // long; catch descriptor size ignored
184 mWord
= firstWord
<< 16;
186 mWordsLeft
= (firstWord
>> 16) & 0xff;
189 // unknown personality
197 // TODO: GCC has been observed not CSEing repeated reads of
198 // mState[R_SP] with writes to mFailed between them, suggesting that
199 // it hasn't determined that they can't alias and is thus missing
200 // optimization opportunities. So, we may want to flatten EHState
201 // into this class; this may also make the code simpler.
203 uint32_t mStackLimit
;
205 const uint32_t* mNextWord
;
212 I_ADDSP
= 0x00, // 0sxxxxxx (subtract if s)
214 I_POPMASK
= 0x80, // 1000iiii iiiiiiii (if any i set)
216 I_MOVSP
= 0x90, // 1001nnnn
218 I_POPN
= 0xa0, // 1010lnnn
220 I_FINISH
= 0xb0, // 10110000
221 I_POPLO
= 0xb1, // 10110001 0000iiii (if any i set)
222 I_ADDSPBIG
= 0xb2, // 10110010 uleb128
223 I_POPFDX
= 0xb3, // 10110011 sssscccc
224 I_POPFDX8
= 0xb8, // 10111nnn
226 // "Intel Wireless MMX" extensions omitted.
227 I_POPFDD
= 0xc8, // 1100100h sssscccc
229 I_POPFDD8
= 0xd0, // 11010nnn
234 if (mBytesLeft
== 0) {
235 if (mWordsLeft
== 0) {
239 mWord
= *mNextWord
++;
243 mWord
= (mWord
<< 8) | (mWord
>> 24); // rotate
247 uint32_t& vSP() { return mState
[R_SP
]; }
248 uint32_t* ptrSP() { return reinterpret_cast<uint32_t*>(vSP()); }
250 void checkStackBase() {
251 if (vSP() > mStackBase
) mFailed
= true;
253 void checkStackLimit() {
254 if (vSP() <= mStackLimit
) mFailed
= true;
256 void checkStackAlign() {
257 if ((vSP() & 3) != 0) mFailed
= true;
265 void popRange(uint8_t first
, uint8_t last
, uint16_t mask
) {
268 if (mask
== 0) mFailed
= true;
269 for (uint8_t r
= first
; r
<= last
; ++r
) {
275 mState
[r
] = *ptrSP();
289 bool EHState::unwind(const EHEntry
* aEntry
, const void* stackBasePtr
) {
290 // The unwinding program cannot set SP to less than the initial value.
291 uint32_t stackLimit
= mRegs
[R_SP
] - 4;
292 uint32_t stackBase
= reinterpret_cast<uint32_t>(stackBasePtr
);
293 EHInterp
interp(*this, aEntry
, stackLimit
, stackBase
);
294 return interp
.unwind();
297 bool EHInterp::unwind() {
301 uint8_t insn
= next();
302 #if DEBUG_EHABI_UNWIND
303 LOG("unwind insn = %02x", (unsigned)insn
);
305 // Try to put the common cases first.
307 // 00xxxxxx: vsp = vsp + (xxxxxx << 2) + 4
308 // 01xxxxxx: vsp = vsp - (xxxxxx << 2) - 4
309 if ((insn
& M_ADDSP
) == I_ADDSP
) {
310 uint32_t offset
= ((insn
& 0x3f) << 2) + 4;
321 // 10100nnn: Pop r4-r[4+nnn]
322 // 10101nnn: Pop r4-r[4+nnn], r14
323 if ((insn
& M_POPN
) == I_POPN
) {
324 uint8_t n
= (insn
& 0x07) + 1;
325 bool lr
= insn
& 0x08;
326 uint32_t* ptr
= ptrSP();
327 vSP() += (n
+ (lr
? 1 : 0)) * 4;
329 for (uint8_t r
= 4; r
< 4 + n
; ++r
) mState
[r
] = *ptr
++;
330 if (lr
) mState
[R_LR
] = *ptr
++;
335 if (insn
== I_FINISH
) {
336 if (mState
[R_PC
] == 0) {
337 mState
[R_PC
] = mState
[R_LR
];
338 // Non-standard change (bug 916106): Prevent the caller from
339 // re-using LR. Since the caller is by definition not a leaf
340 // routine, it will have to restore LR from somewhere to
341 // return to its own caller, so we can safely zero it here.
342 // This makes a difference only if an error in unwinding
343 // (e.g., caused by starting from within a prologue/epilogue)
344 // causes us to load a pointer to a leaf routine as LR; if we
345 // don't do something, we'll go into an infinite loop of
346 // "returning" to that same function.
352 // 1001nnnn: Set vsp = r[nnnn]
353 if ((insn
& M_MOVSP
) == I_MOVSP
) {
354 vSP() = mState
[insn
& 0x0f];
359 // 11001000 sssscccc: Pop VFP regs D[16+ssss]-D[16+ssss+cccc] (as FLDMFDD)
360 // 11001001 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDD)
361 if ((insn
& M_POPFDD
) == I_POPFDD
) {
362 uint8_t n
= (next() & 0x0f) + 1;
363 // Note: if the 16+ssss+cccc > 31, the encoding is reserved.
364 // As the space is currently unused, we don't try to check.
370 // 11010nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDD)
371 if ((insn
& M_POPFDD8
) == I_POPFDD8
) {
372 uint8_t n
= (insn
& 0x07) + 1;
378 // 10110010 uleb128: vsp = vsp + 0x204 + (uleb128 << 2)
379 if (insn
== I_ADDSPBIG
) {
384 if (shift
>= 32) return false;
386 acc
|= (byte
& 0x7f) << shift
;
388 } while (byte
& 0x80);
389 uint32_t offset
= 0x204 + (acc
<< 2);
390 // The calculations above could have overflowed.
391 // But the one we care about is this:
392 if (vSP() + offset
< vSP()) mFailed
= true;
394 // ...so that this is the only other check needed:
399 // 1000iiii iiiiiiii (i not all 0): Pop under masks {r15-r12}, {r11-r4}
400 if ((insn
& M_POPMASK
) == I_POPMASK
) {
401 popRange(4, 15, ((insn
& 0x0f) << 8) | next());
405 // 1011001 0000iiii (i not all 0): Pop under mask {r3-r0}
406 if (insn
== I_POPLO
) {
407 popRange(0, 3, next() & 0x0f);
411 // 10110011 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDX)
412 if (insn
== I_POPFDX
) {
413 uint8_t n
= (next() & 0x0f) + 1;
419 // 10111nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDX)
420 if ((insn
& M_POPFDX8
) == I_POPFDX8
) {
421 uint8_t n
= (insn
& 0x07) + 1;
427 // unhandled instruction
428 #ifdef DEBUG_EHABI_UNWIND
429 LOG("Unhandled EHABI instruction 0x%02x", insn
);
436 bool operator<(const EHTable
& lhs
, const EHTable
& rhs
) {
437 return lhs
.startPC() < rhs
.startPC();
440 // Async signal unsafe.
441 EHAddrSpace::EHAddrSpace(const std::vector
<EHTable
>& aTables
)
443 std::sort(mTables
.begin(), mTables
.end());
444 DebugOnly
<uint32_t> lastEnd
= 0;
445 for (std::vector
<EHTable
>::iterator i
= mTables
.begin(); i
!= mTables
.end();
447 MOZ_ASSERT(i
->startPC() >= lastEnd
);
448 mStarts
.push_back(i
->startPC());
449 lastEnd
= i
->endPC();
453 const EHTable
* EHAddrSpace::lookup(uint32_t aPC
) const {
454 ptrdiff_t i
= (std::upper_bound(mStarts
.begin(), mStarts
.end(), aPC
) -
458 if (i
< 0 || aPC
>= mTables
[i
].endPC()) return 0;
462 const EHEntry
* EHTable::lookup(uint32_t aPC
) const {
463 MOZ_ASSERT(aPC
>= mStartPC
);
464 if (aPC
>= mEndPC
) return nullptr;
466 const EHEntry
* begin
= mEntriesBegin
;
467 const EHEntry
* end
= mEntriesEnd
;
468 MOZ_ASSERT(begin
< end
);
469 if (aPC
< reinterpret_cast<uint32_t>(begin
->startPC
.compute()))
472 while (end
- begin
> 1) {
473 #ifdef EHABI_UNWIND_MORE_ASSERTS
474 if ((end
- 1)->startPC
.compute() < begin
->startPC
.compute()) {
475 MOZ_CRASH("unsorted exidx");
478 const EHEntry
* mid
= begin
+ (end
- begin
) / 2;
479 if (aPC
< reinterpret_cast<uint32_t>(mid
->startPC
.compute()))
487 #if MOZ_LITTLE_ENDIAN()
488 static const unsigned char hostEndian
= ELFDATA2LSB
;
489 #elif MOZ_BIG_ENDIAN()
490 static const unsigned char hostEndian
= ELFDATA2MSB
;
495 // Async signal unsafe: std::vector::reserve, std::string copy ctor.
496 EHTable::EHTable(const void* aELF
, size_t aSize
, const std::string
& aName
)
497 : mStartPC(~0), // largest uint32_t
499 mEntriesBegin(nullptr),
500 mEntriesEnd(nullptr),
502 const uint32_t fileHeaderAddr
= reinterpret_cast<uint32_t>(aELF
);
504 if (aSize
< sizeof(Elf32_Ehdr
)) return;
506 const Elf32_Ehdr
& file
= *(reinterpret_cast<Elf32_Ehdr
*>(fileHeaderAddr
));
507 if (memcmp(&file
.e_ident
[EI_MAG0
], ELFMAG
, SELFMAG
) != 0 ||
508 file
.e_ident
[EI_CLASS
] != ELFCLASS32
||
509 file
.e_ident
[EI_DATA
] != hostEndian
||
510 file
.e_ident
[EI_VERSION
] != EV_CURRENT
|| file
.e_machine
!= EM_ARM
||
511 file
.e_version
!= EV_CURRENT
)
515 MOZ_ASSERT(file
.e_phoff
+ file
.e_phnum
* file
.e_phentsize
<= aSize
);
516 const Elf32_Phdr
*exidxHdr
= 0, *zeroHdr
= 0;
517 for (unsigned i
= 0; i
< file
.e_phnum
; ++i
) {
518 const Elf32_Phdr
& phdr
= *(reinterpret_cast<Elf32_Phdr
*>(
519 fileHeaderAddr
+ file
.e_phoff
+ i
* file
.e_phentsize
));
520 if (phdr
.p_type
== PT_ARM_EXIDX
) {
522 } else if (phdr
.p_type
== PT_LOAD
) {
523 if (phdr
.p_offset
== 0) {
526 if (phdr
.p_flags
& PF_X
) {
527 mStartPC
= std::min(mStartPC
, phdr
.p_vaddr
);
528 mEndPC
= std::max(mEndPC
, phdr
.p_vaddr
+ phdr
.p_memsz
);
532 if (!exidxHdr
) return;
533 if (!zeroHdr
) return;
534 mBaseAddress
= fileHeaderAddr
- zeroHdr
->p_vaddr
;
535 mStartPC
+= mBaseAddress
;
536 mEndPC
+= mBaseAddress
;
538 reinterpret_cast<const EHEntry
*>(mBaseAddress
+ exidxHdr
->p_vaddr
);
539 mEntriesEnd
= reinterpret_cast<const EHEntry
*>(
540 mBaseAddress
+ exidxHdr
->p_vaddr
+ exidxHdr
->p_memsz
);
543 mozilla::Atomic
<const EHAddrSpace
*> EHAddrSpace::sCurrent(nullptr);
545 // Async signal safe; can fail if Update() hasn't returned yet.
546 const EHAddrSpace
* EHAddrSpace::Get() { return sCurrent
; }
548 // Collect unwinding information from loaded objects. Calls after the
549 // first have no effect. Async signal unsafe.
550 void EHAddrSpace::Update() {
551 const EHAddrSpace
* space
= sCurrent
;
554 SharedLibraryInfo info
= SharedLibraryInfo::GetInfoForSelf();
555 std::vector
<EHTable
> tables
;
557 for (size_t i
= 0; i
< info
.GetSize(); ++i
) {
558 const SharedLibrary
& lib
= info
.GetEntry(i
);
559 // FIXME: This isn't correct if the start address isn't p_offset 0, because
560 // the start address will not point at the file header. But this is worked
561 // around by magic number checks in the EHTable constructor.
562 EHTable
tab(reinterpret_cast<const void*>(lib
.GetStart()),
563 lib
.GetEnd() - lib
.GetStart(), lib
.GetDebugPath());
564 if (tab
.isValid()) tables
.push_back(tab
);
566 space
= new EHAddrSpace(tables
);
568 if (!sCurrent
.compareExchange(nullptr, space
)) {
574 EHState::EHState(const mcontext_t
& context
) {
576 mRegs
[0] = context
.arm_r0
;
577 mRegs
[1] = context
.arm_r1
;
578 mRegs
[2] = context
.arm_r2
;
579 mRegs
[3] = context
.arm_r3
;
580 mRegs
[4] = context
.arm_r4
;
581 mRegs
[5] = context
.arm_r5
;
582 mRegs
[6] = context
.arm_r6
;
583 mRegs
[7] = context
.arm_r7
;
584 mRegs
[8] = context
.arm_r8
;
585 mRegs
[9] = context
.arm_r9
;
586 mRegs
[10] = context
.arm_r10
;
587 mRegs
[11] = context
.arm_fp
;
588 mRegs
[12] = context
.arm_ip
;
589 mRegs
[13] = context
.arm_sp
;
590 mRegs
[14] = context
.arm_lr
;
591 mRegs
[15] = context
.arm_pc
;
593 # error "Unhandled OS for ARM EHABI unwinding"
597 } // namespace mozilla