Added gdb-remote tests for Q{Save,Restore}RegisterState.

Tests for both thread suffix and no thread suffix execution.

Moved some bit-flipping helper methods from TestLldbGdbServer
into the base GdbRemoteTestCaseBase class.

llvm-svn: 211381
This commit is contained in:
Todd Fiala 2014-06-20 17:39:24 +00:00
parent eb57aa65dc
commit 9846d45d97
3 changed files with 309 additions and 95 deletions

View File

@ -0,0 +1,132 @@
import unittest2
import gdbremote_testcase
from lldbtest import *
class TestGdbRemoteRegisterState(gdbremote_testcase.GdbRemoteTestCaseBase):
"""Test QSaveRegisterState/QRestoreRegisterState support."""
def grp_register_save_restore_works(self, with_suffix):
# Start up the process, use thread suffix, grab main thread id.
inferior_args = ["message:main entered", "sleep:5"]
procs = self.prep_debug_monitor_and_inferior(inferior_args=inferior_args)
self.add_process_info_collection_packets()
self.add_register_info_collection_packets()
if with_suffix:
self.add_thread_suffix_request_packets()
self.add_threadinfo_collection_packets()
self.test_sequence.add_log_lines([
# Start the inferior...
"read packet: $c#00",
# ... match output....
{ "type":"output_match", "regex":r"^message:main entered\r\n$" },
], True)
# ... then interrupt.
self.add_interrupt_packets()
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
# Gather process info.
process_info = self.parse_process_info_response(context)
endian = process_info.get("endian")
self.assertIsNotNone(endian)
# Gather register info.
reg_infos = self.parse_register_info_packets(context)
self.assertIsNotNone(reg_infos)
self.add_lldb_register_index(reg_infos)
# Pull out the register infos that we think we can bit flip successfully.
gpr_reg_infos = [reg_info for reg_info in reg_infos if self.is_bit_flippable_register(reg_info)]
self.assertTrue(len(gpr_reg_infos) > 0)
# Gather thread info.
if with_suffix:
threads = self.parse_threadinfo_packets(context)
self.assertIsNotNone(threads)
thread_id = threads[0]
self.assertIsNotNone(thread_id)
# print "Running on thread: 0x{:x}".format(thread_id)
else:
thread_id = None
# Save register state.
self.reset_test_sequence()
self.add_QSaveRegisterState_packets(thread_id)
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
(success, state_id) = self.parse_QSaveRegisterState_response(context)
self.assertTrue(success)
self.assertIsNotNone(state_id)
# print "saved register state id: {}".format(state_id)
# Remember initial register values.
initial_reg_values = self.read_register_values(gpr_reg_infos, endian, thread_id=thread_id)
# print "initial_reg_values: {}".format(initial_reg_values)
# Flip gpr register values.
(successful_writes, failed_writes) = self.flip_all_bits_in_each_register_value(gpr_reg_infos, endian, thread_id=thread_id)
# print "successful writes: {}, failed writes: {}".format(successful_writes, failed_writes)
self.assertTrue(successful_writes > 0)
flipped_reg_values = self.read_register_values(gpr_reg_infos, endian, thread_id=thread_id)
# print "flipped_reg_values: {}".format(flipped_reg_values)
# Restore register values.
self.reset_test_sequence()
self.add_QRestoreRegisterState_packets(state_id, thread_id)
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
# Verify registers match initial register values.
final_reg_values = self.read_register_values(gpr_reg_infos, endian, thread_id=thread_id)
# print "final_reg_values: {}".format(final_reg_values)
self.assertIsNotNone(final_reg_values)
self.assertEquals(final_reg_values, initial_reg_values)
@debugserver_test
@dsym_test
def test_grp_register_save_restore_works_with_suffix_debugserver_dsym(self):
USE_THREAD_SUFFIX = True
self.init_debugserver_test()
self.buildDsym()
self.set_inferior_startup_launch()
self.grp_register_save_restore_works(USE_THREAD_SUFFIX)
@llgs_test
@dwarf_test
@unittest2.expectedFailure()
def test_grp_register_save_restore_works_with_suffix_llgs_dwarf(self):
USE_THREAD_SUFFIX = True
self.init_llgs_test()
self.buildDwarf()
self.set_inferior_startup_launch()
self.grp_register_save_restore_works(USE_THREAD_SUFFIX)
@debugserver_test
@dsym_test
def test_grp_register_save_restore_works_no_suffix_debugserver_dsym(self):
USE_THREAD_SUFFIX = False
self.init_debugserver_test()
self.buildDsym()
self.set_inferior_startup_launch()
self.grp_register_save_restore_works(USE_THREAD_SUFFIX)
@llgs_test
@dwarf_test
@unittest2.expectedFailure()
def test_grp_register_save_restore_works_no_suffix_llgs_dwarf(self):
USE_THREAD_SUFFIX = False
self.init_llgs_test()
self.buildDwarf()
self.set_inferior_startup_launch()
self.grp_register_save_restore_works(USE_THREAD_SUFFIX)
if __name__ == '__main__':
unittest2.main()

View File

@ -1670,99 +1670,6 @@ class LldbGdbServerTestCase(gdbremote_testcase.GdbRemoteTestCaseBase):
self.set_inferior_startup_launch()
self.written_M_content_reads_back_correctly()
def flip_all_bits_in_each_register_value(self, reg_infos, endian):
self.assertIsNotNone(reg_infos)
successful_writes = 0
failed_writes = 0
for reg_info in reg_infos:
# Use the lldb register index added to the reg info. We're not necessarily
# working off a full set of register infos, so an inferred register index could be wrong.
reg_index = reg_info["lldb_register_index"]
self.assertIsNotNone(reg_index)
reg_byte_size = int(reg_info["bitsize"])/8
self.assertTrue(reg_byte_size > 0)
# Read the existing value.
self.reset_test_sequence()
self.test_sequence.add_log_lines(
["read packet: $p{0:x}#00".format(reg_index),
{ "direction":"send", "regex":r"^\$([0-9a-fA-F]+)#", "capture":{1:"p_response"} }],
True)
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
# Verify the response length.
p_response = context.get("p_response")
self.assertIsNotNone(p_response)
initial_reg_value = lldbgdbserverutils.unpack_register_hex_unsigned(endian, p_response)
# Flip the value by xoring with all 1s
all_one_bits_raw = "ff" * (int(reg_info["bitsize"]) / 8)
flipped_bits_int = initial_reg_value ^ int(all_one_bits_raw, 16)
# print "reg (index={}, name={}): val={}, flipped bits (int={}, hex={:x})".format(reg_index, reg_info["name"], initial_reg_value, flipped_bits_int, flipped_bits_int)
# Write the flipped value to the register.
self.reset_test_sequence()
self.test_sequence.add_log_lines(
["read packet: $P{0:x}={1}#00".format(reg_index, lldbgdbserverutils.pack_register_hex(endian, flipped_bits_int, byte_size=reg_byte_size)),
{ "direction":"send", "regex":r"^\$(OK|E[0-9a-fA-F]+)#[0-9a-fA-F]{2}", "capture":{1:"P_response"} },
], True)
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
# Determine if the write succeeded. There are a handful of registers that can fail, or partially fail
# (e.g. flags, segment selectors, etc.) due to register value restrictions. Don't worry about them
# all flipping perfectly.
P_response = context.get("P_response")
self.assertIsNotNone(P_response)
if P_response == "OK":
successful_writes += 1
else:
failed_writes += 1
# print "reg (index={}, name={}) write FAILED (error: {})".format(reg_index, reg_info["name"], P_response)
# Read back the register value, ensure it matches the flipped value.
if P_response == "OK":
self.reset_test_sequence()
self.test_sequence.add_log_lines(
["read packet: $p{0:x}#00".format(reg_index),
{ "direction":"send", "regex":r"^\$([0-9a-fA-F]+)#", "capture":{1:"p_response"} }],
True)
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
verify_p_response_raw = context.get("p_response")
self.assertIsNotNone(verify_p_response_raw)
verify_bits = lldbgdbserverutils.unpack_register_hex_unsigned(endian, verify_p_response_raw)
if verify_bits != flipped_bits_int:
# Some registers, like mxcsrmask and others, will permute what's written. Adjust succeed/fail counts.
# print "reg (index={}, name={}): read verify FAILED: wrote {:x}, verify read back {:x}".format(reg_index, reg_info["name"], flipped_bits_int, verify_bits)
successful_writes -= 1
failed_writes +=1
return (successful_writes, failed_writes)
def is_bit_flippable_register(self, reg_info):
if not reg_info:
return False
if not "set" in reg_info:
return False
if reg_info["set"] != "General Purpose Registers":
return False
if ("container-regs" in reg_info) and (len(reg_info["container-regs"]) > 0):
# Don't try to bit flip registers contained in another register.
return False
if re.match("^.s$", reg_info["name"]):
# This is a 2-letter register name that ends in "s", like a segment register.
# Don't try to bit flip these.
return False
# Okay, this looks fine-enough.
return True
def P_writes_all_gpr_registers(self):
# Start inferior debug session, grab all register info.
procs = self.prep_debug_monitor_and_inferior(inferior_args=["sleep:2"])
@ -1784,7 +1691,7 @@ class LldbGdbServerTestCase(gdbremote_testcase.GdbRemoteTestCaseBase):
# Pull out the register infos that we think we can bit flip successfully,.
gpr_reg_infos = [reg_info for reg_info in reg_infos if self.is_bit_flippable_register(reg_info)]
self.assertIsNotNone(len(gpr_reg_infos) > 0)
self.assertTrue(len(gpr_reg_infos) > 0)
# Write flipped bit pattern of existing value to each register.
(successful_writes, failed_writes) = self.flip_all_bits_in_each_register_value(gpr_reg_infos, endian)

