Merge "Fix issues in libunwindstack."

am: e05d6afb90

Change-Id: I69d62a4d9fb9810ce13e22d57b84b1cc3d29f05f
This commit is contained in:
Christopher Ferris 2017-12-16 02:48:54 +00:00 committed by android-build-merger
commit 31bf08431c
23 changed files with 183 additions and 103 deletions

View File

@ -167,6 +167,7 @@ cc_test {
data: [
"tests/files/elf32.xz",
"tests/files/elf64.xz",
"tests/files/offline/gnu_debugdata_arm32/*",
"tests/files/offline/straddle_arm32/*",
"tests/files/offline/straddle_arm64/*",
],

View File

@ -79,6 +79,7 @@ void Elf::InitGnuDebugdata() {
uint64_t load_bias;
if (gnu->Init(&load_bias)) {
gnu->InitHeaders();
interface_->SetGnuDebugdataInterface(gnu);
} else {
// Free all of the memory associated with the gnu_debugdata section.
gnu_debugdata_memory_.reset(nullptr);
@ -115,17 +116,9 @@ bool Elf::Step(uint64_t rel_pc, uint64_t adjusted_rel_pc, uint64_t elf_offset, R
return true;
}
// Adjust the load bias to get the real relative pc.
if (adjusted_rel_pc < load_bias_) {
return false;
}
adjusted_rel_pc -= load_bias_;
// Lock during the step which can update information in the object.
std::lock_guard<std::mutex> guard(lock_);
return interface_->Step(adjusted_rel_pc, regs, process_memory, finished) ||
(gnu_debugdata_interface_ &&
gnu_debugdata_interface_->Step(adjusted_rel_pc, regs, process_memory, finished));
return interface_->Step(adjusted_rel_pc, load_bias_, regs, process_memory, finished);
}
bool Elf::IsValidElf(Memory* memory) {

View File

@ -386,16 +386,29 @@ bool ElfInterface::GetFunctionNameWithTemplate(uint64_t addr, uint64_t load_bias
return false;
}
bool ElfInterface::Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) {
bool ElfInterface::Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
bool* finished) {
// Adjust the load bias to get the real relative pc.
if (pc < load_bias) {
return false;
}
uint64_t adjusted_pc = pc - load_bias;
// Try the eh_frame first.
DwarfSection* eh_frame = eh_frame_.get();
if (eh_frame != nullptr && eh_frame->Step(pc, regs, process_memory, finished)) {
if (eh_frame != nullptr && eh_frame->Step(adjusted_pc, regs, process_memory, finished)) {
return true;
}
// Try the debug_frame next.
DwarfSection* debug_frame = debug_frame_.get();
if (debug_frame != nullptr && debug_frame->Step(pc, regs, process_memory, finished)) {
if (debug_frame != nullptr && debug_frame->Step(adjusted_pc, regs, process_memory, finished)) {
return true;
}
// Finally try the gnu_debugdata interface, but always use a zero load bias.
if (gnu_debugdata_interface_ != nullptr &&
gnu_debugdata_interface_->Step(pc, 0, regs, process_memory, finished)) {
return true;
}
return false;

View File

@ -92,16 +92,24 @@ bool ElfInterfaceArm::HandleType(uint64_t offset, uint32_t type, uint64_t load_b
return true;
}
bool ElfInterfaceArm::Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) {
bool ElfInterfaceArm::Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
bool* finished) {
// Dwarf unwind information is precise about whether a pc is covered or not,
// but arm unwind information only has ranges of pc. In order to avoid
// incorrectly doing a bad unwind using arm unwind information for a
// different function, always try and unwind with the dwarf information first.
return ElfInterface32::Step(pc, regs, process_memory, finished) ||
StepExidx(pc, regs, process_memory, finished);
return ElfInterface32::Step(pc, load_bias, regs, process_memory, finished) ||
StepExidx(pc, load_bias, regs, process_memory, finished);
}
bool ElfInterfaceArm::StepExidx(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) {
bool ElfInterfaceArm::StepExidx(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
bool* finished) {
// Adjust the load bias to get the real relative pc.
if (pc < load_bias) {
return false;
}
pc -= load_bias;
RegsArm* regs_arm = reinterpret_cast<RegsArm*>(regs);
uint64_t entry_offset;
if (!FindEntry(pc, &entry_offset)) {

View File

@ -70,9 +70,11 @@ class ElfInterfaceArm : public ElfInterface32 {
bool HandleType(uint64_t offset, uint32_t type, uint64_t load_bias) override;
bool Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished) override;
bool Step(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
bool* finished) override;
bool StepExidx(uint64_t pc, Regs* regs, Memory* process_memory, bool* finished);
bool StepExidx(uint64_t pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
bool* finished);
uint64_t start_offset() { return start_offset_; }

View File

@ -122,13 +122,21 @@ Elf* MapInfo::GetElf(const std::shared_ptr<Memory>& process_memory, bool init_gn
}
uint64_t MapInfo::GetLoadBias(const std::shared_ptr<Memory>& process_memory) {
uint64_t cur_load_bias = load_bias.load();
if (cur_load_bias != static_cast<uint64_t>(-1)) {
return cur_load_bias;
}
{
// Make sure no other thread is trying to add the elf to this map.
std::lock_guard<std::mutex> guard(mutex_);
if (elf != nullptr) {
if (elf->valid()) {
return elf->GetLoadBias();
cur_load_bias = elf->GetLoadBias();
load_bias = cur_load_bias;
return cur_load_bias;
} else {
load_bias = 0;
return 0;
}
}
@ -137,7 +145,9 @@ uint64_t MapInfo::GetLoadBias(const std::shared_ptr<Memory>& process_memory) {
// Call lightweight static function that will only read enough of the
// elf data to get the load bias.
std::unique_ptr<Memory> memory(CreateMemory(process_memory));
return Elf::GetLoadBias(memory.get());
cur_load_bias = Elf::GetLoadBias(memory.get());
load_bias = cur_load_bias;
return cur_load_bias;
}
} // namespace unwindstack

View File

@ -202,6 +202,13 @@ bool Maps::Parse() {
return return_value;
}
void Maps::Add(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags,
const std::string& name, uint64_t load_bias) {
MapInfo* map_info = new MapInfo(start, end, offset, flags, name);
map_info->load_bias = load_bias;
maps_.push_back(map_info);
}
Maps::~Maps() {
for (auto& map : maps_) {
delete map;
@ -235,61 +242,4 @@ const std::string RemoteMaps::GetMapsFile() const {
return "/proc/" + std::to_string(pid_) + "/maps";
}
bool OfflineMaps::Parse() {
// Format of maps information:
// <uint64_t> StartOffset
// <uint64_t> EndOffset
// <uint64_t> offset
// <uint16_t> flags
// <uint16_t> MapNameLength
// <VariableLengthValue> MapName
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(file_.c_str(), O_RDONLY)));
if (fd == -1) {
return false;
}
std::vector<char> name;
while (true) {
uint64_t start;
ssize_t bytes = TEMP_FAILURE_RETRY(read(fd, &start, sizeof(start)));
if (bytes == 0) {
break;
}
if (bytes == -1 || bytes != sizeof(start)) {
return false;
}
uint64_t end;
bytes = TEMP_FAILURE_RETRY(read(fd, &end, sizeof(end)));
if (bytes == -1 || bytes != sizeof(end)) {
return false;
}
uint64_t offset;
bytes = TEMP_FAILURE_RETRY(read(fd, &offset, sizeof(offset)));
if (bytes == -1 || bytes != sizeof(offset)) {
return false;
}
uint16_t flags;
bytes = TEMP_FAILURE_RETRY(read(fd, &flags, sizeof(flags)));
if (bytes == -1 || bytes != sizeof(flags)) {
return false;
}
uint16_t len;
bytes = TEMP_FAILURE_RETRY(read(fd, &len, sizeof(len)));
if (bytes == -1 || bytes != sizeof(len)) {
return false;
}
if (len > 0) {
name.resize(len);
bytes = TEMP_FAILURE_RETRY(read(fd, name.data(), len));
if (bytes == -1 || bytes != len) {
return false;
}
maps_.push_back(new MapInfo(start, end, offset, flags, std::string(name.data(), len)));
} else {
maps_.push_back(new MapInfo(start, end, offset, flags, ""));
}
}
return true;
}
} // namespace unwindstack

View File

@ -38,6 +38,7 @@ namespace unwindstack {
struct x86_64_stack_t {
uint64_t ss_sp; // void __user*
int32_t ss_flags; // int
int32_t pad;
uint64_t ss_size; // size_t
};

View File

@ -60,7 +60,8 @@ class ElfInterface {
virtual bool GetFunctionName(uint64_t addr, uint64_t load_bias, std::string* name,
uint64_t* offset) = 0;
virtual bool Step(uint64_t rel_pc, Regs* regs, Memory* process_memory, bool* finished);
virtual bool Step(uint64_t rel_pc, uint64_t load_bias, Regs* regs, Memory* process_memory,
bool* finished);
Memory* CreateGnuDebugdataMemory();
@ -68,6 +69,8 @@ class ElfInterface {
const std::unordered_map<uint64_t, LoadInfo>& pt_loads() { return pt_loads_; }
void SetGnuDebugdataInterface(ElfInterface* interface) { gnu_debugdata_interface_ = interface; }
uint64_t dynamic_offset() { return dynamic_offset_; }
uint64_t dynamic_size() { return dynamic_size_; }
uint64_t eh_frame_hdr_offset() { return eh_frame_hdr_offset_; }
@ -134,6 +137,8 @@ class ElfInterface {
std::unique_ptr<DwarfSection> eh_frame_;
std::unique_ptr<DwarfSection> debug_frame_;
// The Elf object owns the gnu_debugdata interface object.
ElfInterface* gnu_debugdata_interface_ = nullptr;
std::vector<Symbols*> symbols_;
};

View File

@ -19,6 +19,7 @@
#include <stdint.h>
#include <atomic>
#include <mutex>
#include <string>
@ -33,7 +34,12 @@ struct MapInfo {
MapInfo() = default;
MapInfo(uint64_t start, uint64_t end) : start(start), end(end) {}
MapInfo(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, const std::string& name)
: start(start), end(end), offset(offset), flags(flags), name(name) {}
: start(start),
end(end),
offset(offset),
flags(flags),
name(name),
load_bias(static_cast<uint64_t>(-1)) {}
~MapInfo() { delete elf; }
uint64_t start = 0;
@ -48,6 +54,8 @@ struct MapInfo {
// instead of a portion of the file.
uint64_t elf_offset = 0;
std::atomic_uint64_t load_bias;
// This function guarantees it will never return nullptr.
Elf* GetElf(const std::shared_ptr<Memory>& process_memory, bool init_gnu_debugdata = false);

View File

@ -42,6 +42,9 @@ class Maps {
virtual const std::string GetMapsFile() const { return ""; }
void Add(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, const std::string& name,
uint64_t load_bias);
typedef std::vector<MapInfo*>::iterator iterator;
iterator begin() { return maps_.begin(); }
iterator end() { return maps_.end(); }
@ -100,14 +103,6 @@ class FileMaps : public Maps {
const std::string file_;
};
class OfflineMaps : public FileMaps {
public:
OfflineMaps(const std::string& file) : FileMaps(file) {}
virtual ~OfflineMaps() = default;
bool Parse() override;
};
} // namespace unwindstack
#endif // _LIBUNWINDSTACK_MAPS_H

View File

@ -43,7 +43,7 @@ bool ElfInterfaceFake::GetFunctionName(uint64_t, uint64_t, std::string* name, ui
return true;
}
bool ElfInterfaceFake::Step(uint64_t, Regs* regs, Memory*, bool* finished) {
bool ElfInterfaceFake::Step(uint64_t, uint64_t, Regs* regs, Memory*, bool* finished) {
if (steps_.empty()) {
return false;
}

View File

@ -68,8 +68,7 @@ class ElfInterfaceFake : public ElfInterface {
bool GetFunctionName(uint64_t, uint64_t, std::string*, uint64_t*) override;
bool Step(uint64_t, Regs*, Memory*, bool*) override;
bool Step(uint64_t, uint64_t, Regs*, Memory*, bool*) override;
static void FakePushFunctionData(const FunctionData data) { functions_.push_back(data); }
static void FakePushStepData(const StepData data) { steps_.push_back(data); }

View File

@ -302,7 +302,7 @@ TEST_F(ElfInterfaceArmTest, StepExidx) {
// FindEntry fails.
bool finished;
ASSERT_FALSE(interface.StepExidx(0x7000, nullptr, nullptr, &finished));
ASSERT_FALSE(interface.StepExidx(0x7000, 0, nullptr, nullptr, &finished));
// ExtractEntry should fail.
interface.FakeSetStartOffset(0x1000);
@ -315,20 +315,26 @@ TEST_F(ElfInterfaceArmTest, StepExidx) {
regs[ARM_REG_LR] = 0x20000;
regs.set_sp(regs[ARM_REG_SP]);
regs.set_pc(0x1234);
ASSERT_FALSE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
ASSERT_FALSE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
// Eval should fail.
memory_.SetData32(0x1004, 0x81000000);
ASSERT_FALSE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
ASSERT_FALSE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
// Everything should pass.
memory_.SetData32(0x1004, 0x80b0b0b0);
ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
ASSERT_FALSE(finished);
ASSERT_EQ(0x1000U, regs.sp());
ASSERT_EQ(0x1000U, regs[ARM_REG_SP]);
ASSERT_EQ(0x20000U, regs.pc());
ASSERT_EQ(0x20000U, regs[ARM_REG_PC]);
// Load bias is non-zero.
ASSERT_TRUE(interface.StepExidx(0x8000, 0x1000, &regs, &process_memory_, &finished));
// Pc too small.
ASSERT_FALSE(interface.StepExidx(0x8000, 0x9000, &regs, &process_memory_, &finished));
}
TEST_F(ElfInterfaceArmTest, StepExidx_pc_set) {
@ -349,7 +355,7 @@ TEST_F(ElfInterfaceArmTest, StepExidx_pc_set) {
// Everything should pass.
bool finished;
ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
ASSERT_FALSE(finished);
ASSERT_EQ(0x10004U, regs.sp());
ASSERT_EQ(0x10004U, regs[ARM_REG_SP]);
@ -372,7 +378,7 @@ TEST_F(ElfInterfaceArmTest, StepExidx_cant_unwind) {
regs.set_pc(0x1234);
bool finished;
ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
ASSERT_TRUE(finished);
ASSERT_EQ(0x10000U, regs.sp());
ASSERT_EQ(0x10000U, regs[ARM_REG_SP]);
@ -394,7 +400,7 @@ TEST_F(ElfInterfaceArmTest, StepExidx_refuse_unwind) {
regs.set_pc(0x1234);
bool finished;
ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
ASSERT_TRUE(finished);
ASSERT_EQ(0x10000U, regs.sp());
ASSERT_EQ(0x10000U, regs[ARM_REG_SP]);
@ -420,7 +426,7 @@ TEST_F(ElfInterfaceArmTest, StepExidx_pc_zero) {
regs.set_pc(0x1234);
bool finished;
ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
ASSERT_TRUE(finished);
ASSERT_EQ(0U, regs.pc());
@ -432,7 +438,7 @@ TEST_F(ElfInterfaceArmTest, StepExidx_pc_zero) {
regs.set_sp(regs[ARM_REG_SP]);
regs.set_pc(0x1234);
ASSERT_TRUE(interface.StepExidx(0x7000, &regs, &process_memory_, &finished));
ASSERT_TRUE(interface.StepExidx(0x7000, 0, &regs, &process_memory_, &finished));
ASSERT_TRUE(finished);
ASSERT_EQ(0U, regs.pc());
}

View File

@ -346,7 +346,7 @@ class ElfInterfaceMock : public ElfInterface {
void InitHeaders() override {}
bool GetSoname(std::string*) override { return false; }
bool GetFunctionName(uint64_t, uint64_t, std::string*, uint64_t*) override { return false; }
MOCK_METHOD4(Step, bool(uint64_t, Regs*, Memory*, bool*));
MOCK_METHOD5(Step, bool(uint64_t, uint64_t, Regs*, Memory*, bool*));
};
TEST_F(ElfTest, step_in_interface) {
@ -361,7 +361,7 @@ TEST_F(ElfTest, step_in_interface) {
MemoryFake process_memory;
bool finished;
EXPECT_CALL(*interface, Step(0x1000, &regs, &process_memory, &finished))
EXPECT_CALL(*interface, Step(0x1000, 0, &regs, &process_memory, &finished))
.WillOnce(::testing::Return(true));
ASSERT_TRUE(elf.Step(0x1004, 0x1000, 0x2000, &regs, &process_memory, &finished));
@ -382,7 +382,7 @@ TEST_F(ElfTest, step_in_interface_non_zero_load_bias) {
bool finished;
ASSERT_FALSE(elf.Step(0x1004, 0x1000, 0x2000, &regs, &process_memory, &finished));
EXPECT_CALL(*interface, Step(0x3300, &regs, &process_memory, &finished))
EXPECT_CALL(*interface, Step(0x7300, 0x4000, &regs, &process_memory, &finished))
.WillOnce(::testing::Return(true));
ASSERT_TRUE(elf.Step(0x7304, 0x7300, 0x2000, &regs, &process_memory, &finished));

View File

@ -68,12 +68,23 @@ TEST_F(MapInfoGetLoadBiasTest, no_elf_and_no_valid_elf_in_memory) {
EXPECT_EQ(0U, info.GetLoadBias(process_memory_));
}
TEST_F(MapInfoGetLoadBiasTest, load_bias_cached_from_elf) {
map_info_->elf = elf_container_.release();
elf_->FakeSetLoadBias(0);
EXPECT_EQ(0U, map_info_->GetLoadBias(process_memory_));
elf_->FakeSetLoadBias(0x1000);
EXPECT_EQ(0U, map_info_->GetLoadBias(process_memory_));
}
TEST_F(MapInfoGetLoadBiasTest, elf_exists) {
map_info_->elf = elf_container_.release();
elf_->FakeSetLoadBias(0);
EXPECT_EQ(0U, map_info_->GetLoadBias(process_memory_));
map_info_->load_bias = static_cast<uint64_t>(-1);
elf_->FakeSetLoadBias(0x1000);
EXPECT_EQ(0x1000U, map_info_->GetLoadBias(process_memory_));
}
@ -141,6 +152,15 @@ TEST_F(MapInfoGetLoadBiasTest, elf_exists_in_memory) {
EXPECT_EQ(0xe000U, map_info_->GetLoadBias(process_memory_));
}
TEST_F(MapInfoGetLoadBiasTest, elf_exists_in_memory_cached) {
InitElfData(memory_, map_info_->start);
EXPECT_EQ(0xe000U, map_info_->GetLoadBias(process_memory_));
memory_->Clear();
EXPECT_EQ(0xe000U, map_info_->GetLoadBias(process_memory_));
}
TEST_F(MapInfoGetLoadBiasTest, multiple_thread_elf_exists_in_memory) {
InitElfData(memory_, map_info_->start);

View File

@ -44,6 +44,24 @@ static void VerifyLine(std::string line, MapInfo* info) {
}
}
TEST(MapsTest, map_add) {
Maps maps;
maps.Add(0x1000, 0x2000, 0, PROT_READ, "fake_map", 0);
maps.Add(0x3000, 0x4000, 0x10, 0, "", 0x1234);
maps.Add(0x5000, 0x6000, 1, 2, "fake_map2", static_cast<uint64_t>(-1));
ASSERT_EQ(3U, maps.Total());
MapInfo* info = maps.Get(0);
ASSERT_EQ(0x1000U, info->start);
ASSERT_EQ(0x2000U, info->end);
ASSERT_EQ(0U, info->offset);
ASSERT_EQ(PROT_READ, info->flags);
ASSERT_EQ("fake_map", info->name);
ASSERT_EQ(0U, info->elf_offset);
ASSERT_EQ(0U, info->load_bias.load());
}
TEST(MapsTest, verify_parse_line) {
MapInfo info;

View File

@ -96,6 +96,54 @@ TEST(UnwindOfflineTest, pc_straddle_arm32) {
frame_info);
}
TEST(UnwindOfflineTest, pc_in_gnu_debugdata_arm32) {
std::string dir(TestGetFileDirectory() + "offline/gnu_debugdata_arm32/");
MemoryOffline* memory = new MemoryOffline;
ASSERT_TRUE(memory->Init((dir + "stack.data").c_str(), 0));
FILE* fp = fopen((dir + "regs.txt").c_str(), "r");
ASSERT_TRUE(fp != nullptr);
RegsArm regs;
uint64_t reg_value;
ASSERT_EQ(1, fscanf(fp, "pc: %" SCNx64 "\n", &reg_value));
regs[ARM_REG_PC] = reg_value;
ASSERT_EQ(1, fscanf(fp, "sp: %" SCNx64 "\n", &reg_value));
regs[ARM_REG_SP] = reg_value;
regs.SetFromRaw();
fclose(fp);
fp = fopen((dir + "maps.txt").c_str(), "r");
ASSERT_TRUE(fp != nullptr);
// The file is guaranteed to be less than 4096 bytes.
std::vector<char> buffer(4096);
ASSERT_NE(0U, fread(buffer.data(), 1, buffer.size(), fp));
fclose(fp);
BufferMaps maps(buffer.data());
ASSERT_TRUE(maps.Parse());
ASSERT_EQ(ARCH_ARM, regs.Arch());
std::shared_ptr<Memory> process_memory(memory);
char* cwd = getcwd(nullptr, 0);
ASSERT_EQ(0, chdir(dir.c_str()));
Unwinder unwinder(128, &maps, &regs, process_memory);
unwinder.Unwind();
ASSERT_EQ(0, chdir(cwd));
free(cwd);
std::string frame_info(DumpFrames(unwinder));
ASSERT_EQ(2U, unwinder.NumFrames()) << "Unwind:\n" << frame_info;
EXPECT_EQ(
" #00 pc 0006dc49 libandroid_runtime.so "
"(_ZN7android14AndroidRuntime15javaThreadShellEPv+80)\n"
" #01 pc 0006dce5 libandroid_runtime.so "
"(_ZN7android14AndroidRuntime19javaCreateThreadEtcEPFiPvES1_PKcijPS1_)\n",
frame_info);
}
TEST(UnwindOfflineTest, pc_straddle_arm64) {
std::string dir(TestGetFileDirectory() + "offline/straddle_arm64/");

View File

@ -0,0 +1 @@
f1f10000-f2049000 r-xp 00000000 00:00 0 libandroid_runtime.so

View File

@ -0,0 +1,2 @@
pc: f1f6dc49
sp: d8fe6930

View File

@ -87,7 +87,7 @@ void DumpDwarfSection(ElfInterface* interface, DwarfSection* section, uint64_t l
for (const DwarfFde* fde : *section) {
// Sometimes there are entries that have empty length, skip those since
// they don't contain any interesting information.
if (fde->pc_start == fde->pc_end) {
if (fde == nullptr || fde->pc_start == fde->pc_end) {
continue;
}
printf("\n PC 0x%" PRIx64, fde->pc_start + load_bias);