mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-29 19:45:01 +02:00
Implementation of mixed disassembly mode.
This commit is contained in:
parent
8ca7329e88
commit
a19d23361a
13 changed files with 553 additions and 260 deletions
|
@ -1,3 +1,12 @@
|
|||
2004-05-06 Mikhail Khodjaiants
|
||||
Implementation of mixed disassembly mode.
|
||||
* IAsmSourceLine.java: new
|
||||
* IDisassembly.java
|
||||
* IDisassemblyBlock.java: new
|
||||
* AsmSourceLine.java: new
|
||||
* Disassembly.java
|
||||
* DisassemblyBlock.java: new
|
||||
|
||||
2004-04-30 Mikhail Khodjaiants
|
||||
New copyright.
|
||||
* IJumpToAddress.java
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/**********************************************************************
|
||||
* Copyright (c) 2004 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Common Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/cpl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* QNX Software Systems - Initial API and implementation
|
||||
***********************************************************************/
|
||||
package org.eclipse.cdt.debug.core.model;
|
||||
|
||||
/**
|
||||
* A source line in disassembly.
|
||||
*/
|
||||
public interface IAsmSourceLine {
|
||||
|
||||
/**
|
||||
* Returns the array of the disassembly instructions associated with this source line.
|
||||
*
|
||||
* @return the array of the disassembly instructions associated with this source line
|
||||
*/
|
||||
IAsmInstruction[] getInstructions();
|
||||
}
|
|
@ -16,26 +16,13 @@ import org.eclipse.debug.core.DebugException;
|
|||
* Represents the disassembly of a debug target.
|
||||
*/
|
||||
public interface IDisassembly extends ICDebugElement {
|
||||
|
||||
/**
|
||||
* Returns the list of disassembly instructions associated
|
||||
* with the given stack frame.
|
||||
*
|
||||
* @param frame the stack frame for which the instructions re required.
|
||||
* @return the list of disassembly instructions associated with
|
||||
* the given stack frame
|
||||
* @throws DebugException if this method fails.
|
||||
*/
|
||||
public IAsmInstruction[] getInstructions( ICStackFrame frame ) throws DebugException;
|
||||
|
||||
/**
|
||||
* Returns the list of disassembly instructions that begins at the given address.
|
||||
* The size of the requested list is specified by <code>length</code>.
|
||||
* Returns the disassembly block for given stack frame.
|
||||
*
|
||||
* @param address the start address
|
||||
* @param length the size of the requested list
|
||||
* @return the specified list of disassembly instructions
|
||||
* @param frame the stack frame for which the disassembly is required
|
||||
* @return the disassembly block for given stack frame
|
||||
* @throws DebugException if this method fails.
|
||||
*/
|
||||
public IAsmInstruction[] getInstructions( long address, int length ) throws DebugException;
|
||||
IDisassemblyBlock getDisassemblyBlock( ICStackFrame frame ) throws DebugException;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/**********************************************************************
|
||||
* Copyright (c) 2004 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Common Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/cpl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* QNX Software Systems - Initial API and implementation
|
||||
***********************************************************************/
|
||||
package org.eclipse.cdt.debug.core.model;
|
||||
|
||||
/**
|
||||
* Represents a contiguous segment of disassembly in an execution context.
|
||||
*/
|
||||
public interface IDisassemblyBlock {
|
||||
|
||||
/**
|
||||
* Returns the parent disassembly object.
|
||||
*
|
||||
* @return the parent disassembly object
|
||||
*/
|
||||
IDisassembly getDisassembly();
|
||||
|
||||
/**
|
||||
* Returns the platform-dependent path of the executable associated
|
||||
* with this segment.
|
||||
*
|
||||
* @return the platform-dependent path of the executable
|
||||
*/
|
||||
String getModuleFile();
|
||||
|
||||
/**
|
||||
* Returns whether this block contains given stack frame.
|
||||
*
|
||||
* @param frame the stack frame
|
||||
* @return whether this block contains given stack frame
|
||||
*/
|
||||
boolean contains( ICStackFrame frame );
|
||||
|
||||
/**
|
||||
* Return the array of source lines associated with this block.
|
||||
*
|
||||
* @return the array of source lines associated with this block
|
||||
*/
|
||||
IAsmSourceLine[] getSourceLines();
|
||||
|
||||
/**
|
||||
* Returns whether this block contains mixed source/disassembly information.
|
||||
*
|
||||
* @return whether this block contains mixed source/disassembly information
|
||||
*/
|
||||
boolean isMixedMode();
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/**********************************************************************
|
||||
* Copyright (c) 2004 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Common Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/cpl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* QNX Software Systems - Initial API and implementation
|
||||
***********************************************************************/
|
||||
package org.eclipse.cdt.debug.internal.core.model;
|
||||
|
||||
import org.eclipse.cdt.debug.core.cdi.model.ICDIInstruction;
|
||||
import org.eclipse.cdt.debug.core.model.IAsmInstruction;
|
||||
import org.eclipse.cdt.debug.core.model.IAsmSourceLine;
|
||||
|
||||
/**
|
||||
* Adapter for ICDIMixedInstruction.
|
||||
*/
|
||||
public class AsmSourceLine implements IAsmSourceLine {
|
||||
|
||||
private String fText;
|
||||
|
||||
private IAsmInstruction[] fInstructions = null;
|
||||
|
||||
/**
|
||||
* Constructor for AsmSourceLine.
|
||||
*/
|
||||
public AsmSourceLine( String text, ICDIInstruction[] cdiInstructions ) {
|
||||
fText = text;
|
||||
fInstructions = new IAsmInstruction[cdiInstructions.length];
|
||||
for ( int i = 0; i < fInstructions.length; ++i ) {
|
||||
fInstructions[i] = new AsmInstruction( cdiInstructions[i] );
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.cdt.debug.core.model.IAsmSourceLine#getInstructions()
|
||||
*/
|
||||
public IAsmInstruction[] getInstructions() {
|
||||
return fInstructions;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return fText;
|
||||
}
|
||||
}
|
|
@ -18,9 +18,10 @@ import org.eclipse.cdt.debug.core.cdi.CDIException;
|
|||
import org.eclipse.cdt.debug.core.cdi.ICDISession;
|
||||
import org.eclipse.cdt.debug.core.cdi.ICDISourceManager;
|
||||
import org.eclipse.cdt.debug.core.cdi.model.ICDIInstruction;
|
||||
import org.eclipse.cdt.debug.core.model.IAsmInstruction;
|
||||
import org.eclipse.cdt.debug.core.cdi.model.ICDIMixedInstruction;
|
||||
import org.eclipse.cdt.debug.core.model.ICStackFrame;
|
||||
import org.eclipse.cdt.debug.core.model.IDisassembly;
|
||||
import org.eclipse.cdt.debug.core.model.IDisassemblyBlock;
|
||||
import org.eclipse.cdt.debug.core.model.IExecFileInfo;
|
||||
import org.eclipse.debug.core.DebugException;
|
||||
|
||||
|
@ -31,7 +32,7 @@ public class Disassembly extends CDebugElement implements IDisassembly {
|
|||
|
||||
final static private int DISASSEMBLY_BLOCK_SIZE = 100;
|
||||
|
||||
private IAsmInstruction[] fInstructions = new IAsmInstruction[0];
|
||||
private DisassemblyBlock[] fBlocks = new DisassemblyBlock[1];
|
||||
|
||||
/**
|
||||
* Constructor for Disassembly.
|
||||
|
@ -43,85 +44,63 @@ public class Disassembly extends CDebugElement implements IDisassembly {
|
|||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.cdt.debug.core.model.IDisassembly#getInstructions(org.eclipse.cdt.debug.core.model.ICStackFrame)
|
||||
* @see org.eclipse.cdt.debug.core.model.IDisassembly#getDisassemblyBlock(org.eclipse.cdt.debug.core.model.ICStackFrame)
|
||||
*/
|
||||
public IAsmInstruction[] getInstructions( ICStackFrame frame ) throws DebugException {
|
||||
long address = frame.getAddress();
|
||||
if ( !containsAddress( address ) ) {
|
||||
loadInstructions( frame );
|
||||
public IDisassemblyBlock getDisassemblyBlock( ICStackFrame frame ) throws DebugException {
|
||||
if ( fBlocks[0] == null || !fBlocks[0].contains( frame ) ) {
|
||||
fBlocks[0] = createBlock( frame );
|
||||
}
|
||||
return fInstructions;
|
||||
return fBlocks[0];
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.cdt.debug.core.model.IDisassembly#getInstructions(long, int)
|
||||
*/
|
||||
public IAsmInstruction[] getInstructions( long address, int length ) throws DebugException {
|
||||
if ( !containsAddress( address ) ) {
|
||||
loadInstructions( address, length );
|
||||
}
|
||||
return fInstructions;
|
||||
}
|
||||
|
||||
private boolean containsAddress( long address ) {
|
||||
for ( int i = 0; i < fInstructions.length; ++i ) {
|
||||
if ( fInstructions[i].getAdress() == address )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean containsAddress( ICDIInstruction[] instructions, long address ) {
|
||||
for( int i = 0; i < instructions.length; ++i ) {
|
||||
if ( instructions[i].getAdress() == address )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void loadInstructions( ICStackFrame frame ) throws DebugException {
|
||||
fInstructions = new IAsmInstruction[0];
|
||||
private DisassemblyBlock createBlock( ICStackFrame frame ) throws DebugException {
|
||||
ICDISession session = (ICDISession)getDebugTarget().getAdapter( ICDISession.class );
|
||||
if ( session != null ) {
|
||||
ICDISourceManager sm = session.getSourceManager();
|
||||
if ( sm != null ) {
|
||||
String fileName = frame.getFile();
|
||||
int lineNumber = frame.getLineNumber();
|
||||
ICDIInstruction[] instructions = new ICDIInstruction[0];
|
||||
ICDIMixedInstruction[] mixedInstrs = new ICDIMixedInstruction[0];
|
||||
long address = frame.getAddress();
|
||||
if ( fileName != null && fileName.length() > 0 ) {
|
||||
try {
|
||||
instructions = sm.getInstructions( fileName,
|
||||
lineNumber,
|
||||
CDebugCorePlugin.getDefault().getPluginPreferences().getInt( ICDebugConstants.PREF_MAX_NUMBER_OF_INSTRUCTIONS ) );
|
||||
mixedInstrs = sm.getMixedInstructions( fileName,
|
||||
lineNumber,
|
||||
CDebugCorePlugin.getDefault().getPluginPreferences().getInt( ICDebugConstants.PREF_MAX_NUMBER_OF_INSTRUCTIONS ) );
|
||||
}
|
||||
catch( CDIException e ) {
|
||||
targetRequestFailed( CoreModelMessages.getString( "Disassembly.Unable_to_get_disassembly_instructions_1" ), e ); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
if ( instructions.length == 0 ||
|
||||
if ( mixedInstrs.length == 0 ||
|
||||
// Double check if debugger returns correct address range.
|
||||
!containsAddress( instructions, address ) ) {
|
||||
!containsAddress( mixedInstrs, address ) ) {
|
||||
if ( address >= 0 ) {
|
||||
try {
|
||||
instructions = getFunctionInstructions( sm.getInstructions( address, address + DISASSEMBLY_BLOCK_SIZE ) );
|
||||
ICDIInstruction[] instructions = getFunctionInstructions( sm.getInstructions( address, address + DISASSEMBLY_BLOCK_SIZE ) );
|
||||
return DisassemblyBlock.create( this, instructions );
|
||||
}
|
||||
catch( CDIException e ) {
|
||||
targetRequestFailed( CoreModelMessages.getString( "Disassembly.Unable_to_get_disassembly_instructions_2" ), e ); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
}
|
||||
fInstructions = new IAsmInstruction[instructions.length];
|
||||
for ( int i = 0; i < fInstructions.length; ++i ) {
|
||||
fInstructions[i] = new AsmInstruction( instructions[i] );
|
||||
else {
|
||||
return DisassemblyBlock.create( this, mixedInstrs );
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void loadInstructions( long address, int length ) throws DebugException {
|
||||
fInstructions = new IAsmInstruction[0];
|
||||
|
||||
private boolean containsAddress( ICDIMixedInstruction[] mi, long address ) {
|
||||
for( int i = 0; i < mi.length; ++i ) {
|
||||
ICDIInstruction[] instructions = mi[i].getInstructions();
|
||||
for ( int j = 0; j < instructions.length; ++j )
|
||||
if ( instructions[j].getAdress() == address )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ICDIInstruction[] getFunctionInstructions( ICDIInstruction[] rawInstructions ) {
|
||||
|
@ -139,7 +118,11 @@ public class Disassembly extends CDebugElement implements IDisassembly {
|
|||
}
|
||||
|
||||
public void dispose() {
|
||||
fInstructions = null;
|
||||
for ( int i = 0; i < fBlocks.length; ++i )
|
||||
if ( fBlocks[i] != null ) {
|
||||
fBlocks[i].dispose();
|
||||
fBlocks[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
/**********************************************************************
|
||||
* Copyright (c) 2004 QNX Software Systems and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Common Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/cpl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* QNX Software Systems - Initial API and implementation
|
||||
***********************************************************************/
|
||||
package org.eclipse.cdt.debug.internal.core.model;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.LineNumberReader;
|
||||
|
||||
import org.eclipse.cdt.debug.core.cdi.model.ICDIInstruction;
|
||||
import org.eclipse.cdt.debug.core.cdi.model.ICDIMixedInstruction;
|
||||
import org.eclipse.cdt.debug.core.model.IAsmInstruction;
|
||||
import org.eclipse.cdt.debug.core.model.IAsmSourceLine;
|
||||
import org.eclipse.cdt.debug.core.model.ICStackFrame;
|
||||
import org.eclipse.cdt.debug.core.model.IDisassembly;
|
||||
import org.eclipse.cdt.debug.core.model.IDisassemblyBlock;
|
||||
import org.eclipse.cdt.debug.core.model.IExecFileInfo;
|
||||
import org.eclipse.cdt.debug.core.sourcelookup.ICSourceLocator;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IStorage;
|
||||
import org.eclipse.core.runtime.IAdaptable;
|
||||
import org.eclipse.debug.core.model.ISourceLocator;
|
||||
|
||||
/**
|
||||
* CDI-based implementation of <code>IDisassemblyBlock</code>.
|
||||
*/
|
||||
public class DisassemblyBlock implements IDisassemblyBlock, IAdaptable {
|
||||
|
||||
private IDisassembly fDisassembly;
|
||||
|
||||
private IAsmSourceLine[] fSourceLines;
|
||||
|
||||
private long fStartAddress = 0;
|
||||
|
||||
private long fEndAddress = 0;
|
||||
|
||||
private boolean fMixedMode = false;
|
||||
|
||||
/**
|
||||
* Constructor for DisassemblyBlock.
|
||||
*/
|
||||
private DisassemblyBlock( IDisassembly disassembly ) {
|
||||
fDisassembly = disassembly;
|
||||
}
|
||||
|
||||
public static DisassemblyBlock create( IDisassembly disassembly, ICDIMixedInstruction[] instructions ) {
|
||||
DisassemblyBlock block = new DisassemblyBlock( disassembly );
|
||||
block.setMixedMode( true );
|
||||
ISourceLocator adapter = disassembly.getDebugTarget().getLaunch().getSourceLocator();
|
||||
ICSourceLocator locator = null;
|
||||
if ( adapter instanceof IAdaptable ) {
|
||||
locator = (ICSourceLocator)((IAdaptable)adapter).getAdapter( ICSourceLocator.class );
|
||||
}
|
||||
block.setSourceLines( createSourceLines( locator, instructions ) );
|
||||
block.initializeAddresses();
|
||||
return block;
|
||||
}
|
||||
|
||||
public static DisassemblyBlock create( IDisassembly disassembly, ICDIInstruction[] instructions ) {
|
||||
DisassemblyBlock block = new DisassemblyBlock( disassembly );
|
||||
block.setMixedMode( false );
|
||||
block.setSourceLines( createSourceLines( instructions ) );
|
||||
block.initializeAddresses();
|
||||
return block;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.cdt.debug.core.model.IDisassemblyBlock#getDisassembly()
|
||||
*/
|
||||
public IDisassembly getDisassembly() {
|
||||
return fDisassembly;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.cdt.debug.core.model.IDisassemblyBlock#getModuleFile()
|
||||
*/
|
||||
public String getModuleFile() {
|
||||
IDisassembly d = getDisassembly();
|
||||
if ( d != null ) {
|
||||
IExecFileInfo info = (IExecFileInfo)d.getAdapter( IExecFileInfo.class );
|
||||
if ( info != null && info.getExecFile() != null ) {
|
||||
return info.getExecFile().getLocation().toOSString();
|
||||
}
|
||||
}
|
||||
return ""; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.cdt.debug.core.model.IDisassemblyBlock#contains(org.eclipse.cdt.debug.core.model.ICStackFrame)
|
||||
*/
|
||||
public boolean contains( ICStackFrame frame ) {
|
||||
if ( !getDisassembly().getDebugTarget().equals( frame.getDebugTarget() ) )
|
||||
return false;
|
||||
long address = frame.getAddress();
|
||||
return (address >= fStartAddress && address <= fEndAddress);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.cdt.debug.core.model.IDisassemblyBlock#getSourceLines()
|
||||
*/
|
||||
public IAsmSourceLine[] getSourceLines() {
|
||||
return fSourceLines;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
|
||||
*/
|
||||
public Object getAdapter( Class adapter ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.cdt.debug.core.model.IDisassemblyBlock#isMixedMode()
|
||||
*/
|
||||
public boolean isMixedMode() {
|
||||
return fMixedMode;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
}
|
||||
|
||||
private static IAsmSourceLine[] createSourceLines( ICSourceLocator locator, ICDIMixedInstruction[] mi ) {
|
||||
IAsmSourceLine[] result = new IAsmSourceLine[mi.length];
|
||||
LineNumberReader reader = null;
|
||||
if ( result.length > 0 && locator != null ) {
|
||||
String fileName = mi[0].getFileName();
|
||||
Object element = locator.findSourceElement( fileName );
|
||||
File file= null;
|
||||
if ( element instanceof IFile ) {
|
||||
file = ((IFile)element).getLocation().toFile();
|
||||
}
|
||||
else if ( element instanceof IStorage ) {
|
||||
file = ((IStorage)element).getFullPath().toFile();
|
||||
}
|
||||
if ( file != null ) {
|
||||
try {
|
||||
reader = new LineNumberReader( new FileReader( file ) );
|
||||
}
|
||||
catch( FileNotFoundException e ) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for ( int i = 0; i < result.length; ++i ) {
|
||||
String text = null;
|
||||
if ( reader != null ) {
|
||||
int lineNumber = mi[i].getLineNumber();
|
||||
while( reader.getLineNumber() + 1 < lineNumber ) {
|
||||
try {
|
||||
reader.readLine();
|
||||
}
|
||||
catch( IOException e ) {
|
||||
}
|
||||
}
|
||||
if ( reader.getLineNumber() + 1 == lineNumber ) {
|
||||
try {
|
||||
text = reader.readLine() + '\n';
|
||||
}
|
||||
catch( IOException e ) {
|
||||
}
|
||||
}
|
||||
}
|
||||
result[i] = new AsmSourceLine( text, mi[i].getInstructions() );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IAsmSourceLine[] createSourceLines( ICDIInstruction[] instructions ) {
|
||||
return new IAsmSourceLine[] { new AsmSourceLine( "", instructions ) }; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
private void initializeAddresses() {
|
||||
if ( fSourceLines.length > 0 ) {
|
||||
IAsmInstruction[] instr = fSourceLines[0].getInstructions();
|
||||
fStartAddress = instr[0].getAdress();
|
||||
instr = fSourceLines[fSourceLines.length - 1].getInstructions();
|
||||
fEndAddress = instr[instr.length - 1].getAdress();
|
||||
}
|
||||
}
|
||||
|
||||
private void setMixedMode( boolean mixedMode ) {
|
||||
this.fMixedMode = mixedMode;
|
||||
}
|
||||
|
||||
private void setSourceLines( IAsmSourceLine[] sourceLines ) {
|
||||
this.fSourceLines = sourceLines;
|
||||
}
|
||||
}
|
|
@ -1,3 +1,11 @@
|
|||
2004-05-06 Mikhail Khodjaiants
|
||||
Implementation of mixed disassembly mode.
|
||||
* ToggleBreakpointAdapter.java
|
||||
* DisassemblyMessages.properties
|
||||
* DisassemblyAnnotationModel.java
|
||||
* DisassemblyEditorInput.java
|
||||
* DisassemblyView.java
|
||||
|
||||
2004-05-02 Mikhail Khodjaiants
|
||||
Check if the new stack frame is of the same debug target as the old one.
|
||||
* DisassemblyEditorInput.java
|
||||
|
|
|
@ -18,7 +18,6 @@ import org.eclipse.cdt.debug.internal.ui.views.disassembly.DisassemblyEditorInpu
|
|||
import org.eclipse.cdt.debug.internal.ui.views.disassembly.DisassemblyView;
|
||||
import org.eclipse.cdt.debug.ui.CDebugUIPlugin;
|
||||
import org.eclipse.cdt.debug.ui.ICDebugUIConstants;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.resources.IWorkspaceRoot;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
|
@ -109,20 +108,25 @@ public class ToggleBreakpointAdapter implements IToggleBreakpointsTarget {
|
|||
IResource resource = ResourcesPlugin.getWorkspace().getRoot();
|
||||
String sourceHandle = getSourceHandle( input );
|
||||
long address = ((DisassemblyEditorInput)input).getAddress( lineNumber );
|
||||
ICAddressBreakpoint breakpoint = CDIDebugModel.addressBreakpointExists( sourceHandle, resource, address );
|
||||
if ( breakpoint != null ) {
|
||||
DebugPlugin.getDefault().getBreakpointManager().removeBreakpoint( breakpoint, true );
|
||||
if ( address != 0 ) {
|
||||
ICAddressBreakpoint breakpoint = CDIDebugModel.addressBreakpointExists( sourceHandle, resource, address );
|
||||
if ( breakpoint != null ) {
|
||||
DebugPlugin.getDefault().getBreakpointManager().removeBreakpoint( breakpoint, true );
|
||||
}
|
||||
else {
|
||||
CDIDebugModel.createAddressBreakpoint( sourceHandle,
|
||||
resource,
|
||||
address,
|
||||
true,
|
||||
0,
|
||||
"", //$NON-NLS-1$
|
||||
true );
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
CDIDebugModel.createAddressBreakpoint( sourceHandle,
|
||||
resource,
|
||||
address,
|
||||
true,
|
||||
0,
|
||||
"", //$NON-NLS-1$
|
||||
true );
|
||||
errorMessage = ActionMessages.getString( "ToggleBreakpointAdapter.Invalid_line_1" ); //$NON-NLS-1$
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +145,7 @@ public class ToggleBreakpointAdapter implements IToggleBreakpointsTarget {
|
|||
if ( !(input instanceof DisassemblyEditorInput) ||
|
||||
((DisassemblyEditorInput)input).equals( DisassemblyEditorInput.EMPTY_EDITOR_INPUT ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ( selection instanceof ITextSelection );
|
||||
}
|
||||
|
@ -258,8 +262,7 @@ public class ToggleBreakpointAdapter implements IToggleBreakpointsTarget {
|
|||
return ((IStorageEditorInput)input).getStorage().getName();
|
||||
}
|
||||
if ( input instanceof DisassemblyEditorInput ) {
|
||||
IFile file = ((DisassemblyEditorInput)input).getModuleFile();
|
||||
return ( file != null ) ? file.getLocation().toOSString() : ""; //$NON-NLS-1$
|
||||
return ((DisassemblyEditorInput)input).getModuleFile();
|
||||
}
|
||||
return ""; //$NON-NLS-1$
|
||||
}
|
||||
|
|
|
@ -137,10 +137,9 @@ public class DisassemblyAnnotationModel extends AnnotationModel {
|
|||
Position position = null;
|
||||
DisassemblyEditorInput input = getInput();
|
||||
if ( input != null ) {
|
||||
long address = input.getBreakpointAddress( breakpoint );
|
||||
int start = -1;
|
||||
if ( address > 0 && document != null ) {
|
||||
int instrNumber = input.getInstructionNumber( address );
|
||||
if ( document != null ) {
|
||||
int instrNumber = input.getInstructionLine( breakpoint );
|
||||
if ( instrNumber > 0 ) {
|
||||
try {
|
||||
start = fDocument.getLineOffset( instrNumber - 1 );
|
||||
|
|
|
@ -13,13 +13,14 @@ package org.eclipse.cdt.debug.internal.ui.views.disassembly;
|
|||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.cdt.debug.core.model.IAsmInstruction;
|
||||
import org.eclipse.cdt.debug.core.model.IAsmSourceLine;
|
||||
import org.eclipse.cdt.debug.core.model.IBreakpointTarget;
|
||||
import org.eclipse.cdt.debug.core.model.ICDebugTarget;
|
||||
import org.eclipse.cdt.debug.core.model.ICLineBreakpoint;
|
||||
import org.eclipse.cdt.debug.core.model.ICStackFrame;
|
||||
import org.eclipse.cdt.debug.core.model.IDisassembly;
|
||||
import org.eclipse.cdt.debug.core.model.IExecFileInfo;
|
||||
import org.eclipse.cdt.debug.core.model.IDisassemblyBlock;
|
||||
import org.eclipse.cdt.debug.internal.ui.CDebugUIUtils;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.debug.core.DebugException;
|
||||
import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.ui.IEditorInput;
|
||||
|
@ -30,148 +31,6 @@ import org.eclipse.ui.IPersistableElement;
|
|||
*/
|
||||
public class DisassemblyEditorInput implements IEditorInput {
|
||||
|
||||
/**
|
||||
* A storage object used by Disassembly view.
|
||||
*/
|
||||
private static class DisassemblyStorage {
|
||||
|
||||
private IDisassembly fDisassembly;
|
||||
private IAsmInstruction[] fInstructions;
|
||||
protected String fContents;
|
||||
protected long fStartAddress = 0;
|
||||
protected long fEndAddress = 0;
|
||||
|
||||
/**
|
||||
* Constructor for DisassemblyStorage.
|
||||
*/
|
||||
public DisassemblyStorage( IDisassembly disassembly, IAsmInstruction[] instructions ) {
|
||||
fDisassembly = disassembly;
|
||||
fInstructions = ( instructions != null ) ? instructions : new IAsmInstruction[0];
|
||||
initializeAddresses();
|
||||
createContent();
|
||||
}
|
||||
|
||||
public String getContents() {
|
||||
return fContents;
|
||||
}
|
||||
|
||||
public IDisassembly getDisassembly() {
|
||||
return this.fDisassembly;
|
||||
}
|
||||
|
||||
public boolean containsFrame( ICStackFrame frame ) {
|
||||
if ( !getDisassembly().getDebugTarget().equals( frame.getDebugTarget() ) )
|
||||
return false;
|
||||
long address = frame.getAddress();
|
||||
return (address >= fStartAddress && address <= fEndAddress);
|
||||
}
|
||||
|
||||
public IFile getModuleFile() {
|
||||
IDisassembly d = getDisassembly();
|
||||
IFile result = null;
|
||||
if ( d != null ) {
|
||||
IExecFileInfo info = (IExecFileInfo)d.getAdapter( IExecFileInfo.class );
|
||||
if ( info != null ) {
|
||||
result = info.getExecFile();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public long getBreakpointAddress( ICLineBreakpoint breakpoint ) {
|
||||
IDisassembly dis = getDisassembly();
|
||||
if ( dis != null ) {
|
||||
IBreakpointTarget bt = (IBreakpointTarget)dis.getDebugTarget().getAdapter( IBreakpointTarget.class );
|
||||
if ( bt != null ) {
|
||||
try {
|
||||
return bt.getBreakpointAddress( breakpoint );
|
||||
}
|
||||
catch( DebugException e ) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void createContent() {
|
||||
StringBuffer lines = new StringBuffer();
|
||||
int maxFunctionName = 0;
|
||||
int maxOpcodeLength = 0;
|
||||
long maxOffset = 0;
|
||||
for( int i = 0; i < fInstructions.length; ++i ) {
|
||||
String functionName = fInstructions[i].getFunctionName();
|
||||
if ( functionName.length() > maxFunctionName ) {
|
||||
maxFunctionName = functionName.length();
|
||||
}
|
||||
String opcode = fInstructions[i].getOpcode();
|
||||
if ( opcode.length() > maxOpcodeLength )
|
||||
maxOpcodeLength = opcode.length();
|
||||
if ( fInstructions[i].getOffset() > maxOffset ) {
|
||||
maxOffset = fInstructions[i].getOffset();
|
||||
}
|
||||
}
|
||||
int instrPos = calculateInstructionPosition( maxFunctionName, maxOffset );
|
||||
int argPosition = instrPos + maxOpcodeLength + 1;
|
||||
for( int i = 0; i < fInstructions.length; ++i ) {
|
||||
lines.append( getInstructionString( fInstructions[i], instrPos, argPosition ) );
|
||||
}
|
||||
fContents = lines.toString();
|
||||
}
|
||||
|
||||
private String getInstructionString( IAsmInstruction instruction, int instrPosition, int argPosition ) {
|
||||
int worstCaseSpace = Math.max( instrPosition, argPosition );
|
||||
char[] spaces = new char[worstCaseSpace];
|
||||
Arrays.fill( spaces, ' ' );
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if ( instruction != null ) {
|
||||
sb.append( CDebugUIUtils.toHexAddressString( instruction.getAdress() ) );
|
||||
sb.append( ' ' );
|
||||
String functionName = instruction.getFunctionName();
|
||||
if ( functionName != null && functionName.length() > 0 ) {
|
||||
sb.append( '<' );
|
||||
sb.append( functionName );
|
||||
if ( instruction.getOffset() != 0 ) {
|
||||
sb.append( '+' );
|
||||
sb.append( instruction.getOffset() );
|
||||
}
|
||||
sb.append( ">:" ); //$NON-NLS-1$
|
||||
sb.append( spaces, 0, instrPosition - sb.length() );
|
||||
}
|
||||
sb.append( instruction.getOpcode() );
|
||||
sb.append( spaces, 0, argPosition - sb.length() );
|
||||
sb.append( instruction.getArguments() );
|
||||
sb.append( '\n' );
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private int calculateInstructionPosition( int maxFunctionName, long maxOffset ) {
|
||||
return (16 + maxFunctionName + Long.toString( maxOffset ).length());
|
||||
}
|
||||
|
||||
private void initializeAddresses() {
|
||||
if ( fInstructions.length > 0 ) {
|
||||
fStartAddress = fInstructions[0].getAdress();
|
||||
fEndAddress = fInstructions[fInstructions.length - 1].getAdress();
|
||||
}
|
||||
}
|
||||
|
||||
public int getLineNumber( long address ) {
|
||||
for( int i = 0; i < fInstructions.length; ++i ) {
|
||||
if ( fInstructions[i].getAdress() == address ) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getAddress( int lineNumber ) throws IllegalArgumentException {
|
||||
if ( lineNumber > 0 && lineNumber <= fInstructions.length )
|
||||
return fInstructions[--lineNumber].getAdress();
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static final IEditorInput EMPTY_EDITOR_INPUT = new DisassemblyEditorInput();
|
||||
|
||||
public static final IEditorInput PENDING_EDITOR_INPUT =
|
||||
|
@ -182,9 +41,11 @@ public class DisassemblyEditorInput implements IEditorInput {
|
|||
};
|
||||
|
||||
/**
|
||||
* Storage associated with this editor input
|
||||
* Disassembly block associated with this editor input
|
||||
*/
|
||||
private DisassemblyStorage fStorage;
|
||||
private IDisassemblyBlock fBlock;
|
||||
|
||||
private String fContents = ""; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Constructor for DisassemblyEditorInput.
|
||||
|
@ -198,8 +59,9 @@ public class DisassemblyEditorInput implements IEditorInput {
|
|||
* @param disassembly
|
||||
* @param instructions
|
||||
*/
|
||||
public DisassemblyEditorInput( IDisassembly disassembly, IAsmInstruction[] instructions ) {
|
||||
fStorage = new DisassemblyStorage( disassembly, instructions );
|
||||
private DisassemblyEditorInput( IDisassemblyBlock block ) {
|
||||
fBlock = block;
|
||||
createContents();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
@ -245,29 +107,158 @@ public class DisassemblyEditorInput implements IEditorInput {
|
|||
}
|
||||
|
||||
public boolean contains( ICStackFrame frame ) {
|
||||
if ( fStorage != null ) {
|
||||
return fStorage.containsFrame( frame );
|
||||
if ( fBlock != null ) {
|
||||
return fBlock.contains( frame );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getContents() {
|
||||
return ( fStorage != null ) ? fStorage.getContents() : ""; //$NON-NLS-1$
|
||||
return fContents;
|
||||
}
|
||||
|
||||
public int getInstructionNumber( long address ) {
|
||||
return ( fStorage != null ) ? fStorage.getLineNumber( address ) : 0;
|
||||
public int getInstructionLine( long address ) {
|
||||
if ( fBlock != null ) {
|
||||
IAsmSourceLine[] lines = fBlock.getSourceLines();
|
||||
int result = 0;
|
||||
for ( int i = 0; i < lines.length; ++i ) {
|
||||
IAsmInstruction[] instructions = lines[i].getInstructions();
|
||||
++result;
|
||||
for ( int j = 0; j < instructions.length; ++j ) {
|
||||
++result;
|
||||
if ( instructions[j].getAdress() == address ) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getAddress( int lineNumber ) throws IllegalArgumentException {
|
||||
return ( fStorage != null ) ? fStorage.getAddress( lineNumber ) : 0;
|
||||
public int getInstructionLine( ICLineBreakpoint breakpoint ) {
|
||||
if ( fBlock != null ) {
|
||||
IDisassembly dis = fBlock.getDisassembly();
|
||||
if ( dis != null ) {
|
||||
IBreakpointTarget bt = (IBreakpointTarget)dis.getDebugTarget().getAdapter( IBreakpointTarget.class );
|
||||
if ( bt != null ) {
|
||||
try {
|
||||
long address = bt.getBreakpointAddress( breakpoint );
|
||||
if ( address != 0 )
|
||||
return getInstructionLine( address );
|
||||
}
|
||||
catch( DebugException e ) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public IFile getModuleFile() {
|
||||
return ( fStorage != null ) ? fStorage.getModuleFile() : null;
|
||||
public long getAddress( int lineNumber ) {
|
||||
if ( fBlock != null ) {
|
||||
IAsmSourceLine[] lines = fBlock.getSourceLines();
|
||||
int current = 0;
|
||||
for ( int i = 0; i < lines.length; ++i ) {
|
||||
IAsmInstruction[] instructions = lines[i].getInstructions();
|
||||
++current;
|
||||
if ( lineNumber == current )
|
||||
return instructions[0].getAdress();
|
||||
if ( lineNumber > current && lineNumber <= current + instructions.length )
|
||||
return instructions[lineNumber - current - 1].getAdress();
|
||||
current += instructions.length;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getBreakpointAddress( ICLineBreakpoint breakpoint ) {
|
||||
return ( fStorage != null ) ? fStorage.getBreakpointAddress( breakpoint ) : 0;
|
||||
public String getModuleFile() {
|
||||
return ( fBlock != null ) ? fBlock.getModuleFile() : null;
|
||||
}
|
||||
|
||||
public static DisassemblyEditorInput create( ICStackFrame frame ) throws DebugException {
|
||||
DisassemblyEditorInput input = null;
|
||||
IDisassembly disassembly = ((ICDebugTarget)frame.getDebugTarget()).getDisassembly();
|
||||
if ( disassembly != null ) {
|
||||
IDisassemblyBlock block = disassembly.getDisassemblyBlock( frame );
|
||||
input = new DisassemblyEditorInput( block );
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
private void createContents() {
|
||||
StringBuffer lines = new StringBuffer();
|
||||
int maxFunctionName = 0;
|
||||
int maxOpcodeLength = 0;
|
||||
long maxOffset = 0;
|
||||
if ( fBlock != null ) {
|
||||
IAsmSourceLine[] mi = fBlock.getSourceLines();
|
||||
for ( int j = 0; j < mi.length; ++j ) {
|
||||
IAsmInstruction[] instructions = mi[j].getInstructions();
|
||||
for( int i = 0; i < instructions.length; ++i ) {
|
||||
String functionName = instructions[i].getFunctionName();
|
||||
if ( functionName.length() > maxFunctionName ) {
|
||||
maxFunctionName = functionName.length();
|
||||
}
|
||||
String opcode = instructions[i].getOpcode();
|
||||
if ( opcode.length() > maxOpcodeLength )
|
||||
maxOpcodeLength = opcode.length();
|
||||
if ( instructions[i].getOffset() > maxOffset ) {
|
||||
maxOffset = instructions[i].getOffset();
|
||||
}
|
||||
}
|
||||
}
|
||||
int instrPos = calculateInstructionPosition( maxFunctionName, maxOffset );
|
||||
int argPosition = instrPos + maxOpcodeLength + 1;
|
||||
for ( int j = 0; j < mi.length; ++j ) {
|
||||
if ( fBlock.isMixedMode() )
|
||||
lines.append( getSourceLineString( mi[j] ) );
|
||||
IAsmInstruction[] instructions = mi[j].getInstructions();
|
||||
for( int i = 0; i < instructions.length; ++i ) {
|
||||
// if ( fBlock.isMixedMode() )
|
||||
// lines.append( "\t" ); //$NON-NLS-1$
|
||||
lines.append( getInstructionString( instructions[i], instrPos, argPosition ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
fContents = lines.toString();
|
||||
}
|
||||
|
||||
private String getInstructionString( IAsmInstruction instruction, int instrPosition, int argPosition ) {
|
||||
int worstCaseSpace = Math.max( instrPosition, argPosition );
|
||||
char[] spaces = new char[worstCaseSpace];
|
||||
Arrays.fill( spaces, ' ' );
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if ( instruction != null ) {
|
||||
sb.append( CDebugUIUtils.toHexAddressString( instruction.getAdress() ) );
|
||||
sb.append( ' ' );
|
||||
String functionName = instruction.getFunctionName();
|
||||
if ( functionName != null && functionName.length() > 0 ) {
|
||||
sb.append( '<' );
|
||||
sb.append( functionName );
|
||||
if ( instruction.getOffset() != 0 ) {
|
||||
sb.append( '+' );
|
||||
sb.append( instruction.getOffset() );
|
||||
}
|
||||
sb.append( ">:" ); //$NON-NLS-1$
|
||||
sb.append( spaces, 0, instrPosition - sb.length() );
|
||||
}
|
||||
sb.append( instruction.getOpcode() );
|
||||
sb.append( spaces, 0, argPosition - sb.length() );
|
||||
sb.append( instruction.getArguments() );
|
||||
sb.append( '\n' );
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private int calculateInstructionPosition( int maxFunctionName, long maxOffset ) {
|
||||
return (16 + maxFunctionName + Long.toString( maxOffset ).length());
|
||||
}
|
||||
|
||||
private String getSourceLineString( IAsmSourceLine line ) {
|
||||
String text = line.toString();
|
||||
if ( text == null ) {
|
||||
text = DisassemblyMessages.getString( "DisassemblyEditorInput.source_line_is_not_available_1" ) + '\n'; //$NON-NLS-1$
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,3 +4,4 @@ DisassemblyInstructionPointerAnnotation.Secondary_Pointer_1=Secondary Disassembl
|
|||
DisassemblyAnnotationHover.Multiple_markers_at_this_line_1=Multiple markers at this line
|
||||
HTMLTextPresenter.ellipsis=
|
||||
HTML2TextReader.dash=-
|
||||
DisassemblyEditorInput.source_line_is_not_available_1=<source line is not available>
|
||||
|
|
|
@ -10,14 +10,11 @@
|
|||
***********************************************************************/
|
||||
package org.eclipse.cdt.debug.internal.ui.views.disassembly;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.eclipse.cdt.debug.core.model.IAsmInstruction;
|
||||
import org.eclipse.cdt.debug.core.model.ICDebugTarget;
|
||||
import org.eclipse.cdt.debug.core.model.ICStackFrame;
|
||||
import org.eclipse.cdt.debug.core.model.IDisassembly;
|
||||
import org.eclipse.cdt.debug.internal.ui.ICDebugHelpContextIds;
|
||||
import org.eclipse.cdt.debug.internal.ui.IInternalCDebugUIConstants;
|
||||
import org.eclipse.cdt.debug.internal.ui.actions.CBreakpointPropertiesRulerAction;
|
||||
|
@ -529,13 +526,8 @@ public class DisassemblyView extends AbstractDebugEventHandlerView
|
|||
input = (IEditorInput)current;
|
||||
}
|
||||
else {
|
||||
IAsmInstruction[] instructions = new IAsmInstruction[0];
|
||||
try {
|
||||
IDisassembly disassembly = ((ICDebugTarget)frame.getDebugTarget()).getDisassembly();
|
||||
if ( disassembly != null ) {
|
||||
instructions = disassembly.getInstructions( frame );
|
||||
input = new DisassemblyEditorInput( disassembly, instructions );
|
||||
}
|
||||
input = DisassemblyEditorInput.create( frame );
|
||||
}
|
||||
catch( DebugException e ) {
|
||||
status = new Status( IStatus.ERROR,
|
||||
|
@ -553,8 +545,7 @@ public class DisassemblyView extends AbstractDebugEventHandlerView
|
|||
}
|
||||
|
||||
protected void selectAndReveal( ICStackFrame frame, IEditorInput input ) {
|
||||
long address = frame.getAddress();
|
||||
IRegion region = getLineInformation( address, input );
|
||||
IRegion region = getLineInformation( frame, input );
|
||||
if ( region != null ) {
|
||||
int start = region.getOffset();
|
||||
int length = region.getLength();
|
||||
|
@ -572,9 +563,9 @@ public class DisassemblyView extends AbstractDebugEventHandlerView
|
|||
/**
|
||||
* Returns the line information for the given line in the given editor
|
||||
*/
|
||||
private IRegion getLineInformation( long address, IEditorInput input ) {
|
||||
private IRegion getLineInformation( ICStackFrame frame, IEditorInput input ) {
|
||||
if ( input instanceof DisassemblyEditorInput ) {
|
||||
int line = ((DisassemblyEditorInput)input).getInstructionNumber( address );
|
||||
int line = ((DisassemblyEditorInput)input).getInstructionLine( frame.getAddress() );
|
||||
if ( line > 0 ) {
|
||||
try {
|
||||
return getSourceViewer().getDocument().getLineInformation( --line );
|
||||
|
@ -703,8 +694,8 @@ public class DisassemblyView extends AbstractDebugEventHandlerView
|
|||
Assert.isNotNull( model );
|
||||
DisassemblyInstructionPointerAnnotation instrPointer = getCurrentInstructionPointer();
|
||||
if ( instrPointer != null ) {
|
||||
model.removeAnnotation( instrPointer );
|
||||
setCurrentInstructionPointer( null );
|
||||
model.removeAnnotation( instrPointer );
|
||||
setCurrentInstructionPointer( null );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue