2015-09-30 21:50:14 +08:00
|
|
|
|
//===-- ArmUnwindInfo.cpp ---------------------------------------*- C++ -*-===//
|
|
|
|
|
//
|
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
|
//
|
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
|
//
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
#include "Utility/ARM_DWARF_Registers.h"
|
2015-09-30 21:50:14 +08:00
|
|
|
|
#include "lldb/Core/Module.h"
|
|
|
|
|
#include "lldb/Core/Section.h"
|
|
|
|
|
#include "lldb/Symbol/ArmUnwindInfo.h"
|
|
|
|
|
#include "lldb/Symbol/SymbolVendor.h"
|
|
|
|
|
#include "lldb/Symbol/UnwindPlan.h"
|
2017-02-15 03:06:07 +08:00
|
|
|
|
#include "lldb/Utility/Endian.h"
|
2015-09-30 21:50:14 +08:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Unwind information reader and parser for the ARM exception handling ABI
|
|
|
|
|
*
|
|
|
|
|
* Implemented based on:
|
|
|
|
|
* Exception Handling ABI for the ARM Architecture
|
|
|
|
|
* Document number: ARM IHI 0038A (current through ABI r2.09)
|
|
|
|
|
* Date of Issue: 25th January 2007, reissued 30th November 2012
|
|
|
|
|
* http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
using namespace lldb;
|
|
|
|
|
using namespace lldb_private;
|
|
|
|
|
|
2015-10-02 19:58:26 +08:00
|
|
|
|
// Converts a prel31 avlue to lldb::addr_t with sign extension
|
2016-09-07 04:57:50 +08:00
|
|
|
|
static addr_t Prel31ToAddr(uint32_t prel31) {
|
|
|
|
|
addr_t res = prel31;
|
|
|
|
|
if (prel31 & (1 << 30))
|
|
|
|
|
res |= 0xffffffff80000000ULL;
|
|
|
|
|
return res;
|
2015-10-02 19:58:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
ArmUnwindInfo::ArmExidxEntry::ArmExidxEntry(uint32_t f, lldb::addr_t a,
|
|
|
|
|
uint32_t d)
|
|
|
|
|
: file_address(f), address(a), data(d) {}
|
2015-10-02 19:58:26 +08:00
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
bool ArmUnwindInfo::ArmExidxEntry::operator<(const ArmExidxEntry &other) const {
|
|
|
|
|
return address < other.address;
|
2015-10-02 19:58:26 +08:00
|
|
|
|
}
|
2015-09-30 21:50:14 +08:00
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
ArmUnwindInfo::ArmUnwindInfo(const ObjectFile &objfile, SectionSP &arm_exidx,
|
|
|
|
|
SectionSP &arm_extab)
|
|
|
|
|
: m_byte_order(objfile.GetByteOrder()), m_arm_exidx_sp(arm_exidx),
|
|
|
|
|
m_arm_extab_sp(arm_extab) {
|
|
|
|
|
objfile.ReadSectionData(arm_exidx.get(), m_arm_exidx_data);
|
|
|
|
|
objfile.ReadSectionData(arm_extab.get(), m_arm_extab_data);
|
|
|
|
|
|
|
|
|
|
addr_t exidx_base_addr = m_arm_exidx_sp->GetFileAddress();
|
|
|
|
|
|
|
|
|
|
offset_t offset = 0;
|
|
|
|
|
while (m_arm_exidx_data.ValidOffset(offset)) {
|
|
|
|
|
lldb::addr_t file_addr = exidx_base_addr + offset;
|
|
|
|
|
lldb::addr_t addr = exidx_base_addr + (addr_t)offset +
|
|
|
|
|
Prel31ToAddr(m_arm_exidx_data.GetU32(&offset));
|
|
|
|
|
uint32_t data = m_arm_exidx_data.GetU32(&offset);
|
|
|
|
|
m_exidx_entries.emplace_back(file_addr, addr, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort the entries in the exidx section. The entries should be sorted inside
|
|
|
|
|
// the section but
|
|
|
|
|
// some old compiler isn't sorted them.
|
|
|
|
|
std::sort(m_exidx_entries.begin(), m_exidx_entries.end());
|
2015-09-30 21:50:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
ArmUnwindInfo::~ArmUnwindInfo() {}
|
2015-09-30 21:50:14 +08:00
|
|
|
|
|
2015-09-30 23:17:06 +08:00
|
|
|
|
// Read a byte from the unwind instruction stream with the given offset.
|
2016-09-07 04:57:50 +08:00
|
|
|
|
// Custom function is required because have to red in order of significance
|
|
|
|
|
// within their containing
|
2015-09-30 23:17:06 +08:00
|
|
|
|
// word (most significant byte first) and in increasing word address order.
|
2016-09-07 04:57:50 +08:00
|
|
|
|
uint8_t ArmUnwindInfo::GetByteAtOffset(const uint32_t *data,
|
|
|
|
|
uint16_t offset) const {
|
|
|
|
|
uint32_t value = data[offset / 4];
|
|
|
|
|
if (m_byte_order != endian::InlHostByteOrder())
|
|
|
|
|
value = llvm::ByteSwap_32(value);
|
|
|
|
|
return (value >> ((3 - (offset % 4)) * 8)) & 0xff;
|
2015-09-30 21:50:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
uint64_t ArmUnwindInfo::GetULEB128(const uint32_t *data, uint16_t &offset,
|
|
|
|
|
uint16_t max_offset) const {
|
|
|
|
|
uint64_t result = 0;
|
|
|
|
|
uint8_t shift = 0;
|
|
|
|
|
while (offset < max_offset) {
|
|
|
|
|
uint8_t byte = GetByteAtOffset(data, offset++);
|
|
|
|
|
result |= (uint64_t)(byte & 0x7f) << shift;
|
|
|
|
|
if ((byte & 0x80) == 0)
|
|
|
|
|
break;
|
|
|
|
|
shift += 7;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
2015-09-30 21:50:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
bool ArmUnwindInfo::GetUnwindPlan(Target &target, const Address &addr,
|
|
|
|
|
UnwindPlan &unwind_plan) {
|
|
|
|
|
const uint32_t *data = (const uint32_t *)GetExceptionHandlingTableEntry(addr);
|
|
|
|
|
if (data == nullptr)
|
|
|
|
|
return false; // No unwind information for the function
|
|
|
|
|
|
|
|
|
|
if (data[0] == 0x1)
|
|
|
|
|
return false; // EXIDX_CANTUNWIND
|
|
|
|
|
|
|
|
|
|
uint16_t byte_count = 0;
|
|
|
|
|
uint16_t byte_offset = 0;
|
|
|
|
|
if (data[0] & 0x80000000) {
|
|
|
|
|
switch ((data[0] >> 24) & 0x0f) {
|
|
|
|
|
case 0:
|
|
|
|
|
byte_count = 4;
|
|
|
|
|
byte_offset = 1;
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
case 2:
|
|
|
|
|
byte_count = 4 * ((data[0] >> 16) & 0xff) + 4;
|
|
|
|
|
byte_offset = 2;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
// Unhandled personality routine index
|
|
|
|
|
return false;
|
2015-09-30 21:50:14 +08:00
|
|
|
|
}
|
2016-09-07 04:57:50 +08:00
|
|
|
|
} else {
|
|
|
|
|
byte_count = 4 * ((data[1] >> 24) & 0xff) + 8;
|
|
|
|
|
byte_offset = 5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t vsp_reg = dwarf_sp;
|
|
|
|
|
int32_t vsp = 0;
|
|
|
|
|
std::vector<std::pair<uint32_t, int32_t>>
|
|
|
|
|
register_offsets; // register -> (offset from vsp_reg)
|
|
|
|
|
|
|
|
|
|
while (byte_offset < byte_count) {
|
|
|
|
|
uint8_t byte1 = GetByteAtOffset(data, byte_offset++);
|
|
|
|
|
if ((byte1 & 0xc0) == 0x00) {
|
|
|
|
|
// 00xxxxxx
|
|
|
|
|
// vsp = vsp + (xxxxxx << 2) + 4. Covers range 0x04-0x100 inclusive
|
|
|
|
|
vsp += ((byte1 & 0x3f) << 2) + 4;
|
|
|
|
|
} else if ((byte1 & 0xc0) == 0x40) {
|
|
|
|
|
// 01xxxxxx
|
|
|
|
|
// vsp = vsp – (xxxxxx << 2) - 4. Covers range 0x04-0x100 inclusive
|
|
|
|
|
vsp -= ((byte1 & 0x3f) << 2) + 4;
|
|
|
|
|
} else if ((byte1 & 0xf0) == 0x80) {
|
|
|
|
|
if (byte_offset >= byte_count)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
uint8_t byte2 = GetByteAtOffset(data, byte_offset++);
|
|
|
|
|
if (byte1 == 0x80 && byte2 == 0) {
|
|
|
|
|
// 10000000 00000000
|
|
|
|
|
// Refuse to unwind (for example, out of a cleanup) (see remark a)
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
// 1000iiii iiiiiiii (i not all 0)
|
|
|
|
|
// Pop up to 12 integer registers under masks {r15-r12}, {r11-r4} (see
|
|
|
|
|
// remark b)
|
|
|
|
|
uint16_t regs = ((byte1 & 0x0f) << 8) | byte2;
|
|
|
|
|
for (uint8_t i = 0; i < 12; ++i) {
|
|
|
|
|
if (regs & (1 << i)) {
|
|
|
|
|
register_offsets.emplace_back(dwarf_r4 + i, vsp);
|
2015-09-30 21:50:14 +08:00
|
|
|
|
vsp += 4;
|
2016-09-07 04:57:50 +08:00
|
|
|
|
}
|
2015-09-30 21:50:14 +08:00
|
|
|
|
}
|
2016-09-07 04:57:50 +08:00
|
|
|
|
}
|
|
|
|
|
} else if ((byte1 & 0xff) == 0x9d) {
|
|
|
|
|
// 10011101
|
|
|
|
|
// Reserved as prefix for ARM register to register moves
|
|
|
|
|
return false;
|
|
|
|
|
} else if ((byte1 & 0xff) == 0x9f) {
|
|
|
|
|
// 10011111
|
|
|
|
|
// Reserved as prefix for Intel Wireless MMX register to register moves
|
|
|
|
|
return false;
|
|
|
|
|
} else if ((byte1 & 0xf0) == 0x90) {
|
|
|
|
|
// 1001nnnn (nnnn != 13,15)
|
|
|
|
|
// Set vsp = r[nnnn]
|
|
|
|
|
vsp_reg = dwarf_r0 + (byte1 & 0x0f);
|
|
|
|
|
} else if ((byte1 & 0xf8) == 0xa0) {
|
|
|
|
|
// 10100nnn
|
|
|
|
|
// Pop r4-r[4+nnn]
|
|
|
|
|
uint8_t n = byte1 & 0x7;
|
|
|
|
|
for (uint8_t i = 0; i <= n; ++i) {
|
|
|
|
|
register_offsets.emplace_back(dwarf_r4 + i, vsp);
|
|
|
|
|
vsp += 4;
|
|
|
|
|
}
|
|
|
|
|
} else if ((byte1 & 0xf8) == 0xa8) {
|
|
|
|
|
// 10101nnn
|
|
|
|
|
// Pop r4-r[4+nnn], r14
|
|
|
|
|
uint8_t n = byte1 & 0x7;
|
|
|
|
|
for (uint8_t i = 0; i <= n; ++i) {
|
|
|
|
|
register_offsets.emplace_back(dwarf_r4 + i, vsp);
|
|
|
|
|
vsp += 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
register_offsets.emplace_back(dwarf_lr, vsp);
|
|
|
|
|
vsp += 4;
|
|
|
|
|
} else if ((byte1 & 0xff) == 0xb0) {
|
|
|
|
|
// 10110000
|
|
|
|
|
// Finish (see remark c)
|
|
|
|
|
break;
|
|
|
|
|
} else if ((byte1 & 0xff) == 0xb1) {
|
|
|
|
|
if (byte_offset >= byte_count)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
uint8_t byte2 = GetByteAtOffset(data, byte_offset++);
|
|
|
|
|
if ((byte2 & 0xff) == 0x00) {
|
|
|
|
|
// 10110001 00000000
|
|
|
|
|
// Spare (see remark f)
|
|
|
|
|
return false;
|
|
|
|
|
} else if ((byte2 & 0xf0) == 0x00) {
|
|
|
|
|
// 10110001 0000iiii (i not all 0)
|
|
|
|
|
// Pop integer registers under mask {r3, r2, r1, r0}
|
|
|
|
|
for (uint8_t i = 0; i < 4; ++i) {
|
|
|
|
|
if (byte2 & (1 << i)) {
|
|
|
|
|
register_offsets.emplace_back(dwarf_r0 + i, vsp);
|
2015-09-30 21:50:14 +08:00
|
|
|
|
vsp += 4;
|
2016-09-07 04:57:50 +08:00
|
|
|
|
}
|
2015-09-30 21:50:14 +08:00
|
|
|
|
}
|
2016-09-07 04:57:50 +08:00
|
|
|
|
} else {
|
|
|
|
|
// 10110001 xxxxyyyy
|
|
|
|
|
// Spare (xxxx != 0000)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else if ((byte1 & 0xff) == 0xb2) {
|
|
|
|
|
// 10110010 uleb128
|
|
|
|
|
// vsp = vsp + 0x204+ (uleb128 << 2)
|
|
|
|
|
uint64_t uleb128 = GetULEB128(data, byte_offset, byte_count);
|
|
|
|
|
vsp += 0x204 + (uleb128 << 2);
|
|
|
|
|
} else if ((byte1 & 0xff) == 0xb3) {
|
|
|
|
|
// 10110011 sssscccc
|
|
|
|
|
// Pop VFP double-precision registers D[ssss]-D[ssss+cccc] saved (as if)
|
|
|
|
|
// by FSTMFDX (see remark d)
|
|
|
|
|
if (byte_offset >= byte_count)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
uint8_t byte2 = GetByteAtOffset(data, byte_offset++);
|
|
|
|
|
uint8_t s = (byte2 & 0xf0) >> 4;
|
|
|
|
|
uint8_t c = (byte2 & 0x0f) >> 0;
|
|
|
|
|
for (uint8_t i = 0; i <= c; ++i) {
|
|
|
|
|
register_offsets.emplace_back(dwarf_d0 + s + i, vsp);
|
|
|
|
|
vsp += 8;
|
|
|
|
|
}
|
|
|
|
|
vsp += 4;
|
|
|
|
|
} else if ((byte1 & 0xfc) == 0xb4) {
|
|
|
|
|
// 101101nn
|
|
|
|
|
// Spare (was Pop FPA)
|
|
|
|
|
return false;
|
|
|
|
|
} else if ((byte1 & 0xf8) == 0xb8) {
|
|
|
|
|
// 10111nnn
|
|
|
|
|
// Pop VFP double-precision registers D[8]-D[8+nnn] saved (as if) by
|
|
|
|
|
// FSTMFDX (see remark d)
|
|
|
|
|
uint8_t n = byte1 & 0x07;
|
|
|
|
|
for (uint8_t i = 0; i <= n; ++i) {
|
|
|
|
|
register_offsets.emplace_back(dwarf_d8 + i, vsp);
|
|
|
|
|
vsp += 8;
|
|
|
|
|
}
|
|
|
|
|
vsp += 4;
|
|
|
|
|
} else if ((byte1 & 0xf8) == 0xc0) {
|
|
|
|
|
// 11000nnn (nnn != 6,7)
|
|
|
|
|
// Intel Wireless MMX pop wR[10]-wR[10+nnn]
|
|
|
|
|
|
|
|
|
|
// 11000110 sssscccc
|
|
|
|
|
// Intel Wireless MMX pop wR[ssss]-wR[ssss+cccc] (see remark e)
|
|
|
|
|
|
|
|
|
|
// 11000111 00000000
|
|
|
|
|
// Spare
|
|
|
|
|
|
|
|
|
|
// 11000111 0000iiii
|
|
|
|
|
// Intel Wireless MMX pop wCGR registers under mask {wCGR3,2,1,0}
|
|
|
|
|
|
|
|
|
|
// 11000111 xxxxyyyy
|
|
|
|
|
// Spare (xxxx != 0000)
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
} else if ((byte1 & 0xff) == 0xc8) {
|
|
|
|
|
// 11001000 sssscccc
|
|
|
|
|
// Pop VFP double precision registers D[16+ssss]-D[16+ssss+cccc] saved (as
|
|
|
|
|
// if) by FSTMFDD (see remarks d,e)
|
|
|
|
|
if (byte_offset >= byte_count)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
uint8_t byte2 = GetByteAtOffset(data, byte_offset++);
|
|
|
|
|
uint8_t s = (byte2 & 0xf0) >> 4;
|
|
|
|
|
uint8_t c = (byte2 & 0x0f) >> 0;
|
|
|
|
|
for (uint8_t i = 0; i <= c; ++i) {
|
|
|
|
|
register_offsets.emplace_back(dwarf_d16 + s + i, vsp);
|
|
|
|
|
vsp += 8;
|
|
|
|
|
}
|
|
|
|
|
} else if ((byte1 & 0xff) == 0xc9) {
|
|
|
|
|
// 11001001 sssscccc
|
|
|
|
|
// Pop VFP double precision registers D[ssss]-D[ssss+cccc] saved (as if)
|
|
|
|
|
// by FSTMFDD (see remark d)
|
|
|
|
|
if (byte_offset >= byte_count)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
uint8_t byte2 = GetByteAtOffset(data, byte_offset++);
|
|
|
|
|
uint8_t s = (byte2 & 0xf0) >> 4;
|
|
|
|
|
uint8_t c = (byte2 & 0x0f) >> 0;
|
|
|
|
|
for (uint8_t i = 0; i <= c; ++i) {
|
|
|
|
|
register_offsets.emplace_back(dwarf_d0 + s + i, vsp);
|
|
|
|
|
vsp += 8;
|
|
|
|
|
}
|
|
|
|
|
} else if ((byte1 & 0xf8) == 0xc8) {
|
|
|
|
|
// 11001yyy
|
|
|
|
|
// Spare (yyy != 000, 001)
|
|
|
|
|
return false;
|
|
|
|
|
} else if ((byte1 & 0xf8) == 0xc0) {
|
|
|
|
|
// 11010nnn
|
|
|
|
|
// Pop VFP double-precision registers D[8]-D[8+nnn] saved (as if) by
|
|
|
|
|
// FSTMFDD (see remark d)
|
|
|
|
|
uint8_t n = byte1 & 0x07;
|
|
|
|
|
for (uint8_t i = 0; i <= n; ++i) {
|
|
|
|
|
register_offsets.emplace_back(dwarf_d8 + i, vsp);
|
|
|
|
|
vsp += 8;
|
|
|
|
|
}
|
|
|
|
|
} else if ((byte1 & 0xc0) == 0xc0) {
|
|
|
|
|
// 11xxxyyy Spare (xxx != 000, 001, 010)
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
2015-09-30 21:50:14 +08:00
|
|
|
|
}
|
2016-09-07 04:57:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UnwindPlan::RowSP row = std::make_shared<UnwindPlan::Row>();
|
|
|
|
|
row->SetOffset(0);
|
|
|
|
|
row->GetCFAValue().SetIsRegisterPlusOffset(vsp_reg, vsp);
|
|
|
|
|
|
|
|
|
|
bool have_location_for_pc = false;
|
|
|
|
|
for (const auto &offset : register_offsets) {
|
|
|
|
|
have_location_for_pc |= offset.first == dwarf_pc;
|
|
|
|
|
row->SetRegisterLocationToAtCFAPlusOffset(offset.first, offset.second - vsp,
|
|
|
|
|
true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!have_location_for_pc) {
|
|
|
|
|
UnwindPlan::Row::RegisterLocation lr_location;
|
|
|
|
|
if (row->GetRegisterInfo(dwarf_lr, lr_location))
|
|
|
|
|
row->SetRegisterInfo(dwarf_pc, lr_location);
|
|
|
|
|
else
|
|
|
|
|
row->SetRegisterLocationToRegister(dwarf_pc, dwarf_lr, false);
|
|
|
|
|
}
|
2015-09-30 21:50:14 +08:00
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
unwind_plan.AppendRow(row);
|
|
|
|
|
unwind_plan.SetSourceName("ARM.exidx unwind info");
|
|
|
|
|
unwind_plan.SetSourcedFromCompiler(eLazyBoolYes);
|
|
|
|
|
unwind_plan.SetUnwindPlanValidAtAllInstructions(eLazyBoolNo);
|
|
|
|
|
unwind_plan.SetRegisterKind(eRegisterKindDWARF);
|
2015-09-30 21:50:14 +08:00
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
return true;
|
2015-09-30 21:50:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
const uint8_t *
|
|
|
|
|
ArmUnwindInfo::GetExceptionHandlingTableEntry(const Address &addr) {
|
|
|
|
|
auto it = std::upper_bound(m_exidx_entries.begin(), m_exidx_entries.end(),
|
|
|
|
|
ArmExidxEntry{0, addr.GetFileAddress(), 0});
|
|
|
|
|
if (it == m_exidx_entries.begin())
|
|
|
|
|
return nullptr;
|
|
|
|
|
--it;
|
2015-09-30 21:50:14 +08:00
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
if (it->data == 0x1)
|
|
|
|
|
return nullptr; // EXIDX_CANTUNWIND
|
2015-09-30 21:50:14 +08:00
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
if (it->data & 0x80000000)
|
|
|
|
|
return (const uint8_t *)&it->data;
|
2015-09-30 21:50:14 +08:00
|
|
|
|
|
2016-09-07 04:57:50 +08:00
|
|
|
|
addr_t data_file_addr = it->file_address + 4 + Prel31ToAddr(it->data);
|
|
|
|
|
return m_arm_extab_data.GetDataStart() +
|
|
|
|
|
(data_file_addr - m_arm_extab_sp->GetFileAddress());
|
2015-09-30 21:50:14 +08:00
|
|
|
|
}
|