[lldb] Fix FindProcessImpl() for iOS simulators (#139174)

# Benefit

This patch fixes:
1. After `platform select ios-simulator`, `platform process list` will
now print processes which are running in the iOS simulator. Previously,
no process will be listed.
2. After `platform select ios-simulator`, `platform attach --name
<name>` will succeed. Previously, it will error out saying no process is
found.


# Several bugs that is being fixed

1. During the process listing, add `aarch64` to the list of CPU types
for which iOS simulators are checked for.
2. Given a candidate process, when checking for simulators, the original
code will find the desired environment variable (`SIMULATOR_UDID`) and
set the OS to iOS, but then the immediate next environment variable will
set it back to macOS.
3. For processes running on simulator, set the triple's `Environment` to
`Simulator`, so that such processes can pass the filtering [in this
line](https://fburl.com/8nivnrjx). The original code leave it as the
default `UnknownEnvironment`.



# Manual test

**With this patch:**
```
royshi-mac-home ~/public_llvm/build % bin/lldb
(lldb) platform select ios-simulator

(lldb) platform process list
240 matching processes were found on "ios-simulator"

PID    PARENT USER       TRIPLE                         NAME
====== ====== ========== ============================== ============================
40511  28844  royshi     arm64-apple-ios-simulator      FocusPlayground // my toy iOS app running on simulator
... // omit
28844  1      royshi     arm64-apple-ios-simulator      launchd_sim

(lldb) process attach --name FocusPlayground
Process 40511 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x0000000104e3cb70 libsystem_kernel.dylib`mach_msg2_trap + 8
libsystem_kernel.dylib`mach_msg2_trap:
->  0x104e3cb70 <+8>: ret
... // omit
```

**Without this patch:**
```
$ bin/lldb
(lldb) platform select ios-simulator

(lldb) platform process list
error: no processes were found on the "ios-simulator" platform

(lldb) process attach --name FocusPlayground
error: attach failed: could not find a process named FocusPlayground
```


# Unittest

See PR.
This commit is contained in:
royitaqi
2025-06-25 16:38:29 -07:00
committed by GitHub
parent 19797500d8
commit f63bc84b0d
7 changed files with 180 additions and 84 deletions

View File

@@ -7,6 +7,7 @@ They can also be useful for general purpose lldb scripting.
# System modules
import errno
import io
import json
import os
import re
import sys
@@ -1704,3 +1705,89 @@ def packetlog_get_dylib_info(log):
expect_dylib_info_response = True
return dylib_info
# ========================
# Utilities for simulators
# ========================
def get_latest_apple_simulator(platform_name, log=None):
# Run simctl to list all simulators
cmd = ["xcrun", "simctl", "list", "-j", "devices"]
cmd_str = " ".join(cmd)
if log:
log(cmd_str)
sim_devices_str = subprocess.check_output(cmd).decode("utf-8")
sim_devices = json.loads(sim_devices_str)["devices"]
# Find an available simulator for the requested platform
device_uuid = None
device_runtime = None
for simulator in sim_devices:
if isinstance(simulator, dict):
runtime = simulator["name"]
devices = simulator["devices"]
else:
runtime = simulator
devices = sim_devices[simulator]
if not platform_name in runtime.lower():
continue
for device in devices:
if "availability" in device and device["availability"] != "(available)":
continue
if "isAvailable" in device and not device["isAvailable"]:
continue
if device_runtime and runtime < device_runtime:
continue
device_uuid = device["udid"]
device_runtime = runtime
# Stop searching in this runtime
break
return device_uuid
def launch_exe_in_apple_simulator(
device_uuid,
exe_path,
exe_args=[],
stderr_lines_to_read=0,
stderr_patterns=[],
log=None,
):
exe_path = os.path.realpath(exe_path)
cmd = [
"xcrun",
"simctl",
"spawn",
"-s",
device_uuid,
exe_path,
] + exe_args
if log:
log(" ".join(cmd))
sim_launcher = subprocess.Popen(cmd, stderr=subprocess.PIPE)
# Read stderr to try to find matches.
# Each pattern will return the value of group[1] of the first match in the stderr.
# Will read at most stderr_lines_to_read lines.
# Will early terminate when all matches have been found.
total_patterns = len(stderr_patterns)
matches_found = 0
matched_strings = [None] * total_patterns
for _ in range(0, stderr_lines_to_read):
stderr = sim_launcher.stderr.readline().decode("utf-8")
if not stderr:
continue
for i, pattern in enumerate(stderr_patterns):
if matched_strings[i] is not None:
continue
match = re.match(pattern, stderr)
if match:
matched_strings[i] = str(match.group(1))
matches_found += 1
if matches_found == total_patterns:
break
return exe_path, matched_strings

View File

@@ -595,7 +595,9 @@ static bool GetMacOSXProcessArgs(const ProcessInstanceInfoMatch *match_info_ptr,
const llvm::Triple::ArchType triple_arch = triple.getArch();
const bool check_for_ios_simulator =
(triple_arch == llvm::Triple::x86 ||
triple_arch == llvm::Triple::x86_64);
triple_arch == llvm::Triple::x86_64 ||
triple_arch == llvm::Triple::aarch64);
const char *cstr = data.GetCStr(&offset);
if (cstr) {
process_info.GetExecutableFile().SetFile(cstr, FileSpec::Style::native);
@@ -621,21 +623,20 @@ static bool GetMacOSXProcessArgs(const ProcessInstanceInfoMatch *match_info_ptr,
}
Environment &proc_env = process_info.GetEnvironment();
while ((cstr = data.GetCStr(&offset))) {
if (cstr[0] == '\0')
break;
if (check_for_ios_simulator) {
if (strncmp(cstr, "SIMULATOR_UDID=", strlen("SIMULATOR_UDID=")) ==
0)
process_info.GetArchitecture().GetTriple().setOS(
llvm::Triple::IOS);
else
process_info.GetArchitecture().GetTriple().setOS(
llvm::Triple::MacOSX);
bool is_simulator = false;
llvm::StringRef env_var;
while (!(env_var = data.GetCStr(&offset)).empty()) {
if (check_for_ios_simulator &&
env_var.starts_with("SIMULATOR_UDID="))
is_simulator = true;
proc_env.insert(env_var);
}
proc_env.insert(cstr);
llvm::Triple &triple = process_info.GetArchitecture().GetTriple();
if (is_simulator) {
triple.setOS(llvm::Triple::IOS);
triple.setEnvironment(llvm::Triple::Simulator);
} else {
triple.setOS(llvm::Triple::MacOSX);
}
return true;
}
@@ -741,8 +742,8 @@ uint32_t Host::FindProcessesImpl(const ProcessInstanceInfoMatch &match_info,
!match_info.ProcessIDsMatch(process_info))
continue;
// Get CPU type first so we can know to look for iOS simulator is we have
// x86 or x86_64
// Get CPU type first so we can know to look for iOS simulator if we have
// a compatible type.
if (GetMacOSXProcessCPUType(process_info)) {
if (GetMacOSXProcessArgs(&match_info, process_info)) {
if (match_info.Matches(process_info))

View File

@@ -1,3 +1,6 @@
C_SOURCES := hello.c
CFLAGS_EXTRAS := -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS
ENABLE_THREADS := YES
CXX_SOURCES := hello.cpp
MAKE_DSYM := NO
include Makefile.rules

View File

@@ -39,7 +39,9 @@ class TestSimulatorPlatformLaunching(TestBase):
if expected_version:
self.assertEqual(aout_info["min_version_os_sdk"], expected_version)
def run_with(self, arch, os, vers, env, expected_load_command):
def run_with(
self, arch, os, vers, env, expected_load_command, expected_platform=None
):
env_list = [env] if env else []
triple = "-".join([arch, "apple", os + vers] + env_list)
sdk = lldbutil.get_xcode_sdk(os, env)
@@ -72,12 +74,47 @@ class TestSimulatorPlatformLaunching(TestBase):
log = self.getBuildArtifact("packets.log")
self.expect("log enable gdb-remote packets -f " + log)
lldbutil.run_to_source_breakpoint(
self, "break here", lldb.SBFileSpec("hello.c")
self, "break here", lldb.SBFileSpec("hello.cpp")
)
triple_re = "-".join([arch, "apple", os + vers + ".*"] + env_list)
self.expect("image list -b -t", patterns=[r"a\.out " + triple_re])
self.check_debugserver(log, os + env, vers)
if expected_platform is not None:
# Verify the platform name.
self.expect(
"platform status",
patterns=[r"Platform: " + expected_platform + "-simulator"],
)
# Launch exe in simulator and verify that `platform process list` can find the process.
# This separate launch is needed because the command ignores processes which are being debugged.
device_udid = lldbutil.get_latest_apple_simulator(
expected_platform, self.trace
)
_, matched_strings = lldbutil.launch_exe_in_apple_simulator(
device_udid,
self.getBuildArtifact("a.out"),
exe_args=[],
stderr_lines_to_read=1, # in hello.cpp, the pid is printed first
stderr_patterns=[r"PID: (.*)"],
log=self.trace,
)
# Make sure we found the PID.
self.assertIsNotNone(matched_strings[0])
pid = int(matched_strings[0])
# Verify that processes on the platform can be listed.
self.expect(
"platform process list",
patterns=[
r"\d+ matching processes were found on \"%s-simulator\""
% expected_platform,
r"%d .+ a.out" % pid,
],
)
@skipIfAsan
@skipUnlessDarwin
@skipIfDarwinEmbedded
@@ -90,6 +127,7 @@ class TestSimulatorPlatformLaunching(TestBase):
vers="",
env="simulator",
expected_load_command="LC_BUILD_VERSION",
expected_platform="ios",
)
@skipIfAsan

View File

@@ -1,5 +0,0 @@
void puts(char *);
int main(int argc, char **argv) {
puts("break here\n");
return 0;
}

View File

@@ -0,0 +1,14 @@
#include <stdio.h>
#include <thread>
#include <unistd.h>
static void print_pid() { fprintf(stderr, "PID: %d\n", getpid()); }
static void sleep() { std::this_thread::sleep_for(std::chrono::seconds(10)); }
int main(int argc, char **argv) {
print_pid();
puts("break here\n");
sleep();
return 0;
}

View File

@@ -14,40 +14,12 @@ class TestAppleSimulatorOSType(gdbremote_testcase.GdbRemoteTestCaseBase):
READ_LINES = 10
def check_simulator_ostype(self, sdk, platform_name, arch=platform.machine()):
cmd = ["xcrun", "simctl", "list", "-j", "devices"]
cmd_str = " ".join(cmd)
self.trace(cmd_str)
sim_devices_str = subprocess.check_output(cmd).decode("utf-8")
try:
sim_devices = json.loads(sim_devices_str)["devices"]
except json.decoder.JSONDecodeError:
self.fail(
"Could not parse '{}' output. Authorization denied?".format(cmd_str)
)
# Find an available simulator for the requested platform
# Get simulator
deviceUDID = None
deviceRuntime = None
for simulator in sim_devices:
if isinstance(simulator, dict):
runtime = simulator["name"]
devices = simulator["devices"]
else:
runtime = simulator
devices = sim_devices[simulator]
if not platform_name in runtime.lower():
continue
for device in devices:
if "availability" in device and device["availability"] != "(available)":
continue
if "isAvailable" in device and not device["isAvailable"]:
continue
if deviceRuntime and runtime < deviceRuntime:
continue
deviceUDID = device["udid"]
deviceRuntime = runtime
# Stop searching in this runtime
break
try:
deviceUDID = lldbutil.get_latest_apple_simulator(platform_name, self.trace)
except json.decoder.JSONDecodeError:
self.fail("Could not parse output. Authorization denied?")
if not deviceUDID:
self.skipTest(
"Could not find a simulator for {} ({})".format(platform_name, arch)
@@ -78,34 +50,20 @@ class TestAppleSimulatorOSType(gdbremote_testcase.GdbRemoteTestCaseBase):
},
compiler=clang,
)
exe_path = os.path.realpath(self.getBuildArtifact(exe_name))
cmd = [
"xcrun",
"simctl",
"spawn",
"-s",
deviceUDID,
exe_path,
"print-pid",
"sleep:10",
]
self.trace(" ".join(cmd))
sim_launcher = subprocess.Popen(cmd, stderr=subprocess.PIPE)
# Get the PID from the process output
pid = None
# Read the first READ_LINES to try to find the PID.
for _ in range(0, self.READ_LINES):
stderr = sim_launcher.stderr.readline().decode("utf-8")
if not stderr:
continue
match = re.match(r"PID: (.*)", stderr)
if match:
pid = int(match.group(1))
break
# Launch the executable in the simulator
exe_path, matched_groups = lldbutil.launch_exe_in_apple_simulator(
deviceUDID,
self.getBuildArtifact(exe_name),
["print-pid", "sleep:10"],
self.READ_LINES,
[r"PID: (.*)"],
self.trace,
)
# Make sure we found the PID.
self.assertIsNotNone(pid)
self.assertIsNotNone(matched_groups[0])
pid = int(matched_groups[0])
# Launch debug monitor attaching to the simulated process
server = self.connect_to_debug_monitor(attach_pid=pid)