Skip to content

Instantly share code, notes, and snippets.

@memfrag
Last active December 14, 2015 11:09
Show Gist options
  • Save memfrag/5076954 to your computer and use it in GitHub Desktop.
Save memfrag/5076954 to your computer and use it in GitHub Desktop.
syscon - A tool for reading the system console from an iOS device via USB and displaying it in the OS X terminal in "real time".
/*
* syscon - A tool for reading the system console from an iOS device
* via USB and displaying it in the OS X terminal in "real time".
*
* This tool and its source code is hereby released into the public domain.
* No warranty given, no responsibility taken. Use at your own risk.
*
* How to build:
*
* clang -o syscon -framework CoreFoundation -framework MobileDevice
* -F /System/Library/PrivateFrameworks syscon.c
*/
#include <CoreFoundation/CoreFoundation.h>
#include <sys/socket.h>
#include <stdint.h>
#include <stdio.h>
#pragma mark - Mobile Device Framework Declarations and Prototypes
#define AM_SUCCESS 0
typedef int AMResult;
typedef struct AMDevice *AMDevice;
typedef struct AMService *AMService;
typedef struct AMDeviceNotification *AMDeviceNotification;
typedef enum ADNCIMessage {
ADNCI_MESSAGE_CONNECTED = 1,
ADNCI_MESSAGE_DISCONNECTED
} ADNCIMessage;
typedef struct AMDeviceNotificationCallbackInfo {
AMDevice device;
ADNCIMessage message;
} __attribute__ ((packed)) AMDeviceNotificationCallbackInfo;
typedef void (*AMDeviceNotificationCallback)(AMDeviceNotificationCallbackInfo *,
void *);
AMResult AMDeviceNotificationSubscribe(AMDeviceNotificationCallback callback,
uint32_t unused0,
uint32_t unused1,
void *arg,
AMDeviceNotification *notification);
AMResult AMDeviceNotificationUnsubscribe(AMDeviceNotification notification);
AMResult AMDeviceConnect(AMDevice device);
int AMDeviceIsPaired(AMDevice device);
AMResult AMDeviceValidatePairing(AMDevice device);
AMResult AMDeviceStartSession(AMDevice device);
AMResult AMDeviceStartService(AMDevice device, CFStringRef serviceName,
AMService *service,
uint32_t *unknown);
AMResult AMDeviceStopSession(AMDevice device);
AMResult AMDeviceDisconnect(AMDevice device);
CFStringRef AMDeviceCopyValue(AMDevice device, uint32_t zero, CFStringRef key);
CFStringRef AMDeviceCopyDeviceIdentifier(AMDevice device);
#pragma mark - Type Declarations
typedef struct CallbackContext {
char *udidFilter;
char *outputFilename;
FILE *outputFile;
CFReadStreamRef stream;
uint8_t buffer[16384];
char entryBuffer[16384];
} CallbackContext;
#pragma mark - Utility Functions
static void closeStream(CFReadStreamRef *stream)
{
CFReadStreamUnscheduleFromRunLoop(*stream,
CFRunLoopGetMain(),
kCFRunLoopCommonModes);
CFReadStreamClose(*stream);
CFRelease(*stream);
*stream = NULL;
}
static void printDeviceKeyAndValue(AMDevice device, CFStringRef key)
{
CFStringRef value = AMDeviceCopyValue(device, 0, key);
if (value) {
CFStringEncoding keyEncoding = CFStringGetFastestEncoding(key);
CFStringEncoding valueEncoding = CFStringGetFastestEncoding(value);
printf("%s: %s\n", CFStringGetCStringPtr(key, keyEncoding),
CFStringGetCStringPtr(value, valueEncoding));
CFRelease(value);
}
}
static bool matchesUDIDFilter(AMDevice device, const char *filter)
{
CFStringRef udidRef = AMDeviceCopyDeviceIdentifier(device);
if (!udidRef) {
fprintf(stderr, "ERROR: Failed to retrieve UDID for device.\n");
exit(1);
}
CFStringEncoding encoding = CFStringGetFastestEncoding(udidRef);
const char *udid = CFStringGetCStringPtr(udidRef, encoding);
bool matches = strncasecmp(filter, udid, strlen(filter));
CFRelease(udidRef);
return matches;
}
#pragma Console Stream Callback
static void logCallback(CFReadStreamRef stream,
CFStreamEventType eventType,
void *callbackContext)
{
CallbackContext *context = callbackContext;
switch (eventType) {
case kCFStreamEventNone:
case kCFStreamEventOpenCompleted:
case kCFStreamEventCanAcceptBytes:
case kCFStreamEventErrorOccurred:
case kCFStreamEventEndEncountered:
break;
case kCFStreamEventHasBytesAvailable:
{
const CFIndex readCount = CFReadStreamRead(stream,
context->buffer,
sizeof(context->buffer));
if (readCount < 0) {
fprintf(stderr, "ERROR: Failed to read from log stream.\n");
exit(1);
}
if (readCount == 0) {
// End of stream
return;
}
uint8_t *entry = context->buffer;
CFIndex remaining = readCount;
context->buffer[sizeof(context->buffer) - 1] = '\0';
while (remaining > 0) {
uint32_t entryLength = 0;
while (entry[entryLength] != '\0' && remaining > 0) {
entryLength++;
remaining--;
}
if (remaining) {
if (entryLength) {
strncpy(context->entryBuffer, (const char *)entry,
entryLength);
context->entryBuffer[entryLength] = '\0';
if (strcmp("> ", context->entryBuffer)) {
printf("%s", context->entryBuffer);
fflush(stdout);
if (context->outputFile) {
fprintf(context->outputFile, "%s",
context->entryBuffer);
fflush(context->outputFile);
}
}
}
entry += entryLength + 1;
remaining--;
}
}
return;
}
}
}
#pragma mark - Mobile Device Notification Callback
static void readConsoleLog(AMService service, CallbackContext *context)
{
CFSocketNativeHandle socket = (CFSocketNativeHandle)service;
CFStreamCreatePairWithSocket(0, socket, &context->stream, NULL);
if (context->stream == NULL) {
fprintf(stderr, "ERROR: Failed to create input stream.\n");
exit(1);
}
CFStreamClientContext clientContext = {0, context, 0, 0, 0};
int events = kCFStreamEventOpenCompleted
| kCFStreamEventHasBytesAvailable
| kCFStreamEventCanAcceptBytes
| kCFStreamEventErrorOccurred
| kCFStreamEventEndEncountered;
if (!CFReadStreamSetClient(context->stream, events,
logCallback, &clientContext)) {
fprintf(stderr, "ERROR: Failed to set stream client.\n");
exit(1);
}
CFReadStreamScheduleWithRunLoop (context->stream,
CFRunLoopGetMain(),
kCFRunLoopCommonModes);
if (!CFReadStreamOpen(context->stream)) {
fprintf(stderr, "ERROR: Failed to open stream.\n");
exit(1);
}
}
static void didReceiveNotification(AMDeviceNotificationCallbackInfo *info,
void *callbackContext)
{
CallbackContext *context = callbackContext;
AMDevice device = info->device;
if (!matchesUDIDFilter(info->device, context->udidFilter)) {
return;
}
if (info->message == ADNCI_MESSAGE_CONNECTED) {
if (context->stream != NULL) {
closeStream(&context->stream);
}
printDeviceKeyAndValue(device, CFSTR("DeviceName"));
printDeviceKeyAndValue(device, CFSTR("WiFiAddress"));
if (AMDeviceConnect(device) != AM_SUCCESS) {
fprintf(stderr, "ERROR: Failed to connect to device.\n");
exit(1);
}
if (!AMDeviceIsPaired(device)) {
fprintf(stderr, "ERROR: Device is not paired.\n");
exit(1);
}
if (AMDeviceValidatePairing(device) != AM_SUCCESS) {
fprintf(stderr, "ERROR: Device pairing failed validation.\n");
exit(1);
}
if (AMDeviceStartSession(device) != AM_SUCCESS) {
fprintf(stderr, "ERROR: Failed to start device session.\n");
exit(1);
}
AMService service = NULL;
AMDeviceStartService(device, CFSTR("com.apple.syslog_relay"),
(void *)&service, NULL);
AMDeviceStopSession(device);
AMDeviceDisconnect(device);
readConsoleLog(service, context);
} else if (info->message == ADNCI_MESSAGE_DISCONNECTED) {
if (context->stream != NULL) {
closeStream(&context->stream);
}
}
}
#pragma mark - Command Line Tool
static void printUsageAndExit(void)
{
fprintf(stderr, "USAGE: syscon [-o <output file>] -u <udid>\n");
exit(1);
}
static void parseArguments(CallbackContext *context, int argc, char *argv[]) {
int index = 1;
while (index + 1 < argc) {
if (!strcmp(argv[index], "-o")) {
context->outputFilename = argv[++index];
} else if (!strcmp(argv[index], "-u")) {
context->udidFilter = argv[++index];
} else {
printUsageAndExit();
}
index++;
}
// UDID argument is mandatory.
if (context->udidFilter == NULL) {
printUsageAndExit();
}
// Check if UDID argument is at least potentially a partial UDID.
size_t udidLength = strlen(context->udidFilter);
if (udidLength < 1 || udidLength > 40) {
printUsageAndExit();
}
}
int main(int argc, char *argv[])
{
CallbackContext callbackContext = {
.udidFilter = NULL,
.outputFilename = NULL,
.outputFile = NULL,
.stream = NULL
};
parseArguments(&callbackContext, argc, argv);
if (callbackContext.outputFilename) {
callbackContext.outputFile = fopen(callbackContext.outputFilename, "w");
if (callbackContext.outputFile == NULL) {
fprintf(stderr, "ERROR: Unable to open file %s for writing.\n",
callbackContext.outputFilename);
exit(1);
}
}
AMDeviceNotification notification = NULL;
AMResult result = AMDeviceNotificationSubscribe(didReceiveNotification,
0, 0, &callbackContext,
&notification);
if (result != AM_SUCCESS) {
fprintf(stderr, "ERROR: Unable to subscribe to notifications.\n");
exit(1);
}
CFRunLoopRun();
if (callbackContext.outputFile) {
fclose(callbackContext.outputFile);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment