#include <CoreFoundation/CoreFoundation.h>
#include <ApplicationServices/ApplicationServices.h>
#include <mach/mach_error.h>
#include <mach/mach_time.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <getopt.h>
#include <memory.h>
#include <time.h>
#include <spawn.h>
#include <sys/wait.h>
#include "IOGDiagnoseUtils/IOGDiagnoseUtils.h"
#include "../GTrace/GTraceTypes.h"
#define kFILENAME_LENGTH 64
void print_usage( const char * name );
void agdcdiagnose(FILE * fOut);
void print_usage( const char * name )
{
fprintf(stdout, "%s options:\n", name);
fprintf(stdout, "\t--file | -f\n");
fprintf(stdout, "\t\tWrite report to file /tmp/com.apple.iokit.IOGraphics_YYYY_MM__DD_HH-MM-SS.txt (instead of stdout)\n");
fprintf(stdout, "\n\t--binary | -b binary_file\n");
fprintf(stdout, "\n");
fflush(stdout);
}
extern char **environ;
void agdcdiagnose(FILE * fOut)
{
pid_t pid = 0;
int status = -1;
char * const agdcArgs[] = {const_cast<char *>("/System/Library/Extensions/AppleGraphicsControl.kext/Contents/MacOS/AGDCDiagnose"),
const_cast<char *>("-a"),
nullptr};
if (NULL != fOut) {
fprintf(fOut, "\n\n------------------------------------- AGDC REPORT -------------------------------------\n\n");
status = posix_spawn(&pid, agdcArgs[0], NULL, NULL, agdcArgs, environ);
if (0 == status) {
(void)waitpid(pid, &status, 0);
} else {
fprintf(fOut, "\tAGDCDiagnose failed to launch\n");
}
fprintf(fOut, "\n---------------------------------------------------------------------------------------\n");
}
}
uint32_t sizeToLines( uint32_t bufSize )
{
uint32_t lines = 0;
if (bufSize > (kGTraceMaximumLineCount * sizeof(sGTrace))) {
lines = kGTraceMaximumLineCount;
}
else if (bufSize >= sizeof(sGTrace)) {
lines = (1 << (32 - (__builtin_clz(bufSize) + 1))) / sizeof(sGTrace);
}
return (lines);
}
bool dumpTokenBuffer(FILE * fOut, uint32_t lastToken, const uint32_t tokenLineCount, const uint32_t tokenSize, uint64_t * tokenBuffer)
{
sGTrace * traceBuffer = reinterpret_cast<sGTrace *>(tokenBuffer);
uint32_t traceLines = 0;
uint32_t currentLine = 0;
bool bRet = false;
fprintf(fOut, "\n\t\tLast Token Recorded: ");
if (NULL != tokenBuffer) {
if (tokenSize >= sizeof(sGTrace)) {
traceLines = sizeToLines(tokenSize);
if (0 != traceLines) {
bRet = true;
lastToken--;
fprintf(fOut, "%u\n", lastToken);
fprintf(fOut, "\t\tToken Buffer Lines : %u\n", tokenLineCount);
fprintf(fOut, "\t\tToken Buffer Size : %u\n", tokenSize);
fprintf(fOut, "\t\tToken Buffer Data :\n");
if (lastToken > traceLines) {
lastToken = traceLines;
}
for (currentLine = 0; currentLine < lastToken; currentLine++) {
fprintf(fOut, "\t\tTkn: %04u\tTS: %llu\tLn: %u\tC: %#llx\tCTID: %u-%#llx\tOID: %#llx\tTag: %#llx\tA: %#llx-%#llx-%#llx-%#llx\n",
currentLine,
traceBuffer[currentLine].traceEntry.timestamp,
traceBuffer[currentLine].traceEntry.traceID.ID.line,
traceBuffer[currentLine].traceEntry.traceID.ID.component,
traceBuffer[currentLine].traceEntry.threadInfo.TI.cpu,
traceBuffer[currentLine].traceEntry.threadInfo.TI.threadID,
traceBuffer[currentLine].traceEntry.threadInfo.TI.registryID,
traceBuffer[currentLine].traceEntry.argsTag.TAG.u64,
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[0],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[1],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[2],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[3]
);
}
fprintf(fOut, "\tTkn: %04u\tTS: %llu\tLn: %u\tC: %#llx\tCTID: %u-%#llx\tOID: %#llx\tTag: %#llx\tA: %#llx-%#llx-%#llx-%#llx\t<--\n",
currentLine,
traceBuffer[currentLine].traceEntry.timestamp,
traceBuffer[currentLine].traceEntry.traceID.ID.line,
traceBuffer[currentLine].traceEntry.traceID.ID.component,
traceBuffer[currentLine].traceEntry.threadInfo.TI.cpu,
traceBuffer[currentLine].traceEntry.threadInfo.TI.threadID,
traceBuffer[currentLine].traceEntry.threadInfo.TI.registryID,
traceBuffer[currentLine].traceEntry.argsTag.TAG.u64,
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[0],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[1],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[2],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[3]
);
currentLine++;
for (; currentLine < traceLines; currentLine++) {
fprintf(fOut, "\t\tTkn: %04u\tTS: %llu\tLn: %u\tC: %#llx\tCTID: %u-%#llx\tOID: %#llx\tTag: %#llx\tA: %#llx-%#llx-%#llx-%#llx\n",
currentLine,
traceBuffer[currentLine].traceEntry.timestamp,
traceBuffer[currentLine].traceEntry.traceID.ID.line,
traceBuffer[currentLine].traceEntry.traceID.ID.component,
traceBuffer[currentLine].traceEntry.threadInfo.TI.cpu,
traceBuffer[currentLine].traceEntry.threadInfo.TI.threadID,
traceBuffer[currentLine].traceEntry.threadInfo.TI.registryID,
traceBuffer[currentLine].traceEntry.argsTag.TAG.u64,
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[0],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[1],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[2],
traceBuffer[currentLine].traceEntry.args.ARGS.u64s[3]
);
}
} else {
fprintf(fOut, "-3 (Token Data Count Invalid (%d vs %d))\n", traceLines, tokenSize);
}
} else {
fprintf(fOut, "-2 (No Token Data Recorded)\n");
}
} else {
fprintf(fOut, "-1 (No Token Data Recorded)\n");
}
fprintf(fOut, "\n");
return (bRet);
}
void writeGTraceBinary( const IOGDiagnose * report, const char * filename )
{
FILE * fp = NULL;
fp = fopen(filename, "wb");
if (fp) {
if ((report->version >= 3)
&& report->tokenSize)
{
fwrite(report->tokenBuffer, report->tokenSize, 1, fp);
fflush(fp);
fclose(fp);
} else {
fprintf(stdout, "iogdiagnose: No GTrace data\n");
}
} else {
fprintf(stdout, "iogdiagnose: Failed to open %s for write access\n",
filename);
}
}
void dumpGTraceReport( const IOGDiagnose * report, const bool bDumpToFile )
{
FILE * fOut = NULL;
FILE * fp = NULL;
tm * timeinfo = NULL;
const IOGReport * fbState = NULL;
time_t rawtime = 0;
uint32_t index = 0;
uint32_t mask = 0x80000000;
unsigned int groupIndex = 0;
unsigned int stampIndex = 0;
mach_timebase_info_data_t info = {0};
char filename[kFILENAME_LENGTH] = {0};
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(filename, kFILENAME_LENGTH, "/tmp/com.apple.iokit.IOGraphics_%F_%H-%M-%S.txt",
timeinfo);
if (bDumpToFile) {
fp = fopen(filename, "w+");
}
if (NULL == fp) {
fOut= stdout;
} else {
fOut = fp;
}
fprintf(fOut, "---------------------------------- IOGRAPHICS REPORT ----------------------------------\n\n");
strftime(filename, kFILENAME_LENGTH, "Report date: %F %H:%M:%S\n", timeinfo);
fprintf(fOut, "%s", filename);
if (report) {
if (report->version <= 6) {
fprintf(fOut, "Report version: %#llx\n", report->version);
fprintf(fOut, "Boot Epoch Time: %llu\n", report->systemBootEpochTime);
fprintf(fOut, "Number of framebuffers: %llu\n\n",
report->framebufferCount);
while(index < report->framebufferCount) {
fbState = &(report->fbState[index]);
index++;
if (strlen(fbState->objectName)) {
fprintf(fOut, "\t%s::", fbState->objectName);
} else {
fprintf(fOut, "\tUNKNOWN::");
}
if (strlen(fbState->framebufferName)) {
fprintf(fOut, "%s", fbState->framebufferName);
} else {
fprintf(fOut, "IOFB?");
}
fprintf(fOut, " (%u %#llx)\n",
fbState->dependentIndex, fbState->regID);
if (fbState->aliasID) {
fprintf(fOut, "\t\tAlias : 0x%08x\n", fbState->aliasID);
}
fprintf(fOut, "\t\tI-State : 0x%08x (b", fbState->stateBits);
for (mask = 0x80000000; 0 != mask; mask >>= 1) {
fprintf(fOut, "%d", (fbState->stateBits & mask) ? 1 : 0);
}
fprintf(fOut, ")\n");
if (report->version >= 6) {
fprintf(fOut, "\t\tStates : OP:ON:US:PS:CS:CC:CL:CO:DS:MR:SG:WL:NA:SN:SS:SA:PP:SP:MS:MP:MU:PPS: NOTIFIED : WSAA \n");
fprintf(fOut, "\t\tState Bits: %02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:0x%01x:0x%08x:0x%08x\n",
fbState->stateBits & kIOGReportState_Opened ? 1 : 0,
fbState->stateBits & kIOGReportState_Online ? 1 : 0,
fbState->stateBits & kIOGReportState_Usable ? 1 : 0,
fbState->stateBits & kIOGReportState_Paging ? 1 : 0,
fbState->stateBits & kIOGReportState_Clamshell ? 1 : 0,
fbState->stateBits & kIOGReportState_ClamshellCurrent ? 1 : 0,
fbState->stateBits & kIOGReportState_ClamshellLast ? 1 : 0,
fbState->stateBits & kIOGReportState_ClamshellOffline ? 1 : 0,
fbState->stateBits & kIOGReportState_SystemDark ? 1 : 0,
fbState->stateBits & kIOGReportState_Mirrored ? 1 : 0,
fbState->stateBits & kIOGReportState_SystemGated ? 1 : 0,
fbState->stateBits & kIOGReportState_WorkloopGated ? 1 : 0,
fbState->stateBits & kIOGReportState_NotificationActive ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerNotified ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerState ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerPendingAck ? 1 : 0,
fbState->stateBits & kIOGReportState_PowerPendingChange ? 1 : 0,
fbState->stateBits & kIOGReportState_SystemPowerAckTo ? 1 : 0,
fbState->stateBits & kIOGReportState_IsMuxSwitching ? 1 : 0,
fbState->stateBits & kIOGReportState_PowerPendingMuxChange ? 1 : 0,
fbState->stateBits & kIOGReportState_Muted ? 1 : 0,
fbState->pendingPowerState,
fbState->notificationGroup,
fbState->wsaaState );
} else if (report->version >= 4) {
fprintf(fOut, "\t\tStates : OP:US:PS:CS:CC:CL:DS:MR:SG:WL:NA:SN:SS:SA:PP:SP:MX:PPS: NOTIFIED : WSAA \n");
fprintf(fOut, "\t\tState Bits: %02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:0x%01x:0x%08x:0x%08x\n",
fbState->stateBits & kIOGReportState_Opened ? 1 : 0,
fbState->stateBits & kIOGReportState_Usable ? 1 : 0,
fbState->stateBits & kIOGReportState_Paging ? 1 : 0,
fbState->stateBits & kIOGReportState_Clamshell ? 1 : 0,
fbState->stateBits & kIOGReportState_ClamshellCurrent ? 1 : 0,
fbState->stateBits & kIOGReportState_ClamshellLast ? 1 : 0,
fbState->stateBits & kIOGReportState_SystemDark ? 1 : 0,
fbState->stateBits & kIOGReportState_Mirrored ? 1 : 0,
fbState->stateBits & kIOGReportState_SystemGated ? 1 : 0,
fbState->stateBits & kIOGReportState_WorkloopGated ? 1 : 0,
fbState->stateBits & kIOGReportState_NotificationActive ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerNotified ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerState ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerPendingAck ? 1 : 0,
fbState->stateBits & kIOGReportState_PowerPendingChange ? 1 : 0,
fbState->stateBits & kIOGReportState_SystemPowerAckTo ? 1 : 0,
fbState->stateBits & kIOGReportState_PowerPendingMuxChange ? 1 : 0,
fbState->pendingPowerState,
fbState->notificationGroup,
fbState->wsaaState );
} else {
fprintf(fOut, "\t\tStates : OP:US:PS:CS:CC:CL:DS:MR:SG:WL:NA:SN:SS:SA:PP:MX:PPS: NOTIFIED : WSAA \n");
fprintf(fOut, "\t\tState Bits: %02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:%02d:0x%01x:0x%08x:0x%08x\n",
fbState->stateBits & kIOGReportState_Opened ? 1 : 0,
fbState->stateBits & kIOGReportState_Usable ? 1 : 0,
fbState->stateBits & kIOGReportState_Paging ? 1 : 0,
fbState->stateBits & kIOGReportState_Clamshell ? 1 : 0,
fbState->stateBits & kIOGReportState_ClamshellCurrent ? 1 : 0,
fbState->stateBits & kIOGReportState_ClamshellLast ? 1 : 0,
fbState->stateBits & kIOGReportState_SystemDark ? 1 : 0,
fbState->stateBits & kIOGReportState_Mirrored ? 1 : 0,
fbState->stateBits & kIOGReportState_SystemGated ? 1 : 0,
fbState->stateBits & kIOGReportState_WorkloopGated ? 1 : 0,
fbState->stateBits & kIOGReportState_NotificationActive ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerNotified ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerState ? 1 : 0,
fbState->stateBits & kIOGReportState_ServerPendingAck ? 1 : 0,
fbState->stateBits & kIOGReportState_PowerPendingChange ? 1 : 0,
fbState->stateBits & kIOGReportState_PowerPendingMuxChange ? 1 : 0,
fbState->pendingPowerState,
fbState->notificationGroup,
fbState->wsaaState );
}
fprintf(fOut, "\t\tA-State : 0x%08x (b",
fbState->externalAPIState);
for (mask = 0x80000000; 0 != mask; mask >>= 1) {
fprintf(fOut, "%d",
(fbState->externalAPIState & mask) ? 1 : 0);
}
fprintf(fOut, ")\n");
if (report->version >= 2) {
fprintf(fOut, "\t\tMode ID : %#x\n",
fbState->lastSuccessfulMode);
}
fprintf(fOut, "\t\tSystem : %llu (%#llx) (%u)\n",
fbState->systemOwner,
fbState->systemOwner,
fbState->systemGatedCount);
fprintf(fOut, "\t\tController: %llu (%#llx) (%u)\n",
fbState->workloopOwner,
fbState->workloopOwner,
fbState->workloopGatedCount);
if (report->version >= 2) {
mach_timebase_info(&info);
for (groupIndex = 0;
groupIndex < IOGRAPHICS_MAXIMUM_REPORTS;
groupIndex++)
{
if (0 != fbState->notifications[groupIndex].groupID) {
fprintf(fOut, "\n\t\tGroup ID: %#llx\n",
fbState->notifications[groupIndex].groupID - 1);
for (stampIndex = 0;
stampIndex < IOGRAPHICS_MAXIMUM_REPORTS;
stampIndex++)
{
if ((0 != fbState->notifications[groupIndex].stamp[stampIndex].lastEvent) ||
(0 != fbState->notifications[groupIndex].stamp[stampIndex].start) ||
(0 != fbState->notifications[groupIndex].stamp[stampIndex].end) ||
(0 != strlen(fbState->notifications[groupIndex].stamp[stampIndex].name)) )
{
fprintf(fOut, "\t\t\tComponent : %s\n",
fbState->notifications[groupIndex].stamp[stampIndex].name);
fprintf(fOut, "\t\t\tLast Event : %u (%#x)\n",
fbState->notifications[groupIndex].stamp[stampIndex].lastEvent,
fbState->notifications[groupIndex].stamp[stampIndex].lastEvent);
fprintf(fOut, "\t\t\tStamp Start : %#llx\n",
fbState->notifications[groupIndex].stamp[stampIndex].start);
fprintf(fOut, "\t\t\tStamp End : %#llx\n",
fbState->notifications[groupIndex].stamp[stampIndex].end);
fprintf(fOut, "\t\t\tStamp Delta : ");
if (fbState->notifications[groupIndex].stamp[stampIndex].start
<= fbState->notifications[groupIndex].stamp[stampIndex].end)
{
fprintf(fOut, "%llu ns\n",
((fbState->notifications[groupIndex].stamp[stampIndex].end
- fbState->notifications[groupIndex].stamp[stampIndex].start)
* static_cast<uint64_t>(info.numer))
/ static_cast<uint64_t>(info.denom));
} else {
fprintf(fOut, "Notifier Active\n");
}
}
}
}
}
}
}
if (report->version >= 3)
{
(void)dumpTokenBuffer(fOut,
report->tokenLine,
report->tokenLineCount,
report->tokenSize,
const_cast<uint64_t *>(report->tokenBuffer));
}
} else {
fprintf(fOut, "Unsupported report version: %#llx\n",
report->version);
}
} else {
fprintf(fOut, "Parameter error: No reporting buffer.\n");
}
fprintf(fOut, "\n---------------------------------------------------------------------------------------\n");
fflush(fOut);
agdcdiagnose(fOut);
fflush(fOut);
if (NULL != fp) {
fclose(fp);
}
}
int main(int argc, char * argv[])
{
IOGDiagnose * report = NULL;
kern_return_t kr = kIOReturnNoMemory;
int exitValue = EXIT_SUCCESS;
bool bDumpToFile = false;
bool bBinaryToFile = false;
int optsIndex = 0;
int option = 0;
char inputFilename[256]={0};
static struct option opts[] = {
"file", optional_argument, NULL, 'f',
"binary", required_argument, NULL, 'b',
NULL, no_argument, NULL, 0,
};
const char * argKeys = "fb:";
if (argc > 1) {
option = getopt_long(argc, argv, argKeys, opts, &optsIndex);
if (-1 == option) {
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
do {
switch (option) {
case 'f': {
bDumpToFile = true;
break;
}
case 'b': {
if (optarg != NULL) {
const size_t len = strlen(optarg);
if (len <= sizeof(inputFilename)) {
strncpy(inputFilename, optarg, len);
bBinaryToFile = true;
}
}
break;
}
default: {
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
}
option = getopt_long(argc, argv, argKeys, opts, &optsIndex);
} while (-1 != option);
}
size_t reportLength = sizeof(IOGDiagnose);
report = (IOGDiagnose *)malloc(reportLength);
if (NULL != report) {
const char *error = NULL;
kr = iogDiagnose(report,
reportLength,
IOGRAPHICS_DIAGNOSE_VERSION,
&error);
if (kIOReturnSuccess != kr) {
fprintf(stderr, "%s %s (%#x)\n",
error, mach_error_string(kr), kr);
}
} else {
fprintf(stderr, "IOGDiagnose malloc failed %s (%#x)\n",
mach_error_string(kr), kr);
}
if (kIOReturnSuccess == kr) {
if (bBinaryToFile) {
writeGTraceBinary( report, inputFilename );
} else {
dumpGTraceReport( report, bDumpToFile );
}
} else {
exitValue = EXIT_FAILURE;
}
if (NULL != report) {
free(report);
}
exit(exitValue);
}