More symbol support
This commit is contained in:
parent
3f42a9d85b
commit
1d448f14ee
2 changed files with 269 additions and 141 deletions
|
|
@ -113,7 +113,7 @@ function Debugger(parmsDbg)
|
||||||
* call and simply represents the number of cycles performed by the last run of instructions.
|
* call and simply represents the number of cycles performed by the last run of instructions.
|
||||||
*/
|
*/
|
||||||
this.nCycles = -1;
|
this.nCycles = -1;
|
||||||
this.cInstructions = -1;
|
this.cOpcodes = -1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Default number of hex chars in a register and a linear address (ie, for real-mode);
|
* Default number of hex chars in a register and a linear address (ie, for real-mode);
|
||||||
|
|
@ -627,7 +627,7 @@ if (DEBUGGER) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Debugger.TRACE_LIMIT = 100000;
|
Debugger.TRACE_LIMIT = 100000;
|
||||||
Debugger.HISTORY_LIMIT = 100000;
|
Debugger.HISTORY_LIMIT = DEBUG? 100000 : 10000;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Opcode 0x0F has a distinguished history:
|
* Opcode 0x0F has a distinguished history:
|
||||||
|
|
@ -1963,6 +1963,39 @@ if (DEBUGGER) {
|
||||||
this.dumpBlocks(this.cpu.aBusBlocks, sAddr);
|
this.dumpBlocks(this.cpu.aBusBlocks, sAddr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dumpInfo(sAddr)
|
||||||
|
*
|
||||||
|
* @this {Debugger}
|
||||||
|
* @param {string|undefined} sAddr
|
||||||
|
*/
|
||||||
|
Debugger.prototype.dumpInfo = function(sAddr)
|
||||||
|
{
|
||||||
|
var sInfo = "no information";
|
||||||
|
if (BACKTRACK) {
|
||||||
|
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_CODE, true, true);
|
||||||
|
if (dbgAddr) {
|
||||||
|
var addr = this.getAddr(dbgAddr);
|
||||||
|
sInfo = '%' + str.toHex(addr) + ": " + (this.bus.getSymbol(addr, true) || sInfo);
|
||||||
|
} else {
|
||||||
|
var component, componentPrev = null;
|
||||||
|
while (component = this.cmp.getComponentByType("Disk", componentPrev)) {
|
||||||
|
var aInfo = component.getSymbolInfo(sAddr);
|
||||||
|
if (aInfo.length) {
|
||||||
|
sInfo = "";
|
||||||
|
for (var i in aInfo) {
|
||||||
|
var a = aInfo[i];
|
||||||
|
if (sInfo) sInfo += '\n';
|
||||||
|
sInfo += a[0] + ": " + a[1] + ' ' + str.toHex(a[2], 4) + ':' + str.toHex(a[3], 4) + " len " + str.toHexWord(a[4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentPrev = component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sInfo;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dumpMem(sAddr)
|
* dumpMem(sAddr)
|
||||||
*
|
*
|
||||||
|
|
@ -2061,41 +2094,61 @@ if (DEBUGGER) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dumpHistory(sCount, cLines)
|
* dumpHistory(sPrev, sLines)
|
||||||
|
*
|
||||||
|
* If sLines is not a number, it can be a instruction filter. However, for the moment, the only
|
||||||
|
* supported filter is "call", which filters the history buffer for all CALL and RET instructions
|
||||||
|
* from the specified previous point forward.
|
||||||
*
|
*
|
||||||
* @this {Debugger}
|
* @this {Debugger}
|
||||||
* @param {string} [sCount] is the number of instructions to rewind to (default is 10)
|
* @param {string} [sPrev] is a (decimal) number of instructions to rewind to (default is 10)
|
||||||
* @param {number} [cLines] is the number of instructions to print (default is, again, 10)
|
* @param {string} [sLines] is a (decimal) number of instructions to print (default is, again, 10)
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.dumpHistory = function(sCount, cLines)
|
Debugger.prototype.dumpHistory = function(sPrev, sLines)
|
||||||
{
|
{
|
||||||
var sMore = "";
|
var sMore = "";
|
||||||
cLines = cLines || 10;
|
|
||||||
var cHistory = 0;
|
var cHistory = 0;
|
||||||
var iHistory = this.iOpcodeHistory;
|
var iHistory = this.iOpcodeHistory;
|
||||||
var aHistory = this.aOpcodeHistory;
|
var aHistory = this.aOpcodeHistory;
|
||||||
|
|
||||||
if (aHistory.length) {
|
if (aHistory.length) {
|
||||||
var n = (sCount === undefined? this.nextHistory : +sCount); // warning: decimal instead of hex conversion
|
var nPrev = +sPrev || this.nextHistory;
|
||||||
if (isNaN(n))
|
var nLines = +sLines || 10;
|
||||||
n = cLines;
|
|
||||||
else
|
if (isNaN(nPrev)) {
|
||||||
|
nPrev = nLines;
|
||||||
|
} else {
|
||||||
sMore = "more ";
|
sMore = "more ";
|
||||||
if (n > aHistory.length) {
|
|
||||||
this.println("note: only " + aHistory.length + " available");
|
|
||||||
n = aHistory.length;
|
|
||||||
}
|
}
|
||||||
iHistory -= n;
|
|
||||||
|
if (nPrev > aHistory.length) {
|
||||||
|
this.println("note: only " + aHistory.length + " available");
|
||||||
|
nPrev = aHistory.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
iHistory -= nPrev;
|
||||||
if (iHistory < 0) {
|
if (iHistory < 0) {
|
||||||
if (aHistory[aHistory.length - 1][1] != null) {
|
/*
|
||||||
iHistory += aHistory.length;
|
* If the dbgAddr of the last aHistory element contains a valid selector, wrap around.
|
||||||
} else {
|
*/
|
||||||
n = iHistory + n;
|
if (aHistory[aHistory.length - 1].sel == null) {
|
||||||
|
nPrev = iHistory + nPrev;
|
||||||
iHistory = 0;
|
iHistory = 0;
|
||||||
|
} else {
|
||||||
|
iHistory += aHistory.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sCount !== undefined) {
|
|
||||||
this.println(n + " instructions earlier:");
|
var aFilters = [];
|
||||||
|
if (sLines == "call") {
|
||||||
|
nLines = 100000;
|
||||||
|
aFilters = ["CALL"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sPrev !== undefined) {
|
||||||
|
this.println(nPrev + " instructions earlier:");
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: The following is necessary to prevent dumpHistory() from causing additional (or worse, recursive)
|
* TODO: The following is necessary to prevent dumpHistory() from causing additional (or worse, recursive)
|
||||||
* faults due to segmented addresses that are no longer valid, but the only alternative is to dramatically
|
* faults due to segmented addresses that are no longer valid, but the only alternative is to dramatically
|
||||||
|
|
@ -2109,15 +2162,22 @@ if (DEBUGGER) {
|
||||||
* If you re-enable this protection, be sure to re-enable the decrement below, too.
|
* If you re-enable this protection, be sure to re-enable the decrement below, too.
|
||||||
*/
|
*/
|
||||||
var fData32 = null, fAddr32 = null;
|
var fData32 = null, fAddr32 = null;
|
||||||
while (cLines > 0 && iHistory != this.iOpcodeHistory) {
|
|
||||||
|
while (nLines > 0 && iHistory != this.iOpcodeHistory) {
|
||||||
|
|
||||||
var dbgAddr = aHistory[iHistory++];
|
var dbgAddr = aHistory[iHistory++];
|
||||||
if (dbgAddr.sel == null) break;
|
if (dbgAddr.sel == null) break;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We must create a new dbgAddr from the address in aHistory, because dbgAddr was
|
* We must create a new dbgAddr from the address in aHistory, because dbgAddr was
|
||||||
* a reference, not a copy, and we don't want getInstruction() modifying the original.
|
* a reference, not a copy, and we don't want getInstruction() modifying the original.
|
||||||
*/
|
*/
|
||||||
dbgAddr = this.newAddr(dbgAddr.off, dbgAddr.sel, dbgAddr.addr, dbgAddr.fProt, fData32 == null? dbgAddr.fData32 : fData32, fAddr32 == null? dbgAddr.fAddr32 : fAddr32);
|
dbgAddr = this.newAddr(dbgAddr.off, dbgAddr.sel, dbgAddr.addr, dbgAddr.fProt, fData32 == null? dbgAddr.fData32 : fData32, fAddr32 == null? dbgAddr.fAddr32 : fAddr32);
|
||||||
this.println(this.getInstruction(dbgAddr, "history", n--));
|
var sInstruction = this.getInstruction(dbgAddr, "history", nPrev--);
|
||||||
|
if (!aFilters.length || sInstruction.indexOf(aFilters[0]) >= 0) {
|
||||||
|
this.println(sInstruction);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If there were OPERAND or ADDRESS overrides on the previous instruction, getInstruction()
|
* If there were OPERAND or ADDRESS overrides on the previous instruction, getInstruction()
|
||||||
* will have automatically disassembled additional bytes, so skip additional history entries.
|
* will have automatically disassembled additional bytes, so skip additional history entries.
|
||||||
|
|
@ -2125,13 +2185,14 @@ if (DEBUGGER) {
|
||||||
if (!dbgAddr.cOverrides) {
|
if (!dbgAddr.cOverrides) {
|
||||||
fData32 = fAddr32 = null;
|
fData32 = fAddr32 = null;
|
||||||
} else {
|
} else {
|
||||||
iHistory += dbgAddr.cOverrides; cLines -= dbgAddr.cOverrides; n -= dbgAddr.cOverrides;
|
iHistory += dbgAddr.cOverrides; nLines -= dbgAddr.cOverrides; nPrev -= dbgAddr.cOverrides;
|
||||||
fData32 = dbgAddr.fData32; fAddr32 = dbgAddr.fAddr32;
|
fData32 = dbgAddr.fData32; fAddr32 = dbgAddr.fAddr32;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iHistory >= aHistory.length) iHistory = 0;
|
if (iHistory >= aHistory.length) iHistory = 0;
|
||||||
this.nextHistory = n;
|
this.nextHistory = nPrev;
|
||||||
cHistory++;
|
cHistory++;
|
||||||
cLines--;
|
nLines--;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* See comments above.
|
* See comments above.
|
||||||
|
|
@ -2139,6 +2200,7 @@ if (DEBUGGER) {
|
||||||
* this.nSuppressBreaks--;
|
* this.nSuppressBreaks--;
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cHistory) {
|
if (!cHistory) {
|
||||||
this.println("no " + sMore + "history available");
|
this.println("no " + sMore + "history available");
|
||||||
this.nextHistory = undefined;
|
this.nextHistory = undefined;
|
||||||
|
|
@ -2258,9 +2320,14 @@ if (DEBUGGER) {
|
||||||
* @return {number} register index, or -1 if not found
|
* @return {number} register index, or -1 if not found
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.getRegIndex = function(sReg, off) {
|
Debugger.prototype.getRegIndex = function(sReg, off) {
|
||||||
off = off || 0;
|
var i;
|
||||||
var i = usr.indexOf(Debugger.REGS, sReg.substr(off, 3).toUpperCase());
|
sReg = sReg.toUpperCase();
|
||||||
if (i < 0) i = usr.indexOf(Debugger.REGS, sReg.substr(off, 2).toUpperCase());
|
if (off == null) {
|
||||||
|
i = usr.indexOf(Debugger.REGS, sReg);
|
||||||
|
} else {
|
||||||
|
i = usr.indexOf(Debugger.REGS, sReg.substr(off, 3));
|
||||||
|
if (i < 0) i = usr.indexOf(Debugger.REGS, sReg.substr(off, 2));
|
||||||
|
}
|
||||||
return i;
|
return i;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2806,7 +2873,7 @@ if (DEBUGGER) {
|
||||||
this.nCycles += nCyclesStep;
|
this.nCycles += nCyclesStep;
|
||||||
this.cpu.addCycles(nCyclesStep, true);
|
this.cpu.addCycles(nCyclesStep, true);
|
||||||
this.cpu.updateChecksum(nCyclesStep);
|
this.cpu.updateChecksum(nCyclesStep);
|
||||||
this.cInstructions++;
|
this.cOpcodes++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
|
|
@ -2933,7 +3000,7 @@ if (DEBUGGER) {
|
||||||
Debugger.prototype.reset = function(fQuiet)
|
Debugger.prototype.reset = function(fQuiet)
|
||||||
{
|
{
|
||||||
this.historyInit();
|
this.historyInit();
|
||||||
this.cInstructions = 0;
|
this.cOpcodes = 0;
|
||||||
this.sMessagePrev = null;
|
this.sMessagePrev = null;
|
||||||
this.nCycles = 0;
|
this.nCycles = 0;
|
||||||
this.dbgAddrNextCode = this.newAddr(this.cpu.getIP(), this.cpu.getCS());
|
this.dbgAddrNextCode = this.newAddr(this.cpu.getIP(), this.cpu.getCS());
|
||||||
|
|
@ -3025,8 +3092,8 @@ if (DEBUGGER) {
|
||||||
var nCyclesPerSecond = (msTotal > 0? Math.round(this.nCycles * 1000 / msTotal) : 0);
|
var nCyclesPerSecond = (msTotal > 0? Math.round(this.nCycles * 1000 / msTotal) : 0);
|
||||||
sStopped += " (";
|
sStopped += " (";
|
||||||
if (this.checksEnabled()) {
|
if (this.checksEnabled()) {
|
||||||
sStopped += this.cInstructions + " ops, ";
|
sStopped += this.cOpcodes + " opcodes, ";
|
||||||
this.cInstructions = 0; // remove this line if you want to maintain a longer total
|
this.cOpcodes = 0; // remove this line if you want to maintain a longer total
|
||||||
}
|
}
|
||||||
sStopped += this.nCycles + " cycles, " + msTotal + " ms, " + nCyclesPerSecond + " hz)";
|
sStopped += this.nCycles + " cycles, " + msTotal + " ms, " + nCyclesPerSecond + " hz)";
|
||||||
if (MAXDEBUG && this.chipset) {
|
if (MAXDEBUG && this.chipset) {
|
||||||
|
|
@ -3114,7 +3181,7 @@ if (DEBUGGER) {
|
||||||
* well, OK, and a few other things now, like enabling Messages.INT messages.
|
* well, OK, and a few other things now, like enabling Messages.INT messages.
|
||||||
*/
|
*/
|
||||||
if (nState >= 0 && this.aaOpcodeCounts.length) {
|
if (nState >= 0 && this.aaOpcodeCounts.length) {
|
||||||
this.cInstructions++;
|
this.cOpcodes++;
|
||||||
var bOpcode = this.cpu.probeAddr(addr);
|
var bOpcode = this.cpu.probeAddr(addr);
|
||||||
if (bOpcode != null) {
|
if (bOpcode != null) {
|
||||||
this.aaOpcodeCounts[bOpcode][1]++;
|
this.aaOpcodeCounts[bOpcode][1]++;
|
||||||
|
|
@ -3713,7 +3780,7 @@ if (DEBUGGER) {
|
||||||
}
|
}
|
||||||
offset = (dbgAddr.off + disp) & (dbgAddr.fData32? -1 : 0xffff);
|
offset = (dbgAddr.off + disp) & (dbgAddr.fData32? -1 : 0xffff);
|
||||||
sOperand = str.toHex(offset, dbgAddr.fData32? 8: 4);
|
sOperand = str.toHex(offset, dbgAddr.fData32? 8: 4);
|
||||||
var aSymbol = this.findSymbolAtAddr(this.newAddr(offset, dbgAddr.sel));
|
var aSymbol = this.findSymbol(this.newAddr(offset, dbgAddr.sel));
|
||||||
if (aSymbol[0]) sOperand += " (" + aSymbol[0] + ")";
|
if (aSymbol[0]) sOperand += " (" + aSymbol[0] + ")";
|
||||||
}
|
}
|
||||||
else if (typeMode == Debugger.TYPE_IMPREG) {
|
else if (typeMode == Debugger.TYPE_IMPREG) {
|
||||||
|
|
@ -3806,7 +3873,7 @@ if (DEBUGGER) {
|
||||||
case Debugger.TYPE_FARP:
|
case Debugger.TYPE_FARP:
|
||||||
dbgAddr = this.newAddr(this.getWord(dbgAddr, true), this.getShort(dbgAddr, 2), null, dbgAddr.fProt, dbgAddr.fData32, dbgAddr.fAddr32);
|
dbgAddr = this.newAddr(this.getWord(dbgAddr, true), this.getShort(dbgAddr, 2), null, dbgAddr.fProt, dbgAddr.fData32, dbgAddr.fAddr32);
|
||||||
sOperand = this.hexAddr(dbgAddr);
|
sOperand = this.hexAddr(dbgAddr);
|
||||||
var aSymbol = this.findSymbolAtAddr(dbgAddr);
|
var aSymbol = this.findSymbol(dbgAddr);
|
||||||
if (aSymbol[0]) sOperand += " (" + aSymbol[0] + ")";
|
if (aSymbol[0]) sOperand += " (" + aSymbol[0] + ")";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -4182,7 +4249,7 @@ if (DEBUGGER) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parseAddr(sAddr, type, fNoChecks)
|
* parseAddr(sAddr, type, fNoChecks, fQuiet)
|
||||||
*
|
*
|
||||||
* As discussed above, dbgAddr variables contain one or more of: off, sel, and addr. They represent
|
* As discussed above, dbgAddr variables contain one or more of: off, sel, and addr. They represent
|
||||||
* a segmented address (sel:off) when sel is defined or a linear address (addr) when sel is undefined
|
* a segmented address (sel:off) when sel is defined or a linear address (addr) when sel is undefined
|
||||||
|
|
@ -4207,14 +4274,17 @@ if (DEBUGGER) {
|
||||||
* @param {string|undefined} sAddr
|
* @param {string|undefined} sAddr
|
||||||
* @param {number|undefined} [type] is the address segment type, in case sAddr doesn't specify a segment
|
* @param {number|undefined} [type] is the address segment type, in case sAddr doesn't specify a segment
|
||||||
* @param {boolean} [fNoChecks] (eg, true when setting breakpoints that may not be valid now, but will be later)
|
* @param {boolean} [fNoChecks] (eg, true when setting breakpoints that may not be valid now, but will be later)
|
||||||
|
* @param {boolean} [fQuiet]
|
||||||
* @return {DbgAddr|null|undefined}
|
* @return {DbgAddr|null|undefined}
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.parseAddr = function(sAddr, type, fNoChecks)
|
Debugger.prototype.parseAddr = function(sAddr, type, fNoChecks, fQuiet)
|
||||||
{
|
{
|
||||||
var dbgAddr;
|
var dbgAddr, fPrint;
|
||||||
var dbgAddrNext = (type === Debugger.ADDR_CODE? this.dbgAddrNextCode : this.dbgAddrNextData);
|
var dbgAddrNext = (type === Debugger.ADDR_CODE? this.dbgAddrNextCode : this.dbgAddrNextData);
|
||||||
var off = dbgAddrNext.off, sel = dbgAddrNext.sel, addr = dbgAddrNext.addr;
|
var off = dbgAddrNext.off, sel = dbgAddrNext.sel, addr = dbgAddrNext.addr;
|
||||||
|
|
||||||
|
if (fQuiet) fPrint = false;
|
||||||
|
|
||||||
if (sAddr !== undefined) {
|
if (sAddr !== undefined) {
|
||||||
|
|
||||||
if (sAddr.charAt(0) == '%') {
|
if (sAddr.charAt(0) == '%') {
|
||||||
|
|
@ -4230,16 +4300,16 @@ if (DEBUGGER) {
|
||||||
var iColon = sAddr.indexOf(':');
|
var iColon = sAddr.indexOf(':');
|
||||||
if (iColon < 0) {
|
if (iColon < 0) {
|
||||||
if (sel != null) {
|
if (sel != null) {
|
||||||
off = this.parseExpression(sAddr);
|
off = this.parseExpression(sAddr, fPrint);
|
||||||
addr = null;
|
addr = null;
|
||||||
} else {
|
} else {
|
||||||
addr = this.parseExpression(sAddr);
|
addr = this.parseExpression(sAddr, fPrint);
|
||||||
if (addr == null) off = null;
|
if (addr == null) off = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sel = this.parseExpression(sAddr.substring(0, iColon));
|
sel = this.parseExpression(sAddr.substring(0, iColon), fPrint);
|
||||||
off = this.parseExpression(sAddr.substring(iColon + 1));
|
off = this.parseExpression(sAddr.substring(iColon + 1), fPrint);
|
||||||
addr = null;
|
addr = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4385,7 +4455,7 @@ if (DEBUGGER) {
|
||||||
*
|
*
|
||||||
* @this {Debugger}
|
* @this {Debugger}
|
||||||
* @param {string|undefined} sExp
|
* @param {string|undefined} sExp
|
||||||
* @param {boolean} [fPrint] is true to print all resolved values
|
* @param {boolean} [fPrint] is true to print all resolved values, false for quiet parsing
|
||||||
* @return {number|undefined} numeric value, or undefined if sExp contains any undefined or invalid values
|
* @return {number|undefined} numeric value, or undefined if sExp contains any undefined or invalid values
|
||||||
*/
|
*/
|
||||||
Debugger.prototype.parseExpression = function(sExp, fPrint)
|
Debugger.prototype.parseExpression = function(sExp, fPrint)
|
||||||
|
|
@ -4413,7 +4483,7 @@ if (DEBUGGER) {
|
||||||
fError = true;
|
fError = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var v = this.parseValue(s);
|
var v = this.parseValue(s, null, fPrint === false);
|
||||||
if (v === undefined) {
|
if (v === undefined) {
|
||||||
fError = true;
|
fError = true;
|
||||||
fPrint = false;
|
fPrint = false;
|
||||||
|
|
@ -4442,7 +4512,7 @@ if (DEBUGGER) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parseValue(sValue, sName)
|
* parseValue(sValue, sName, fQuiet)
|
||||||
*
|
*
|
||||||
* @this {Debugger}
|
* @this {Debugger}
|
||||||
* @param {string|undefined} sValue
|
* @param {string|undefined} sValue
|
||||||
|
|
@ -4603,16 +4673,16 @@ if (DEBUGGER) {
|
||||||
*
|
*
|
||||||
* [addr, size, aSymbols, aOffsetPairs]
|
* [addr, size, aSymbols, aOffsetPairs]
|
||||||
*
|
*
|
||||||
* There are two basic symbol operations: findSymbolAddr(), which takes a string and attempts to match it
|
* There are two basic symbol operations: findSymbol(), which takes an address and finds the symbol, if any,
|
||||||
* to a non-anonymous symbol with a matching offset ('o') property, and findSymbolAtAddr(), which takes an
|
* at that address, and findSymbolAddr(), which takes a string and attempts to match it to a non-anonymous
|
||||||
* address and finds the symbol, if any, at that address.
|
* symbol with a matching offset ('o') property.
|
||||||
*
|
*
|
||||||
* To implement findSymbolAtAddr() efficiently, addSymbols() creates an array of [offset, sSymbol] pairs
|
* To implement findSymbol() efficiently, addSymbols() creates an array of [offset, sSymbol] pairs
|
||||||
* (aOffsetPairs), one pair for each symbol that corresponds to an offset within the specified address space.
|
* (aOffsetPairs), one pair for each symbol that corresponds to an offset within the specified address space.
|
||||||
*
|
*
|
||||||
* We guarantee the elements of aOffsetPairs are in offset order, because we build it using binaryInsert();
|
* We guarantee the elements of aOffsetPairs are in offset order, because we build it using binaryInsert();
|
||||||
* it's quite likely that the MAP file already ordered all its symbols in offset order, but since they're
|
* it's quite likely that the MAP file already ordered all its symbols in offset order, but since they're
|
||||||
* hand-edited files, we can't assume that. This ensures that findSymbolAtAddr()'s binarySearch() will operate
|
* hand-edited files, we can't assume that. This ensures that findSymbol()'s binarySearch() will operate
|
||||||
* properly.
|
* properly.
|
||||||
*
|
*
|
||||||
* @this {Debugger}
|
* @this {Debugger}
|
||||||
|
|
@ -4689,6 +4759,55 @@ if (DEBUGGER) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* findSymbol(dbgAddr, fNearest)
|
||||||
|
*
|
||||||
|
* Search aSymbolTable for dbgAddr, and return an Array for the corresponding symbol (empty if not found).
|
||||||
|
*
|
||||||
|
* If fNearest is true, and no exact match was found, then the Array returned will contain TWO sets of
|
||||||
|
* entries: [0]-[3] will refer to closest preceding symbol, and [4]-[7] will refer to the closest subsequent symbol.
|
||||||
|
*
|
||||||
|
* @this {Debugger}
|
||||||
|
* @param {DbgAddr} dbgAddr
|
||||||
|
* @param {boolean} [fNearest]
|
||||||
|
* @return {Array} where [0] == symbol name, [1] == symbol value, [2] == any annotation, and [3] == any associated comment
|
||||||
|
*/
|
||||||
|
Debugger.prototype.findSymbol = function(dbgAddr, fNearest)
|
||||||
|
{
|
||||||
|
var aSymbol = [];
|
||||||
|
var addr = this.getAddr(dbgAddr);
|
||||||
|
for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) {
|
||||||
|
var addrSymbol = this.aSymbolTable[iTable][0];
|
||||||
|
var sizeSymbol = this.aSymbolTable[iTable][1];
|
||||||
|
if (addr >= addrSymbol && addr < addrSymbol + sizeSymbol) {
|
||||||
|
var offset = dbgAddr.off;
|
||||||
|
var aOffsetPairs = this.aSymbolTable[iTable][3];
|
||||||
|
var fnComparePairs = function(p1, p2)
|
||||||
|
{
|
||||||
|
return p1[0] > p2[0]? 1 : p1[0] < p2[0]? -1 : 0;
|
||||||
|
};
|
||||||
|
var result = usr.binarySearch(aOffsetPairs, [offset], fnComparePairs);
|
||||||
|
if (result >= 0) {
|
||||||
|
this.returnSymbol(iTable, result, aSymbol);
|
||||||
|
}
|
||||||
|
else if (fNearest) {
|
||||||
|
result = ~result;
|
||||||
|
this.returnSymbol(iTable, result-1, aSymbol);
|
||||||
|
this.returnSymbol(iTable, result, aSymbol);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!aSymbol.length) {
|
||||||
|
var sSymbol = this.bus.getSymbol(addr, true);
|
||||||
|
if (sSymbol) {
|
||||||
|
aSymbol.push(sSymbol);
|
||||||
|
aSymbol.push(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aSymbol;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* findSymbolAddr(sSymbol)
|
* findSymbolAddr(sSymbol)
|
||||||
*
|
*
|
||||||
|
|
@ -4731,59 +4850,10 @@ if (DEBUGGER) {
|
||||||
return dbgAddr;
|
return dbgAddr;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* findSymbolAtAddr(dbgAddr, fNearest)
|
|
||||||
*
|
|
||||||
* Search aSymbolTable for dbgAddr, and return an Array for the corresponding symbol (empty if not found).
|
|
||||||
*
|
|
||||||
* If fNearest is true, and no exact match was found, then the Array returned will contain TWO sets of
|
|
||||||
* entries: [0]-[3] will refer to closest preceding symbol, and [4]-[7] will refer to the closest subsequent symbol.
|
|
||||||
*
|
|
||||||
* @this {Debugger}
|
|
||||||
* @param {DbgAddr} dbgAddr
|
|
||||||
* @param {boolean} [fNearest]
|
|
||||||
* @return {Array} where [0] == symbol name, [1] == symbol value, [2] == any annotation, and [3] == any associated comment
|
|
||||||
*/
|
|
||||||
Debugger.prototype.findSymbolAtAddr = function(dbgAddr, fNearest)
|
|
||||||
{
|
|
||||||
var aSymbol = [];
|
|
||||||
var addr = this.getAddr(dbgAddr);
|
|
||||||
for (var iTable = 0; iTable < this.aSymbolTable.length; iTable++) {
|
|
||||||
var addrSymbol = this.aSymbolTable[iTable][0];
|
|
||||||
var sizeSymbol = this.aSymbolTable[iTable][1];
|
|
||||||
if (addr >= addrSymbol && addr < addrSymbol + sizeSymbol) {
|
|
||||||
var offset = dbgAddr.off;
|
|
||||||
var aOffsetPairs = this.aSymbolTable[iTable][3];
|
|
||||||
var fnComparePairs = function(p1, p2)
|
|
||||||
{
|
|
||||||
return p1[0] > p2[0]? 1 : p1[0] < p2[0]? -1 : 0;
|
|
||||||
};
|
|
||||||
var result = usr.binarySearch(aOffsetPairs, [offset], fnComparePairs);
|
|
||||||
if (result >= 0) {
|
|
||||||
this.returnSymbol(iTable, result, aSymbol);
|
|
||||||
}
|
|
||||||
else if (fNearest) {
|
|
||||||
result = ~result;
|
|
||||||
this.returnSymbol(iTable, result-1, aSymbol);
|
|
||||||
this.returnSymbol(iTable, result, aSymbol);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!aSymbol.length) {
|
|
||||||
var sSymbol = this.bus.getSymbol(addr, true);
|
|
||||||
if (sSymbol) {
|
|
||||||
aSymbol.push(sSymbol);
|
|
||||||
aSymbol.push(addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return aSymbol;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returnSymbol(iTable, iOffset, aSymbol)
|
* returnSymbol(iTable, iOffset, aSymbol)
|
||||||
*
|
*
|
||||||
* Helper function for findSymbolAtAddr().
|
* Helper function for findSymbol().
|
||||||
*
|
*
|
||||||
* @param {number} iTable
|
* @param {number} iTable
|
||||||
* @param {number} iOffset
|
* @param {number} iOffset
|
||||||
|
|
@ -5108,40 +5178,38 @@ if (DEBUGGER) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sCmd == "dh") {
|
if (sCmd == "dh") {
|
||||||
this.dumpHistory(sAddr, cb);
|
this.dumpHistory(sAddr, sLen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sCmd == "di") {
|
||||||
|
var sInfo = this.dumpInfo(sAddr);
|
||||||
|
this.println(sInfo);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_DATA);
|
var dbgAddr = this.parseAddr(sAddr, Debugger.ADDR_DATA);
|
||||||
if (!dbgAddr) return;
|
if (!dbgAddr || dbgAddr.sel == null && dbgAddr.addr == null) return;
|
||||||
|
|
||||||
var sDump = "";
|
var sDump = "";
|
||||||
if (BACKTRACK && sCmd == "di") {
|
var cLines = (((cb || 128) + 15) >> 4) || 1;
|
||||||
var addr = this.getAddr(dbgAddr);
|
var size = (sCmd == "dd"? 4 : (sCmd == "dw"? 2 : 1));
|
||||||
sDump += '%' + str.toHex(addr) + ": ";
|
for (var iLine = 0; iLine < cLines; iLine++) {
|
||||||
var sInfo = this.bus.getSymbol(addr, true);
|
var data = 0, iByte = 0;
|
||||||
sDump += sInfo || "no information";
|
var sData = "", sChars = "";
|
||||||
}
|
sAddr = this.hexAddr(dbgAddr);
|
||||||
else {
|
for (var i = 0; i < 16; i++) {
|
||||||
var cLines = (((cb || 128) + 15) >> 4) || 1;
|
var b = this.getByte(dbgAddr, 1);
|
||||||
var size = (sCmd == "dd"? 4 : (sCmd == "dw"? 2 : 1));
|
data |= (b << (iByte++ << 3));
|
||||||
for (var iLine = 0; iLine < cLines; iLine++) {
|
if (iByte == size) {
|
||||||
var data = 0, iByte = 0;
|
sData += str.toHex(data, size * 2);
|
||||||
var sData = "", sChars = "";
|
sData += (size == 1? (i == 7? '-' : ' ') : " ");
|
||||||
sAddr = this.hexAddr(dbgAddr);
|
data = iByte = 0;
|
||||||
for (var i = 0; i < 16; i++) {
|
|
||||||
var b = this.getByte(dbgAddr, 1);
|
|
||||||
data |= (b << (iByte++ << 3));
|
|
||||||
if (iByte == size) {
|
|
||||||
sData += str.toHex(data, size * 2);
|
|
||||||
sData += (size == 1? (i == 7? '-' : ' ') : " ");
|
|
||||||
data = iByte = 0;
|
|
||||||
}
|
|
||||||
sChars += (b >= 32 && b < 128? String.fromCharCode(b) : '.');
|
|
||||||
}
|
}
|
||||||
if (sDump) sDump += '\n';
|
sChars += (b >= 32 && b < 128? String.fromCharCode(b) : '.');
|
||||||
sDump += sAddr + " " + sData + ' ' + sChars;
|
|
||||||
}
|
}
|
||||||
|
if (sDump) sDump += '\n';
|
||||||
|
sDump += sAddr + " " + sData + ' ' + sChars;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sDump) this.println(sDump);
|
if (sDump) this.println(sDump);
|
||||||
|
|
@ -5418,7 +5486,7 @@ if (DEBUGGER) {
|
||||||
sSymbol = sSymbol? (sSymbol + ": ") : "";
|
sSymbol = sSymbol? (sSymbol + ": ") : "";
|
||||||
this.println(sSymbol + this.hexAddr(dbgAddr) + " (%" + str.toHex(addr, this.cchAddr) + ')');
|
this.println(sSymbol + this.hexAddr(dbgAddr) + " (%" + str.toHex(addr, this.cchAddr) + ')');
|
||||||
|
|
||||||
var aSymbol = this.findSymbolAtAddr(dbgAddr, true);
|
var aSymbol = this.findSymbol(dbgAddr, true);
|
||||||
if (aSymbol.length) {
|
if (aSymbol.length) {
|
||||||
var nDelta, sDelta;
|
var nDelta, sDelta;
|
||||||
if (aSymbol[0]) {
|
if (aSymbol[0]) {
|
||||||
|
|
@ -5496,7 +5564,7 @@ if (DEBUGGER) {
|
||||||
* this point (ie, if the disk is uninitialized and unformatted), but that will only affect whether the
|
* this point (ie, if the disk is uninitialized and unformatted), but that will only affect whether the
|
||||||
* read succeeds or not.
|
* read succeeds or not.
|
||||||
*/
|
*/
|
||||||
var dc = this .fdc;
|
var dc = this.fdc;
|
||||||
if (iDrive >= 2 && this.hdc) {
|
if (iDrive >= 2 && this.hdc) {
|
||||||
iDrive -= 2;
|
iDrive -= 2;
|
||||||
dc = this.hdc;
|
dc = this.hdc;
|
||||||
|
|
@ -6380,7 +6448,7 @@ if (DEBUGGER) {
|
||||||
var addr = dbgAddr.addr;
|
var addr = dbgAddr.addr;
|
||||||
var nSequence = (this.isBusy(false) || this.nStep)? this.nCycles : null;
|
var nSequence = (this.isBusy(false) || this.nStep)? this.nCycles : null;
|
||||||
var sComment = (nSequence != null? "cycles" : null);
|
var sComment = (nSequence != null? "cycles" : null);
|
||||||
var aSymbol = this.findSymbolAtAddr(dbgAddr);
|
var aSymbol = this.findSymbol(dbgAddr);
|
||||||
|
|
||||||
if (aSymbol[0] && n) {
|
if (aSymbol[0] && n) {
|
||||||
if (!cLines && n || aSymbol[0].indexOf('+') < 0) {
|
if (!cLines && n || aSymbol[0].indexOf('+') < 0) {
|
||||||
|
|
@ -6395,7 +6463,7 @@ if (DEBUGGER) {
|
||||||
nSequence = null;
|
nSequence = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sIns = this.getInstruction(dbgAddr, sComment, nSequence);
|
var sInstruction = this.getInstruction(dbgAddr, sComment, nSequence);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If getInstruction() reported that it did not yet process a complete instruction (via dbgAddr.fComplete),
|
* If getInstruction() reported that it did not yet process a complete instruction (via dbgAddr.fComplete),
|
||||||
|
|
@ -6404,7 +6472,7 @@ if (DEBUGGER) {
|
||||||
*/
|
*/
|
||||||
if (!dbgAddr.fComplete && !n) n++;
|
if (!dbgAddr.fComplete && !n) n++;
|
||||||
|
|
||||||
this.println(sIns);
|
this.println(sInstruction);
|
||||||
this.dbgAddrNextCode = dbgAddr;
|
this.dbgAddrNextCode = dbgAddr;
|
||||||
cb -= dbgAddr.addr - addr;
|
cb -= dbgAddr.addr - addr;
|
||||||
cLines++;
|
cLines++;
|
||||||
|
|
|
||||||
|
|
@ -477,6 +477,29 @@ FileInfo.prototype.loadSegmentTable = function(offEntries, nEntries, nSegOffShif
|
||||||
}
|
}
|
||||||
offEntries += 8;
|
offEntries += 8;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* Although not documented (at least not in any of the early Windows "New Executable" documents I've seen),
|
||||||
|
* the Entry Table may also contain entries whose bSegment field is 0xFE, which doesn't correspond to a valid
|
||||||
|
* segment number. That pseudo-segment number appears to be reserved for constants. Here are some examples
|
||||||
|
* from a 3.1-vintage KRNL386.EXE:
|
||||||
|
*
|
||||||
|
* cannot find segment 254 (offset 0xF000) for symbol __ROMBIOS with ordinal 173
|
||||||
|
* cannot find segment 254 (offset 0x0000) for symbol __0000H with ordinal 183
|
||||||
|
* cannot find segment 254 (offset 0x0040) for symbol __0040H with ordinal 193
|
||||||
|
* cannot find segment 254 (offset 0x0008) for symbol __AHINCR with ordinal 114
|
||||||
|
* cannot find segment 254 (offset 0x0003) for symbol __AHSHIFT with ordinal 113
|
||||||
|
* cannot find segment 254 (offset 0xA000) for symbol __A000H with ordinal 174
|
||||||
|
* cannot find segment 254 (offset 0xB000) for symbol __B000H with ordinal 181
|
||||||
|
* cannot find segment 254 (offset 0xC000) for symbol __C000H with ordinal 195
|
||||||
|
* cannot find segment 254 (offset 0xB800) for symbol __B800H with ordinal 182
|
||||||
|
* cannot find segment 254 (offset 0xD000) for symbol __D000H with ordinal 179
|
||||||
|
* cannot find segment 254 (offset 0xE000) for symbol __E000H with ordinal 190
|
||||||
|
* cannot find segment 254 (offset 0xF000) for symbol __F000H with ordinal 194
|
||||||
|
* cannot find segment 254 (offset 0x0001) for symbol __WINFLAGS with ordinal 178
|
||||||
|
*
|
||||||
|
* The simplest way to handle those Entry Table entries is creating an additional (fake) aSegments table entry.
|
||||||
|
*/
|
||||||
|
this.aSegments[0xFE] = {offStart: 0, offEnd: 0, aEntries: []};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -535,13 +558,11 @@ FileInfo.prototype.loadEntryTable = function(offEntries, offEntriesEnd)
|
||||||
}
|
}
|
||||||
if (!this.aSegments[iSegment]) {
|
if (!this.aSegments[iSegment]) {
|
||||||
if (MAXDEBUG) this.disk.println("invalid segment: " + iSegment);
|
if (MAXDEBUG) this.disk.println("invalid segment: " + iSegment);
|
||||||
break;
|
} else {
|
||||||
|
this.aSegments[iSegment].aEntries[iOrdinal] = [offEntry];
|
||||||
|
if (MAXDEBUG) this.disk.println("ordinal " + iOrdinal + ": segment=" + iSegment + " offset=" + str.toHexLong(offEntry) + " @" + str.toHex(offDebug));
|
||||||
}
|
}
|
||||||
this.aSegments[iSegment].aEntries[iOrdinal] = [offEntry];
|
this.aOrdinals[iOrdinal] = [iSegment, offEntry];
|
||||||
|
|
||||||
if (MAXDEBUG) this.disk.println("ordinal " + iOrdinal + ": segment=" + iSegment + " offset=" + str.toHexLong(offEntry) + " @" + str.toHex(offDebug));
|
|
||||||
|
|
||||||
this.aOrdinals[iOrdinal] = iSegment;
|
|
||||||
iOrdinal++;
|
iOrdinal++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -571,7 +592,7 @@ FileInfo.prototype.loadNameTable = function(offEntries, offEntriesEnd)
|
||||||
if (!bLength) break;
|
if (!bLength) break;
|
||||||
|
|
||||||
var sSymbol = this.loadString(offEntries + 1, bLength);
|
var sSymbol = this.loadString(offEntries + 1, bLength);
|
||||||
if (!sSymbol) break; // an error must have occurred (this is not a natural way to end)
|
if (!sSymbol) break; // an error must have occurred (this is not a natural way to end)
|
||||||
offEntries += 1 + bLength;
|
offEntries += 1 + bLength;
|
||||||
|
|
||||||
if (!cNames) {
|
if (!cNames) {
|
||||||
|
|
@ -583,14 +604,19 @@ FileInfo.prototype.loadNameTable = function(offEntries, offEntriesEnd)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var iOrdinal = this.loadValue(offEntries);
|
var iOrdinal = this.loadValue(offEntries);
|
||||||
var iSegment = this.aOrdinals[iOrdinal];
|
var tuple = this.aOrdinals[iOrdinal];
|
||||||
if (iSegment !== undefined) {
|
if (tuple) {
|
||||||
var aEntries = this.aSegments[iSegment].aEntries[iOrdinal];
|
var iSegment = tuple[0]; // tuple[0] is the segment number and tuple[1] is the corresponding offEntry
|
||||||
this.disk.assert(aEntries && aEntries.length == 1);
|
if (this.aSegments[iSegment]) {
|
||||||
aEntries.push(sSymbol);
|
var aEntries = this.aSegments[iSegment].aEntries[iOrdinal];
|
||||||
if (MAXDEBUG) this.disk.println("segment " + iSegment + " offset " + str.toHexWord(aEntries[0]) + " ordinal " + iOrdinal + ": " + sSymbol + " @" + str.toHex(offDebug));
|
this.disk.assert(aEntries && aEntries.length == 1);
|
||||||
|
aEntries.push(sSymbol);
|
||||||
|
if (MAXDEBUG) this.disk.println("segment " + iSegment + " offset " + str.toHexWord(aEntries[0]) + " ordinal " + iOrdinal + ": " + sSymbol + " @" + str.toHex(offDebug));
|
||||||
|
} else {
|
||||||
|
if (MAXDEBUG) this.disk.println(this.sPath + ": cannot find segment " + iSegment + " (offset " + str.toHexWord(tuple[1]) + ") for symbol " + sSymbol + " with ordinal " + iOrdinal + " @" + str.toHex(offDebug));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (MAXDEBUG) this.disk.println(this.sPath + ": cannot find ordinal " + iOrdinal + " @" + str.toHex(offDebug));
|
if (MAXDEBUG) this.disk.println(this.sPath + ": cannot find ordinal " + iOrdinal + " for symbol " + sSymbol + " @" + str.toHex(offDebug));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
offEntries += 2;
|
offEntries += 2;
|
||||||
|
|
@ -700,7 +726,7 @@ FileInfo.prototype.getSymbol = function(off, fNearest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sSymbol || this.sName + '[' + str.toHexLong(off) + ']';
|
return sSymbol || this.sName + '+' + str.toHexLong(off);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1537,6 +1563,40 @@ Disk.prototype.buildFileTable = function()
|
||||||
return this.aFileTable;
|
return this.aFileTable;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getSymbolInfo(sSymbol)
|
||||||
|
*
|
||||||
|
* For all whole or partial symbol matches, return them in an Array of entries:
|
||||||
|
*
|
||||||
|
* [symbol, file name, segment number, segment offset, segment size].
|
||||||
|
*
|
||||||
|
* TODO: This function has many limitations (ie, slow, case-sensitive), but it gets the job done for now.
|
||||||
|
*
|
||||||
|
* @this {Disk}
|
||||||
|
* @param {string} sSymbol
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
Disk.prototype.getSymbolInfo = function(sSymbol)
|
||||||
|
{
|
||||||
|
var aInfo = [];
|
||||||
|
if (this.aFileTable) {
|
||||||
|
var sSymbolUpper = sSymbol.toUpperCase();
|
||||||
|
for (var iFile = 0; iFile < this.aFileTable.length; iFile++) {
|
||||||
|
var file = this.aFileTable[iFile];
|
||||||
|
for (var iSegment in file.aSegments) {
|
||||||
|
var segment = file.aSegments[iSegment];
|
||||||
|
for (var iEntry in segment.aEntries) {
|
||||||
|
var entry = segment.aEntries[iEntry];
|
||||||
|
if (entry[1] && entry[1].indexOf(sSymbolUpper) >= 0) {
|
||||||
|
aInfo.push([entry[1], file.sName, iSegment, entry[0], segment.offEnd - segment.offStart]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aInfo;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getDir(dir, sDisk, sDir, apba)
|
* getDir(dir, sDisk, sDir, apba)
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue