/// <summary> /// Analyzes uncategorized regions of the file to see if they fit common patterns. /// /// This is re-run after most changes to the project, so we don't want to do anything /// crazily expensive. /// </summary> /// <returns>True on success.</returns> public void AnalyzeUncategorized() { // TODO(someday): we can make this faster. The data doesn't change, so we // only need to do a full scan once, when the file is first loaded. We can // create a TypedRangeSet for runs of identical bytes, using the byte value // as the type. A second TypedRangeSet would identify runs of ASCII chars, // with different types for high/low ASCII (and PETSCII?). AnalyzeRange() would // then just need to find the intersection with the sets, which should be // significantly faster. We would need to re-do the scan if the parameters // for things like min match length change. FormatDescriptor oneByteDefault = FormatDescriptor.Create(1, FormatDescriptor.Type.Default, FormatDescriptor.SubType.None); FormatDescriptor.DebugPrefabBump(-1); // If it hasn't been identified as code or data, set the "data" flag to // give it a positive identification as data. (This should be the only // place outside of CodeAnalysis that sets this flag.) This isn't strictly // necessary, but it helps us assert things when pieces start moving around. for (int offset = 0; offset < mAnattribs.Length; offset++) { Anattrib attr = mAnattribs[offset]; if (attr.IsInlineData) { // While we're here, add a default format descriptor for inline data // that doesn't have one. We don't try to analyze it otherwise. if (attr.DataDescriptor == null) { mAnattribs[offset].DataDescriptor = oneByteDefault; FormatDescriptor.DebugPrefabBump(); } } else if (!attr.IsInstruction) { mAnattribs[offset].IsData = true; } } mDebugLog.LogI("Analyzing uncategorized data..."); int startOffset = -1; for (int offset = 0; offset < mAnattribs.Length;) { // We want to find a contiguous series of offsets which are not known // to hold code or data. We stop if we encounter a user-defined label // or format descriptor. Anattrib attr = mAnattribs[offset]; if (attr.IsInstruction || attr.IsInlineData || attr.IsDataStart) { // Instruction, inline data, or formatted data known to be here. Analyze // previous chunk, then advance past this. if (startOffset >= 0) { AnalyzeRange(startOffset, offset - 1); startOffset = -1; } if (attr.IsInstruction) { // Because of embedded instructions, we can't simply leap forward. offset++; } else { Debug.Assert(attr.Length > 0); offset += attr.Length; } } else if (attr.Symbol != null || mProject.HasCommentOrNote(offset)) { // In an uncategorized area, but we want to break at this byte // so the user or auto label doesn't get buried in the middle of // a large chunk. // // This is similar to, but independent of, GroupedOffsetSetFromSelected() // in ProjectView. This is for auto-detection, the other is for user // selection. It's best if the two behave similarly though. if (startOffset >= 0) { AnalyzeRange(startOffset, offset - 1); } startOffset = offset; offset++; } else { // This offset is uncategorized, keep gathering. if (startOffset < 0) { startOffset = offset; } offset++; // Check to see if the address has changed from the previous entry. if (offset < mAnattribs.Length && mAnattribs[offset - 1].Address + 1 != mAnattribs[offset].Address) { // Must be an ORG here. Scan previous region. AnalyzeRange(startOffset, offset - 1); startOffset = -1; } } } if (startOffset >= 0) { AnalyzeRange(startOffset, mAnattribs.Length - 1); } }
/// <summary> /// Formats the address map for viewing. /// </summary> public static string GenerateString(DisasmProject project, Formatter formatter) { AddressMap addrMap = project.AddrMap; bool showBank = !project.CpuDef.HasAddr16; StringBuilder sb = new StringBuilder(); int depth = 0; int prevOffset = -1; int prevAddr = 0; int lastEndOffset = -1; sb.AppendLine("Address region map for \"" + project.DataFileName + "\""); sb.Append(CRLF); IEnumerator <AddressChange> iter = addrMap.AddressChangeIterator; while (iter.MoveNext()) { AddressChange change = iter.Current; if (change.IsStart) { //if (change.Offset == lastEndOffset) { // // Extra vertical space for a START following an END at the same offset. // PrintDepthLines(sb, depth, true); // sb.Append(CRLF); // lastEndOffset = -1; //} if (prevOffset >= 0 && change.Offset != prevOffset) { // Start of region at new offset. Output address info for space // between previous start or end. PrintAddressInfo(sb, formatter, depth, prevAddr, change.Offset - prevOffset, showBank); } // Start following end, or start following start after a gap. sb.Append(formatter.FormatOffset24(change.Offset)); PrintDepthLines(sb, depth, false); sb.Append("+- " + "start"); if (change.IsSynthetic) { sb.Append(" (auto-generated)"); } else { // If there's a label here, show it. Anattrib attr = project.GetAnattrib(change.Offset); if (attr.Symbol != null && !string.IsNullOrEmpty(attr.Symbol.Label)) { sb.Append(" '"); sb.Append(attr.Symbol.GenerateDisplayLabel(formatter)); sb.Append("'"); } } if (change.Region.HasValidPreLabel) { sb.Append(" pre='"); sb.Append(change.Region.PreLabel); sb.Append("'"); } sb.Append(CRLF); prevOffset = change.Offset; prevAddr = change.Address; depth++; } else { Debug.Assert(prevOffset >= 0); depth--; if (change.Offset + 1 != prevOffset) { // End of region at new offset. Output address info for space // between previous start or end. PrintAddressInfo(sb, formatter, depth + 1, prevAddr, change.Offset + 1 - prevOffset, showBank); } sb.Append(formatter.FormatOffset24(change.Offset)); PrintDepthLines(sb, depth, false); sb.Append("+- " + "end"); if (change.Region.IsFloating) { sb.Append(" (floating)"); } //PrintAddress(sb, formatter, change.Address, showBank); //sb.Append(")"); sb.Append(CRLF); // Add a blank line, but with the depth lines. if (depth > 0) { PrintDepthLines(sb, depth, true); } sb.Append(CRLF); // Use offset+1 here so it lines up with start records. prevOffset = lastEndOffset = change.Offset + 1; prevAddr = change.Address; } } Debug.Assert(depth == 0); return(sb.ToString()); }
/// <summary> /// Analyzes instruction operands and Address data descriptors to identify references /// to offsets within the file. /// /// Instructions with format descriptors are left alone. Instructions with /// operand offsets but no descriptor will have a descriptor generated /// using the label at the target offset; if the target offset is unlabeled, /// a unique label will be generated. Data descriptors with type=Address are /// handled the same way. /// /// In some cases, such as a reference to the middle of an instruction, we will /// label a nearby location instead. /// /// This should be called after code analysis has run, user labels and format /// descriptors have been applied, and platform/project symbols have been merged /// into the symbol table. /// </summary> /// <returns>True on success.</returns> public void AnalyzeDataTargets() { mDebugLog.LogI("Analyzing data targets..."); for (int offset = 0; offset < mAnattribs.Length; offset++) { Anattrib attr = mAnattribs[offset]; if (attr.IsInstructionStart) { if (attr.DataDescriptor != null) { // It's being shown as numeric, or as a reference to some other symbol. // Either way there's nothing further for us to do. (Technically we // would want to treat it like the no-descriptor case if the type was // numeric/Address, but we don't allow that for instructions.) Debug.Assert(attr.DataDescriptor.FormatSubType != FormatDescriptor.SubType.Address); continue; } int operandOffset = attr.OperandOffset; if (operandOffset >= 0) { // This is an offset reference: a branch or data access instruction whose // target is inside the file. Create a FormatDescriptor for it, and // generate a label at the target if one is not already present. SetDataTarget(offset, attr.Length, operandOffset); } // We advance by a single byte, rather than .Length, in case there's // an instruction embedded inside another one. } else if (attr.DataDescriptor != null) { // We can't check IsDataStart / IsInlineDataStart because the bytes might // still be uncategorized. If there's a user-specified format, check it // to see if it's an address. FormatDescriptor dfd = attr.DataDescriptor; // Is this numeric/Address? if ((dfd.FormatType == FormatDescriptor.Type.NumericLE || dfd.FormatType == FormatDescriptor.Type.NumericBE) && dfd.FormatSubType == FormatDescriptor.SubType.Address) { // Treat like an absolute address. Convert the operand // to an address, then resolve the file offset. int address = RawData.GetWord(mFileData, offset, dfd.Length, (dfd.FormatType == FormatDescriptor.Type.NumericBE)); if (dfd.Length < 3) { // Bank not specified by data, add current program bank. Not always // correct, but should be often enough. In most cases we'd just // assume a correct data bank register, but here we need to find // a file offset, so we have to assume data bank == program bank // (unless we find a good way to track the data bank register). address |= attr.Address & 0x7fff0000; } int operandOffset = mProject.AddrMap.AddressToOffset(offset, address); if (operandOffset >= 0) { SetDataTarget(offset, dfd.Length, operandOffset); } } // For other formats, we don't need to do anything. Numeric/Address is // the only one that represents an offset reference. Numeric/Symbol // is a name reference. The others are just data. // There shouldn't be any data items inside other data items, so we // can just skip forward. offset += mAnattribs[offset].DataDescriptor.Length - 1; } } }