[debugserver] Fix LC_BUILD_VERSION load command handling.

Summary:
In one of the 2 places the LC_BUILD_VERSION load command is handled, there
is a bug preventing us from actually handling them (the address where to
read the load command was not updated). This patch factors reading the
deployment target load commands into a helper and adds testing for the 2
code paths calling the helper.

The testing is a little bit complicated because the only times those load
commands matter is when debugging a simulator process. I added a new
decorator to check that a specific SDK is available. The actual testing was
fairly easy once I knew how to run a simulated process.

Reviewers: jasonmolenda, labath

Subscribers: lldb-commits

Differential Revision: https://reviews.llvm.org/D45298

llvm-svn: 329374
This commit is contained in:
Frederic Riss 2018-04-06 04:28:12 +00:00
parent 1e989deadd
commit cd90f878d4
8 changed files with 246 additions and 169 deletions

View File

@ -344,6 +344,28 @@ def no_debug_info_test(func):
wrapper.__no_debug_info_test__ = True
return wrapper
def apple_simulator_test(platform):
"""
Decorate the test as a test requiring a simulator for a specific platform.
Consider that a simulator is available if you have the corresponding SDK installed.
The SDK identifiers for simulators are iphonesimulator, appletvsimulator, watchsimulator
"""
def should_skip_simulator_test():
if lldbplatformutil.getHostPlatform() != 'darwin':
return "simulator tests are run only on darwin hosts"
try:
DEVNULL = open(os.devnull, 'w')
output = subprocess.check_output(["xcodebuild", "-showsdks"], stderr=DEVNULL)
if re.search('%ssimulator' % platform, output):
return None
else:
return "%s simulator is not supported on this system." % platform
except subprocess.CalledProcessError:
return "%s is not supported on this system (xcodebuild failed)." % feature
return skipTestIfFn(should_skip_simulator_test)
def debugserver_test(func):
"""Decorate the item as a debugserver test."""

View File

@ -0,0 +1,112 @@
from __future__ import print_function
import gdbremote_testcase
import lldbgdbserverutils
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
import json
class TestAppleSimulatorOSType(gdbremote_testcase.GdbRemoteTestCaseBase):
mydir = TestBase.compute_mydir(__file__)
def check_simulator_ostype(self, sdk, platform_names, arch='x86_64'):
sim_devices_str = subprocess.check_output(['xcrun', 'simctl', 'list',
'-j', 'devices'])
sim_devices = json.loads(sim_devices_str)['devices']
# Find an available simulator for the requested platform
deviceUDID = None
for (runtime,devices) in sim_devices.items():
if not any(p in runtime.lower() for p in platform_names):
continue
for device in devices:
if device['availability'] != '(available)':
continue
deviceUDID = device['udid']
break
if deviceUDID != None:
break
# Launch the process using simctl
self.assertIsNotNone(deviceUDID)
exe_name = 'test_simulator_platform_{}'.format(platform_names[0])
sdkroot = subprocess.check_output(['xcrun', '--show-sdk-path', '--sdk',
sdk])
self.build(dictionary={ 'EXE': exe_name, 'SDKROOT': sdkroot.strip(),
'ARCH': arch })
exe_path = self.getBuildArtifact(exe_name)
sim_launcher = subprocess.Popen(['xcrun', 'simctl', 'spawn',
deviceUDID, exe_path,
'print-pid', 'sleep:10'],
stderr=subprocess.PIPE)
# Get the PID from the process output
pid = None
while not pid:
stderr = sim_launcher.stderr.readline()
if stderr == '':
continue
m = re.match(r"PID: (.*)", stderr)
self.assertIsNotNone(m)
pid = int(m.group(1))
# Launch debug monitor attaching to the simulated process
self.init_debugserver_test()
server = self.connect_to_debug_monitor(attach_pid=pid)
# Setup packet sequences
self.add_no_ack_remote_stream()
self.add_process_info_collection_packets()
self.test_sequence.add_log_lines(
["read packet: " +
"$jGetLoadedDynamicLibrariesInfos:{\"fetch_all_solibs\" : true}]#ce",
{"direction": "send", "regex": r"^\$(.+)#[0-9a-fA-F]{2}$",
"capture": {1: "dylib_info_raw"}}],
True)
# Run the stream
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
# Gather process info response
process_info = self.parse_process_info_response(context)
self.assertIsNotNone(process_info)
# Check that ostype is correct
self.assertTrue(process_info['ostype'] in platform_names)
# Now for dylibs
dylib_info_raw = context.get("dylib_info_raw")
dylib_info = json.loads(self.decode_gdbremote_binary(dylib_info_raw))
images = dylib_info['images']
image_info = None
for image in images:
if image['pathname'] != exe_path:
continue
image_info = image
break
self.assertIsNotNone(image_info)
self.assertTrue(image['min_version_os_name'] in platform_names)
@apple_simulator_test('iphone')
@debugserver_test
def test_simulator_ostype_ios(self):
self.check_simulator_ostype(sdk='iphonesimulator',
platform_names=['iphoneos', 'ios'])
@apple_simulator_test('appletv')
@debugserver_test
def test_simulator_ostype_tvos(self):
self.check_simulator_ostype(sdk='appletvsimulator',
platform_names=['tvos'])
@apple_simulator_test('watch')
@debugserver_test
def test_simulator_ostype_watchos(self):
self.check_simulator_ostype(sdk='watchsimulator',
platform_names=['watchos'], arch='i386')

View File

@ -48,6 +48,8 @@ static const char *const THREAD_COMMAND_NEW = "new";
static const char *const THREAD_COMMAND_PRINT_IDS = "print-ids";
static const char *const THREAD_COMMAND_SEGFAULT = "segfault";
static const char *const PRINT_PID_COMMAND = "print-pid";
static bool g_print_thread_ids = false;
static pthread_mutex_t g_print_mutex = PTHREAD_MUTEX_INITIALIZER;
static bool g_threads_do_segfault = false;
@ -61,6 +63,10 @@ static char g_message[256];
static volatile char g_c1 = '0';
static volatile char g_c2 = '1';
static void print_pid() {
fprintf(stderr, "PID: %d\n", getpid());
}
static void print_thread_id() {
// Put in the right magic here for your platform to spit out the thread id (tid)
// that debugserver/lldb-gdbserver would see as a TID. Otherwise, let the else
@ -349,6 +355,8 @@ int main(int argc, char **argv) {
// At this point we don't do anything else with threads.
// Later use thread index and send command to thread.
}
} else if (std::strstr(argv[i], PRINT_PID_COMMAND)) {
print_pid();
} else {
// Treat the argument as text for stdout.
printf("%s\n", argv[i]);

View File

@ -1426,6 +1426,21 @@ nub_bool_t DNBProcessSharedLibrariesUpdated(nub_process_t pid) {
return false;
}
const char *DNBGetDeploymentInfo(nub_process_t pid,
const struct load_command& lc,
uint64_t load_command_address,
uint32_t& major_version,
uint32_t& minor_version,
uint32_t& patch_version) {
MachProcessSP procSP;
if (GetProcessSP(pid, procSP))
return procSP->GetDeploymentInfo(lc, load_command_address,
major_version, minor_version,
patch_version);
return nullptr;
}
//----------------------------------------------------------------------
// Get the current shared library information for a process. Only return
// the shared libraries that have changed since the last shared library

View File

@ -127,6 +127,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,
uint64_t load_command_address,
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

@ -246,7 +246,10 @@ public:
uint64_t plo_pthread_tsd_base_address_offset,
uint64_t plo_pthread_tsd_base_offset,
uint64_t plo_pthread_tsd_entry_size);
const char *
GetDeploymentInfo(const struct load_command&, uint64_t load_command_address,
uint32_t& major_version, uint32_t& minor_version,
uint32_t& patch_version);
bool GetMachOInformationFromMemory(nub_addr_t mach_o_header_addr,
int wordsize,
struct mach_o_information &inf);

View File

@ -572,6 +572,68 @@ nub_addr_t MachProcess::GetTSDAddressForThread(
plo_pthread_tsd_entry_size);
}
const char *MachProcess::GetDeploymentInfo(const struct load_command& lc,
uint64_t load_command_address,
uint32_t& major_version,
uint32_t& minor_version,
uint32_t& patch_version) {
uint32_t cmd = lc.cmd & ~LC_REQ_DYLD;
bool lc_cmd_known =
cmd == LC_VERSION_MIN_IPHONEOS || cmd == LC_VERSION_MIN_MACOSX ||
cmd == LC_VERSION_MIN_TVOS || cmd == LC_VERSION_MIN_WATCHOS;
if (lc_cmd_known) {
struct version_min_command vers_cmd;
if (ReadMemory(load_command_address, sizeof(struct version_min_command),
&vers_cmd) != sizeof(struct version_min_command)) {
return nullptr;
}
major_version = vers_cmd.sdk >> 16;
minor_version = (vers_cmd.sdk >> 8) & 0xffu;
patch_version = vers_cmd.sdk & 0xffu;
switch (cmd) {
case LC_VERSION_MIN_IPHONEOS:
return "iphoneos";
case LC_VERSION_MIN_MACOSX:
return "macosx";
case LC_VERSION_MIN_TVOS:
return "tvos";
case LC_VERSION_MIN_WATCHOS:
return "watchos";
default:
return nullptr;
}
}
#if defined (LC_BUILD_VERSION)
if (cmd == LC_BUILD_VERSION) {
struct build_version_command build_vers;
if (ReadMemory(load_command_address, sizeof(struct build_version_command),
&build_vers) != sizeof(struct build_version_command)) {
return nullptr;
}
major_version = build_vers.sdk >> 16;;
minor_version = (build_vers.sdk >> 8) & 0xffu;
patch_version = build_vers.sdk & 0xffu;
switch (build_vers.platform) {
case PLATFORM_MACOS:
return "macosx";
case PLATFORM_IOS:
return "iphoneos";
case PLATFORM_TVOS:
return "tvos";
case PLATFORM_WATCHOS:
return "watchos";
case PLATFORM_BRIDGEOS:
return "bridgeos";
}
}
#endif
return nullptr;
}
// Given an address, read the mach-o header and load commands out of memory to
// fill in
// the mach_o_information "inf" object.
@ -670,91 +732,22 @@ bool MachProcess::GetMachOInformationFromMemory(
sizeof(struct uuid_command))
uuid_copy(inf.uuid, uuidcmd.uuid);
}
bool lc_cmd_known =
lc.cmd == LC_VERSION_MIN_IPHONEOS || lc.cmd == LC_VERSION_MIN_MACOSX;
#if defined(LC_VERSION_MIN_TVOS)
lc_cmd_known |= lc.cmd == LC_VERSION_MIN_TVOS;
#endif
#if defined(LC_VERSION_MIN_WATCHOS)
lc_cmd_known |= lc.cmd == LC_VERSION_MIN_WATCHOS;
#endif
if (lc_cmd_known) {
struct version_min_command vers_cmd;
if (ReadMemory(load_cmds_p, sizeof(struct version_min_command),
&vers_cmd) != sizeof(struct version_min_command)) {
return false;
}
switch (lc.cmd) {
case LC_VERSION_MIN_IPHONEOS:
inf.min_version_os_name = "iphoneos";
break;
case LC_VERSION_MIN_MACOSX:
inf.min_version_os_name = "macosx";
break;
#if defined(LC_VERSION_MIN_TVOS)
case LC_VERSION_MIN_TVOS:
inf.min_version_os_name = "tvos";
break;
#endif
#if defined(LC_VERSION_MIN_WATCHOS)
case LC_VERSION_MIN_WATCHOS:
inf.min_version_os_name = "watchos";
break;
#endif
default:
return false;
}
uint32_t xxxx = vers_cmd.sdk >> 16;
uint32_t yy = (vers_cmd.sdk >> 8) & 0xffu;
uint32_t zz = vers_cmd.sdk & 0xffu;
uint32_t major_version, minor_version, patch_version;
if (const char *platform = GetDeploymentInfo(lc, load_cmds_p,
major_version, minor_version,
patch_version)) {
inf.min_version_os_name = platform;
inf.min_version_os_version = "";
inf.min_version_os_version += std::to_string(xxxx);
inf.min_version_os_version += std::to_string(major_version);
inf.min_version_os_version += ".";
inf.min_version_os_version += std::to_string(yy);
if (zz != 0) {
inf.min_version_os_version += std::to_string(minor_version);
if (patch_version != 0) {
inf.min_version_os_version += ".";
inf.min_version_os_version += std::to_string(zz);
inf.min_version_os_version += std::to_string(patch_version);
}
}
#if defined (LC_BUILD_VERSION)
if (lc.cmd == LC_BUILD_VERSION)
{
struct build_version_command build_vers;
if (ReadMemory(load_cmds_p, sizeof(struct build_version_command),
&build_vers) != sizeof(struct build_version_command)) {
return false;
}
switch (build_vers.platform)
{
case PLATFORM_MACOS:
inf.min_version_os_name = "macosx";
break;
case PLATFORM_IOS:
inf.min_version_os_name = "iphoneos";
break;
case PLATFORM_TVOS:
inf.min_version_os_name = "tvos";
break;
case PLATFORM_WATCHOS:
inf.min_version_os_name = "watchos";
break;
case PLATFORM_BRIDGEOS:
inf.min_version_os_name = "bridgeos";
break;
}
uint32_t xxxx = build_vers.sdk >> 16;;
uint32_t yy = (build_vers.sdk >> 8) & 0xffu;
uint32_t zz = build_vers.sdk & 0xffu;
inf.min_version_os_version = "";
inf.min_version_os_version += std::to_string(xxxx);
inf.min_version_os_version += ".";
inf.min_version_os_version += std::to_string(yy);
if (zz != 0) {
inf.min_version_os_version += ".";
inf.min_version_os_version += std::to_string(zz);
}
}
#endif
load_cmds_p += lc.cmdsize;
}
return true;

View File

@ -6091,100 +6091,18 @@ rnb_err_t RNBRemote::HandlePacket_qProcessInfo(const char *p) {
for (uint32_t i = 0; i < mh.ncmds && !os_handled; ++i) {
const nub_size_t bytes_read =
DNBProcessMemoryRead(pid, load_command_addr, sizeof(lc), &lc);
uint32_t raw_cmd = lc.cmd & ~LC_REQ_DYLD;
if (bytes_read != sizeof(lc))
break;
switch (raw_cmd) {
case LC_VERSION_MIN_IPHONEOS:
os_handled = true;
rep << "ostype:ios;";
DNBLogThreadedIf(LOG_RNB_PROC,
"LC_VERSION_MIN_IPHONEOS -> 'ostype:ios;'");
break;
case LC_VERSION_MIN_MACOSX:
uint32_t major_version, minor_version, patch_version;
auto *platform = DNBGetDeploymentInfo(pid, lc, load_command_addr,
major_version, minor_version,
patch_version);
if (platform) {
os_handled = true;
rep << "ostype:macosx;";
DNBLogThreadedIf(LOG_RNB_PROC,
"LC_VERSION_MIN_MACOSX -> 'ostype:macosx;'");
break;
#if defined(LC_VERSION_MIN_TVOS)
case LC_VERSION_MIN_TVOS:
os_handled = true;
rep << "ostype:tvos;";
DNBLogThreadedIf(LOG_RNB_PROC,
"LC_VERSION_MIN_TVOS -> 'ostype:tvos;'");
break;
#endif
#if defined(LC_VERSION_MIN_WATCHOS)
case LC_VERSION_MIN_WATCHOS:
os_handled = true;
rep << "ostype:watchos;";
DNBLogThreadedIf(LOG_RNB_PROC,
"LC_VERSION_MIN_WATCHOS -> 'ostype:watchos;'");
break;
#endif
default:
rep << "ostype:" << platform << ";";
break;
}
load_command_addr = load_command_addr + lc.cmdsize;
}
// Test that the PLATFORM_* defines are available from mach-o/loader.h
#if defined (PLATFORM_MACOS)
for (uint32_t i = 0; i < mh.ncmds && !os_handled; ++i)
{
nub_size_t bytes_read =
DNBProcessMemoryRead(pid, load_command_addr, sizeof(lc), &lc);
uint32_t raw_cmd = lc.cmd & ~LC_REQ_DYLD;
if (bytes_read != sizeof(lc))
break;
if (raw_cmd == LC_BUILD_VERSION)
{
uint32_t platform; // first field of 'struct build_version_command'
bytes_read = DNBProcessMemoryRead(pid, load_command_addr + 8, sizeof(platform), &platform);
if (bytes_read != sizeof (platform))
break;
switch (platform)
{
case PLATFORM_MACOS:
os_handled = true;
rep << "ostype:macosx;";
DNBLogThreadedIf(LOG_RNB_PROC,
"LC_BUILD_VERSION PLATFORM_MACOS -> 'ostype:macosx;'");
break;
case PLATFORM_IOS:
os_handled = true;
rep << "ostype:ios;";
DNBLogThreadedIf(LOG_RNB_PROC,
"LC_BUILD_VERSION PLATFORM_IOS -> 'ostype:ios;'");
break;
case PLATFORM_TVOS:
os_handled = true;
rep << "ostype:tvos;";
DNBLogThreadedIf(LOG_RNB_PROC,
"LC_BUILD_VERSION PLATFORM_TVOS -> 'ostype:tvos;'");
break;
case PLATFORM_WATCHOS:
os_handled = true;
rep << "ostype:watchos;";
DNBLogThreadedIf(LOG_RNB_PROC,
"LC_BUILD_VERSION PLATFORM_WATCHOS -> 'ostype:watchos;'");
break;
case PLATFORM_BRIDGEOS:
os_handled = true;
rep << "ostype:bridgeos;";
DNBLogThreadedIf(LOG_RNB_PROC,
"LC_BUILD_VERSION PLATFORM_BRIDGEOS -> 'ostype:bridgeos;'");
break;
}
}
}
#endif // PLATFORM_MACOS
}
#endif // when compiling this on x86 targets
}