244d4da93a
Makes things more consistent, at least within this directory.
286 linhas
10 KiB
C++
286 linhas
10 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "hphp/runtime/vm/jit/unwind-x64.h"
|
|
|
|
#include <libunwind.h>
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <cxxabi.h>
|
|
#include <boost/mpl/identity.hpp>
|
|
|
|
#include "hphp/runtime/vm/jit/abi-x64.h"
|
|
#include "hphp/runtime/vm/jit/runtime-type.h"
|
|
#include "hphp/runtime/vm/jit/target-cache.h"
|
|
#include "hphp/runtime/vm/jit/translator-x64.h"
|
|
#include "hphp/runtime/base/stats.h"
|
|
#include "hphp/runtime/vm/runtime.h"
|
|
#include "hphp/runtime/vm/member_operations.h"
|
|
|
|
// libgcc exports this for registering eh information for
|
|
// dynamically-loaded objects. The pointer is to data in the format
|
|
// you find in a .eh_frame section.
|
|
extern "C" void __register_frame(void*);
|
|
extern "C" void __deregister_frame(void*);
|
|
|
|
TRACE_SET_MOD(unwind);
|
|
|
|
namespace HPHP { namespace Transl {
|
|
|
|
namespace {
|
|
|
|
template<class T>
|
|
void append_vec(std::vector<char>& v,
|
|
// Prevent template argument deduction:
|
|
typename boost::mpl::identity<T>::type t) {
|
|
size_t idx = v.size();
|
|
v.resize(idx + sizeof(T));
|
|
char* caddr = &v[idx];
|
|
std::memcpy(caddr, &t, sizeof t);
|
|
}
|
|
|
|
void sync_regstate(_Unwind_Context* context) {
|
|
assert(tl_regState == VMRegState::DIRTY);
|
|
|
|
uintptr_t frameRbp = _Unwind_GetGR(context, Debug::RBP);
|
|
uintptr_t frameRip = _Unwind_GetGR(context, Debug::RIP);
|
|
|
|
/*
|
|
* fixupWork expects to be looking at the first frame that is out of
|
|
* the TC. We have RBP/RIP for the TC frame that called out here,
|
|
* so we make a fake ActRec here to give it what it expects.
|
|
*
|
|
* Note: this doesn't work for IndirectFixup situations. However,
|
|
* currently IndirectFixup is only used for destructors, which
|
|
* aren't allowed to throw, so this is ok.
|
|
*/
|
|
ActRec fakeAr;
|
|
fakeAr.m_savedRbp = frameRbp;
|
|
fakeAr.m_savedRip = frameRip;
|
|
|
|
Stats::inc(Stats::TC_SyncUnwind);
|
|
tx64->fixupWork(g_vmContext, &fakeAr);
|
|
tl_regState = VMRegState::CLEAN;
|
|
}
|
|
|
|
bool install_catch_trace(_Unwind_Context* ctx, _Unwind_Exception* exn,
|
|
InvalidSetMException* ism) {
|
|
const CTCA rip = (CTCA)_Unwind_GetIP(ctx);
|
|
TCA catchTrace = tx64->getCatchTrace(rip);
|
|
assert(IMPLIES(ism, catchTrace));
|
|
if (!catchTrace) return false;
|
|
|
|
FTRACE(1, "installing catch trace {} for call {} with ism {}, "
|
|
"returning _URC_INSTALL_CONTEXT\n",
|
|
catchTrace, rip, ism);
|
|
|
|
// In theory the unwind api will let us set registers in the frame before
|
|
// executing our landing pad. In practice, trying to use their recommended
|
|
// scratch registers results in a SEGV inside _Unwind_SetGR, so we pass
|
|
// things to the handler using the target cache. This also simplifies the
|
|
// handler code because it doesn't have to worry about saving its arguments
|
|
// somewhere while executing the exit trace.
|
|
TargetCache::header()->unwinderScratch = (int64_t)exn;
|
|
TargetCache::header()->doSideExit = ism;
|
|
if (ism) {
|
|
TargetCache::header()->unwinderTv = ism->tv();
|
|
}
|
|
_Unwind_SetIP(ctx, (uint64_t)catchTrace);
|
|
tl_regState = VMRegState::DIRTY;
|
|
|
|
return true;
|
|
}
|
|
|
|
_Unwind_Reason_Code
|
|
tc_unwind_personality(int version,
|
|
_Unwind_Action actions,
|
|
uint64_t exceptionClass,
|
|
_Unwind_Exception* exceptionObj,
|
|
_Unwind_Context* context) {
|
|
using namespace abi;
|
|
// Exceptions thrown by g++-generated code will have the class "GNUCC++"
|
|
// packed into a 64-bit int. For now we shouldn't be seeing exceptions from
|
|
// any other runtimes but this may change in the future.
|
|
DEBUG_ONLY constexpr uint64_t kMagicClass = 0x474e5543432b2b00;
|
|
assert(exceptionClass == kMagicClass);
|
|
assert(version == 1);
|
|
|
|
auto const& ti = typeInfoFromUnwindException(exceptionObj);
|
|
InvalidSetMException* ism = nullptr;
|
|
if (ti == typeid(InvalidSetMException)) {
|
|
ism = static_cast<InvalidSetMException*>(
|
|
exceptionFromUnwindException(exceptionObj));
|
|
}
|
|
|
|
|
|
if (Trace::moduleEnabled(TRACEMOD, 1)) {
|
|
DEBUG_ONLY auto const* unwindType =
|
|
(actions & _UA_SEARCH_PHASE) ? "search" : "cleanup";
|
|
int status;
|
|
auto* exnType = __cxa_demangle(ti.name(), nullptr, nullptr, &status);
|
|
SCOPE_EXIT { free(exnType); };
|
|
assert(status == 0);
|
|
FTRACE(1, "unwind {} exn {}: regState: {} ip: {} type: {}. ",
|
|
unwindType, exceptionObj,
|
|
tl_regState == VMRegState::DIRTY ? "dirty" : "clean",
|
|
(TCA)_Unwind_GetIP(context), exnType);
|
|
}
|
|
|
|
/*
|
|
* We don't do anything during the search phase---before attempting
|
|
* cleanup, we want all deeper frames to have run their object
|
|
* destructors (which can have side effects like setting
|
|
* tl_regState) and spilled any values they may have been holding in
|
|
* callee-saved regs.
|
|
*/
|
|
if (actions & _UA_SEARCH_PHASE) {
|
|
if (ism) {
|
|
FTRACE(1, "thrown value: {} returning _URC_HANDLER_FOUND\n ",
|
|
ism->tv().pretty());
|
|
return _URC_HANDLER_FOUND;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* During the cleanup phase, we can either use a landing pad to perform
|
|
* cleanup (with _Unwind_SetIP and _URC_INSTALL_CONTEXT), or we can do it
|
|
* here. We sync the VM registers here, then optionally use a landing pad,
|
|
* which is an exit traces from hhir with a few special instructions.
|
|
*/
|
|
else if (actions & _UA_CLEANUP_PHASE) {
|
|
if (tl_regState == VMRegState::DIRTY) {
|
|
sync_regstate(context);
|
|
}
|
|
if (install_catch_trace(context, exceptionObj, ism)) {
|
|
return _URC_INSTALL_CONTEXT;
|
|
}
|
|
}
|
|
|
|
FTRACE(1, "returning _URC_CONTINUE_UNWIND\n");
|
|
return _URC_CONTINUE_UNWIND;
|
|
}
|
|
|
|
void deregister_unwind_region(std::vector<char>* p) {
|
|
std::auto_ptr<std::vector<char> > del(p);
|
|
__deregister_frame(&(*p)[0]);
|
|
}
|
|
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
UnwindInfoHandle
|
|
register_unwind_region(unsigned char* startAddr, size_t size) {
|
|
std::unique_ptr<std::vector<char>> bufferMem(new std::vector<char>);
|
|
std::vector<char>& buffer = *bufferMem;
|
|
|
|
{
|
|
// This is a dwarf CIE header. Looks the same as a fde except the
|
|
// second field is zero.
|
|
append_vec<uint32_t>(buffer, 0); // Room for length later
|
|
append_vec<int32_t>(buffer, 0); // CIE_id
|
|
append_vec<uint8_t>(buffer, 1); // version
|
|
|
|
/*
|
|
* Null-terminated "augmentation string" (defines what the rest of
|
|
* this thing is going to have.
|
|
*
|
|
* TODO: probably we should have a 'z' field to indicate length.
|
|
*/
|
|
append_vec<char>(buffer, 'P');
|
|
append_vec<char>(buffer, '\0');
|
|
|
|
// Code and data alignment.
|
|
append_vec<uint8_t>(buffer, 1);
|
|
append_vec<uint8_t>(buffer, 8); // Multiplies offsets below.
|
|
|
|
// Return address column (in version 1, this is a single byte).
|
|
append_vec<uint8_t>(buffer, Debug::RIP);
|
|
|
|
// Pointer to the personality routine for the TC.
|
|
append_vec<uint8_t>(buffer, DW_EH_PE_absptr);
|
|
append_vec<uintptr_t>(buffer, uintptr_t(tc_unwind_personality));
|
|
|
|
/*
|
|
* Define a program for the CIE. This explains to the unwinder
|
|
* how to figure out where the frame pointer was, etc.
|
|
*
|
|
* Arguments to some of these are encoded in LEB128, so we have to
|
|
* clear the high bit for the signed values.
|
|
*/
|
|
// Previous FP (CFA) is at rbp + 16.
|
|
append_vec<uint8_t>(buffer, DW_CFA_def_cfa);
|
|
append_vec<uint8_t>(buffer, Debug::RBP);
|
|
append_vec<uint8_t>(buffer, 16);
|
|
// rip is at CFA - 1 * data_align.
|
|
append_vec<uint8_t>(buffer, DW_CFA_offset_extended_sf);
|
|
append_vec<uint8_t>(buffer, Debug::RIP);
|
|
append_vec<uint8_t>(buffer, -1u & 0x7f);
|
|
// rbp is at CFA - 2 * data_align.
|
|
append_vec<uint8_t>(buffer, DW_CFA_offset_extended_sf);
|
|
append_vec<uint8_t>(buffer, Debug::RBP);
|
|
append_vec<uint8_t>(buffer, -2u & 0x7f);
|
|
/*
|
|
* Leave rsp unchanged.
|
|
*
|
|
* Note that some things in the translator do actually change rsp,
|
|
* but we assume they cannot throw so this is ok. If rVmSp ever
|
|
* changes to use rsp this code must change.
|
|
*/
|
|
append_vec<uint8_t>(buffer, DW_CFA_same_value);
|
|
append_vec<uint8_t>(buffer, Debug::RSP);
|
|
|
|
// Fixup the length field. Note that it doesn't include the space
|
|
// for itself.
|
|
void* vp = &buffer[0];
|
|
*static_cast<uint32_t*>(vp) = buffer.size() - sizeof(uint32_t);
|
|
}
|
|
const size_t fdeIdx = buffer.size();
|
|
{
|
|
// Reserve space for FDE length.
|
|
append_vec<uint32_t>(buffer, 0);
|
|
|
|
// Negative offset to the CIE for this FDE---the offset is
|
|
// relative to this field.
|
|
append_vec<int32_t>(buffer, int32_t(buffer.size()));
|
|
|
|
// We're using the addressing mode DW_EH_PE_absptr, which means it
|
|
// wants a 8 byte pointer and a 8 byte size indicating the region
|
|
// this FDE applies to.
|
|
append_vec<unsigned char*>(buffer, startAddr);
|
|
append_vec<size_t>(buffer, size);
|
|
|
|
// Fixup the length field for this FDE. Again length doesn't
|
|
// include the length field itself.
|
|
void* vp = &buffer[fdeIdx];
|
|
*static_cast<uint32_t*>(vp) = buffer.size() - fdeIdx - sizeof(uint32_t);
|
|
}
|
|
// Add one more zero'd length field---this indicates that there are
|
|
// no more FDEs sharing this CIE.
|
|
append_vec<uint32_t>(buffer, 0);
|
|
|
|
__register_frame(&buffer[0]);
|
|
|
|
return boost::shared_ptr<std::vector<char> >(
|
|
bufferMem.release(),
|
|
deregister_unwind_region
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
}}
|