您的位置:首页 > 编程语言 > Java开发

Eclipse调试之内存视图原理解析:调试开发者指南

2015-11-04 08:41 417 查看
概述:许多编程语言都允许访问和离散控制系统内存。如果你是这些语言的调试提供者,你可能需要提供对调试期间的内存查看和操作的支持。Eclipse调试框架提供了一个内存视图,同时提供一个可扩展的框架来简化和标准化这个过程。这篇文章主要介绍了Eclipse的内存视图以及如何定制你自己的内存支持。】
作者:SamanthaChan, IBM Toronto Lab
翻译:Winger 校对:Nancy
前言
内存视图是由Eclipse调试框架提供,用来检查程序内存的。它是一个灵活的视图,允许调试扩展者自定义内存监视器的展示方式来满足他们的用户需求。你可以使用SWT的控件来展示内存监视器。

内存视图在开发之初,一个目标就是支持不同人的不同需求和模型。它需要能够支持从在大型机上工作的开发人员到小型嵌入式设备上的开发者的需求。这么大范围的群体的需求千差万别。当在一个平台上使用某种特定的、可能对用户有意义的内存展示方式来表示内存时,在另一个平台可能对用户来说完全是混乱不堪的。

上面的问题的解决办法就是,提供一个视图和一个框架,允许调试扩展者来定义自己的内存监视器展示方式。这篇文章就要讨论这个框架,演示如何在调试器中添加内存视图支持。




图1
内存视图
图1是内存视图,它由三窗格组成。左边的窗格叫做监视器(Monitors)窗格,列出了被当前调试会话监视的所有的内存监视器。右边的两个窗格叫做渲染结果(Renderings)窗格。它们显示的是从第一个监视窗格中选中的内存监视器的监视结果。一个内存渲染器就是一个内存监视器的用户展示,它使用一种展示的格式来展示内存监视的内容。在图1中,内存视图显示了内存监视器“&str”的两个渲染效果。十六进制渲染使用传统的表格格式显示,十六进制树渲染使用树的格式显示。
做为一个调试扩展者,可以通过两种方法来扩展内存视图:
重用调试平台提供的内存渲染器

调试平台提供了可重用的四个表格渲染器集合:十六进制、ASCII、有符号整形和无符号整形。你可以在你的调试器中很简单地添加内存监视器,重用这些渲染器。这些渲染器支持自定制。图1中十六进制渲染器就是调试平台提供的一个表格渲染器。

创建自定义的内存渲染器

这个方式需要更多的工作,但是可以控制内存监视器对用户的展示。另外,你可以在调试器中定义新的内存渲染器类型来提供内存监视支持,然后使用自定义的渲染器类型来展示内存监视器。图1中的十六进制树就是一个自定义的渲染器类型。

这篇文章教你怎样在调试器中添加内存监视支持。同时,也会解释怎样重用调试平台提供的表格渲染器。另外,这篇文章将会讨论在表格渲染器中的可用定制点。

运行实例

这篇文章附带了一个简单的实例,用来帮助学习怎么添加一个内存监视器支持到调试器中。这个例子由一个启动配置、调试模型对象、调试引擎组成。
运行这个例子:
1、 将例子解压到eclipse plugin目录
2、 重启工作区、重建一个“Debug Nothing”的启动配置
3、点击“Debug”
启动以后,内存视图会自动打开,“Add Memory Monitor”按钮变为可用。点击然后输入任一表达式,调试器就会模拟计算表达式为一个地址,然后你可以添加一个内存渲染器到视图中。

内存视图框架

为了能够添加内存监视器到你的调试器中,你需要对内存视图框架有一个基本的了解。图2是内存视图框架。



图2
内存视图框架
内存块Memory Block
一个内存块代表调试器中分配的一块内存。它是一个调试模型对象,扩展IMemoryBlock或者IMemoryBlockExtension。在后面的章节我们会讨论IMemoryBlock与IMemoryBlockExtension的不同。
内存块管理器Memory Block Manager
内存块管理器管理工作台中的所有内存块。你可以使用内存块管理器查询当前显示的内存块。另外,当一个新的内存块被添加或删除时,管理器会广播添加删除事件。你可能向内存块管理器注册一个监听,用来接收这些事件的通知。
内存视图
内存视图是一个内存渲染位置。内存渲染地址中存放了工作台拥有的内存渲染器。内存视图同时提供了一个同步服务,使得同一个内存渲染地址中的渲染器可以交互。
内存渲染类型
一个内存渲染类型表示一种内存渲染方式。它是通过plugin.xml提供的。一个内存渲染类型指定一个渲染类型代理,代理的作用就是在内存视图中创建一个内存渲染器。内存渲染类型不会绑定到某个调试模型上,它是在工作台全局定义的。重用一个已经存在的渲染类型,模型必须创建一个内存渲染绑定来将渲染类型绑定到模型上。
内存渲染绑定
定义对一个调试模型起作用的内存渲染类型。可以通过plugin.xml配置。
内存渲染管理器
管理工作台中所有的渲染类型和渲染绑定。

为了使用内存视图,你的调试器必须能够创建并返回一个内存块。内存块用来从调试引擎/系统中获取内存。另外,你的调试器必须定义你所需要的内存渲染绑定。下面的章节将会解释怎样向你的调试器中添加内存块,也会讲到如何定义内存渲染绑定和重用调试平台提供的内存渲染器。

向调试器添加内存监视器

你的调试模型需要扩展两个关键的接口:
1. 调试目标必须扩展IMemoryBlockRetrieval或者IMemoryBlockRetrivalExtension。IMemoryBlockRetrieval和它的扩展,能够在需要的时候向调试目标查询内存块。
2. 调试模型必须扩展IMemoryBlock或者它的扩展IMemoryBlockExtension。它们代表了调试器中的内存监视。在用户需要查询内存时,通过这个接口与调试模型进行交互。

扩展IMemoryBlockRetrieval
一般由调试目标扩展这个接口。扩展的两个方法:
IMemoryBlockRetrieval:
public IMemoryBlockExtension getExtendedMemoryBlock(String expression,Object context) throws DebugException {
// ask debug engine for anaddress
BigIntegeraddress = getEngine().evaluateExpression(expression, context);
// if address can beevaluated to an address, create memory block
if (address != null)
{
IMemoryBlockExtensionmemoryBlock =  newSampleMemoryBlock(this, expression, address);
fMemoryBlocks.add(memoryBlock);
returnmemoryBlock;
}
// otherwise throw debugexception
IStatus status = newStatus(IStatus.ERROR, "example.debug.memoryview", 0, "Expressioncannot be evaluated to an address", null);
DebugException exception =new DebugException(status);
throw exception;
}

当请求一个内存块时,调试目标将请求发送给调试引擎来计算地址,如果地址计算成功,返回一个SampleMemoryBlock,它扩展了IMemoryBlockExtension。如果失败则抛出异常。

IMemoryBlockRetrievalExtension和IMemoryBlockRetrieval选择
IMemoryBlockRetrieval和IMemoryBlock在eclipse2.0创建,是一套允许调试目标创建一个给定地址的简单内存块的简单API,内存块结果是固定的,而且只允许调用者从整个内存块中查看内存。当一个IMemoryBlock在渲染器中显示时,用户不能查看超出范围的内存块。因为扩展这些接口只需要很少的方法,所以使用起来比较简单。
IMemoryBlockRetrievalExtension和IMemoryBlockExtension在eclipse3.1中引入。它提供了丰富的方法集来允许用户获取目标系统的更多信息。由于IMemoryBlockRetrievalExtension能够根据一个表达式或者调试上下文创建内存块,所以在创建一个内存监视器时不会被强制输入一个地址。另外,内存块结果(一个IMemoryBlockExtension)是动态的,允许调用者从任意地址查看内存。允许在表格渲染器中动态使用滚动条,允许用户在系统的任意可用地址查看内存。
下表总结了IMemoryBlockRetrievalExtension和IMemoryBlockRetrieval的区别:
IMemoryBlockRetrieval
IMemoryBlockRetrievalExtension
Creates IMemoryBlock
, a basic static memory block. Does not offer as much functionality, but easier to implement.
Creates IMemoryBlockExtension
,provides much more functionality. More difficult to implement.
User must enter an address and length to create a memory monitor.
User can enter an arbitrary expression when creating a memory monitor (e.g. variable name).
Content of the resulting memory monitor must be retrieved as a whole and dynamic scrolling is not allowed.
Resulting memory monitor can retrieve memory from any valid location, which enables dynamic scrolling in table renderings.
下一节详细讨论IMemoryBlock与IMemoryBlockExtension的不同。

扩展IMemoryBlock和IMemoryBlockExtension
一个内存块在调试模型中代表一个内存监视器。允许用户从调试模型中请求一块内存放入视图中。IMemoryBlock是一个非常简单的扩展接口。它设计的时候对目标系统做了少许的假定。IMemoryBlock不向用户提供扩展和更多的信息。
IMemoryBlockExtension是一个复杂的接口,它提供扩展和更多的目标系统信息。但是,用户在内存查看时需要提供其他一些更丰富的内容。
IMemoryBlock提供以下方法用于扩展:
固定长度的内存块
IMemoryBlock是一个固定长度的内存监视器。内存范围由起始地址和长度确定。getBytes方法的调用者只能查看整个这一块内存,不能查看其他的地址。当查看结果使用渲染器显示时,将会不允许用户滚动查看超过内存范围的内存。
只支持32位的地址
IMemoryBlock支持少于等于32位的地址。在内存块中地址用long类型表示。所以,最大的地址是long的最大值。如果目标系统支持的内存地址超过32位,就不能使用IMemoryBlock来表示目标系统了。
只支持字节地址单元(byte-size addressable unit)
IMemoryBlock假设目标系统有字节地址单元。因为getBytes返回的一个字节数组,内存块长度是内存块的字节数。它只能表示最小的地址为8位目标系统。在一些系统中,最小地址单元大于8位,比如16位和32位,IMemoryBlock是无法表示的。
不支持字节序(Endianness)
IMemoryBlock不提供查询目标系统字节序的方法,所以渲染器渲染内存时比较难。
IMemoryBlockExtension提供以下扩展方法:
    public BigInteger getBigBaseAddress() throws DebugException;
public int getAddressSize() throws DebugException;
public BigInteger getMemoryBlockStartAddress() throws DebugException;
public BigInteger getMemoryBlockEndAddress() throws DebugException;
public int getAddressableSize() throws DebugException;
public MemoryByte[] getBytesFromAddress(BigInteger address, long units) throws DebugException;
public MemoryByte[] getBytesFromOffset(BigInteger unitOffset, long addressableUnits) throws DebugException

支持任意大小的地址
IMemoryBlockExtension能够处理大于32位地址长度的平台。因为所有的地址用BigInteger表示,所以没有地址长度限制。
内存块边界
IMemoryBlockExtension允许指定一个内存块边界。边界一旦确定,用户不能检查超出边界的内存。这样可以阻止用户访问他们不应该访问的内存区域。
支持多字节地址单元
在一些系统中,最小的内存地址单元是大于一个字节的。IMemoryBlockExtension允许调试提供者指定地址单元的大小。内存渲染器通过运用这些信息来正确展示内存块。
允许调用者动态检索内存
当申请检索一个块内存时,IMemoryBlockExtension返回一个MemoryByte而不是字节数组。MemoryByte是字节的包装类,它由一个属性和一个值组成。属性允许在特定字节提供系统的额外信息,比如你可以在MemoryByte中让writable属性可用来指定一个内存字节可写。
在做扩展时,通过用户是否需要IMemoryBlockExtension提供的额外功能来决定扩展哪些内容。扩展IMemoryBlockExtension的主要好处就是,它支持动态检索内存,也因此在内存查看时有更好的用户体验。它非常的灵活,在调试时允许调试模型提供很多应用程序的信息。
例子:
实例中的SampleMemoryBlock就是扩展IMemoryBlockExtension。内存块的BaseAddress在各个地方使用,它是内存块中一个非常重要的属性。它用来产生渲染器的显示,计算渲染器读取内存的开始位置。在例子中getBigBaseAddress()方法的实现如下:
public BigInteger getBigBaseAddress() throws DebugException {
fBaseAddress = fDebugTarget.getEngine().evaluateExpression(fExpression, null);
return fBaseAddress;
}

每当需要返回内存块的BaseAddress时,SampleMemoryBlock重新计算表达式的值然后返回新的地址。如果计算出错,内存块抛出DebugException。为了优化代码,避免与调试引擎交互,设计BaseAddress的缓存,只有在需要时更新它的值。
调试平台提供的表格渲染器使用getBytesFromAddress方法从内存块中请求内容:
public MemoryByte[] getBytesFromAddress(BigInteger address, long length) throws DebugException {
try {
MemoryByte[] bytes = new MemoryByte[(int)length * fDebugTarget.getEngine().getAddressableSize()];
BigInteger addressCnt = address;
int lengthCnt = (int)length;
int i=0;
// asks engine to get bytes from address
MemoryByte[] engineBytes =  fDebugTarget.getEngine().getBytesFromAddress(addressCnt, lengthCnt);
System.arraycopy(engineBytes, 0, bytes, i, engineBytes.length);
// pad with dummy memory if engine did not return enough memory
return bytes;
} catch (RuntimeException e) {
throw e;
}
}

当请求发生时,内存块首先构造一个MemoryByte的缓存区用以保存引擎返回的内存结果。然后内存块向引擎请求需要的内存,内存块拷贝缓存区内容,避免引擎修改内存,拷贝完成后返回MemoryByte给它的调用者。如果检索内存过程中出现错误,引擎抛出一个内存块的运行时异常。
如果开发者允许用户修改内存块,必须扩展supportsValueModification()方法:
如果平台支持内存修改,这个方法会返回TRUE。否则返回FALSE,渲染器中的action就会不可用。
如果用户从表格渲染器中完成了内存的修改,渲染器会告知内存块使用setValues修改内存。然后内存块告诉调试引擎在指定的位置修改内存。如果出错,会抛出调试异常:
[code]当setValues完成后,内存块需要发出一个内容改变的调试事件。表格渲染器从内存块中监听修改事件然后更新。如果出问题,引擎抛出调试异常。
当用户删除一个内存块时,内存视图框架调用IMemoryBlockExtension.dispose() 方法。这时候调试器需要执行必要的清除工作。比如你的内存块是缓存数据,你需要在dispose调用时清空缓存。如果你的引擎正在某个确定位置监控内存,你需要通知它内存监视器已经删除,它需要停止监视。

在我们的例子中,调试目标跟踪内存监视器。Dispose方法中给我们一个从调试目标中删除内存块的机会:
[code=java;toolbar:false]     public void dispose() throws DebugException {
// remove this memory block from debug target
fDebugTarget.removeMemoryBlock(this);
}


重用表格渲染器

现在你已经在内存模型中扩展了内存监视器,接下来准备使用表格渲染器显示内存块。调试平台提供了四个渲染类型:
Name
Rendering Type Id
Description
Hex
org.eclipse.debug.ui.rendering.raw_memory
Renders memory as raw memory. It does not take endianness of the platform into account. Content of the memory block is shown in hexadecimal values.
ASCII
org.eclipse.debug.ui.rendering.ascii
Renders memory into ASCII strings. The codepage used to convert memory into ASCII strings is defined by a code page preference that can be found in the
Memory View. The default code page is CP1252.
Signed Integer
org.eclipse.debug.ui.rendering.signedint
Renders memory into signed integers. If the endianness of the memory block is known, the rendering renders memory according to the endianness of the memory
block. Otherwise, the rendering defaults to render memory as big endian. Users can switch between big and little endian when working with this rendering.
Unsigned Integer
org.eclipse.debug.ui.rendering.unsignedint
Renders memory into unsigned integers. If the endianness of the memory block is known, the rendering renders memory according to the endianness of the
memory block. Otherwise, the rendering defaults to render memory as big endian. Users can switch between big and little endian when working with this rendering.
要重用这些渲染器,必须在plugin.xml中创建一个内存渲染绑定。内存渲染绑定定义了你的内存块中支持的渲染类型。它列出了当用户使用你的内存块能看到的渲染类型。我们提供的例子中,平台提供的所有四种类型SampleMemoryBlock能够使用:
<extension point="org.eclipse.debug.ui.memoryRenderings">
<renderingBindings
defaultIds="org.eclipse.debug.ui.rendering.raw_memory,org.eclipse.debug.ui.rendering.signedint"
primaryId="org.eclipse.debug.ui.rendering.raw_memory"
renderingIds="org.eclipse.debug.ui.rendering.raw_memory,
org.eclipse.debug.ui.rendering.ascii,
org.eclipse.debug.ui.rendering.signedint,
org.eclipse.debug.ui.rendering.unsignedint">
<enablement>
<instanceof value="example.debug.memoryview.internal.core.SampleMemoryBlock"/>
</enablement>
</renderingBindings>
</extension>

如上,一个渲染绑定由四个属性组成:
l
renderingIds,定义了内存模型支持的渲染类型列表
l
defaultIds,定义了默认需要创建的渲染器
l
primaryId,定义了在渲染器面板上,默认渲染器里需要默认选中的渲染器
内存视图用两个渲染面板来显示内存渲染器。左边的面板是主渲染面板,右边的面试为第二面板。主渲染面板在内存视图打开时默认打开。第二个面板在需要时由用户打开。
当在defaultIds属性中制定了多个渲染器,在主面板中就不能确定哪一个应该被打开。为了解决这个问题,内存渲染绑定定义了primaryid属性。主渲染类型只有一个,它默认会在主面板创建。Defaultids定义的其他渲染器将会在第二面板创建。
内存渲染绑定可以使用表达式关联内存块类型。例子中,当SampleMemoryBlock添加到工作台,渲染器绑定就会决定应该创建哪个渲染器。
为了查看内存渲染绑定怎样工作,删除renderingids属性的一个id。当渲染器类型没有在renderingids中时,这个渲染器对用户就会不可用。

定制表格渲染器

几乎表格渲染器的所有内容都可以定制。你可以定制标签的装饰,给每一个单元格添加不同的图标,使用不同的颜色渲染内容,甚至改变行与列的表头。下表总结了表格渲染器可以定制的点,同时描述了在定制表格渲染器时你需要提供的适配器。
Attribute
Description
Adapter
Text in a cell
The text displayed in each cell.
ILabelProvider
Icon in a cell
The icon displayed in each cell.
ILabelProvider
Color
The foreground and background color in each cell.
IColorProvider
Column Heading
The column headings displayed in the table rendering.
IMemoryBlockTablePresentation
Row Heading
The text displayed in the address column.
IMemoryBlockTablePresentation
Label or Icon of the rendering
The label displayed in the tab item.
ILabelDecorator
表格渲染器的可定制点使用IAdaptable提供扩展。定制表格渲染器的一个属性,你的内存块必须提供对应的适配器,并在调用getAdapter时返回。比如,定制每个单元格的文本,你的内存块需要提供ILabelProvider适配器。在文本显示之前,渲染器从内存块向ILabelProvider发送请求。如果适配器返回,渲染器根据适配器去显示每个单元格的文本。
当调用适配器时会给一个MemoryRederingElement的对象,MemoryRederingElement描述了当前被渲染的内存,它提供以下信息:
l
MemoryRederingElement的渲染器
l
要渲染的MemoryByte数组
l
内存的地址
适配器可以使用这些信息来渲染出不同的内存结果
例子:
我们的例子中,当内存是只读时使用蓝色显示。如果可写,使用默认的颜色。为了定制颜色,需要在SampleMemoryBlock中提供IColorProvider适配器:
public Object getAdapter(Class adapter) {
if (adapter.equals(IMemoryBlockRetrievalExtension.class))
return getDebugTarget();
if (adapter == IColorProvider.class)
{
return SampleModelPresentation.getSampleModelPresentation();
}
return super.getAdapter(adapter);
}

当SampleMemoryBlock需要返回一个IColorProvider适配器时,它返回实现了IColorProvider的模型展示SampleModelPresentation,SampleModelPresesntation.getForeground()的实现是:
public Color getForeground(Object element) {
if (element instanceof MemoryRenderingElement)
{
MemoryRenderingElement elm = (MemoryRenderingElement) element;
MemoryByte[] bytes = elm.getBytes();
if (!bytes[0].isWritable())
{
return blue;
}
}
return null;
}

这个例子中假定第一个字节可写,则整个单元格可写。如果返回null,渲染器将使用默认的前端颜色。

示例调试引擎从0xAB123456到0xAB123556为只读内存。转到0xAB123456地址你将会看到单元格为蓝色。

总结

这篇文章教你怎样使用内存视图框架来支持自己的模型监视器。你的调试适配器需要扩展IMemoryBlockRetrieval 和IMemoryBlock。如果你想优化体验,提供更多高级特性,调试适配器需要扩展IMemoryBlockRetreivalExtension和IMemoryBlockExtension。然后你需要决定你需要使用的渲染器。为了在渲染器中显示内存块,你需要绑定你的内存块和渲染器类型。最后,表格渲染器是非常灵活的,你可以通过提供适当的适配器来定制你的表格渲染器。
使用表格格式显示内存监视器只是一种方式。如果表格渲染器不足以满足用户,你可以自己创建一个定制化的渲染类型。你可以随意扩展。

原文地址:http://www.eclipse.org/articles/article.php?file=Article-MemoryView/index.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: