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