View File

@ -677,3 +677,178 @@ class GdbRemoteTestCaseBase(TestBase):
self.assertIsNotNone(context.get("stop_signo"))
self.assertIsNotNone(context.get("stop_key_val_text"))
return (int(context["stop_signo"], 16), self.parse_key_val_dict(context["stop_key_val_text"]))
def add_QSaveRegisterState_packets(self, thread_id):
if thread_id:
# Use the thread suffix form.
request = "read packet: $QSaveRegisterState;thread:{:x}#00".format(thread_id)
else:
request = "read packet: $QSaveRegisterState#00"
self.test_sequence.add_log_lines([
request,
{"direction":"send", "regex":r"^\$(E?.*)#[0-9a-fA-F]{2}$", "capture":{1:"save_response" } },
], True)
def parse_QSaveRegisterState_response(self, context):
self.assertIsNotNone(context)
save_response = context.get("save_response")
self.assertIsNotNone(save_response)
if len(save_response) < 1 or save_response[0] == "E":
# error received
return (False, None)
else:
return (True, int(save_response))
def add_QRestoreRegisterState_packets(self, save_id, thread_id=None):
if thread_id:
# Use the thread suffix form.
request = "read packet: $QRestoreRegisterState:{};thread:{:x}#00".format(save_id, thread_id)
else:
request = "read packet: $QRestoreRegisterState:{}#00".format(save_id)
self.test_sequence.add_log_lines([
request,
"send packet: $OK#00"
], True)
def flip_all_bits_in_each_register_value(self, reg_infos, endian, thread_id=None):
self.assertIsNotNone(reg_infos)
successful_writes = 0
failed_writes = 0
for reg_info in reg_infos:
# Use the lldb register index added to the reg info. We're not necessarily
# working off a full set of register infos, so an inferred register index could be wrong.
reg_index = reg_info["lldb_register_index"]
self.assertIsNotNone(reg_index)
reg_byte_size = int(reg_info["bitsize"])/8
self.assertTrue(reg_byte_size > 0)
# Handle thread suffix.
if thread_id:
p_request = "read packet: $p{:x};thread:{:x}#00".format(reg_index, thread_id)
else:
p_request = "read packet: $p{:x}#00".format(reg_index)
# Read the existing value.
self.reset_test_sequence()
self.test_sequence.add_log_lines([
p_request,
{ "direction":"send", "regex":r"^\$([0-9a-fA-F]+)#", "capture":{1:"p_response"} },
], True)
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
# Verify the response length.
p_response = context.get("p_response")
self.assertIsNotNone(p_response)
initial_reg_value = unpack_register_hex_unsigned(endian, p_response)
# Flip the value by xoring with all 1s
all_one_bits_raw = "ff" * (int(reg_info["bitsize"]) / 8)
flipped_bits_int = initial_reg_value ^ int(all_one_bits_raw, 16)
# print "reg (index={}, name={}): val={}, flipped bits (int={}, hex={:x})".format(reg_index, reg_info["name"], initial_reg_value, flipped_bits_int, flipped_bits_int)
# Handle thread suffix for P.
if thread_id:
P_request = "read packet: $P{:x}={};thread:{:x}#00".format(reg_index, pack_register_hex(endian, flipped_bits_int, byte_size=reg_byte_size), thread_id)
else:
P_request = "read packet: $P{:x}={}#00".format(reg_index, pack_register_hex(endian, flipped_bits_int, byte_size=reg_byte_size))
# Write the flipped value to the register.
self.reset_test_sequence()
self.test_sequence.add_log_lines([
P_request,
{ "direction":"send", "regex":r"^\$(OK|E[0-9a-fA-F]+)#[0-9a-fA-F]{2}", "capture":{1:"P_response"} },
], True)
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
# Determine if the write succeeded. There are a handful of registers that can fail, or partially fail
# (e.g. flags, segment selectors, etc.) due to register value restrictions. Don't worry about them
# all flipping perfectly.
P_response = context.get("P_response")
self.assertIsNotNone(P_response)
if P_response == "OK":
successful_writes += 1
else:
failed_writes += 1
# print "reg (index={}, name={}) write FAILED (error: {})".format(reg_index, reg_info["name"], P_response)
# Read back the register value, ensure it matches the flipped value.
if P_response == "OK":
self.reset_test_sequence()
self.test_sequence.add_log_lines([
p_request,
{ "direction":"send", "regex":r"^\$([0-9a-fA-F]+)#", "capture":{1:"p_response"} },
], True)
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
verify_p_response_raw = context.get("p_response")
self.assertIsNotNone(verify_p_response_raw)
verify_bits = unpack_register_hex_unsigned(endian, verify_p_response_raw)
if verify_bits != flipped_bits_int:
# Some registers, like mxcsrmask and others, will permute what's written. Adjust succeed/fail counts.
# print "reg (index={}, name={}): read verify FAILED: wrote {:x}, verify read back {:x}".format(reg_index, reg_info["name"], flipped_bits_int, verify_bits)
successful_writes -= 1
failed_writes +=1
return (successful_writes, failed_writes)
def is_bit_flippable_register(self, reg_info):
if not reg_info:
return False
if not "set" in reg_info:
return False
if reg_info["set"] != "General Purpose Registers":
return False
if ("container-regs" in reg_info) and (len(reg_info["container-regs"]) > 0):
# Don't try to bit flip registers contained in another register.
return False
if re.match("^.s$", reg_info["name"]):
# This is a 2-letter register name that ends in "s", like a segment register.
# Don't try to bit flip these.
return False
# Okay, this looks fine-enough.
return True
def read_register_values(self, reg_infos, endian, thread_id=None):
self.assertIsNotNone(reg_infos)
values = {}
for reg_info in reg_infos:
# We append a register index when load reg infos so we can work with subsets.
reg_index = reg_info.get("lldb_register_index")
self.assertIsNotNone(reg_index)
# Handle thread suffix.
if thread_id:
p_request = "read packet: $p{:x};thread:{:x}#00".format(reg_index, thread_id)
else:
p_request = "read packet: $p{:x}#00".format(reg_index)
# Read it with p.
self.reset_test_sequence()
self.test_sequence.add_log_lines([
p_request,
{ "direction":"send", "regex":r"^\$([0-9a-fA-F]+)#", "capture":{1:"p_response"} },
], True)
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
# Convert value from target endian to integral.
p_response = context.get("p_response")
self.assertIsNotNone(p_response)
self.assertTrue(len(p_response) > 0)
self.assertFalse(p_response[0] == "E")
values[reg_index] = unpack_register_hex_unsigned(endian, p_response)
return values