199451b44SJordan Rupprechtimport json
299451b44SJordan Rupprechtimport re
399451b44SJordan Rupprecht
499451b44SJordan Rupprechtimport gdbremote_testcase
599451b44SJordan Rupprechtfrom lldbsuite.test.decorators import *
699451b44SJordan Rupprechtfrom lldbsuite.test.lldbtest import *
799451b44SJordan Rupprechtfrom lldbsuite.test import lldbutil
899451b44SJordan Rupprecht
999451b44SJordan Rupprechtclass TestGdbRemoteThreadsInStopReply(
1099451b44SJordan Rupprecht        gdbremote_testcase.GdbRemoteTestCaseBase):
1199451b44SJordan Rupprecht
1299451b44SJordan Rupprecht    ENABLE_THREADS_IN_STOP_REPLY_ENTRIES = [
1399451b44SJordan Rupprecht        "read packet: $QListThreadsInStopReply#21",
1499451b44SJordan Rupprecht        "send packet: $OK#00",
1599451b44SJordan Rupprecht    ]
1699451b44SJordan Rupprecht
175a4fe166SPavel Labath    def gather_stop_reply_fields(self, thread_count, field_names):
185a4fe166SPavel Labath        context, threads = self.launch_with_threads(thread_count)
195a4fe166SPavel Labath        key_vals_text = context.get("stop_reply_kv")
205a4fe166SPavel Labath        self.assertIsNotNone(key_vals_text)
2199451b44SJordan Rupprecht
225a4fe166SPavel Labath        self.reset_test_sequence()
2399451b44SJordan Rupprecht        self.add_register_info_collection_packets()
2499451b44SJordan Rupprecht        self.add_process_info_collection_packets()
2599451b44SJordan Rupprecht
2699451b44SJordan Rupprecht        context = self.expect_gdbremote_sequence()
2799451b44SJordan Rupprecht        self.assertIsNotNone(context)
2899451b44SJordan Rupprecht        hw_info = self.parse_hw_info(context)
2999451b44SJordan Rupprecht
3099451b44SJordan Rupprecht        # Parse the stop reply contents.
3199451b44SJordan Rupprecht        kv_dict = self.parse_key_val_dict(key_vals_text)
3299451b44SJordan Rupprecht
3399451b44SJordan Rupprecht        result = dict();
3499451b44SJordan Rupprecht        result["pc_register"] = hw_info["pc_register"]
3599451b44SJordan Rupprecht        result["little_endian"] = hw_info["little_endian"]
3699451b44SJordan Rupprecht        for key_field in field_names:
3799451b44SJordan Rupprecht            result[key_field] = kv_dict.get(key_field)
3899451b44SJordan Rupprecht
3999451b44SJordan Rupprecht        return result
4099451b44SJordan Rupprecht
415a4fe166SPavel Labath    def gather_stop_reply_threads(self, thread_count):
4299451b44SJordan Rupprecht        # Pull out threads from stop response.
4399451b44SJordan Rupprecht        stop_reply_threads_text = self.gather_stop_reply_fields(
445a4fe166SPavel Labath                thread_count, ["threads"])["threads"]
4599451b44SJordan Rupprecht        if stop_reply_threads_text:
4699451b44SJordan Rupprecht            return [int(thread_id, 16)
4799451b44SJordan Rupprecht                    for thread_id in stop_reply_threads_text.split(",")]
4899451b44SJordan Rupprecht        else:
4999451b44SJordan Rupprecht            return []
5099451b44SJordan Rupprecht
515a4fe166SPavel Labath    def gather_stop_reply_pcs(self, thread_count):
525a4fe166SPavel Labath        results = self.gather_stop_reply_fields(thread_count, ["threads", "thread-pcs"])
5399451b44SJordan Rupprecht        if not results:
5499451b44SJordan Rupprecht            return []
5599451b44SJordan Rupprecht
5699451b44SJordan Rupprecht        threads_text = results["threads"]
5799451b44SJordan Rupprecht        pcs_text = results["thread-pcs"]
5899451b44SJordan Rupprecht        thread_ids = threads_text.split(",")
5999451b44SJordan Rupprecht        pcs = pcs_text.split(",")
60b3a0c4d7SRaphael Isemann        self.assertEquals(len(thread_ids), len(pcs))
6199451b44SJordan Rupprecht
6299451b44SJordan Rupprecht        thread_pcs = dict()
6399451b44SJordan Rupprecht        for i in range(0, len(pcs)):
6499451b44SJordan Rupprecht            thread_pcs[int(thread_ids[i], 16)] = pcs[i]
6599451b44SJordan Rupprecht
6699451b44SJordan Rupprecht        result = dict()
6799451b44SJordan Rupprecht        result["thread_pcs"] = thread_pcs
6899451b44SJordan Rupprecht        result["pc_register"] = results["pc_register"]
6999451b44SJordan Rupprecht        result["little_endian"] = results["little_endian"]
7099451b44SJordan Rupprecht        return result
7199451b44SJordan Rupprecht
7299451b44SJordan Rupprecht    def switch_endian(self, egg):
7399451b44SJordan Rupprecht        return "".join(reversed(re.findall("..", egg)))
7499451b44SJordan Rupprecht
7599451b44SJordan Rupprecht    def parse_hw_info(self, context):
7699451b44SJordan Rupprecht        self.assertIsNotNone(context)
7799451b44SJordan Rupprecht        process_info = self.parse_process_info_response(context)
7899451b44SJordan Rupprecht        endian = process_info.get("endian")
7999451b44SJordan Rupprecht        reg_info = self.parse_register_info_packets(context)
8099451b44SJordan Rupprecht        (pc_lldb_reg_index, pc_reg_info) = self.find_pc_reg_info(reg_info)
8199451b44SJordan Rupprecht
8299451b44SJordan Rupprecht        hw_info = dict()
8399451b44SJordan Rupprecht        hw_info["pc_register"] = pc_lldb_reg_index
8499451b44SJordan Rupprecht        hw_info["little_endian"] = (endian == "little")
8599451b44SJordan Rupprecht        return hw_info
8699451b44SJordan Rupprecht
8799451b44SJordan Rupprecht    def gather_threads_info_pcs(self, pc_register, little_endian):
8899451b44SJordan Rupprecht        self.reset_test_sequence()
8999451b44SJordan Rupprecht        self.test_sequence.add_log_lines(
9099451b44SJordan Rupprecht                [
9199451b44SJordan Rupprecht                    "read packet: $jThreadsInfo#c1",
9299451b44SJordan Rupprecht                    {
9399451b44SJordan Rupprecht                        "direction": "send",
9499451b44SJordan Rupprecht                        "regex": r"^\$(.*)#[0-9a-fA-F]{2}$",
9599451b44SJordan Rupprecht                        "capture": {
9699451b44SJordan Rupprecht                            1: "threads_info"}},
9799451b44SJordan Rupprecht                ],
9899451b44SJordan Rupprecht                True)
9999451b44SJordan Rupprecht
10099451b44SJordan Rupprecht        context = self.expect_gdbremote_sequence()
10199451b44SJordan Rupprecht        self.assertIsNotNone(context)
10299451b44SJordan Rupprecht        threads_info = context.get("threads_info")
10399451b44SJordan Rupprecht        register = str(pc_register)
10499451b44SJordan Rupprecht        # The jThreadsInfo response is not valid JSON data, so we have to
10599451b44SJordan Rupprecht        # clean it up first.
106e609fe68SMuhammad Omair Javaid        jthreads_info = json.loads(re.sub(r"}]", "}", threads_info))
10799451b44SJordan Rupprecht        thread_pcs = dict()
10899451b44SJordan Rupprecht        for thread_info in jthreads_info:
10999451b44SJordan Rupprecht            tid = thread_info["tid"]
11099451b44SJordan Rupprecht            pc = thread_info["registers"][register]
11199451b44SJordan Rupprecht            thread_pcs[tid] = self.switch_endian(pc) if little_endian else pc
11299451b44SJordan Rupprecht
11399451b44SJordan Rupprecht        return thread_pcs
11499451b44SJordan Rupprecht
1150a8a2453SPavel Labath
1160a8a2453SPavel Labath    def test_QListThreadsInStopReply_supported(self):
1170a8a2453SPavel Labath        self.build()
1180a8a2453SPavel Labath        self.set_inferior_startup_launch()
11999451b44SJordan Rupprecht        procs = self.prep_debug_monitor_and_inferior()
12099451b44SJordan Rupprecht        self.test_sequence.add_log_lines(
12199451b44SJordan Rupprecht            self.ENABLE_THREADS_IN_STOP_REPLY_ENTRIES, True)
12299451b44SJordan Rupprecht
12399451b44SJordan Rupprecht        context = self.expect_gdbremote_sequence()
12499451b44SJordan Rupprecht        self.assertIsNotNone(context)
12599451b44SJordan Rupprecht
12699451b44SJordan Rupprecht    @skipIfNetBSD
127*1046b726SPavel Labath    @expectedFailureAll(oslist=["windows"]) # Extra threads present
1280a8a2453SPavel Labath    def test_stop_reply_reports_multiple_threads(self):
12999451b44SJordan Rupprecht        self.build()
13099451b44SJordan Rupprecht        self.set_inferior_startup_launch()
1310a8a2453SPavel Labath        # Gather threads from stop notification when QThreadsInStopReply is
13299451b44SJordan Rupprecht        # enabled.
1335a4fe166SPavel Labath        self.test_sequence.add_log_lines(
1345a4fe166SPavel Labath            self.ENABLE_THREADS_IN_STOP_REPLY_ENTRIES, True)
1355a4fe166SPavel Labath        stop_reply_threads = self.gather_stop_reply_threads(5)
1360a8a2453SPavel Labath        self.assertEqual(len(stop_reply_threads), 5)
13799451b44SJordan Rupprecht
1380a8a2453SPavel Labath    @skipIfNetBSD
1390a8a2453SPavel Labath    def test_no_QListThreadsInStopReply_supplies_no_threads(self):
14099451b44SJordan Rupprecht        self.build()
14199451b44SJordan Rupprecht        self.set_inferior_startup_launch()
1420a8a2453SPavel Labath        # Gather threads from stop notification when QThreadsInStopReply is not
1430a8a2453SPavel Labath        # enabled.
1445a4fe166SPavel Labath        stop_reply_threads = self.gather_stop_reply_threads(5)
1450a8a2453SPavel Labath        self.assertEqual(len(stop_reply_threads), 0)
14699451b44SJordan Rupprecht
14799451b44SJordan Rupprecht    @skipIfNetBSD
1480a8a2453SPavel Labath    def test_stop_reply_reports_correct_threads(self):
14999451b44SJordan Rupprecht        self.build()
15099451b44SJordan Rupprecht        self.set_inferior_startup_launch()
15199451b44SJordan Rupprecht        # Gather threads from stop notification when QThreadsInStopReply is
15299451b44SJordan Rupprecht        # enabled.
1530a8a2453SPavel Labath        thread_count = 5
1545a4fe166SPavel Labath        self.test_sequence.add_log_lines(
1555a4fe166SPavel Labath            self.ENABLE_THREADS_IN_STOP_REPLY_ENTRIES, True)
1565a4fe166SPavel Labath        stop_reply_threads = self.gather_stop_reply_threads(thread_count)
15799451b44SJordan Rupprecht
15899451b44SJordan Rupprecht        # Gather threads from q{f,s}ThreadInfo.
15999451b44SJordan Rupprecht        self.reset_test_sequence()
16099451b44SJordan Rupprecht        self.add_threadinfo_collection_packets()
16199451b44SJordan Rupprecht
16299451b44SJordan Rupprecht        context = self.expect_gdbremote_sequence()
16399451b44SJordan Rupprecht        self.assertIsNotNone(context)
16499451b44SJordan Rupprecht
16599451b44SJordan Rupprecht        threads = self.parse_threadinfo_packets(context)
16699451b44SJordan Rupprecht        self.assertIsNotNone(threads)
167*1046b726SPavel Labath        self.assertGreaterEqual(len(threads), thread_count)
16899451b44SJordan Rupprecht
16999451b44SJordan Rupprecht        # Ensure each thread in q{f,s}ThreadInfo appears in stop reply threads
17099451b44SJordan Rupprecht        for tid in threads:
1710a8a2453SPavel Labath            self.assertIn(tid, stop_reply_threads)
17299451b44SJordan Rupprecht
17399451b44SJordan Rupprecht    @skipIfNetBSD
1740a8a2453SPavel Labath    def test_stop_reply_contains_thread_pcs(self):
17599451b44SJordan Rupprecht        self.build()
17699451b44SJordan Rupprecht        self.set_inferior_startup_launch()
1770a8a2453SPavel Labath        thread_count = 5
1785a4fe166SPavel Labath        self.test_sequence.add_log_lines(
1795a4fe166SPavel Labath            self.ENABLE_THREADS_IN_STOP_REPLY_ENTRIES, True)
1805a4fe166SPavel Labath        results = self.gather_stop_reply_pcs(thread_count)
18199451b44SJordan Rupprecht        stop_reply_pcs = results["thread_pcs"]
18299451b44SJordan Rupprecht        pc_register = results["pc_register"]
18399451b44SJordan Rupprecht        little_endian = results["little_endian"]
184*1046b726SPavel Labath        self.assertGreaterEqual(len(stop_reply_pcs), thread_count)
18599451b44SJordan Rupprecht
18699451b44SJordan Rupprecht        threads_info_pcs = self.gather_threads_info_pcs(pc_register,
18799451b44SJordan Rupprecht                little_endian)
18899451b44SJordan Rupprecht
189*1046b726SPavel Labath        self.assertEqual(len(threads_info_pcs), len(stop_reply_pcs))
19099451b44SJordan Rupprecht        for thread_id in stop_reply_pcs:
1910a8a2453SPavel Labath            self.assertIn(thread_id, threads_info_pcs)
1920a8a2453SPavel Labath            self.assertEqual(int(stop_reply_pcs[thread_id], 16),
1930a8a2453SPavel Labath                    int(threads_info_pcs[thread_id], 16))
194