Skip to content

Commit b563b27

Browse files
[lldb] Convert registers values into target endian for expressions (llvm#148836)
Relates to llvm#135707 Where it was reported that reading the PC using "register read" had different results to an expression "$pc". This was happening because registers are treated in lldb as pure "values" that don't really have an endian. We have to store them somewhere on the host of course, so the endian becomes host endian. When you want to use a register as a value in an expression you're pretending that it's a variable in memory. In target memory. Therefore we must convert the register value to that endian before use. The test I have added is based on the one used for XML register flags. Where I fake an AArch64 little endian and an s390x big endian target. I set up the data in such a way the pc value should print the same for both, either with register read or an expression. I considered just adding a live process test that checks the two are the same but with on one doing cross endian testing, I doubt it would have ever caught this bug. Simulating this means most of the time, little endian hosts will test little to little and little to big. In the minority of cases with a big endian host, they'll check the reverse. Covering all the combinations.
1 parent dc41571 commit b563b27

File tree

2 files changed

+97
-14
lines changed

2 files changed

+97
-14
lines changed

lldb/source/Expression/Materializer.cpp

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,29 +1377,26 @@ class EntityRegister : public Materializer::Entity {
13771377
return;
13781378
}
13791379

1380-
DataExtractor register_data;
1381-
1382-
if (!reg_value.GetData(register_data)) {
1383-
err = Status::FromErrorStringWithFormat(
1384-
"couldn't get the data for register %s", m_register_info.name);
1385-
return;
1386-
}
1387-
1388-
if (register_data.GetByteSize() != m_register_info.byte_size) {
1380+
if (reg_value.GetByteSize() != m_register_info.byte_size) {
13891381
err = Status::FromErrorStringWithFormat(
13901382
"data for register %s had size %llu but we expected %llu",
1391-
m_register_info.name, (unsigned long long)register_data.GetByteSize(),
1383+
m_register_info.name, (unsigned long long)reg_value.GetByteSize(),
13921384
(unsigned long long)m_register_info.byte_size);
13931385
return;
13941386
}
13951387

1396-
m_register_contents = std::make_shared<DataBufferHeap>(
1397-
register_data.GetDataStart(), register_data.GetByteSize());
1388+
lldb_private::DataBufferHeap buf(reg_value.GetByteSize(), 0);
1389+
reg_value.GetAsMemoryData(m_register_info, buf.GetBytes(),
1390+
buf.GetByteSize(), map.GetByteOrder(), err);
1391+
if (!err.Success())
1392+
return;
1393+
1394+
m_register_contents = std::make_shared<DataBufferHeap>(buf);
13981395

13991396
Status write_error;
14001397

1401-
map.WriteMemory(load_addr, register_data.GetDataStart(),
1402-
register_data.GetByteSize(), write_error);
1398+
map.WriteMemory(load_addr, buf.GetBytes(), reg_value.GetByteSize(),
1399+
write_error);
14031400

14041401
if (!write_error.Success()) {
14051402
err = Status::FromErrorStringWithFormat(
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
""" Check that registers written to memory for expression evaluation are
2+
written using the target's endian not the host's.
3+
"""
4+
5+
from enum import Enum
6+
from textwrap import dedent
7+
import lldb
8+
from lldbsuite.test.lldbtest import *
9+
from lldbsuite.test.decorators import *
10+
from lldbsuite.test.gdbclientutils import *
11+
from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase
12+
13+
14+
class Endian(Enum):
15+
BIG = 0
16+
LITTLE = 1
17+
18+
19+
class Responder(MockGDBServerResponder):
20+
def __init__(self, doc, endian):
21+
super().__init__()
22+
self.target_xml = doc
23+
self.endian = endian
24+
25+
def qXferRead(self, obj, annex, offset, length):
26+
if annex == "target.xml":
27+
return self.target_xml, False
28+
return (None,)
29+
30+
def readRegister(self, regnum):
31+
return "E01"
32+
33+
def readRegisters(self):
34+
# 64 bit pc value.
35+
data = ["00", "00", "00", "00", "00", "00", "12", "34"]
36+
if self.endian == Endian.LITTLE:
37+
data.reverse()
38+
return "".join(data)
39+
40+
41+
class TestXMLRegisterFlags(GDBRemoteTestBase):
42+
def do_endian_test(self, endian):
43+
architecture, pc_reg_name = {
44+
Endian.BIG: ("s390x", "pswa"),
45+
Endian.LITTLE: ("aarch64", "pc"),
46+
}[endian]
47+
48+
self.server.responder = Responder(
49+
dedent(
50+
f"""\
51+
<?xml version="1.0"?>
52+
<target version="1.0">
53+
<architecture>{architecture}</architecture>
54+
<feature>
55+
<reg name="{pc_reg_name}" bitsize="64"/>
56+
</feature>
57+
</target>"""
58+
),
59+
endian,
60+
)
61+
target = self.dbg.CreateTarget("")
62+
process = self.connect(target)
63+
lldbutil.expect_state_changes(
64+
self, self.dbg.GetListener(), process, [lldb.eStateStopped]
65+
)
66+
67+
# If expressions convert register values into target endian, the
68+
# result of register read and expr should be the same.
69+
pc_value = "0x0000000000001234"
70+
self.expect(
71+
"register read pc",
72+
substrs=[pc_value],
73+
)
74+
self.expect("expr --format hex -- $pc", substrs=[pc_value])
75+
76+
@skipIfXmlSupportMissing
77+
@skipIfRemote
78+
def test_little_endian_target(self):
79+
self.do_endian_test(Endian.LITTLE)
80+
81+
@skipIfXmlSupportMissing
82+
@skipIfRemote
83+
# Unlike AArch64, we do need the backend present for this test to work.
84+
@skipIfLLVMTargetMissing("SystemZ")
85+
def test_big_endian_target(self):
86+
self.do_endian_test(Endian.BIG)

0 commit comments

Comments
 (0)