Last active
December 14, 2015 11:09
-
-
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".
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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, | |
¬ification); | |
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