#include <cassert>
#include <cstdlib>
#include <cstdint>
#include <cstdio>
#include <ctime>
#include <string>
#include <vector>
extern char **environ;
#include <mach/mach_error.h>
#include <mach/mach_time.h>
#include <getopt.h>
#include <spawn.h>
#include <sys/wait.h>
#include "IOGDiagnoseUtils/iokit"
#include "IOGDiagnoseUtils/IOGDiagnoseUtils.hpp"
#include "GTrace/GTraceTypes.hpp"
#define kFILENAME_LENGTH 64
using std::string;
using std::vector;
namespace {
void print_usage(const char* name)
{
fprintf(stdout, "%s options:\n", name);
fprintf(stdout, "\t--file | -f\n");
fprintf(stdout, "\t\tWrite diag 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);
}
void foutsep(FILE* outfile, const string title)
{
static const char kSep[] = "---------------------------------------------------------------------------------------";
static const int kSepLen = static_cast<int>(sizeof(kSep) - 1);
if (title.empty()) {
fprintf(outfile, "\n%s\n", kSep);
return;
}
const int titlelen = static_cast<int>(title.length()) + 2;
assert(titlelen < kSepLen);
const int remainsep = kSepLen - titlelen;
const int prefixsep = remainsep / 2;
const int sufficsep = prefixsep + (remainsep & 1);
fprintf(outfile, "%.*s %s %.*s\n\n",
prefixsep, kSep, title.c_str(), sufficsep, kSep);
}
inline void foutsep(FILE* outfile) { foutsep(outfile, string()); }
string stringf(const char *fmt, ...)
{
va_list args;
char buffer[1024];
va_start(args, fmt);
const auto len = vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
assert(len < sizeof(buffer)); (void) len;
return buffer;
}
string formatEntry(const int currentLine, const GTraceEntry& entry)
{
return stringf(
"\t\tTkn: %06u\tTS: %llu\tLn: %u\tC: %#llx\tCTID: %u-%#llx\t"
"OID: %#llx\tTag: %#llx\tA: %#llx-%#llx-%#llx-%#llx\n",
currentLine, entry.timestamp(), entry.line(), entry.component(),
entry.cpu(), entry.threadID(), entry.registryID(),
entry.tag(), entry.arg64(0), entry.arg64(1),
entry.arg64(2), entry.arg64(3));
}
void agdcdiagnose(FILE * outfile)
{
#define AGCKEXT "/System/Library/Extensions/AppleGraphicsControl.kext/"
#define BNDLBINDIR "Contents/MacOS/"
#define AGDCDIAGEXEC "AGDCDiagnose"
static const char* kDiagArgs[]
= { AGCKEXT BNDLBINDIR AGDCDIAGEXEC, "-a", nullptr };
if (static_cast<bool>(outfile)) {
pid_t pid = 0;
foutsep(outfile, "AGDC REPORT"); fflush(outfile);
auto agdcArgs = const_cast<char* const *>(kDiagArgs);
int status = posix_spawn(&pid, kDiagArgs[0], nullptr, nullptr,
agdcArgs, environ);
if (0 == status)
(void) waitpid(pid, &status, 0);
else
fprintf(outfile, "\tAGDCDiagnose failed to launch\n");
foutsep(outfile);
}
}
void dumpTokenBuffer(FILE* outfile, const vector<GTraceBuffer>& gtraces)
{
unsigned currentLine = 0;
long total_lines = 1; for (const GTraceBuffer& buffer : gtraces)
total_lines += buffer.vec().size();
if (!total_lines) {
fprintf(outfile, "-1 (No Token Data Recorded)\n\n");
return;
}
const GTraceEntry magic(gtraces.size(), GTRACE_REVISION);
string line = formatEntry(currentLine++, magic);
fputs("\n\n", outfile);
fprintf(outfile, "Token Buffers Recorded: %d\n",
static_cast<int>(gtraces.size()));
fprintf(outfile, "Token Buffer Lines : %ld\n", total_lines);
fprintf(outfile, "Token Buffer Size : %ld\n",
total_lines * kGTraceEntrySize);
fprintf(outfile, "Token Buffer Data :\n");
fputs(line.c_str(), outfile);
for (const GTraceBuffer& buffer : gtraces) {
for (const GTraceEntry& entry : buffer.vec()) {
line = formatEntry(currentLine++, entry);
fputs(line.c_str(), outfile);
}
}
fputc('\n', outfile);
}
void writeGTraceBinary(const vector<GTraceBuffer>& gtraces,
const char* filename)
{
if (gtraces.empty()) {
fprintf(stdout, "iogdiagnose: No GTrace data\n");
return;
}
FILE* fp = fopen(filename, "w");
if (!static_cast<bool>(fp)) {
fprintf(stdout,
"iogdiagnose: Failed to open %s for write access\n", filename);
return;
}
const GTraceEntry magic(gtraces.size(), GTRACE_REVISION);
fwrite(&magic, sizeof(magic), 1, fp); for (const GTraceBuffer& buf : gtraces)
fwrite(buf.data(), sizeof(*buf.data()), buf.size(), fp);
fclose(fp);
}
inline int bitIsSet(const uint32_t value, const uint32_t bit)
{ return static_cast<bool>(value & bit); }
inline int isStateSet(const IOGReport& fbState, uint32_t bit)
{ return bitIsSet(fbState.stateBits, bit); }
inline mach_timebase_info_data_t getMachTimebaseInfo()
{
mach_timebase_info_data_t ret = { 0 };
mach_timebase_info(&ret);
return ret;
}
inline tm getTime()
{
tm ret;
time_t rawtime = 0; time(&rawtime);
localtime_r(&rawtime, &ret);
return ret;
}
void dumpGTraceReport(const IOGDiagnose& diag,
const vector<GTraceBuffer>& gtraces,
const bool bDumpToFile)
{
if (diag.version < 7) {
fprintf(stderr, "iogdiagnose: Can't read pre v7 IOGDiagnose reports\n");
return;
}
const mach_timebase_info_data_t info = getMachTimebaseInfo();
const tm timeinfo = getTime();
char filename[kFILENAME_LENGTH] = {0};
strftime(filename, sizeof(filename),
"/tmp/com.apple.iokit.IOGraphics_%F_%H-%M-%S.txt", &timeinfo);
FILE* const fp = (bDumpToFile) ? fopen(filename, "w+") : nullptr;
FILE* const outfile = (static_cast<bool>(fp)) ? fp : stdout;
foutsep(outfile, "IOGRAPHICS REPORT");
strftime(filename, sizeof(filename), "Report date: %F %H:%M:%S", &timeinfo);
fprintf(outfile, "%s\n", filename);
fprintf(outfile, "Report version: %#llx\n", diag.version);
fprintf(outfile, "Boot Epoch Time: %llu\n", diag.systemBootEpochTime);
fprintf(outfile, "Number of framebuffers: %llu\n\n", diag.framebufferCount);
for (uint32_t i = 0; i < diag.framebufferCount; i++) {
const IOGReport& fbState = diag.fbState[i];
fprintf(outfile, "\t%s::",
(*fbState.objectName) ? fbState.objectName : "UNKNOWN::");
fputs((*fbState.framebufferName) ? fbState.framebufferName : "IOFB?",
outfile);
fprintf(outfile,
" (%u %#llx)\n", fbState.dependentIndex, fbState.regID);
if (fbState.aliasID)
fprintf(outfile, "\t\tAlias : 0x%08x\n", fbState.aliasID);
fprintf(outfile, "\t\tI-State : 0x%08x (b", fbState.stateBits);
for (uint32_t mask = 0x80000000; 0 != mask; mask >>= 1)
fprintf(outfile, "%d", isStateSet(fbState, mask));
fprintf(outfile, ")\n");
fprintf(outfile, "\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(outfile, "\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",
isStateSet(fbState, kIOGReportState_Opened),
isStateSet(fbState, kIOGReportState_Online),
isStateSet(fbState, kIOGReportState_Usable),
isStateSet(fbState, kIOGReportState_Paging),
isStateSet(fbState, kIOGReportState_Clamshell),
isStateSet(fbState, kIOGReportState_ClamshellCurrent),
isStateSet(fbState, kIOGReportState_ClamshellLast),
isStateSet(fbState, kIOGReportState_ClamshellOffline),
isStateSet(fbState, kIOGReportState_SystemDark),
isStateSet(fbState, kIOGReportState_Mirrored),
isStateSet(fbState, kIOGReportState_SystemGated),
isStateSet(fbState, kIOGReportState_WorkloopGated),
isStateSet(fbState, kIOGReportState_NotificationActive),
isStateSet(fbState, kIOGReportState_ServerNotified),
isStateSet(fbState, kIOGReportState_ServerState),
isStateSet(fbState, kIOGReportState_ServerPendingAck),
isStateSet(fbState, kIOGReportState_PowerPendingChange),
isStateSet(fbState, kIOGReportState_SystemPowerAckTo),
isStateSet(fbState, kIOGReportState_IsMuxSwitching),
isStateSet(fbState, kIOGReportState_PowerPendingMuxChange),
isStateSet(fbState, kIOGReportState_Muted),
fbState.pendingPowerState,
fbState.notificationGroup,
fbState.wsaaState);
fprintf(outfile, "\t\tA-State : 0x%08x (b", fbState.externalAPIState);
for (uint32_t mask = 0x80000000; 0 != mask; mask >>= 1)
fprintf(outfile, "%d", bitIsSet(fbState.externalAPIState, mask));
fprintf(outfile, ")\n");
fprintf(outfile, "\t\tMode ID : %#x\n", fbState.lastSuccessfulMode);
fprintf(outfile, "\t\tSystem : %llu (%#llx) (%u)\n",
fbState.systemOwner, fbState.systemOwner,
fbState.systemGatedCount);
fprintf(outfile, "\t\tController: %llu (%#llx) (%u)\n",
fbState.workloopOwner, fbState.workloopOwner,
fbState.workloopGatedCount);
for (int gi = 0; gi < IOGRAPHICS_MAXIMUM_REPORTS; ++gi) {
const auto& group = fbState.notifications[gi];
if (!group.groupID)
continue;
fprintf(outfile, "\n\t\tGroup ID: %#llx\n", group.groupID - 1);
for (int si = 0; si < IOGRAPHICS_MAXIMUM_REPORTS; ++si) {
const auto& stamp = group.stamp[si];
if (stamp.lastEvent || stamp.start || stamp.end || *stamp.name)
{
fprintf(outfile, "\t\t\tComponent : %s\n", stamp.name);
fprintf(outfile, "\t\t\tLast Event : %u (%#x)\n",
stamp.lastEvent, stamp.lastEvent);
fprintf(outfile, "\t\t\tStamp Start : %#llx\n", stamp.start);
fprintf(outfile, "\t\t\tStamp End : %#llx\n", stamp.end);
fprintf(outfile, "\t\t\tStamp Delta : ");
if (stamp.start <= stamp.end) {
fprintf(outfile, "%llu ns\n",
((stamp.end - stamp.start)
* static_cast<uint64_t>(info.numer))
/ static_cast<uint64_t>(info.denom));
} else
fprintf(outfile, "Notifier Active\n");
}
}
}
}
dumpTokenBuffer(outfile, gtraces);
fflush(outfile);
foutsep(outfile);
agdcdiagnose(outfile);
fflush(outfile);
if (static_cast<bool>(fp))
fclose(fp);
}
};
int main(int argc, char * argv[])
{
bool bDumpToFile = false;
bool bBinaryToFile = false;
int flagInd = 0;
int flag = 0;
char inputFilename[256] = { 0 };
static struct option opts[] = {
"file", optional_argument, nullptr, 'f',
"binary", required_argument, nullptr, 'b',
nullptr, no_argument, nullptr, 0 ,
};
const char* argKeys = "fb:";
while (-1 != (flag = getopt_long(argc, argv, argKeys, opts, &flagInd)) ) {
switch (flag) {
case 'f':
bDumpToFile = true;
break;
case 'b':
if (optarg != nullptr) {
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);
}
}
int exitValue = EXIT_SUCCESS;
char *error = nullptr;
IOConnect dc; kern_return_t err = openDiagnostics(&dc, &error);
if (err) {
fprintf(stderr, "%s %s (%#x)\n", error, mach_error_string(err), err);
exit(EXIT_FAILURE);
}
vector<GTraceBuffer> gtraces;
if (bBinaryToFile) {
err = fetchGTraceBuffers(dc, >races);
if (err)
exitValue = EXIT_FAILURE; else
writeGTraceBinary(gtraces, inputFilename);
exit(exitValue);
}
IOGDiagnose report = { 0 };
err = iogDiagnose(dc, &report, sizeof(report), >races, &error);
if (err) {
fprintf(stderr, "%s %s (%#x)\n", error, mach_error_string(err), err);
exitValue = EXIT_FAILURE;
}
else
dumpGTraceReport(report, gtraces, bDumpToFile);
return exitValue;
}