xref: /llvm-project/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py (revision ea82853499cc8780cfc86a6913d851f05d527188)
1"""
2Test python scripted process in lldb
3"""
4
5import os, shutil
6
7import lldb
8from lldbsuite.test.decorators import *
9from lldbsuite.test.lldbtest import *
10from lldbsuite.test import lldbutil
11from lldbsuite.test import lldbtest
12
13import dummy_scripted_process
14
15
16class ScriptedProcesTestCase(TestBase):
17    NO_DEBUG_INFO_TESTCASE = True
18
19    @skipUnlessDarwin
20    def test_python_plugin_package(self):
21        """Test that the lldb python module has a `plugins.scripted_process`
22        package."""
23        self.expect(
24            "script import lldb.plugins",
25            substrs=["ModuleNotFoundError"],
26            matching=False,
27        )
28
29        self.expect("script dir(lldb.plugins)", substrs=["scripted_process"])
30
31        self.expect(
32            "script import lldb.plugins.scripted_process",
33            substrs=["ModuleNotFoundError"],
34            matching=False,
35        )
36
37        self.expect(
38            "script dir(lldb.plugins.scripted_process)", substrs=["ScriptedProcess"]
39        )
40
41        self.expect(
42            "script from lldb.plugins.scripted_process import ScriptedProcess",
43            substrs=["ImportError"],
44            matching=False,
45        )
46
47        self.expect("script dir(ScriptedProcess)", substrs=["launch"])
48
49    def move_blueprint_to_dsym(self, blueprint_name):
50        blueprint_origin_path = os.path.join(self.getSourceDir(), blueprint_name)
51        dsym_bundle = self.getBuildArtifact("a.out.dSYM")
52        blueprint_destination_path = os.path.join(
53            dsym_bundle, "Contents", "Resources", "Python"
54        )
55        if not os.path.exists(blueprint_destination_path):
56            os.mkdir(blueprint_destination_path)
57
58        blueprint_destination_path = os.path.join(
59            blueprint_destination_path, "a_out.py"
60        )
61        shutil.copy(blueprint_origin_path, blueprint_destination_path)
62
63    # No dylib on Windows.
64    @skipIfWindows
65    def test_missing_methods_scripted_register_context(self):
66        """Test that we only instanciate scripted processes if they implement
67        all the required abstract methods."""
68        self.build()
69
70        os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"] = "1"
71
72        def cleanup():
73            del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]
74
75        self.addTearDownHook(cleanup)
76
77        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
78        self.assertTrue(target, VALID_TARGET)
79        log_file = self.getBuildArtifact("script.log")
80        self.runCmd("log enable lldb script -f " + log_file)
81        self.assertTrue(os.path.isfile(log_file))
82        script_path = os.path.join(
83            self.getSourceDir(), "missing_methods_scripted_process.py"
84        )
85        self.runCmd("command script import " + script_path)
86
87        launch_info = lldb.SBLaunchInfo(None)
88        launch_info.SetProcessPluginName("ScriptedProcess")
89        launch_info.SetScriptedProcessClassName(
90            "missing_methods_scripted_process.MissingMethodsScriptedProcess"
91        )
92        error = lldb.SBError()
93
94        process = target.Launch(launch_info, error)
95
96        self.assertFailure(error)
97
98        with open(log_file, "r") as f:
99            log = f.read()
100
101        self.assertIn(
102            "Abstract method MissingMethodsScriptedProcess.read_memory_at_address not implemented",
103            log,
104        )
105        self.assertIn(
106            "Abstract method MissingMethodsScriptedProcess.is_alive not implemented",
107            log,
108        )
109        self.assertIn(
110            "Abstract method MissingMethodsScriptedProcess.get_scripted_thread_plugin not implemented",
111            log,
112        )
113
114    @skipUnlessDarwin
115    def test_invalid_scripted_register_context(self):
116        """Test that we can launch an lldb scripted process with an invalid
117        Scripted Thread, with invalid register context."""
118        self.build()
119
120        os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"] = "1"
121
122        def cleanup():
123            del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]
124
125        self.addTearDownHook(cleanup)
126
127        self.runCmd("settings set target.load-script-from-symbol-file true")
128        self.move_blueprint_to_dsym("invalid_scripted_process.py")
129        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
130        self.assertTrue(target, VALID_TARGET)
131        log_file = self.getBuildArtifact("thread.log")
132        self.runCmd("log enable lldb thread -f " + log_file)
133        self.assertTrue(os.path.isfile(log_file))
134
135        launch_info = lldb.SBLaunchInfo(None)
136        launch_info.SetProcessPluginName("ScriptedProcess")
137        launch_info.SetScriptedProcessClassName("a_out.InvalidScriptedProcess")
138        error = lldb.SBError()
139
140        process = target.Launch(launch_info, error)
141
142        self.assertSuccess(error)
143        self.assertTrue(process, PROCESS_IS_VALID)
144        self.assertEqual(process.GetProcessID(), 666)
145        self.assertEqual(process.GetNumThreads(), 0)
146
147        impl = process.GetScriptedImplementation()
148        self.assertTrue(impl)
149        impl = process.GetScriptedImplementation()
150        self.assertTrue(impl)
151        impl = process.GetScriptedImplementation()
152        self.assertTrue(impl)
153        impl = process.GetScriptedImplementation()
154        self.assertTrue(impl)
155
156        addr = 0x500000000
157        buff = process.ReadMemory(addr, 4, error)
158        self.assertEqual(buff, None)
159        self.assertTrue(error.Fail())
160        self.assertEqual(error.GetCString(), "This is an invalid scripted process!")
161
162        with open(log_file, "r") as f:
163            log = f.read()
164
165        self.assertIn("Failed to get scripted thread registers data.", log)
166
167    @skipUnlessDarwin
168    def test_scripted_process_and_scripted_thread(self):
169        """Test that we can launch an lldb scripted process using the SBAPI,
170        check its process ID, read string from memory, check scripted thread
171        id, name stop reason and register context.
172        """
173        self.build()
174        target_0 = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
175        self.assertTrue(target_0, VALID_TARGET)
176
177        os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"] = "1"
178
179        def cleanup():
180            del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]
181
182        self.addTearDownHook(cleanup)
183
184        scripted_process_example_relpath = "dummy_scripted_process.py"
185        self.runCmd(
186            "command script import "
187            + os.path.join(self.getSourceDir(), scripted_process_example_relpath)
188        )
189
190        launch_info = lldb.SBLaunchInfo(None)
191        launch_info.SetProcessPluginName("ScriptedProcess")
192        launch_info.SetScriptedProcessClassName(
193            "dummy_scripted_process.DummyScriptedProcess"
194        )
195
196        error = lldb.SBError()
197        process_0 = target_0.Launch(launch_info, error)
198        self.assertTrue(process_0 and process_0.IsValid(), PROCESS_IS_VALID)
199        self.assertEqual(process_0.GetProcessID(), 42)
200        self.assertEqual(process_0.GetNumThreads(), 1)
201
202        py_impl = process_0.GetScriptedImplementation()
203        self.assertTrue(py_impl)
204        self.assertTrue(
205            isinstance(py_impl, dummy_scripted_process.DummyScriptedProcess)
206        )
207        self.assertFalse(hasattr(py_impl, "my_super_secret_member"))
208        py_impl.my_super_secret_member = 42
209        self.assertTrue(hasattr(py_impl, "my_super_secret_member"))
210        self.assertEqual(py_impl.my_super_secret_method(), 42)
211
212        # Try reading from target #0 process ...
213        addr = 0x500000000
214        message = "Hello, target 0"
215        buff = process_0.ReadCStringFromMemory(addr, len(message) + 1, error)
216        self.assertSuccess(error)
217        self.assertEqual(buff, message)
218
219        target_1 = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
220        self.assertTrue(target_1, VALID_TARGET)
221
222        # We still need to specify a PID when attaching even for scripted processes
223        attach_info = lldb.SBAttachInfo(42)
224        attach_info.SetProcessPluginName("ScriptedProcess")
225        attach_info.SetScriptedProcessClassName(
226            "dummy_scripted_process.DummyScriptedProcess"
227        )
228
229        error = lldb.SBError()
230        process_1 = target_1.Attach(attach_info, error)
231        self.assertTrue(process_1 and process_1.IsValid(), PROCESS_IS_VALID)
232        self.assertEqual(process_1.GetProcessID(), 42)
233        self.assertEqual(process_1.GetNumThreads(), 1)
234
235        # ... then try reading from target #1 process ...
236        message = "Hello, target 1"
237        buff = process_1.ReadCStringFromMemory(addr, len(message) + 1, error)
238        self.assertSuccess(error)
239        self.assertEqual(buff, message)
240
241        # ... now, reading again from target #0 process to make sure the call
242        # gets dispatched to the right target.
243        message = "Hello, target 0"
244        buff = process_0.ReadCStringFromMemory(addr, len(message) + 1, error)
245        self.assertSuccess(error)
246        self.assertEqual(buff, message)
247
248        # Let's write some memory.
249        message = "Hello, world!"
250        bytes_written = process_0.WriteMemoryAsCString(addr, message, error)
251        self.assertSuccess(error)
252        self.assertEqual(bytes_written, len(message) + 1)
253
254        # ... and check if that memory was saved properly.
255        buff = process_0.ReadCStringFromMemory(addr, len(message) + 1, error)
256        self.assertSuccess(error)
257        self.assertEqual(buff, message)
258
259        thread = process_0.GetSelectedThread()
260        self.assertTrue(thread, "Invalid thread.")
261        self.assertEqual(thread.GetThreadID(), 0x19)
262        self.assertEqual(thread.GetName(), "DummyScriptedThread.thread-1")
263        self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonTrace)
264
265        self.assertGreater(thread.GetNumFrames(), 0)
266
267        frame = thread.GetFrameAtIndex(0)
268        GPRs = None
269        register_set = frame.registers  # Returns an SBValueList.
270        for regs in register_set:
271            if "general purpose" in regs.name.lower():
272                GPRs = regs
273                break
274
275        self.assertTrue(GPRs, "Invalid General Purpose Registers Set")
276        self.assertGreater(GPRs.GetNumChildren(), 0)
277        for idx, reg in enumerate(GPRs, start=1):
278            if idx > 21:
279                break
280            self.assertEqual(idx, int(reg.value, 16))
281
282        self.assertTrue(frame.IsArtificial(), "Frame is not artificial")
283        pc = frame.GetPCAddress().GetLoadAddress(target_0)
284        self.assertEqual(pc, 0x0100001B00)
285