xref: /llvm-project/lldb/test/API/qemu/TestQemuLaunch.py (revision 2238dcc39358353cac21df75c3c3286ab20b8f53)
1import lldb
2import unittest
3import os
4import json
5import stat
6import sys
7from textwrap import dedent
8import lldbsuite.test.lldbutil
9from lldbsuite.test.lldbtest import *
10from lldbsuite.test.decorators import *
11from lldbsuite.test.gdbclientutils import *
12
13
14@skipIfRemote
15@skipIfWindows
16class TestQemuLaunch(TestBase):
17    NO_DEBUG_INFO_TESTCASE = True
18
19    def set_emulator_setting(self, name, value):
20        self.runCmd("settings set -- platform.plugin.qemu-user.%s %s" % (name, value))
21
22    def setUp(self):
23        super().setUp()
24        emulator = self.getBuildArtifact("qemu.py")
25        with os.fdopen(
26            os.open(emulator, os.O_WRONLY | os.O_CREAT, stat.S_IRWXU), "w"
27        ) as e:
28            e.write(
29                dedent(
30                    """\
31                    #! {exec!s}
32
33                    import runpy
34                    import sys
35
36                    sys.path = {path!r}
37                    runpy.run_path({source!r}, run_name='__main__')
38                    """
39                ).format(
40                    exec=sys.executable,
41                    path=sys.path,
42                    source=self.getSourcePath("qemu.py"),
43                )
44            )
45
46        self.set_emulator_setting("architecture", self.getArchitecture())
47        self.set_emulator_setting("emulator-path", emulator)
48
49    def _create_target(self):
50        self.build()
51        exe = self.getBuildArtifact()
52
53        # Create a target using our platform
54        error = lldb.SBError()
55        target = self.dbg.CreateTarget(exe, "", "qemu-user", False, error)
56        self.assertSuccess(error)
57        self.assertEqual(target.GetPlatform().GetName(), "qemu-user")
58        return target
59
60    def _run_and_get_state(self, target=None, info=None):
61        if target is None:
62            target = self._create_target()
63
64        if info is None:
65            info = target.GetLaunchInfo()
66
67        # "Launch" the process. Our fake qemu implementation will pretend it
68        # immediately exited.
69        info.SetArguments(["dump:" + self.getBuildArtifact("state.log")], True)
70        error = lldb.SBError()
71        process = target.Launch(info, error)
72        self.assertSuccess(error)
73        self.assertIsNotNone(process)
74        self.assertState(process.GetState(), lldb.eStateExited)
75        self.assertEqual(process.GetExitStatus(), 0x47)
76
77        # Verify the qemu invocation parameters.
78        with open(self.getBuildArtifact("state.log")) as s:
79            return json.load(s)
80
81    def test_basic_launch(self):
82        state = self._run_and_get_state()
83
84        self.assertEqual(state["program"], self.getBuildArtifact())
85        self.assertEqual(state["args"], ["dump:" + self.getBuildArtifact("state.log")])
86
87    def test_stdio_pty(self):
88        target = self._create_target()
89
90        info = target.GetLaunchInfo()
91        info.SetArguments(
92            [
93                "stdin:stdin",
94                "stdout:STDOUT CONTENT\n",
95                "stderr:STDERR CONTENT\n",
96                "dump:" + self.getBuildArtifact("state.log"),
97            ],
98            False,
99        )
100
101        listener = lldb.SBListener("test_stdio")
102        info.SetListener(listener)
103
104        self.dbg.SetAsync(True)
105        error = lldb.SBError()
106        process = target.Launch(info, error)
107        self.assertSuccess(error)
108        lldbutil.expect_state_changes(self, listener, process, [lldb.eStateRunning])
109
110        process.PutSTDIN("STDIN CONTENT\n")
111
112        lldbutil.expect_state_changes(self, listener, process, [lldb.eStateExited])
113
114        # Echoed stdin, stdout and stderr. With a pty we cannot split standard
115        # output and error.
116        self.assertEqual(
117            process.GetSTDOUT(1000),
118            "STDIN CONTENT\r\nSTDOUT CONTENT\r\nSTDERR CONTENT\r\n",
119        )
120        with open(self.getBuildArtifact("state.log")) as s:
121            state = json.load(s)
122        self.assertEqual(state["stdin"], "STDIN CONTENT\n")
123
124    def test_stdio_redirect(self):
125        self.build()
126        exe = self.getBuildArtifact()
127
128        # Create a target using our platform
129        error = lldb.SBError()
130        target = self.dbg.CreateTarget(exe, "", "qemu-user", False, error)
131        self.assertSuccess(error)
132
133        info = lldb.SBLaunchInfo(
134            [
135                "stdin:stdin",
136                "stdout:STDOUT CONTENT",
137                "stderr:STDERR CONTENT",
138                "dump:" + self.getBuildArtifact("state.log"),
139            ]
140        )
141
142        info.AddOpenFileAction(0, self.getBuildArtifact("stdin.txt"), True, False)
143        info.AddOpenFileAction(1, self.getBuildArtifact("stdout.txt"), False, True)
144        info.AddOpenFileAction(2, self.getBuildArtifact("stderr.txt"), False, True)
145
146        with open(self.getBuildArtifact("stdin.txt"), "w") as f:
147            f.write("STDIN CONTENT")
148
149        process = target.Launch(info, error)
150        self.assertSuccess(error)
151        self.assertState(process.GetState(), lldb.eStateExited)
152
153        with open(self.getBuildArtifact("stdout.txt")) as f:
154            self.assertEqual(f.read(), "STDOUT CONTENT")
155        with open(self.getBuildArtifact("stderr.txt")) as f:
156            self.assertEqual(f.read(), "STDERR CONTENT")
157        with open(self.getBuildArtifact("state.log")) as s:
158            state = json.load(s)
159        self.assertEqual(state["stdin"], "STDIN CONTENT")
160
161    def test_find_in_PATH(self):
162        emulator = self.getBuildArtifact("qemu-" + self.getArchitecture())
163        os.rename(self.getBuildArtifact("qemu.py"), emulator)
164        self.set_emulator_setting("emulator-path", "''")
165
166        original_path = os.environ["PATH"]
167        os.environ["PATH"] = (
168            self.getBuildDir()
169            + self.platformContext.shlib_path_separator
170            + original_path
171        )
172
173        def cleanup():
174            os.environ["PATH"] = original_path
175
176        self.addTearDownHook(cleanup)
177        state = self._run_and_get_state()
178
179        self.assertEqual(state["program"], self.getBuildArtifact())
180        self.assertEqual(state["args"], ["dump:" + self.getBuildArtifact("state.log")])
181
182    def test_bad_emulator_path(self):
183        self.set_emulator_setting(
184            "emulator-path", self.getBuildArtifact("nonexistent.file")
185        )
186
187        target = self._create_target()
188        info = lldb.SBLaunchInfo([])
189        error = lldb.SBError()
190        target.Launch(info, error)
191        self.assertTrue(error.Fail())
192        self.assertIn("doesn't exist", error.GetCString())
193
194    def test_extra_args(self):
195        self.set_emulator_setting("emulator-args", "-fake-arg fake-value")
196        state = self._run_and_get_state()
197
198        self.assertEqual(state["fake-arg"], "fake-value")
199
200    def test_env_vars(self):
201        # First clear any global environment to have a clean slate for this test
202        self.runCmd("settings clear target.env-vars")
203        self.runCmd("settings clear target.unset-env-vars")
204
205        def var(i):
206            return "LLDB_TEST_QEMU_VAR%d" % i
207
208        # Set some variables in the host environment.
209        for i in range(4):
210            os.environ[var(i)] = "from host"
211
212        def cleanup():
213            for i in range(4):
214                del os.environ[var(i)]
215
216        self.addTearDownHook(cleanup)
217
218        # Set some emulator-only variables.
219        self.set_emulator_setting("emulator-env-vars", "%s='emulator only'" % var(4))
220
221        # And through the platform setting.
222        self.set_emulator_setting(
223            "target-env-vars",
224            "%s='from platform' %s='from platform'" % (var(1), var(2)),
225        )
226
227        target = self._create_target()
228        info = target.GetLaunchInfo()
229        env = info.GetEnvironment()
230
231        # Platform settings should trump host values. Emulator-only variables
232        # should not be visible.
233        self.assertEqual(env.Get(var(0)), "from host")
234        self.assertEqual(env.Get(var(1)), "from platform")
235        self.assertEqual(env.Get(var(2)), "from platform")
236        self.assertEqual(env.Get(var(3)), "from host")
237        self.assertIsNone(env.Get(var(4)))
238
239        # Finally, make some launch_info specific changes.
240        env.Set(var(2), "from target", True)
241        env.Unset(var(3))
242        info.SetEnvironment(env, False)
243
244        # Now check everything. Launch info changes should trump everything, but
245        # only for the target environment -- the emulator should still get the
246        # host values.
247        state = self._run_and_get_state(target, info)
248        for i in range(4):
249            self.assertEqual(state["environ"][var(i)], "from host")
250        self.assertEqual(state["environ"][var(4)], "emulator only")
251        self.assertEqual(
252            state["environ"]["QEMU_SET_ENV"],
253            "%s=from platform,%s=from target" % (var(1), var(2)),
254        )
255        self.assertEqual(
256            state["environ"]["QEMU_UNSET_ENV"],
257            "%s,%s,QEMU_SET_ENV,QEMU_UNSET_ENV" % (var(3), var(4)),
258        )
259
260    def test_arg0(self):
261        target = self._create_target()
262        self.runCmd("settings set target.arg0 ARG0")
263        state = self._run_and_get_state(target)
264
265        self.assertEqual(state["program"], self.getBuildArtifact())
266        self.assertEqual(state["0"], "ARG0")
267
268    def test_sysroot(self):
269        sysroot = self.getBuildArtifact("sysroot")
270        self.runCmd("platform select qemu-user --sysroot %s" % sysroot)
271        state = self._run_and_get_state()
272        self.assertEqual(state["environ"]["QEMU_LD_PREFIX"], sysroot)
273        self.assertIn("QEMU_LD_PREFIX", state["environ"]["QEMU_UNSET_ENV"].split(","))
274