singrdk/base/Windows/singx86/log.cpp

921 lines
30 KiB
C++

/////////////////////////////////////////////////////////////////////////////
//
// log.cpp - Extension to find parse Singularity trace log.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
#include "singx86.h"
bool fCachedLogStateValid = false;
static ULONG64 c_txtBegin = 0;
static ULONG64 c_txtLimit = 0;
static ULONG64 c_txtHead = 0;
static ULONG64 c_logBegin = 0;
static ULONG64 c_logLimit = 0;
static ULONG64 c_logHead = 0;
static LONG c_logHeadPos = 0;
static LONG c_logCount = 0;
static ULONG64 c_tscOffsets = 0;
static bool difftsc = true;
static ULONG64 tsc = 0;
static bool filter_by_cpu = false;
static bool filter_by_process = false;
static bool filter_by_thread = false;
static bool filter_by_tag = false;
static int filter_by_class = 0;
static int filter_by_not_class = 0;
static int filter_by_method = false;
static int filter_by_not_method = false;
static bool filter_by_severity = false;
static LONG filter_range_low = 0;
static LONG filter_range_high = 0;
static int filter_cpu = 0;
static int filter_process = 0;
static int filter_thread = 0;
static int filter_tag = 0;
static char filter_class[16][128];
static int filter_class_wild[16];
static bool filter_class_not[16];
static char filter_method[16][128];
static int filter_method_wild[16];
static bool filter_method_not[16];
static int filter_severity = 0;
static char szSymbol[512];
static char *pszSymbol;
static char *pszClass;
static char *pszMethod;
//////////////////////////////////////////////////////////////////////////////
//
static HRESULT Usage()
{
ExtOut("Usage:\n"
" !log {options} {entry}\n"
" !log clear\n"
"Options:\n"
" -a : Print all matching entries.\n"
" -c class : Filter by class (can use tail wild card like `cl*').\n"
" -nc class : Filter out class (can use tail wild card).\n"
" -m method : Filter by method (can use tail wild card).\n"
" -nc method : Filter out method (can use tail wild card).\n"
" -p pid : Filter by process ID.\n"
" -t tid : Filter by thread ID.\n"
" -u cpuid : Filter by cpu ID.\n"
" -x tag : Filter by tag.\n"
" -s severity : Filter by severity.\n"
" -r begin[,number] : Filter by range of entry ids.\n"
" -n [skip,]number : Print at most `number' entries (default is 10).\n"
" -f : Display full timespaces instead of indexes and diffs.\n"
" -q : Quiet, not class and method.\n"
"Examples:\n"
" !log -c timer* -d : Print last entries from timer* class with diffs.\n"
" !log -a -c timer* -c irq* : Print all timer or irq entries.\n"
" !log -n 15 -p 020 : Print last 15 entries for process 020.\n"
" !log -n 5,10 : Print the first 10 of the last 15.\n"
" !log -c t* -nc ti* : Print class entries starting with t, but not ti.\n"
" !log clear : Clear the log.\n"
"Notes:\n"
" Log index numbers count backwards in time from 0.\n"
" 0 is the most recent log entry, 1 the entry before that, etc.\n"
" The -c and -m filters are applied first, followed by -nc and -nm filters.\n"
" String and method name comparisons are case insensitive.\n"
);
return S_FALSE;
}
HRESULT DumpLogHead(bool detail)
{
if (detail) {
ExtOut(" Cycle Count: CPU EIP PID TID TAG S Class.Method : Message\n");
}
else {
ExtOut(" Cycle Count: CPU EIP PID TID TAG S: Message\n");
}
return S_OK;
}
HRESULT FindBound(const char *symbol, ULONG64 *ptrval)
{
HRESULT status = S_OK;
ULONG64 address;
EXT_CHECK(g_ExtSymbols->GetOffsetByName(symbol, &address));
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, address, ptrval));
ExtVerb("Find(%s) = %p\n", symbol, *ptrval);
Exit:
return status;
}
HRESULT SetBound(const char *symbol, ULONG64 ptrval)
{
HRESULT status = S_OK;
ULONG64 address;
EXT_CHECK(g_ExtSymbols->GetOffsetByName(symbol, &address));
EXT_CHECK(g_ExtData->WritePointersVirtual(1, address, &ptrval));
Exit:
return status;
}
HRESULT FindBounds()
{
HRESULT status = S_OK;
if (!fCachedLogStateValid) {
EXT_CHECK(FindBound("nt!Class_Microsoft_Singularity_Tracing::c_txtBegin",
&c_txtBegin));
EXT_CHECK(FindBound("nt!Class_Microsoft_Singularity_Tracing::c_txtLimit",
&c_txtLimit));
EXT_CHECK(FindBound("nt!Class_Microsoft_Singularity_Tracing::c_txtHead",
&c_txtHead));
EXT_CHECK(FindBound("nt!Class_Microsoft_Singularity_Tracing::c_logBegin",
&c_logBegin));
EXT_CHECK(FindBound("nt!Class_Microsoft_Singularity_Tracing::c_logLimit",
&c_logLimit));
EXT_CHECK(FindBound("nt!Class_Microsoft_Singularity_Tracing::c_logHead",
&c_logHead));
EXT_CHECK(FindBound("nt!Class_Microsoft_Singularity_Tracing::c_logHead",
&c_logHead));
EXT_CHECK(FindBound("nt!Class_Microsoft_Singularity_Tracing::c_tscOffsets",
&c_tscOffsets));
c_logHeadPos = (LONG)((c_logHead - c_logBegin) / LogEntryStruct.size);
c_logCount = (LONG)((c_logLimit - c_logBegin) / LogEntryStruct.size);
fCachedLogStateValid = true;
status = S_OK;
}
Exit:
return status;
}
static LONG64 GetTscOffset(int cpuId, PLONG64 pValue)
{
HRESULT status = S_OK;
ULONG64 address = c_tscOffsets + cpuId * sizeof(LONG64);
EXT_CHECK(g_ExtData->ReadPointersVirtual(1, address, (PULONG64)pValue));
// ExtOut("Cpu %d tscOffset %I64d\n", cpuId, *pValue);
Exit:
return status;
}
static ULONG64 IdToEntry(LONG id)
{
LONG pos = (c_logHeadPos + (c_logCount - (id + 1))) % c_logCount;
ULONG64 entry = c_logBegin + pos * LogEntryStruct.size;
return entry;
}
static LONG EntryToId(ULONG64 entry)
{
LONG pos = (ULONG)((entry - c_logBegin) / LogEntryStruct.size);
return ((c_logCount - (pos + 1)) + c_logHeadPos) % c_logCount;
}
HRESULT FilterLogEntry(LONG id, LogEntry& log, bool detail)
{
pszSymbol = NULL;
pszClass = NULL;
pszMethod = NULL;
HRESULT status = S_OK;
if ((ULONG64)log._text < c_txtBegin || (ULONG64)log._text > c_txtLimit) {
return S_FALSE;
}
if (id < filter_range_low || id >= filter_range_high) {
return S_FALSE;
}
if (filter_by_cpu && log._cpuId != filter_cpu) {
return S_FALSE;
}
if (filter_by_process && log._processId != filter_process) {
return S_FALSE;
}
if (filter_by_thread && log._threadId != filter_thread) {
return S_FALSE;
}
if (filter_by_tag && log._tag != filter_tag) {
return S_FALSE;
}
if (filter_by_severity && log._severity < filter_process) {
return S_FALSE;
}
if (filter_by_class > 0 || filter_by_method > 0 || detail) {
ULONG64 displacement = 0;
status = g_ExtSymbols->GetNameByOffset((ULONG64)log._eip,
szSymbol,
arrayof(szSymbol),
NULL,
&displacement);
if (status == S_OK) {
CHAR *p;
pszSymbol = szSymbol;
if ((p = strrchr(szSymbol, '!')) != NULL) {
pszSymbol = p + 1;
}
if ((p = strchr(szSymbol, ':')) != NULL && p[1] == ':') {
pszClass = pszSymbol;
pszMethod = p + 2;
*p = '\0';
if ((p = strrchr(pszClass, '_')) != NULL) {
pszClass = p + 1;
}
if ((p = strrchr(pszMethod, '_')) != NULL) {
pszMethod = p + 1;
}
}
}
PCHAR pszC = pszClass != NULL ? pszClass : (pszSymbol != NULL ? pszSymbol : "::");
PCHAR pszM = pszMethod ? pszMethod : "::";
// Do inclusive filters first.
if (filter_by_class > filter_by_not_class) {
bool found = false;
for (int i = 0; i < filter_by_class; i++) {
if (filter_class_not[i]) {
continue;
}
if (filter_class_wild[i] > 0) {
if (_strnicmp(filter_class[i], pszC, filter_class_wild[i]) == 0) {
found = true;
break;
}
}
else {
if (_strcmpi(filter_class[i], pszC) == 0) {
found = true;
break;
}
}
}
if (!found) {
return S_FALSE;
}
}
if (filter_by_method > filter_by_not_method) {
if (pszMethod == NULL) {
return S_FALSE;
}
bool found = false;
for (int i = 0; i < filter_by_method; i++) {
if (filter_method_not[i]) {
continue;
}
if (filter_method_wild[i] > 0) {
if (_strnicmp(filter_method[i], pszM, filter_method_wild[i]) == 0) {
found = true;
break;
}
}
else {
if (_strcmpi(filter_method[i], pszM) == 0) {
found = true;
break;
}
}
}
if (!found) {
return S_FALSE;
}
}
// Then do exclusive filters.
if (filter_by_not_class > 0) {
for (int i = 0; i < filter_by_class; i++) {
if (!filter_class_not[i]) {
continue;
}
if (filter_class_wild[i] > 0) {
if (_strnicmp(filter_class[i], pszC, filter_class_wild[i]) == 0) {
return S_FALSE;
}
}
else {
if (_strcmpi(filter_class[i], pszC) == 0) {
return S_FALSE;
}
}
}
}
if (filter_by_not_method > 0) {
for (int i = 0; i < filter_by_method; i++) {
if (!filter_method_not[i]) {
continue;
}
if (filter_method_wild[i] > 0) {
if (_strnicmp(filter_method[i], pszM, filter_method_wild[i]) == 0) {
return S_FALSE;
}
}
else {
if (_strcmpi(filter_method[i], pszM) == 0) {
return S_FALSE;
}
}
}
}
}
return S_OK;
}
static PCHAR Skip(PCHAR psz)
{
while (*psz) {
psz++;
}
return psz + 1;
}
HRESULT DumpLogEntry(LONG id, LogEntry& log, bool detail)
{
HRESULT status = S_OK;
CHAR msg[512];
BYTE text[256];
ULONG ret;
EXT_CHECK(g_ExtData->ReadVirtual((ULONG64)log._text, text, sizeof(text), &ret));
if (text[0] != (BYTE)log._cycleCount) {
sprintf(msg, "<%p: message not available>", log._text);
}
else {
CHAR * pmsg = msg;
CHAR * ptxt = (CHAR *)(text + 1);
CHAR * sargs[6];
CHAR * pstr = Skip(ptxt);
for (int i = 0 ; i < arrayof(sargs); i++) {
if ((log._strings & ((ULONG64)1 << i)) != 0) {
sargs[i] = pstr;
pstr = Skip(pstr);
}
else {
sargs[i] = NULL;
}
}
while (*ptxt != '\0') {
if (*ptxt == '{') {
char * pbeg = ptxt;
bool bad = false;
ptxt++;
int ndx = 0;
int aln = 0;
int wid = 0;
char fmt = 'd';
if (*ptxt == '{') {
*pmsg++ = *ptxt++;
}
else if (*ptxt >= '0' && *ptxt <= '9') {
// {index,alignment:type width}
// Get Index
while (*ptxt >= '0' && *ptxt <= '9') {
ndx = ndx * 10 + (*ptxt++ - '0');
}
// Get Alignment
if (*ptxt == ',') {
ptxt++;
while (*ptxt >= '0' && *ptxt <= '9') {
aln = aln * 10 + (*ptxt++ - '0');
}
}
// Get FormatString
if (*ptxt == ':') {
ptxt++;
if (*ptxt >= 'a' && *ptxt <= 'z') {
fmt = *ptxt++;
}
else if (*ptxt >= 'A' && *ptxt <= 'Z') {
fmt = *ptxt++ - 'A' + 'a';
}
while (*ptxt >= '0' && *ptxt <= '9') {
wid = wid * 10 + (*ptxt++ - '0');
}
}
// Get closing brace.
if (*ptxt == '}') {
ptxt++;
}
else {
bad = true;
ExtErr("Missing closing brace: `%s'\n", ptxt);
}
if (ndx >= arrayof(sargs)) {
bad = true;
ExtErr("Aln too large: %d\n", ndx);
}
if (sargs[ndx] != NULL) {
if (wid > 0) {
if (aln < wid) {
aln = wid;
}
pmsg += sprintf(pmsg, "%*.*s", aln, wid, sargs[ndx]);
}
else {
pmsg += sprintf(pmsg, "%*s", aln, sargs[ndx]);
}
}
else {
if (aln < wid) {
aln = wid;
}
if (fmt == 'x') {
if (wid > 0) {
pmsg += sprintf(pmsg, "%0*x", aln, log._args[ndx]);
}
else {
pmsg += sprintf(pmsg, "%*x", aln, log._args[ndx]);
}
}
else {
pmsg += sprintf(pmsg, "%*d", aln, log._args[ndx]);
}
}
// If the format was bad, then copy it.
if (bad) {
ExtErr("Format error: `%*s'\n", ptxt - pbeg, pbeg);
while (pbeg < ptxt) {
*pmsg++ = *pbeg++;
}
}
}
}
else if (*ptxt == '}') {
ptxt++;
*pmsg++ = *ptxt++;
}
else if (*ptxt == '\n') {
ptxt++;
}
else {
*pmsg++ = *ptxt++;
}
}
*pmsg++ = '\0';
}
LONG64 tscOffset = 0;
if (GetTscOffset((int)log._cpuId, &tscOffset) != S_OK) {
tscOffset = 0;
}
ULONG64 cycleCount = log._cycleCount - tscOffset;
if (difftsc) {
if (tsc == 0) {
ExtOut("%16I64x: ", cycleCount);
}
else {
ExtOut("%5d: %9I64x+ ", id, cycleCount - tsc);
}
}
else {
ExtOut("%16I64x: ", cycleCount);
}
tsc = cycleCount;
if (detail) {
CHAR name[512] = "";
if (pszClass != NULL && pszMethod != NULL) {
sprintf(name, "%s.%s", pszClass, pszMethod);
}
else if (pszSymbol != NULL) {
strcat(name, pszSymbol);
}
ExtOut("%03x %p %03x %03x %03x %x %-25.25s: %s\n",
(ULONG)log._cpuId,
log._eip,
(ULONG)log._processId,
(ULONG)log._threadId,
(ULONG)log._tag,
(ULONG)log._severity,
name,
msg);
}
else {
ExtOut("%03x %p %03x %03x %03x %x: %s\n",
(ULONG)log._cpuId,
log._eip,
(ULONG)log._processId,
(ULONG)log._threadId,
(ULONG)log._tag,
(ULONG)log._severity,
msg);
}
status = S_OK;
Exit:
return status;
}
HRESULT ClearLog()
{
HRESULT status = S_OK;
ExtOut("Clearing %p..%p\n", c_logBegin, c_logLimit);
LogEntryStruct.Clear();
for (ULONG64 entry = c_logBegin; entry < c_logLimit; entry += LogEntryStruct.size) {
if ((entry & 0xff) == 0) {
ExtOut(" %p\r", entry);
}
EXT_CHECK(LogEntryStruct.Flush(entry));
}
ExtOut("\n");
EXT_CHECK(SetBound("nt!Class_Microsoft_Singularity_Tracing::c_txtHead", c_txtBegin));
EXT_CHECK(SetBound("nt!Class_Microsoft_Singularity_Tracing::c_logHead", c_logBegin));
Exit:
return status;
}
static ULONG64 GetValue(PCSTR& args, bool fHex)
{
ULONG base = fHex ? 16 : 10;
if (*args == '0') {
fHex = true;
base = 16;
}
ULONG64 value = 0;
while (*args && *args != ' ' && *args != '\t') {
if (*args >= '0' && *args <= '9') {
value = value * base + (*args++ - '0');
}
else if (*args >= 'A' && *args <= 'F' && fHex) {
value = value * base + (*args++ - 'A') + 10;
}
else if (*args >= 'a' && *args <= 'f' && fHex) {
value = value * base + (*args++ - 'a') + 10;
}
else {
break;
}
}
return value;
}
EXT_DECL(log) // Defines: PDEBUG_CLIENT Client, PCSTR args
{
EXT_ENTER(); // Defines: HRESULT status = S_OK;
bool detail = true;
bool doall = false;
status = FindBounds();
#if TEST_ENTRY
if (fCachedLogStateValid) {
for (LONG id = 0; id < 10; id++) {
ULONG64 entry = IdToEntry(id);
ULONG bid = EntryToId(entry);
ExtOut("%4d: %p :%4d\n", id, entry, bid);
}
for (LONG id = c_logCount - 10; id < c_logCount; id++) {
ULONG64 entry = IdToEntry(id);
ULONG bid = EntryToId(entry);
ExtOut("%4d: %p :%4d\n", id, entry, bid);
}
goto Exit;
}
#endif
difftsc = true;
filter_range_low = 0;
filter_range_high = c_logCount;
filter_by_cpu = false;
filter_by_process = false;
filter_by_thread = false;
filter_by_tag = false;
filter_by_class = 0;
filter_by_not_class = 0;
filter_by_method = 0;
filter_by_not_method = 0;
filter_by_severity = false;
filter_cpu = 0;
filter_process = 0;
filter_thread = 0;
filter_tag = 0;
filter_severity = 0;
int limit = 10;
int skip = 0;
char *p;
tsc = 0;
if (_strcmpi(args, "clear") == 0) {
return ClearLog();
}
while (*args != '\0') {
// skip whitespace
while (*args == ' ' || *args == '\t') {
args++;
}
// process argument
if (*args == '-' || *args == '/') {
args++;
switch (*args++) {
case 'q': // Quiet
case 'Q':
detail = false;
break;
case 'f': // Full timestamps, not diffs (not absolute value)
case 'F':
difftsc = false;
break;
case 'a': // All records
case 'A':
doall = true;
limit = 100000;
break;
case 'n':
case 'N':
if (*args == 'c') {
// Not class
args++;
while (*args == ' ' || *args == '\t') {
args++;
}
filter_class[filter_by_class][0] = '\0';
filter_class_wild[filter_by_class] = 0;
filter_class_not[filter_by_class] = true;
p = filter_class[filter_by_class];
while (*args && *args != ' ' && *args != '\t') {
*p++ = *args++;
}
*p = '\0';
if ((p = strchr(filter_class[filter_by_class], '*')) != NULL) {
filter_class_wild[filter_by_class] = p - filter_class[filter_by_class];
}
ExtVerb("Filter out class: `%s'\n", filter_class[filter_by_class]);
filter_by_class++;
filter_by_not_class++;
}
else if (*args == 'm') {
// Not method
args++;
while (*args == ' ' || *args == '\t') {
args++;
}
filter_method[filter_by_method][0] = '\0';
filter_method_wild[filter_by_method] = 0;
filter_method_not[filter_by_method] = true;
p = filter_method[filter_by_method];
while (*args && *args != ' ' && *args != '\t') {
*p++ = *args++;
}
*p = '\0';
if ((p = strchr(filter_method[filter_by_method], '*')) != NULL) {
filter_method_wild[filter_by_method] = p - filter_method[filter_by_method];
}
ExtVerb("Filter not method: `%s'\n", filter_method[filter_by_method]);
filter_by_method++;
filter_by_not_method++;
}
else {
// Number of entries to display
while (*args == ' ' || *args == '\t') {
args++;
}
limit = (int)GetValue(args, false);
if (*args == ',') {
skip = limit;
args++;
limit = (int)GetValue(args, false);
}
ExtVerb("Limit to: %d (skip %d)\n", limit, skip);
}
break;
case 'r': // filter by range
case 'R':
while (*args == ' ' || *args == '\t') {
args++;
}
filter_range_low = (LONG)GetValue(args, false);
if (filter_range_low < 0) {
filter_range_low = 0;
}
if (filter_range_low >= c_logCount) {
filter_range_low = c_logCount - 1;
}
if (*args == ',') {
args++;
filter_range_high = filter_range_low + (LONG)GetValue(args, false);
if (filter_range_high > c_logCount) {
filter_range_high = c_logCount;
}
}
ExtVerb("Range %d..%d\n", filter_range_low, filter_range_high);
break;
case 'u': // filter by cpu
case 'U':
filter_by_cpu = true;
while (*args == ' ' || *args == '\t') {
args++;
}
filter_cpu = (int)GetValue(args, true);
ExtVerb("Filter by cpu: 0x%x\n", filter_cpu);
break;
case 'p': // filter by process
case 'P':
filter_by_process = true;
while (*args == ' ' || *args == '\t') {
args++;
}
filter_process = (int)GetValue(args, true);
ExtVerb("Filter by process: 0x%x\n", filter_process);
break;
case 't': // filter by thread
case 'T':
filter_by_thread = true;
while (*args == ' ' || *args == '\t') {
args++;
}
filter_thread = (int)GetValue(args, true);
ExtVerb("Filter by tid: 0x%x\n", filter_thread);
break;
case 'x': // filter by tag
case 'X':
filter_by_tag = true;
while (*args == ' ' || *args == '\t') {
args++;
}
filter_tag = (int)GetValue(args, true);
ExtVerb("Filter by tag: 0x%x\n", filter_tag);
break;
case 'c': // filter by class
case 'C':
while (*args == ' ' || *args == '\t') {
args++;
}
filter_class[filter_by_class][0] = '\0';
filter_class_wild[filter_by_class] = 0;
filter_class_not[filter_by_class] = false;
p = filter_class[filter_by_class];
while (*args && *args != ' ' && *args != '\t') {
*p++ = *args++;
}
*p = '\0';
if ((p = strchr(filter_class[filter_by_class], '*')) != NULL) {
filter_class_wild[filter_by_class] = p - filter_class[filter_by_class];
}
ExtVerb("Filter by class: `%s'\n", filter_class[filter_by_class]);
filter_by_class++;
break;
case 'm': // filter by method
case 'M':
while (*args == ' ' || *args == '\t') {
args++;
}
filter_method[filter_by_method][0] = '\0';
filter_method_wild[filter_by_method] = 0;
filter_method_not[filter_by_method] = false;
p = filter_method[filter_by_method];
while (*args && *args != ' ' && *args != '\t') {
*p++ = *args++;
}
*p = '\0';
if ((p = strchr(filter_method[filter_by_method], '*')) != NULL) {
filter_method_wild[filter_by_method] = p - filter_method[filter_by_method];
}
ExtVerb("Filter by method: `%s'\n", filter_method[filter_by_method]);
filter_by_method++;
break;
case 's': // filter by severity
case 'S':
filter_by_severity = true;
while (*args == ' ' || *args == '\t') {
args++;
}
filter_severity = (int)GetValue(args, true);
break;
case '?': // Help
case 'h':
case 'H':
status = Usage();
goto Exit;
}
while (*args && *args != ' ') {
args++;
}
}
else {
break;
}
}
DumpLogHead(detail);
ULONG64 entry = 0;
LogEntry log;
if (*args != '\0') {
status = ExtEvalU64(&args, &entry);
if (status == S_OK) {
if (entry < c_logBegin || entry >= c_logLimit) {
ExtErr("Entry value %p not supported.\n", entry);
return S_FALSE;
}
EXT_CHECK(LogEntryStruct.Read(entry, &log));
LONG id = EntryToId(entry);
status = FilterLogEntry(id, log, detail);
status = DumpLogEntry(id, log, detail);
goto Exit;
}
else {
ExtErr("Invalid argument %s.\n", args);
goto Exit;
}
}
ExtVerb("Range [%d..%d], Skip=%d, Limit=%d\n",
filter_range_low, filter_range_high, skip, limit);
// First, we search backward until we find the right number of records
if (!doall) {
int find = limit + skip;
for (LONG id = filter_range_low; id < filter_range_high; id++) {
entry = IdToEntry(id);
EXT_CHECK(LogEntryStruct.Read(entry, &log));
if (log._eip == 0 || log._cycleCount == 0) {
continue;
}
status = FilterLogEntry(id, log, detail);
if (status == S_OK) {
find--;
if (find < 0) {
filter_range_high = id;
break;
}
}
}
}
ExtVerb("Begin: %p, Head: %p, Limit: %p, Base: %p [%d..%d]\n",
c_logBegin, c_logHead, c_logLimit, entry, filter_range_low, filter_range_high);
for (LONG id = filter_range_high - 1; id >= filter_range_low; id--) {
entry = IdToEntry(id);
EXT_CHECK(LogEntryStruct.Read(entry, &log));
if (log._eip == 0 || log._cycleCount == 0) {
continue;
}
status = FilterLogEntry(id, log, detail);
if (status != S_OK) {
continue;
}
status = DumpLogEntry(id, log, detail);
limit--;
if (limit <= 0) {
break;
}
}
EXT_LEAVE(); // Macro includes: return status;
}