Fix debugserver's qProcessInfo reporting of maccatalyst binaries

This patch is similar in spirit to https://reviews.llvm.org/D84480,
but does the maccatalyst/macosx disambiguation. I also took the
opportunity to factor out the gdb-remote packet log scanning used by
several testcases into lldbutil functions.

rdar://problem/66059257

Differential Revision: https://reviews.llvm.org/D84576
This commit is contained in:
Adrian Prantl 2020-07-24 18:36:18 -07:00
parent 06d425737b
commit 7e9bab6ad5
16 changed files with 252 additions and 122 deletions

View File

@ -1458,3 +1458,40 @@ def wait_for_file_on_target(testcase, file_path, max_attempts=6):
(file_path, max_attempts))
return read_file_on_target(testcase, file_path)
def packetlog_get_process_info(log):
"""parse a gdb-remote packet log file and extract the response to qProcessInfo"""
process_info = dict()
with open(log, "r") as logfile:
process_info_ostype = None
expect_process_info_response = False
for line in logfile:
if expect_process_info_response:
for pair in line.split(';'):
keyval = pair.split(':')
if len(keyval) == 2:
process_info[keyval[0]] = keyval[1]
break
if 'send packet: $qProcessInfo#' in line:
expect_process_info_response = True
return process_info
def packetlog_get_dylib_info(log):
"""parse a gdb-remote packet log file and extract the *last* response to jGetLoadedDynamicLibrariesInfos"""
import json
dylib_info = None
with open(log, "r") as logfile:
dylib_info = None
expect_dylib_info_response = False
for line in logfile:
if expect_dylib_info_response:
while line[0] != '$':
line = line[1:]
line = line[1:]
# Unescape '}'.
dylib_info = json.loads(line.replace('}]','}')[:-4])
expect_dylib_info_response = False
if 'send packet: $jGetLoadedDynamicLibrariesInfos:{' in line:
expect_dylib_info_response = True
return dylib_info

View File

@ -0,0 +1,10 @@
C_SOURCES := main.c
TRIPLE := $(ARCH)-apple-ios13.0-macabi
CFLAGS_EXTRAS := -target $(TRIPLE)
# FIXME: rdar://problem/54986190
# There is a Clang driver change missing on llvm.org.
override CC=xcrun clang
include Makefile.rules

View File

@ -0,0 +1,43 @@
import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbutil as lldbutil
import os
import unittest2
class TestMacCatalyst(TestBase):
mydir = TestBase.compute_mydir(__file__)
@skipIf(macos_version=["<", "10.15"])
@skipUnlessDarwin
@skipIfDarwinEmbedded
@skipIfReproducer # This is hitting https://bugs.python.org/issue22393
def test_macabi(self):
"""Test the x86_64-apple-ios-macabi target linked against a macos dylib"""
self.build()
log = self.getBuildArtifact('packets.log')
self.expect("log enable gdb-remote packets -f "+log)
lldbutil.run_to_source_breakpoint(self, "break here",
lldb.SBFileSpec('main.c'))
self.expect("image list -t -b",
patterns=[self.getArchitecture() +
r'.*-apple-ios.*-macabi a\.out'])
self.expect("fr v s", "Hello macCatalyst")
self.expect("p s", "Hello macCatalyst")
self.check_debugserver(log)
def check_debugserver(self, log):
"""scan the debugserver packet log"""
process_info = lldbutil.packetlog_get_process_info(log)
self.assertTrue('ostype' in process_info)
self.assertEquals(process_info['ostype'], 'maccatalyst')
aout_info = None
dylib_info = lldbutil.packetlog_get_dylib_info(log)
for image in dylib_info['images']:
if image['pathname'].endswith('a.out'):
aout_info = image
self.assertTrue(aout_info)
self.assertEquals(aout_info['min_version_os_name'], 'maccatalyst')

View File

@ -0,0 +1,4 @@
int main() {
const char *s = "Hello macCatalyst!";
return 0; // break here
}

View File

@ -1,9 +1,12 @@
C_SOURCES := main.c
LD_EXTRAS := -L. -lfoo
TRIPLE := x86_64-apple-ios13.0-macabi
TRIPLE := $(ARCH)-apple-ios13.0-macabi
CFLAGS_EXTRAS := -target $(TRIPLE)
# FIXME: rdar://problem/54986190
override CC=xcrun clang
all: libfoo.dylib a.out
libfoo.dylib: foo.c

View File

@ -0,0 +1,51 @@
import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbutil as lldbutil
import os
import unittest2
class TestMacCatalystAppWithMacOSFramework(TestBase):
mydir = TestBase.compute_mydir(__file__)
@skipIf(macos_version=["<", "10.15"])
@skipUnlessDarwin
@skipIfDarwinEmbedded
# There is a Clang driver change missing on llvm.org.
@expectedFailureAll(bugnumber="rdar://problem/54986190>")
@skipIfReproducer # This is hitting https://bugs.python.org/issue22393
def test(self):
"""Test the x86_64-apple-ios-macabi target linked against a macos dylib"""
self.build()
log = self.getBuildArtifact('packets.log')
self.expect("log enable gdb-remote packets -f "+log)
lldbutil.run_to_source_breakpoint(self, "break here",
lldb.SBFileSpec('main.c'))
arch = self.getArchitecture()
self.expect("image list -t -b",
patterns=[arch + r'.*-apple-ios.*-macabi a\.out',
arch + r'.*-apple-macosx.* libfoo.dylib[^(]'])
self.expect("fr v s", "Hello macCatalyst")
self.expect("p s", "Hello macCatalyst")
self.check_debugserver(log)
def check_debugserver(self, log):
"""scan the debugserver packet log"""
process_info = lldbutil.packetlog_get_process_info(log)
self.assertTrue('ostype' in process_info)
self.assertEquals(process_info['ostype'], 'maccatalyst')
aout_info = None
libfoo_info = None
dylib_info = lldbutil.packetlog_get_dylib_info(log)
for image in dylib_info['images']:
if image['pathname'].endswith('a.out'):
aout_info = image
if image['pathname'].endswith('libfoo.dylib'):
libfoo_info = image
self.assertTrue(aout_info)
self.assertTrue(libfoo_info)
self.assertEquals(aout_info['min_version_os_name'], 'maccatalyst')
self.assertEquals(libfoo_info['min_version_os_name'], 'macosx')

View File

@ -1,5 +1,5 @@
#include "foo.h"
int main() {
const char *s = "Hello MacABI!";
const char *s = "Hello macCatalyst!";
return foo(); // break here
}

View File

@ -1,28 +0,0 @@
import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbutil as lldbutil
import os
import unittest2
class TestMacABImacOSFramework(TestBase):
mydir = TestBase.compute_mydir(__file__)
@skipIf(macos_version=["<", "10.15"])
@skipUnlessDarwin
@skipIfDarwinEmbedded
# There is a Clang driver change missing on llvm.org.
@expectedFailureAll(bugnumber="rdar://problem/54986190>")
@skipIfReproducer # This is hitting https://bugs.python.org/issue22393
def test_macabi(self):
"""Test the x86_64-apple-ios-macabi target linked against a macos dylib"""
self.build()
lldbutil.run_to_source_breakpoint(self, "break here",
lldb.SBFileSpec('main.c'))
self.expect("image list -t -b",
patterns=["x86_64.*-apple-ios.*-macabi a\.out",
"x86_64.*-apple-macosx.* libfoo.dylib[^(]"])
self.expect("fr v s", "Hello MacABI")
self.expect("p s", "Hello MacABI")

View File

@ -25,30 +25,10 @@ class TestSimulatorPlatformLaunching(TestBase):
def check_debugserver(self, log, expected_platform, expected_version):
"""scan the debugserver packet log"""
logfile = open(log, "r")
dylib_info = None
process_info_ostype = None
expect_dylib_info_response = False
expect_process_info_response = False
for line in logfile:
if expect_dylib_info_response:
while line[0] != '$':
line = line[1:]
line = line[1:]
# Unescape '}'.
dylib_info = json.loads(line.replace('}]','}')[:-4])
expect_dylib_info_response = False
if 'send packet: $jGetLoadedDynamicLibrariesInfos:{' in line:
expect_dylib_info_response = True
if expect_process_info_response:
for pair in line.split(';'):
keyval = pair.split(':')
if len(keyval) == 2 and keyval[0] == 'ostype':
process_info_ostype = keyval[1]
if 'send packet: $qProcessInfo#' in line:
expect_process_info_response = True
self.assertEquals(process_info_ostype, expected_platform)
process_info = lldbutil.packetlog_get_process_info(log)
self.assertTrue('ostype' in process_info)
self.assertEquals(process_info['ostype'], expected_platform)
dylib_info = lldbutil.packetlog_get_dylib_info(log)
self.assertTrue(dylib_info)
aout_info = None
for image in dylib_info['images']:

View File

@ -1418,19 +1418,20 @@ nub_bool_t DNBProcessSharedLibrariesUpdated(nub_process_t pid) {
return false;
}
const char *DNBGetDeploymentInfo(nub_process_t pid,
const struct load_command& lc,
const char *DNBGetDeploymentInfo(nub_process_t pid, bool is_executable,
const struct load_command &lc,
uint64_t load_command_address,
uint32_t& major_version,
uint32_t& minor_version,
uint32_t& patch_version) {
uint32_t &major_version,
uint32_t &minor_version,
uint32_t &patch_version) {
MachProcessSP procSP;
if (GetProcessSP(pid, procSP)) {
// FIXME: This doesn't return the correct result when xctest (a
// macOS binary) is loaded with the macCatalyst dyld platform
// override. The image info corrects for this, but qProcessInfo
// will return what is in the binary.
auto info = procSP->GetDeploymentInfo(lc, load_command_address);
auto info =
procSP->GetDeploymentInfo(lc, load_command_address, is_executable);
major_version = info.major_version;
minor_version = info.minor_version;
patch_version = info.patch_version;
@ -1439,7 +1440,6 @@ const char *DNBGetDeploymentInfo(nub_process_t pid,
return nullptr;
}
// Get the current shared library information for a process. Only return
// the shared libraries that have changed since the last shared library
// state changed event if only_changed is non-zero.

View File

@ -128,12 +128,12 @@ nub_bool_t DNBProcessSharedLibrariesUpdated(nub_process_t pid) DNB_EXPORT;
nub_size_t
DNBProcessGetSharedLibraryInfo(nub_process_t pid, nub_bool_t only_changed,
DNBExecutableImageInfo **image_infos) DNB_EXPORT;
const char *DNBGetDeploymentInfo(nub_process_t pid,
const struct load_command& lc,
const char *DNBGetDeploymentInfo(nub_process_t pid, bool is_executable,
const struct load_command &lc,
uint64_t load_command_address,
uint32_t& major_version,
uint32_t& minor_version,
uint32_t& patch_version);
uint32_t &major_version,
uint32_t &minor_version,
uint32_t &patch_version);
nub_bool_t DNBProcessSetNameToAddressCallback(nub_process_t pid,
DNBCallbackNameToAddress callback,
void *baton) DNB_EXPORT;

View File

@ -241,7 +241,8 @@ public:
uint32_t patch_version = 0;
};
DeploymentInfo GetDeploymentInfo(const struct load_command &,
uint64_t load_command_address);
uint64_t load_command_address,
bool is_executable);
static const char *GetPlatformString(unsigned char platform);
bool GetMachOInformationFromMemory(uint32_t platform,
nub_addr_t mach_o_header_addr,
@ -249,7 +250,15 @@ public:
struct mach_o_information &inf);
JSONGenerator::ObjectSP FormatDynamicLibrariesIntoJSON(
const std::vector<struct binary_image_information> &image_infos);
uint32_t GetAllLoadedBinariesViaDYLDSPI(
/// Get the runtime platform from DYLD via SPI.
uint32_t GetProcessPlatformViaDYLDSPI();
/// Use the dyld SPI present in macOS 10.12, iOS 10, tvOS 10,
/// watchOS 3 and newer to get the load address, uuid, and filenames
/// of all the libraries. This only fills in those three fields in
/// the 'struct binary_image_information' - call
/// GetMachOInformationFromMemory to fill in the mach-o header/load
/// command details.
void GetAllLoadedBinariesViaDYLDSPI(
std::vector<struct binary_image_information> &image_infos);
JSONGenerator::ObjectSP GetLoadedDynamicLibrariesInfos(
nub_process_t pid, nub_addr_t image_list_address, nub_addr_t image_count);

View File

@ -93,6 +93,7 @@ typedef void (*SetErrorFunction)(NSInteger, std::string, DNBError &);
typedef bool (*CallOpenApplicationFunction)(NSString *bundleIDNSStr,
NSDictionary *options,
DNBError &error, pid_t *return_pid);
// This function runs the BKSSystemService (or FBSSystemService) method
// openApplication:options:clientPort:withResult,
// messaging the app passed in bundleIDNSStr.
@ -483,6 +484,7 @@ static CallOpenApplicationFunction FBSCallOpenApplicationFunction =
#define _POSIX_SPAWN_DISABLE_ASLR 0x0100
#endif
MachProcess::MachProcess()
: m_pid(0), m_cpu_type(0), m_child_stdin(-1), m_child_stdout(-1),
m_child_stderr(-1), m_path(), m_args(), m_task(this),
@ -603,9 +605,11 @@ nub_addr_t MachProcess::GetTSDAddressForThread(
MachProcess::DeploymentInfo
MachProcess::GetDeploymentInfo(const struct load_command &lc,
uint64_t load_command_address) {
uint64_t load_command_address,
bool is_executable) {
DeploymentInfo info;
uint32_t cmd = lc.cmd & ~LC_REQ_DYLD;
// Handle the older LC_VERSION load commands, which don't
// distinguish between simulator and real hardware.
auto handle_version_min = [&](char platform) {
@ -640,6 +644,7 @@ MachProcess::GetDeploymentInfo(const struct load_command &lc,
// unambiguous LC_BUILD_VERSION load commands.
#endif
};
switch (cmd) {
case LC_VERSION_MIN_IPHONEOS:
handle_version_min(PLATFORM_IOS);
@ -667,6 +672,27 @@ MachProcess::GetDeploymentInfo(const struct load_command &lc,
}
#endif
}
// The xctest binary is a pure macOS binary but is launched with
// DYLD_FORCE_PLATFORM=6. In that case, force the platform to
// macCatalyst and use the macCatalyst version of the host OS
// instead of the macOS deployment target.
if (is_executable && GetProcessPlatformViaDYLDSPI() == PLATFORM_MACCATALYST) {
info.platform = PLATFORM_MACCATALYST;
std::string catalyst_version = GetMacCatalystVersionString();
const char *major = catalyst_version.c_str();
char *minor = nullptr;
char *patch = nullptr;
info.major_version = std::strtoul(major, &minor, 10);
info.minor_version = 0;
info.patch_version = 0;
if (minor && *minor == '.') {
info.minor_version = std::strtoul(++minor, &patch, 10);
if (patch && *patch == '.')
info.patch_version = std::strtoul(++patch, nullptr, 10);
}
}
return info;
}
@ -798,37 +824,21 @@ bool MachProcess::GetMachOInformationFromMemory(
sizeof(struct uuid_command))
uuid_copy(inf.uuid, uuidcmd.uuid);
}
if (DeploymentInfo deployment_info = GetDeploymentInfo(lc, load_cmds_p)) {
if (DeploymentInfo deployment_info = GetDeploymentInfo(
lc, load_cmds_p, inf.mach_header.filetype == MH_EXECUTE)) {
const char *lc_platform = GetPlatformString(deployment_info.platform);
// macCatalyst support.
//
// This handles two special cases:
//
// 1. Frameworks that have both a PLATFORM_MACOS and a
// PLATFORM_MACCATALYST load command. Make sure to select
// the requested one.
//
// 2. The xctest binary is a pure macOS binary but is launched
// with DYLD_FORCE_PLATFORM=6.
if (dyld_platform == PLATFORM_MACCATALYST &&
inf.mach_header.filetype == MH_EXECUTE &&
inf.min_version_os_name.empty() &&
(strcmp("macosx", lc_platform) == 0)) {
// DYLD says this *is* a macCatalyst process. If we haven't
// parsed any load commands, transform a macOS load command
// into a generic macCatalyst load command. It will be
// overwritten by a more specific one if there is one. This
// is only done for the main executable. It is perfectly fine
// for a macCatalyst binary to link against a macOS-only framework.
inf.min_version_os_name = "maccatalyst";
inf.min_version_os_version = GetMacCatalystVersionString();
} else if (dyld_platform != PLATFORM_MACCATALYST &&
inf.min_version_os_name == "macosx") {
// This is a binary with both PLATFORM_MACOS and
// PLATFORM_MACCATALYST load commands and the process is not
// running as PLATFORM_MACCATALYST. Stick with the
// "macosx" load command that we've already processed,
// ignore this one, which is presumed to be a
if (dyld_platform != PLATFORM_MACCATALYST &&
inf.min_version_os_name == "macosx") {
// macCatalyst support.
//
// This the special case of "zippered" frameworks that have both
// a PLATFORM_MACOS and a PLATFORM_MACCATALYST load command.
//
// When we are in this block, this is a binary with both
// PLATFORM_MACOS and PLATFORM_MACCATALYST load commands and
// the process is not running as PLATFORM_MACCATALYST. Stick
// with the "macosx" load command that we've already
// processed, ignore this one, which is presumed to be a
// PLATFORM_MACCATALYST one.
} else {
inf.min_version_os_name = lc_platform;
@ -1056,25 +1066,36 @@ JSONGenerator::ObjectSP MachProcess::GetLoadedDynamicLibrariesInfos(
return reply_sp;
}
// From dyld SPI header dyld_process_info.h
/// From dyld SPI header dyld_process_info.h
typedef void *dyld_process_info;
struct dyld_process_cache_info {
uuid_t cacheUUID; // UUID of cache used by process
uint64_t cacheBaseAddress; // load address of dyld shared cache
bool noCache; // process is running without a dyld cache
bool privateCache; // process is using a private copy of its dyld cache
/// UUID of cache used by process.
uuid_t cacheUUID;
/// Load address of dyld shared cache.
uint64_t cacheBaseAddress;
/// Process is running without a dyld cache.
bool noCache;
/// Process is using a private copy of its dyld cache.
bool privateCache;
};
// Use the dyld SPI present in macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer
// to get
// the load address, uuid, and filenames of all the libraries.
// This only fills in those three fields in the 'struct
// binary_image_information' - call
// GetMachOInformationFromMemory to fill in the mach-o header/load command
// details.
uint32_t MachProcess::GetAllLoadedBinariesViaDYLDSPI(
std::vector<struct binary_image_information> &image_infos) {
uint32_t MachProcess::GetProcessPlatformViaDYLDSPI() {
kern_return_t kern_ret;
uint32_t platform = 0;
if (m_dyld_process_info_create) {
dyld_process_info info =
m_dyld_process_info_create(m_task.TaskPort(), 0, &kern_ret);
if (info) {
if (m_dyld_process_info_get_platform)
platform = m_dyld_process_info_get_platform(info);
m_dyld_process_info_release(info);
}
}
return platform;
}
void MachProcess::GetAllLoadedBinariesViaDYLDSPI(
std::vector<struct binary_image_information> &image_infos) {
kern_return_t kern_ret;
if (m_dyld_process_info_create) {
dyld_process_info info =
@ -1089,12 +1110,9 @@ uint32_t MachProcess::GetAllLoadedBinariesViaDYLDSPI(
image.load_address = mach_header_addr;
image_infos.push_back(image);
});
if (m_dyld_process_info_get_platform)
platform = m_dyld_process_info_get_platform(info);
m_dyld_process_info_release(info);
}
}
return platform;
}
// Fetch information about all shared libraries using the dyld SPIs that exist
@ -1115,7 +1133,8 @@ MachProcess::GetAllLoadedLibrariesInfos(nub_process_t pid) {
pointer_size = 8;
std::vector<struct binary_image_information> image_infos;
uint32_t platform = GetAllLoadedBinariesViaDYLDSPI(image_infos);
GetAllLoadedBinariesViaDYLDSPI(image_infos);
uint32_t platform = GetProcessPlatformViaDYLDSPI();
const size_t image_count = image_infos.size();
for (size_t i = 0; i < image_count; i++) {
GetMachOInformationFromMemory(platform,
@ -1145,7 +1164,8 @@ JSONGenerator::ObjectSP MachProcess::GetLibrariesInfoForAddresses(
pointer_size = 8;
std::vector<struct binary_image_information> all_image_infos;
uint32_t platform = GetAllLoadedBinariesViaDYLDSPI(all_image_infos);
GetAllLoadedBinariesViaDYLDSPI(all_image_infos);
uint32_t platform = GetProcessPlatformViaDYLDSPI();
std::vector<struct binary_image_information> image_infos;
const size_t macho_addresses_count = macho_addresses.size();
@ -1173,7 +1193,7 @@ JSONGenerator::ObjectSP MachProcess::GetLibrariesInfoForAddresses(
JSONGenerator::ObjectSP MachProcess::GetSharedCacheInfo(nub_process_t pid) {
JSONGenerator::DictionarySP reply_sp(new JSONGenerator::Dictionary());
;
kern_return_t kern_ret;
if (m_dyld_process_info_create && m_dyld_process_info_get_cache) {
dyld_process_info info =

View File

@ -6356,10 +6356,11 @@ rnb_err_t RNBRemote::HandlePacket_qProcessInfo(const char *p) {
DNBProcessMemoryRead(pid, load_command_addr, sizeof(lc), &lc);
(void)bytes_read;
bool is_executable = true;
uint32_t major_version, minor_version, patch_version;
auto *platform = DNBGetDeploymentInfo(pid, lc, load_command_addr,
major_version, minor_version,
patch_version);
auto *platform =
DNBGetDeploymentInfo(pid, is_executable, lc, load_command_addr,
major_version, minor_version, patch_version);
if (platform) {
os_handled = true;
rep << "ostype:" << platform << ";";