xref: /llvm-project/lldb/test/API/tools/lldb-server/TestGdbRemotePlatformFile.py (revision b62ba7f5b1caf99a3cbbe06d0e1c788c2dc85416)
1# lldb test suite imports
2from lldbsuite.test.decorators import *
3from lldbsuite.test.lldbtest import TestBase
4from lldbsuite.test import lldbutil
5
6# gdb-remote-specific imports
7import lldbgdbserverutils
8from gdbremote_testcase import GdbRemoteTestCaseBase
9
10import binascii
11import os
12import stat
13import struct
14import typing
15
16
17class GDBStat(typing.NamedTuple):
18    st_dev: int
19    st_ino: int
20    st_mode: int
21    st_nlink: int
22    st_uid: int
23    st_gid: int
24    st_rdev: int
25    st_size: int
26    st_blksize: int
27    st_blocks: int
28    st_atime: int
29    st_mtime: int
30    st_ctime: int
31
32
33def uint32_or_zero(x):
34    return x if x < 2**32 and x >= 0 else 0
35
36
37def uint32_or_max(x):
38    return x if x < 2**32 and x >= 0 else 2**32 - 1
39
40
41def uint32_trunc(x):
42    return x & (2**32 - 1)
43
44
45class TestGdbRemotePlatformFile(GdbRemoteTestCaseBase):
46    @skipIfWindows
47    @add_test_categories(["llgs"])
48    def test_platform_file_rdonly(self):
49        self.vFile_test(read=True)
50
51    @skipIfWindows
52    @add_test_categories(["llgs"])
53    def test_platform_file_wronly(self):
54        self.vFile_test(write=True)
55
56    @skipIfWindows
57    @add_test_categories(["llgs"])
58    def test_platform_file_rdwr(self):
59        self.vFile_test(read=True, write=True)
60
61    @skipIfWindows
62    @add_test_categories(["llgs"])
63    def test_platform_file_wronly_append(self):
64        self.vFile_test(write=True, append=True)
65
66    @skipIfWindows
67    @add_test_categories(["llgs"])
68    def test_platform_file_rdwr_append(self):
69        self.vFile_test(read=True, write=True, append=True)
70
71    @skipIfWindows
72    @add_test_categories(["llgs"])
73    def test_platform_file_wronly_trunc(self):
74        self.vFile_test(write=True, trunc=True)
75
76    @skipIfWindows
77    @add_test_categories(["llgs"])
78    def test_platform_file_rdwr_trunc(self):
79        self.vFile_test(read=True, write=True, trunc=True)
80
81    @skipIfWindows
82    @add_test_categories(["llgs"])
83    def test_platform_file_wronly_creat(self):
84        self.vFile_test(write=True, creat=True)
85
86    @skipIfWindows
87    @add_test_categories(["llgs"])
88    def test_platform_file_wronly_creat_excl(self):
89        self.vFile_test(write=True, creat=True, excl=True)
90
91    @skipIfWindows
92    @add_test_categories(["llgs"])
93    def test_platform_file_wronly_fail(self):
94        server = self.connect_to_debug_monitor()
95        self.assertIsNotNone(server)
96
97        temp_path = self.getBuildArtifact("test")
98        self.assertFalse(os.path.exists(temp_path))
99
100        # attempt to open the file without O_CREAT
101        self.do_handshake()
102        self.test_sequence.add_log_lines(
103            [
104                "read packet: $vFile:open:%s,1,0#00"
105                % (binascii.b2a_hex(temp_path.encode()).decode(),),
106                {"direction": "send", "regex": r"^\$F-1,[0-9a-fA-F]+#[0-9a-fA-F]{2}$"},
107            ],
108            True,
109        )
110        self.expect_gdbremote_sequence()
111
112    @skipIfWindows
113    @add_test_categories(["llgs"])
114    def test_platform_file_wronly_creat_excl_fail(self):
115        server = self.connect_to_debug_monitor()
116        self.assertIsNotNone(server)
117
118        temp_file = self.getBuildArtifact("test")
119        with open(temp_file, "wb"):
120            pass
121        temp_file = lldbutil.install_to_target(self, temp_file)
122
123        # attempt to open the file with O_CREAT|O_EXCL
124        self.do_handshake()
125        self.test_sequence.add_log_lines(
126            [
127                "read packet: $vFile:open:%s,a01,0#00"
128                % (binascii.b2a_hex(temp_file.encode()).decode(),),
129                {"direction": "send", "regex": r"^\$F-1,[0-9a-fA-F]+#[0-9a-fA-F]{2}$"},
130            ],
131            True,
132        )
133        self.expect_gdbremote_sequence()
134
135    @skipIfWindows
136    @add_test_categories(["llgs"])
137    def test_platform_file_size(self):
138        server = self.connect_to_debug_monitor()
139        self.assertIsNotNone(server)
140
141        temp_path = self.getBuildArtifact("test")
142        test_data = b"test data of some length"
143        with open(temp_path, "wb") as temp_file:
144            temp_file.write(test_data)
145        temp_path = lldbutil.install_to_target(self, temp_path)
146
147        self.do_handshake()
148        self.test_sequence.add_log_lines(
149            [
150                "read packet: $vFile:size:%s#00"
151                % (binascii.b2a_hex(temp_path.encode()).decode(),),
152                {
153                    "direction": "send",
154                    "regex": r"^\$F([0-9a-fA-F]+)+#[0-9a-fA-F]{2}$",
155                    "capture": {1: "size"},
156                },
157            ],
158            True,
159        )
160        context = self.expect_gdbremote_sequence()
161        self.assertEqual(int(context["size"], 16), len(test_data))
162
163    @skipIfWindows
164    @add_test_categories(["llgs"])
165    def test_platform_file_mode(self):
166        server = self.connect_to_debug_monitor()
167        self.assertIsNotNone(server)
168
169        temp_path = self.getBuildArtifact("test")
170        test_mode = 0o751
171
172        with open(temp_path, "wb") as temp_file:
173            if lldbplatformutil.getHostPlatform() == "windows":
174                test_mode = 0o700
175            else:
176                os.chmod(temp_file.fileno(), test_mode)
177        temp_path = lldbutil.install_to_target(self, temp_path)
178
179        self.do_handshake()
180        self.test_sequence.add_log_lines(
181            [
182                "read packet: $vFile:mode:%s#00"
183                % (binascii.b2a_hex(temp_path.encode()).decode(),),
184                {
185                    "direction": "send",
186                    "regex": r"^\$F([0-9a-fA-F]+)+#[0-9a-fA-F]{2}$",
187                    "capture": {1: "mode"},
188                },
189            ],
190            True,
191        )
192        context = self.expect_gdbremote_sequence()
193        self.assertEqual(int(context["mode"], 16), test_mode)
194
195    @skipIfWindows
196    @add_test_categories(["llgs"])
197    def test_platform_file_mode_fail(self):
198        server = self.connect_to_debug_monitor()
199        self.assertIsNotNone(server)
200
201        temp_path = self.getBuildArtifact("nonexist")
202
203        self.do_handshake()
204        self.test_sequence.add_log_lines(
205            [
206                "read packet: $vFile:mode:%s#00"
207                % (binascii.b2a_hex(temp_path.encode()).decode(),),
208                {"direction": "send", "regex": r"^\$F-1,0*2+#[0-9a-fA-F]{2}$"},
209            ],
210            True,
211        )
212        self.expect_gdbremote_sequence()
213
214    @skipIfWindows
215    @add_test_categories(["llgs"])
216    def test_platform_file_exists(self):
217        server = self.connect_to_debug_monitor()
218        self.assertIsNotNone(server)
219
220        temp_path = self.getBuildArtifact("test")
221        with open(temp_path, "wb"):
222            pass
223        temp_path = lldbutil.install_to_target(self, temp_path)
224
225        self.do_handshake()
226        self.test_sequence.add_log_lines(
227            [
228                "read packet: $vFile:exists:%s#00"
229                % (binascii.b2a_hex(temp_path.encode()).decode(),),
230                "send packet: $F,1#00",
231            ],
232            True,
233        )
234        self.expect_gdbremote_sequence()
235
236    @skipIfWindows
237    @add_test_categories(["llgs"])
238    def test_platform_file_exists_not(self):
239        server = self.connect_to_debug_monitor()
240        self.assertIsNotNone(server)
241
242        test_path = self.getBuildArtifact("nonexist")
243        self.do_handshake()
244        self.test_sequence.add_log_lines(
245            [
246                "read packet: $vFile:exists:%s#00"
247                % (binascii.b2a_hex(test_path.encode()).decode(),),
248                "send packet: $F,0#00",
249            ],
250            True,
251        )
252        self.expect_gdbremote_sequence()
253
254    @skipIfWindows
255    # FIXME: lldb.remote_platform.Install() cannot copy opened temp file on Windows.
256    # It is possible to use tempfile.NamedTemporaryFile(..., delete=False) and
257    # delete the temp file manually at the end.
258    @skipIf(hostoslist=["windows"])
259    @add_test_categories(["llgs"])
260    def test_platform_file_fstat(self):
261        server = self.connect_to_debug_monitor()
262        self.assertIsNotNone(server)
263
264        with tempfile.NamedTemporaryFile() as temp_file:
265            temp_file.write(b"some test data for stat")
266            temp_file.flush()
267            temp_path = lldbutil.install_to_target(self, temp_file.name)
268
269            self.do_handshake()
270            self.test_sequence.add_log_lines(
271                [
272                    "read packet: $vFile:open:%s,0,0#00"
273                    % (binascii.b2a_hex(temp_path.encode()).decode(),),
274                    {
275                        "direction": "send",
276                        "regex": r"^\$F([0-9a-fA-F]+)#[0-9a-fA-F]{2}$",
277                        "capture": {1: "fd"},
278                    },
279                ],
280                True,
281            )
282
283            context = self.expect_gdbremote_sequence()
284            self.assertIsNotNone(context)
285            fd = int(context["fd"], 16)
286
287            self.reset_test_sequence()
288            self.test_sequence.add_log_lines(
289                [
290                    "read packet: $vFile:fstat:%x#00" % (fd,),
291                    {
292                        "direction": "send",
293                        "regex": r"^\$F([0-9a-fA-F]+);(.*)#[0-9a-fA-F]{2}$",
294                        "capture": {1: "size", 2: "data"},
295                    },
296                ],
297                True,
298            )
299            context = self.expect_gdbremote_sequence()
300            self.assertEqual(int(context["size"], 16), 64)
301            # NB: we're using .encode() as a hack because the test suite
302            # is wrongly using (unicode) str instead of bytes
303            gdb_stat = GDBStat(
304                *struct.unpack(
305                    ">IIIIIIIQQQIII",
306                    self.decode_gdbremote_binary(context["data"]).encode("iso-8859-1"),
307                )
308            )
309            sys_stat = os.fstat(temp_file.fileno())
310
311            self.assertEqual(gdb_stat.st_mode, uint32_trunc(sys_stat.st_mode))
312            self.assertEqual(gdb_stat.st_nlink, uint32_or_max(sys_stat.st_nlink))
313            self.assertEqual(gdb_stat.st_rdev, uint32_or_zero(sys_stat.st_rdev))
314            self.assertEqual(gdb_stat.st_size, sys_stat.st_size)
315            if not lldb.remote_platform:
316                self.assertEqual(gdb_stat.st_dev, uint32_or_zero(sys_stat.st_dev))
317                self.assertEqual(gdb_stat.st_ino, uint32_or_zero(sys_stat.st_ino))
318                self.assertEqual(gdb_stat.st_uid, uint32_or_zero(sys_stat.st_uid))
319                self.assertEqual(gdb_stat.st_gid, uint32_or_zero(sys_stat.st_gid))
320                self.assertEqual(gdb_stat.st_blksize, sys_stat.st_blksize)
321                self.assertEqual(gdb_stat.st_blocks, sys_stat.st_blocks)
322                self.assertEqual(
323                    gdb_stat.st_atime, uint32_or_zero(int(sys_stat.st_atime))
324                )
325                self.assertEqual(
326                    gdb_stat.st_mtime, uint32_or_zero(int(sys_stat.st_mtime))
327                )
328                self.assertEqual(
329                    gdb_stat.st_ctime, uint32_or_zero(int(sys_stat.st_ctime))
330                )
331
332            self.reset_test_sequence()
333            self.test_sequence.add_log_lines(
334                ["read packet: $vFile:close:%x#00" % (fd,), "send packet: $F0#00"], True
335            )
336            self.expect_gdbremote_sequence()
337
338    def expect_error(self):
339        self.test_sequence.add_log_lines(
340            [{"direction": "send", "regex": r"^\$F-1,[0-9a-fA-F]+#[0-9a-fA-F]{2}$"}],
341            True,
342        )
343        self.expect_gdbremote_sequence()
344
345    def vFile_test(
346        self,
347        read=False,
348        write=False,
349        append=False,
350        trunc=False,
351        creat=False,
352        excl=False,
353    ):
354        if read and write:
355            mode = 2
356        elif write:
357            mode = 1
358        else:  # read
359            mode = 0
360        if append:
361            mode |= 8
362        if creat:
363            mode |= 0x200
364        if trunc:
365            mode |= 0x400
366        if excl:
367            mode |= 0x800
368
369        old_umask = os.umask(0o22)
370        try:
371            server = self.connect_to_debug_monitor()
372        finally:
373            os.umask(old_umask)
374        self.assertIsNotNone(server)
375
376        # create a temporary file with some data
377        temp_path = self.getBuildArtifact("test")
378        test_data = "some test data longer than 16 bytes\n"
379
380        if creat:
381            self.assertFalse(os.path.exists(temp_path))
382            if lldb.remote_platform:
383                temp_path = lldbutil.append_to_process_working_directory(self, "test")
384        else:
385            with open(temp_path, "wb") as temp_file:
386                temp_file.write(test_data.encode())
387            temp_path = lldbutil.install_to_target(self, temp_path)
388
389        # open the file for reading
390        self.do_handshake()
391        self.test_sequence.add_log_lines(
392            [
393                "read packet: $vFile:open:%s,%x,1a0#00"
394                % (binascii.b2a_hex(temp_path.encode()).decode(), mode),
395                {
396                    "direction": "send",
397                    "regex": r"^\$F([0-9a-fA-F]+)#[0-9a-fA-F]{2}$",
398                    "capture": {1: "fd"},
399                },
400            ],
401            True,
402        )
403
404        context = self.expect_gdbremote_sequence()
405        self.assertIsNotNone(context)
406        fd = int(context["fd"], 16)
407
408        # read data from the file
409        self.reset_test_sequence()
410        self.test_sequence.add_log_lines(
411            ["read packet: $vFile:pread:%x,11,10#00" % (fd,)], True
412        )
413        if read:
414            self.test_sequence.add_log_lines(
415                [
416                    {
417                        "direction": "send",
418                        "regex": r"^\$F([0-9a-fA-F]+);(.*)#[0-9a-fA-F]{2}$",
419                        "capture": {1: "size", 2: "data"},
420                    }
421                ],
422                True,
423            )
424            context = self.expect_gdbremote_sequence()
425            self.assertIsNotNone(context)
426            if trunc:
427                self.assertEqual(context["size"], "0")
428                self.assertEqual(context["data"], "")
429            else:
430                self.assertEqual(context["size"], "11")  # hex
431                self.assertEqual(context["data"], test_data[0x10 : 0x10 + 0x11])
432        else:
433            self.expect_error()
434
435        # another offset
436        if read and not trunc:
437            self.reset_test_sequence()
438            self.test_sequence.add_log_lines(
439                [
440                    "read packet: $vFile:pread:%x,6,3#00" % (fd,),
441                    {
442                        "direction": "send",
443                        "regex": r"^\$F([0-9a-fA-F]+);(.+)#[0-9a-fA-F]{2}$",
444                        "capture": {1: "size", 2: "data"},
445                    },
446                ],
447                True,
448            )
449            context = self.expect_gdbremote_sequence()
450            self.assertIsNotNone(context)
451            self.assertEqual(context["size"], "6")  # hex
452            self.assertEqual(context["data"], test_data[3 : 3 + 6])
453
454        # write data to the file
455        self.reset_test_sequence()
456        self.test_sequence.add_log_lines(
457            ["read packet: $vFile:pwrite:%x,6,somedata#00" % (fd,)], True
458        )
459        if write:
460            self.test_sequence.add_log_lines(["send packet: $F8#00"], True)
461            self.expect_gdbremote_sequence()
462        else:
463            self.expect_error()
464
465        # close the file
466        self.reset_test_sequence()
467        self.test_sequence.add_log_lines(
468            ["read packet: $vFile:close:%x#00" % (fd,), "send packet: $F0#00"], True
469        )
470        self.expect_gdbremote_sequence()
471
472        if write:
473            # check if the data was actually written
474            if lldb.remote_platform:
475                local_path = self.getBuildArtifact("file_from_target")
476                error = lldb.remote_platform.Get(
477                    lldb.SBFileSpec(temp_path, False), lldb.SBFileSpec(local_path, True)
478                )
479                self.assertTrue(
480                    error.Success(),
481                    "Reading file {0} failed: {1}".format(temp_path, error),
482                )
483                temp_path = local_path
484
485            with open(temp_path, "rb") as temp_file:
486                if creat and lldbplatformutil.getHostPlatform() != "windows":
487                    self.assertEqual(
488                        os.fstat(temp_file.fileno()).st_mode & 0o7777, 0o640
489                    )
490                data = test_data.encode()
491                if trunc or creat:
492                    data = b"\0" * 6 + b"somedata"
493                elif append:
494                    data += b"somedata"
495                else:
496                    data = data[:6] + b"somedata" + data[6 + 8 :]
497                self.assertEqual(temp_file.read(), data)
498