mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2025-07-23 06:15:46 +00:00
552 lines
19 KiB
C++
552 lines
19 KiB
C++
// Copyright 2010 Google LLC
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
// in the documentation and/or other materials provided with the
|
|
// distribution.
|
|
// * Neither the name of Google LLC nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
|
|
|
|
// cfi_frame_info_unittest.cc: Unit tests for CFIFrameInfo,
|
|
// CFIRuleParser, CFIFrameInfoParseHandler, and SimpleCFIWalker.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h> // Must come first
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#include "breakpad_googletest_includes.h"
|
|
#include "common/using_std_string.h"
|
|
#include "processor/cfi_frame_info.h"
|
|
#include "google_breakpad/processor/memory_region.h"
|
|
|
|
using google_breakpad::CFIFrameInfo;
|
|
using google_breakpad::CFIFrameInfoParseHandler;
|
|
using google_breakpad::CFIRuleParser;
|
|
using google_breakpad::MemoryRegion;
|
|
using google_breakpad::SimpleCFIWalker;
|
|
using testing::_;
|
|
using testing::A;
|
|
using testing::AtMost;
|
|
using testing::DoAll;
|
|
using testing::Return;
|
|
using testing::SetArgumentPointee;
|
|
using testing::Test;
|
|
|
|
class MockMemoryRegion: public MemoryRegion {
|
|
public:
|
|
MOCK_CONST_METHOD0(GetBase, uint64_t());
|
|
MOCK_CONST_METHOD0(GetSize, uint32_t());
|
|
MOCK_CONST_METHOD2(GetMemoryAtAddress, bool(uint64_t, uint8_t*));
|
|
MOCK_CONST_METHOD2(GetMemoryAtAddress, bool(uint64_t, uint16_t*));
|
|
MOCK_CONST_METHOD2(GetMemoryAtAddress, bool(uint64_t, uint32_t*));
|
|
MOCK_CONST_METHOD2(GetMemoryAtAddress, bool(uint64_t, uint64_t*));
|
|
MOCK_CONST_METHOD0(Print, void());
|
|
};
|
|
|
|
// Handy definitions for all tests.
|
|
struct CFIFixture {
|
|
|
|
// Set up the mock memory object to expect no references.
|
|
void ExpectNoMemoryReferences() {
|
|
EXPECT_CALL(memory, GetBase()).Times(0);
|
|
EXPECT_CALL(memory, GetSize()).Times(0);
|
|
EXPECT_CALL(memory, GetMemoryAtAddress(_, A<uint8_t*>())).Times(0);
|
|
EXPECT_CALL(memory, GetMemoryAtAddress(_, A<uint16_t*>())).Times(0);
|
|
EXPECT_CALL(memory, GetMemoryAtAddress(_, A<uint32_t*>())).Times(0);
|
|
EXPECT_CALL(memory, GetMemoryAtAddress(_, A<uint64_t*>())).Times(0);
|
|
}
|
|
|
|
CFIFrameInfo cfi;
|
|
MockMemoryRegion memory;
|
|
CFIFrameInfo::RegisterValueMap<uint64_t> registers, caller_registers;
|
|
};
|
|
|
|
class Simple: public CFIFixture, public Test { };
|
|
|
|
// FindCallerRegs should fail if no .cfa rule is provided.
|
|
TEST_F(Simple, NoCFA) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetRARule("0");
|
|
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(".ra: 0", cfi.Serialize());
|
|
}
|
|
|
|
// FindCallerRegs should fail if no .ra rule is provided.
|
|
TEST_F(Simple, NoRA) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetCFARule("0");
|
|
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(".cfa: 0", cfi.Serialize());
|
|
}
|
|
|
|
TEST_F(Simple, SetCFAAndRARule) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetCFARule("330903416631436410");
|
|
cfi.SetRARule("5870666104170902211");
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(2U, caller_registers.size());
|
|
ASSERT_EQ(330903416631436410ULL, caller_registers[".cfa"]);
|
|
ASSERT_EQ(5870666104170902211ULL, caller_registers[".ra"]);
|
|
|
|
ASSERT_EQ(".cfa: 330903416631436410 .ra: 5870666104170902211",
|
|
cfi.Serialize());
|
|
}
|
|
|
|
TEST_F(Simple, SetManyRules) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetCFARule("$temp1 68737028 = $temp2 61072337 = $temp1 $temp2 -");
|
|
cfi.SetRARule(".cfa 99804755 +");
|
|
cfi.SetRegisterRule("register1", ".cfa 54370437 *");
|
|
cfi.SetRegisterRule("vodkathumbscrewingly", "24076308 .cfa +");
|
|
cfi.SetRegisterRule("pubvexingfjordschmaltzy", ".cfa 29801007 -");
|
|
cfi.SetRegisterRule("uncopyrightables", "92642917 .cfa /");
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(6U, caller_registers.size());
|
|
ASSERT_EQ(7664691U, caller_registers[".cfa"]);
|
|
ASSERT_EQ(107469446U, caller_registers[".ra"]);
|
|
ASSERT_EQ(416732599139967ULL, caller_registers["register1"]);
|
|
ASSERT_EQ(31740999U, caller_registers["vodkathumbscrewingly"]);
|
|
ASSERT_EQ(-22136316ULL, caller_registers["pubvexingfjordschmaltzy"]);
|
|
ASSERT_EQ(12U, caller_registers["uncopyrightables"]);
|
|
ASSERT_EQ(".cfa: $temp1 68737028 = $temp2 61072337 = $temp1 $temp2 - "
|
|
".ra: .cfa 99804755 + "
|
|
"pubvexingfjordschmaltzy: .cfa 29801007 - "
|
|
"register1: .cfa 54370437 * "
|
|
"uncopyrightables: 92642917 .cfa / "
|
|
"vodkathumbscrewingly: 24076308 .cfa +",
|
|
cfi.Serialize());
|
|
}
|
|
|
|
TEST_F(Simple, RulesOverride) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetCFARule("330903416631436410");
|
|
cfi.SetRARule("5870666104170902211");
|
|
cfi.SetCFARule("2828089117179001");
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(2U, caller_registers.size());
|
|
ASSERT_EQ(2828089117179001ULL, caller_registers[".cfa"]);
|
|
ASSERT_EQ(5870666104170902211ULL, caller_registers[".ra"]);
|
|
ASSERT_EQ(".cfa: 2828089117179001 .ra: 5870666104170902211",
|
|
cfi.Serialize());
|
|
}
|
|
|
|
class Scope: public CFIFixture, public Test { };
|
|
|
|
// There should be no value for .cfa in scope when evaluating the CFA rule.
|
|
TEST_F(Scope, CFALacksCFA) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetCFARule(".cfa");
|
|
cfi.SetRARule("0");
|
|
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
}
|
|
|
|
// There should be no value for .ra in scope when evaluating the CFA rule.
|
|
TEST_F(Scope, CFALacksRA) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetCFARule(".ra");
|
|
cfi.SetRARule("0");
|
|
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
}
|
|
|
|
// The current frame's registers should be in scope when evaluating
|
|
// the CFA rule.
|
|
TEST_F(Scope, CFASeesCurrentRegs) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
registers[".baraminology"] = 0x06a7bc63e4f13893ULL;
|
|
registers[".ornithorhynchus"] = 0x5e0bf850bafce9d2ULL;
|
|
cfi.SetCFARule(".baraminology .ornithorhynchus +");
|
|
cfi.SetRARule("0");
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(2U, caller_registers.size());
|
|
ASSERT_EQ(0x06a7bc63e4f13893ULL + 0x5e0bf850bafce9d2ULL,
|
|
caller_registers[".cfa"]);
|
|
}
|
|
|
|
// .cfa should be in scope in the return address expression.
|
|
TEST_F(Scope, RASeesCFA) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetCFARule("48364076");
|
|
cfi.SetRARule(".cfa");
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(2U, caller_registers.size());
|
|
ASSERT_EQ(48364076U, caller_registers[".ra"]);
|
|
}
|
|
|
|
// There should be no value for .ra in scope when evaluating the CFA rule.
|
|
TEST_F(Scope, RALacksRA) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetCFARule("0");
|
|
cfi.SetRARule(".ra");
|
|
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
}
|
|
|
|
// The current frame's registers should be in scope in the return
|
|
// address expression.
|
|
TEST_F(Scope, RASeesCurrentRegs) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
registers["noachian"] = 0x54dc4a5d8e5eb503ULL;
|
|
cfi.SetCFARule("10359370");
|
|
cfi.SetRARule("noachian");
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(2U, caller_registers.size());
|
|
ASSERT_EQ(0x54dc4a5d8e5eb503ULL, caller_registers[".ra"]);
|
|
}
|
|
|
|
// .cfa should be in scope for register rules.
|
|
TEST_F(Scope, RegistersSeeCFA) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetCFARule("6515179");
|
|
cfi.SetRARule(".cfa");
|
|
cfi.SetRegisterRule("rogerian", ".cfa");
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(3U, caller_registers.size());
|
|
ASSERT_EQ(6515179U, caller_registers["rogerian"]);
|
|
}
|
|
|
|
// The return address should not be in scope for register rules.
|
|
TEST_F(Scope, RegsLackRA) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetCFARule("42740329");
|
|
cfi.SetRARule("27045204");
|
|
cfi.SetRegisterRule("$r1", ".ra");
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(caller_registers.end(), caller_registers.find("$r1"));
|
|
}
|
|
|
|
// Register rules can see the current frame's register values.
|
|
TEST_F(Scope, RegsSeeRegs) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
registers["$r1"] = 0x6ed3582c4bedb9adULL;
|
|
registers["$r2"] = 0xd27d9e742b8df6d0ULL;
|
|
cfi.SetCFARule("88239303");
|
|
cfi.SetRARule("30503835");
|
|
cfi.SetRegisterRule("$r1", "$r1 42175211 = $r2");
|
|
cfi.SetRegisterRule("$r2", "$r2 21357221 = $r1");
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(4U, caller_registers.size());
|
|
ASSERT_EQ(0xd27d9e742b8df6d0ULL, caller_registers["$r1"]);
|
|
ASSERT_EQ(0x6ed3582c4bedb9adULL, caller_registers["$r2"]);
|
|
}
|
|
|
|
// Each rule's temporaries are separate.
|
|
TEST_F(Scope, SeparateTempsRA) {
|
|
ExpectNoMemoryReferences();
|
|
|
|
cfi.SetCFARule("$temp1 76569129 = $temp1");
|
|
cfi.SetRARule("0");
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
|
|
cfi.SetCFARule("$temp1 76569129 = $temp1");
|
|
cfi.SetRARule("$temp1");
|
|
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
}
|
|
|
|
class MockCFIRuleParserHandler: public CFIRuleParser::Handler {
|
|
public:
|
|
MOCK_METHOD1(CFARule, void(const string&));
|
|
MOCK_METHOD1(RARule, void(const string&));
|
|
MOCK_METHOD2(RegisterRule, void(const string&, const string&));
|
|
};
|
|
|
|
// A fixture class for testing CFIRuleParser.
|
|
class CFIParserFixture {
|
|
public:
|
|
CFIParserFixture() : parser(&mock_handler) {
|
|
// Expect no parsing results to be reported to mock_handler. Individual
|
|
// tests can override this.
|
|
EXPECT_CALL(mock_handler, CFARule(_)).Times(0);
|
|
EXPECT_CALL(mock_handler, RARule(_)).Times(0);
|
|
EXPECT_CALL(mock_handler, RegisterRule(_, _)).Times(0);
|
|
}
|
|
|
|
MockCFIRuleParserHandler mock_handler;
|
|
CFIRuleParser parser;
|
|
};
|
|
|
|
class Parser: public CFIParserFixture, public Test { };
|
|
|
|
TEST_F(Parser, Empty) {
|
|
EXPECT_FALSE(parser.Parse(""));
|
|
}
|
|
|
|
TEST_F(Parser, LoneColon) {
|
|
EXPECT_FALSE(parser.Parse(":"));
|
|
}
|
|
|
|
TEST_F(Parser, CFANoExpr) {
|
|
EXPECT_FALSE(parser.Parse(".cfa:"));
|
|
}
|
|
|
|
TEST_F(Parser, CFANoColonNoExpr) {
|
|
EXPECT_FALSE(parser.Parse(".cfa"));
|
|
}
|
|
|
|
TEST_F(Parser, RANoExpr) {
|
|
EXPECT_FALSE(parser.Parse(".ra:"));
|
|
}
|
|
|
|
TEST_F(Parser, RANoColonNoExpr) {
|
|
EXPECT_FALSE(parser.Parse(".ra"));
|
|
}
|
|
|
|
TEST_F(Parser, RegNoExpr) {
|
|
EXPECT_FALSE(parser.Parse("reg:"));
|
|
}
|
|
|
|
TEST_F(Parser, NoName) {
|
|
EXPECT_FALSE(parser.Parse("expr"));
|
|
}
|
|
|
|
TEST_F(Parser, NoNameTwo) {
|
|
EXPECT_FALSE(parser.Parse("expr1 expr2"));
|
|
}
|
|
|
|
TEST_F(Parser, StartsWithExpr) {
|
|
EXPECT_FALSE(parser.Parse("expr1 reg: expr2"));
|
|
}
|
|
|
|
TEST_F(Parser, CFA) {
|
|
EXPECT_CALL(mock_handler, CFARule("spleen")).WillOnce(Return());
|
|
EXPECT_TRUE(parser.Parse(".cfa: spleen"));
|
|
}
|
|
|
|
TEST_F(Parser, RA) {
|
|
EXPECT_CALL(mock_handler, RARule("notoriety")).WillOnce(Return());
|
|
EXPECT_TRUE(parser.Parse(".ra: notoriety"));
|
|
}
|
|
|
|
TEST_F(Parser, Reg) {
|
|
EXPECT_CALL(mock_handler, RegisterRule("nemo", "mellifluous"))
|
|
.WillOnce(Return());
|
|
EXPECT_TRUE(parser.Parse("nemo: mellifluous"));
|
|
}
|
|
|
|
TEST_F(Parser, CFARARegs) {
|
|
EXPECT_CALL(mock_handler, CFARule("cfa expression")).WillOnce(Return());
|
|
EXPECT_CALL(mock_handler, RARule("ra expression")).WillOnce(Return());
|
|
EXPECT_CALL(mock_handler, RegisterRule("galba", "praetorian"))
|
|
.WillOnce(Return());
|
|
EXPECT_CALL(mock_handler, RegisterRule("otho", "vitellius"))
|
|
.WillOnce(Return());
|
|
EXPECT_TRUE(parser.Parse(".cfa: cfa expression .ra: ra expression "
|
|
"galba: praetorian otho: vitellius"));
|
|
}
|
|
|
|
TEST_F(Parser, Whitespace) {
|
|
EXPECT_CALL(mock_handler, RegisterRule("r1", "r1 expression"))
|
|
.WillOnce(Return());
|
|
EXPECT_CALL(mock_handler, RegisterRule("r2", "r2 expression"))
|
|
.WillOnce(Return());
|
|
EXPECT_TRUE(parser.Parse(" r1:\tr1\nexpression \tr2:\t\rr2\r\n "
|
|
"expression \n"));
|
|
}
|
|
|
|
TEST_F(Parser, WhitespaceLoneColon) {
|
|
EXPECT_FALSE(parser.Parse(" \n:\t "));
|
|
}
|
|
|
|
TEST_F(Parser, EmptyName) {
|
|
EXPECT_CALL(mock_handler, RegisterRule("reg", _))
|
|
.Times(AtMost(1))
|
|
.WillRepeatedly(Return());
|
|
EXPECT_FALSE(parser.Parse("reg: expr1 : expr2"));
|
|
}
|
|
|
|
TEST_F(Parser, RuleLoneColon) {
|
|
EXPECT_CALL(mock_handler, RegisterRule("r1", "expr"))
|
|
.Times(AtMost(1))
|
|
.WillRepeatedly(Return());
|
|
EXPECT_FALSE(parser.Parse(" r1: expr :"));
|
|
}
|
|
|
|
TEST_F(Parser, RegNoExprRule) {
|
|
EXPECT_CALL(mock_handler, RegisterRule("r1", "expr"))
|
|
.Times(AtMost(1))
|
|
.WillRepeatedly(Return());
|
|
EXPECT_FALSE(parser.Parse("r0: r1: expr"));
|
|
}
|
|
|
|
class ParseHandlerFixture: public CFIFixture {
|
|
public:
|
|
ParseHandlerFixture() : CFIFixture(), handler(&cfi) { }
|
|
CFIFrameInfoParseHandler handler;
|
|
};
|
|
|
|
class ParseHandler: public ParseHandlerFixture, public Test { };
|
|
|
|
TEST_F(ParseHandler, CFARARule) {
|
|
handler.CFARule("reg-for-cfa");
|
|
handler.RARule("reg-for-ra");
|
|
registers["reg-for-cfa"] = 0x268a9a4a3821a797ULL;
|
|
registers["reg-for-ra"] = 0x6301b475b8b91c02ULL;
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(0x268a9a4a3821a797ULL, caller_registers[".cfa"]);
|
|
ASSERT_EQ(0x6301b475b8b91c02ULL, caller_registers[".ra"]);
|
|
}
|
|
|
|
TEST_F(ParseHandler, RegisterRules) {
|
|
handler.CFARule("reg-for-cfa");
|
|
handler.RARule("reg-for-ra");
|
|
handler.RegisterRule("reg1", "reg-for-reg1");
|
|
handler.RegisterRule("reg2", "reg-for-reg2");
|
|
handler.RegisterRule("reg3", "reg3");
|
|
registers["reg-for-cfa"] = 0x268a9a4a3821a797ULL;
|
|
registers["reg-for-ra"] = 0x6301b475b8b91c02ULL;
|
|
registers["reg-for-reg1"] = 0x06cde8e2ff062481ULL;
|
|
registers["reg-for-reg2"] = 0xff0c4f76403173e2ULL;
|
|
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
|
|
&caller_registers));
|
|
ASSERT_EQ(0x268a9a4a3821a797ULL, caller_registers[".cfa"]);
|
|
ASSERT_EQ(0x6301b475b8b91c02ULL, caller_registers[".ra"]);
|
|
ASSERT_EQ(0x06cde8e2ff062481ULL, caller_registers["reg1"]);
|
|
ASSERT_EQ(0xff0c4f76403173e2ULL, caller_registers["reg2"]);
|
|
ASSERT_EQ(caller_registers.end(), caller_registers.find("reg3"));
|
|
}
|
|
|
|
struct SimpleCFIWalkerFixture {
|
|
struct RawContext {
|
|
uint64_t r0, r1, r2, r3, r4, sp, pc;
|
|
};
|
|
enum Validity {
|
|
R0_VALID = 0x01,
|
|
R1_VALID = 0x02,
|
|
R2_VALID = 0x04,
|
|
R3_VALID = 0x08,
|
|
R4_VALID = 0x10,
|
|
SP_VALID = 0x20,
|
|
PC_VALID = 0x40
|
|
};
|
|
typedef SimpleCFIWalker<uint64_t, RawContext> CFIWalker;
|
|
|
|
SimpleCFIWalkerFixture()
|
|
: walker(register_map,
|
|
sizeof(register_map) / sizeof(register_map[0])) { }
|
|
|
|
static CFIWalker::RegisterSet register_map[7];
|
|
CFIFrameInfo call_frame_info;
|
|
CFIWalker walker;
|
|
MockMemoryRegion memory;
|
|
RawContext callee_context, caller_context;
|
|
};
|
|
|
|
SimpleCFIWalkerFixture::CFIWalker::RegisterSet
|
|
SimpleCFIWalkerFixture::register_map[7] = {
|
|
{ "r0", NULL, true, R0_VALID, &RawContext::r0 },
|
|
{ "r1", NULL, true, R1_VALID, &RawContext::r1 },
|
|
{ "r2", NULL, false, R2_VALID, &RawContext::r2 },
|
|
{ "r3", NULL, false, R3_VALID, &RawContext::r3 },
|
|
{ "r4", NULL, true, R4_VALID, &RawContext::r4 },
|
|
{ "sp", ".cfa", true, SP_VALID, &RawContext::sp },
|
|
{ "pc", ".ra", true, PC_VALID, &RawContext::pc },
|
|
};
|
|
|
|
class SimpleWalker: public SimpleCFIWalkerFixture, public Test { };
|
|
|
|
TEST_F(SimpleWalker, Walk) {
|
|
// Stack_top is the current stack pointer, pointing to the lowest
|
|
// address of a frame that looks like this (all 64-bit words):
|
|
//
|
|
// sp -> saved r0
|
|
// garbage
|
|
// return address
|
|
// cfa ->
|
|
//
|
|
// r0 has been saved on the stack.
|
|
// r1 has been saved in r2.
|
|
// r2 and r3 are not recoverable.
|
|
// r4 is not recoverable, even though it is a callee-saves register.
|
|
// Some earlier frame's unwinder must have failed to recover it.
|
|
|
|
uint64_t stack_top = 0x83254944b20d5512ULL;
|
|
|
|
// Saved r0.
|
|
EXPECT_CALL(memory,
|
|
GetMemoryAtAddress(stack_top, A<uint64_t*>()))
|
|
.WillRepeatedly(DoAll(SetArgumentPointee<1>(0xdc1975eba8602302ULL),
|
|
Return(true)));
|
|
// Saved return address.
|
|
EXPECT_CALL(memory,
|
|
GetMemoryAtAddress(stack_top + 16, A<uint64_t*>()))
|
|
.WillRepeatedly(DoAll(SetArgumentPointee<1>(0xba5ad6d9acce28deULL),
|
|
Return(true)));
|
|
|
|
call_frame_info.SetCFARule("sp 24 +");
|
|
call_frame_info.SetRARule(".cfa 8 - ^");
|
|
call_frame_info.SetRegisterRule("r0", ".cfa 24 - ^");
|
|
call_frame_info.SetRegisterRule("r1", "r2");
|
|
|
|
callee_context.r0 = 0x94e030ca79edd119ULL;
|
|
callee_context.r1 = 0x937b4d7e95ce52d9ULL;
|
|
callee_context.r2 = 0x5fe0027416b8b62aULL; // caller's r1
|
|
// callee_context.r3 is not valid in callee.
|
|
// callee_context.r4 is not valid in callee.
|
|
callee_context.sp = stack_top;
|
|
callee_context.pc = 0x25b21b224311d280ULL;
|
|
int callee_validity = R0_VALID | R1_VALID | R2_VALID | SP_VALID | PC_VALID;
|
|
|
|
memset(&caller_context, 0, sizeof(caller_context));
|
|
|
|
int caller_validity;
|
|
EXPECT_TRUE(walker.FindCallerRegisters(memory, call_frame_info,
|
|
callee_context, callee_validity,
|
|
&caller_context, &caller_validity));
|
|
EXPECT_EQ(R0_VALID | R1_VALID | SP_VALID | PC_VALID, caller_validity);
|
|
EXPECT_EQ(0xdc1975eba8602302ULL, caller_context.r0);
|
|
EXPECT_EQ(0x5fe0027416b8b62aULL, caller_context.r1);
|
|
EXPECT_EQ(stack_top + 24, caller_context.sp);
|
|
EXPECT_EQ(0xba5ad6d9acce28deULL, caller_context.pc);
|
|
}
|