xref: /llvm-project/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py (revision b3a835e129ed8a67cf393f9ee26989b36a3eff1c)
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        self.runCmd(
191            "target stop-hook add -k first -v 1 -k second -v 2 -P dummy_scripted_process.DummyStopHook"
192        )
193
194        launch_info = lldb.SBLaunchInfo(None)
195        launch_info.SetProcessPluginName("ScriptedProcess")
196        launch_info.SetScriptedProcessClassName(
197            "dummy_scripted_process.DummyScriptedProcess"
198        )
199
200        error = lldb.SBError()
201        process_0 = target_0.Launch(launch_info, error)
202        self.assertTrue(process_0 and process_0.IsValid(), PROCESS_IS_VALID)
203        self.assertEqual(process_0.GetProcessID(), 42)
204        self.assertEqual(process_0.GetNumThreads(), 1)
205
206        py_impl = process_0.GetScriptedImplementation()
207        self.assertTrue(py_impl)
208        self.assertIsInstance(py_impl, dummy_scripted_process.DummyScriptedProcess)
209        self.assertFalse(hasattr(py_impl, "my_super_secret_member"))
210        py_impl.my_super_secret_member = 42
211        self.assertTrue(hasattr(py_impl, "my_super_secret_member"))
212        self.assertEqual(py_impl.my_super_secret_method(), 42)
213
214        self.assertTrue(hasattr(py_impl, "handled_stop"))
215        self.assertTrue(py_impl.handled_stop)
216
217        # Try reading from target #0 process ...
218        addr = 0x500000000
219        message = "Hello, target 0"
220        buff = process_0.ReadCStringFromMemory(addr, len(message) + 1, error)
221        self.assertSuccess(error)
222        self.assertEqual(buff, message)
223
224        target_1 = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
225        self.assertTrue(target_1, VALID_TARGET)
226
227        # We still need to specify a PID when attaching even for scripted processes
228        attach_info = lldb.SBAttachInfo(42)
229        attach_info.SetProcessPluginName("ScriptedProcess")
230        attach_info.SetScriptedProcessClassName(
231            "dummy_scripted_process.DummyScriptedProcess"
232        )
233
234        error = lldb.SBError()
235        process_1 = target_1.Attach(attach_info, error)
236        self.assertTrue(process_1 and process_1.IsValid(), PROCESS_IS_VALID)
237        self.assertEqual(process_1.GetProcessID(), 42)
238        self.assertEqual(process_1.GetNumThreads(), 1)
239
240        # ... then try reading from target #1 process ...
241        message = "Hello, target 1"
242        buff = process_1.ReadCStringFromMemory(addr, len(message) + 1, error)
243        self.assertSuccess(error)
244        self.assertEqual(buff, message)
245
246        # ... now, reading again from target #0 process to make sure the call
247        # gets dispatched to the right target.
248        message = "Hello, target 0"
249        buff = process_0.ReadCStringFromMemory(addr, len(message) + 1, error)
250        self.assertSuccess(error)
251        self.assertEqual(buff, message)
252
253        # Let's write some memory.
254        message = "Hello, world!"
255        bytes_written = process_0.WriteMemoryAsCString(addr, message, error)
256        self.assertSuccess(error)
257        self.assertEqual(bytes_written, len(message) + 1)
258
259        # ... and check if that memory was saved properly.
260        buff = process_0.ReadCStringFromMemory(addr, len(message) + 1, error)
261        self.assertSuccess(error)
262        self.assertEqual(buff, message)
263
264        thread = process_0.GetSelectedThread()
265        self.assertTrue(thread, "Invalid thread.")
266        self.assertEqual(thread.GetThreadID(), 0x19)
267        self.assertEqual(thread.GetName(), "DummyScriptedThread.thread-1")
268        self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonTrace)
269
270        self.assertGreater(thread.GetNumFrames(), 0)
271
272        frame = thread.GetFrameAtIndex(0)
273        GPRs = None
274        register_set = frame.registers  # Returns an SBValueList.
275        for regs in register_set:
276            if "general purpose" in regs.name.lower():
277                GPRs = regs
278                break
279
280        self.assertTrue(GPRs, "Invalid General Purpose Registers Set")
281        self.assertGreater(GPRs.GetNumChildren(), 0)
282        for idx, reg in enumerate(GPRs, start=1):
283            if idx > 21:
284                break
285            self.assertEqual(idx, int(reg.value, 16))
286
287        self.assertTrue(frame.IsArtificial(), "Frame is not artificial")
288        pc = frame.GetPCAddress().GetLoadAddress(target_0)
289        self.assertEqual(pc, 0x0100001B00)
290