概述:许多编程语言都允许访问和离散控制系统内存。如果你是这些语言的调试提供者,你可能需要提供对调试期间的内存查看和操作的支持。Eclipse调试框架提供了一个内存视图,同时提供一个可扩展的框架来简化和标准化这个过程。这篇文章主要介绍了Eclipse的内存视图以及如何定制你自己的内存支持。】

作者:SamanthaChan, IBM Toronto Lab

翻译:Winger 校对:Nancy

前言

    内存视图是由Eclipse调试框架提供,用来检查程序内存的。它是一个灵活的视图,允许调试扩展者自定义内存监视器的展示方式来满足他们的用户需求。你可以使用SWT的控件来展示内存监视器。

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

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

【译】Eclipse调试之内存视图原理解析:调试开发者指南_调试视图  定制 MemoryView

1 内存视图

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

做为一个调试扩展者,可以通过两种方法来扩展内存视图:

    重用调试平台提供的内存渲染器   

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

     创建自定义的内存渲染器

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

 

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

 

运行实例

这篇文章附带了一个简单的实例,用来帮助学习怎么添加一个内存监视器支持到调试器中。这个例子由一个启动配置、调试模型对象、调试引擎组成。

运行这个例子:

1、 将例子解压到eclipse plugin目录

2、 重启工作区、重建一个“Debug Nothing”的启动配置

3、点击“Debug

启动以后,内存视图会自动打开,“Add Memory Monitor”按钮变为可用。点击然后输入任一表达式,调试器就会模拟计算表达式为一个地址,然后你可以添加一个内存渲染器到视图中。

 

内存视图框架

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

【译】Eclipse调试之内存视图原理解析:调试开发者指南_调试视图  定制 MemoryView _02

2 内存视图框架

内存块Memory Block

一个内存块代表调试器中分配的一块内存。它是一个调试模型对象,扩展IMemoryBlock或者IMemoryBlockExtension。在后面的章节我们会讨论IMemoryBlockIMemoryBlockExtension的不同。

内存块管理器Memory Block Manager

内存块管理器管理工作台中的所有内存块。你可以使用内存块管理器查询当前显示的内存块。另外,当一个新的内存块被添加或删除时,管理器会广播添加删除事件。你可能向内存块管理器注册一个监听,用来接收这些事件的通知。

内存视图

内存视图是一个内存渲染位置。内存渲染地址中存放了工作台拥有的内存渲染器。内存视图同时提供了一个同步服务,使得同一个内存渲染地址中的渲染器可以交互。

内存渲染类型

一个内存渲染类型表示一种内存渲染方式。它是通过plugin.xml提供的。一个内存渲染类型指定一个渲染类型代理,代理的作用就是在内存视图中创建一个内存渲染器。内存渲染类型不会绑定到某个调试模型上,它是在工作台全局定义的。重用一个已经存在的渲染类型,模型必须创建一个内存渲染绑定来将渲染类型绑定到模型上。

内存渲染绑定

定义对一个调试模型起作用的内存渲染类型。可以通过plugin.xml配置。

内存渲染管理器

管理工作台中所有的渲染类型和渲染绑定。

 

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

 

向调试器添加内存监视器

你的调试模型需要扩展两个关键的接口:

1.        调试目标必须扩展IMemoryBlockRetrieval或者IMemoryBlockRetrivalExtensionIMemoryBlockRetrieval和它的扩展,能够在需要的时候向调试目标查询内存块。

2.        调试模型必须扩展IMemoryBlock或者它的扩展IMemoryBlockExtension。它们代表了调试器中的内存监视。在用户需要查询内存时,通过这个接口与调试模型进行交互。

 

扩展IMemoryBlockRetrieval

一般由调试目标扩展这个接口。扩展的两个方法:

  IMemoryBlockRetrieval:

 public boolean supportsStorageRetrieval();
 public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException;

supportsStorageRetrieval()方法允许用户查询调试目标是否支持内存块查看。如果返回true,”Add Memory Moditor”的按钮会变为可用,否则,将一直不可用。

getMemoryBlock()方法允许用户从调试目标中请求一个内存块。调用,调试目标会创建并返回一个内存块,这个内存块就是你所给的起始地址加上长度。如果创建出现问题,调试目标需要通过抛出DebugException报告一个错误。

IMemoryBlockIMemoryBlockRetrieval允许用户创建具有基本功能的简单内存监视器。比如,扩展IMemoryBlock,将限制用户只能查看固定范围的内存,用户不能查看超过范围(起始地址加长度)的内存块。

为了提供更多的高级功能给用户,调试模型必须扩展IMemoryBlockRetrievalExtension和与它对应的IMemoryBlockExtensionIMemoryBlockRetrievalExtension要求调试模型扩展额外的方法:

    IMemoryBlockRetrievalExtension:

   public IMemoryBlockExtension getExtendedMemoryBlock(String expression, Object context)throws DebugException;

当请求一个内存块时,用户通过IMemoryBlockRetrievalExtension来向调试目标请求IMemoryBlockExtension。调用时,getExtendedMemoryBlock()方法将给定的表达式转化为一个地址,然后返回一个IMemoryBlockExtension。如果这个过程出错,调试目标需要通过抛出DebugException报告一个错误。

SmapleDebugTarget示例扩展了IMemoryBlockRetrievalExtension

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。如果失败则抛出异常。

 

IMemoryBlockRetrievalExtensionIMemoryBlockRetrieval选择 

IMemoryBlockRetrievalIMemoryBlockeclipse2.0创建,是一套允许调试目标创建一个给定地址的简单内存块的简单API,内存块结果是固定的,而且只允许调用者从整个内存块中查看内存。当一个IMemoryBlock在渲染器中显示时,用户不能查看超出范围的内存块。因为扩展这些接口只需要很少的方法,所以使用起来比较简单。

IMemoryBlockRetrievalExtensionIMemoryBlockExtensioneclipse3.1中引入。它提供了丰富的方法集来允许用户获取目标系统的更多信息。由于IMemoryBlockRetrievalExtension能够根据一个表达式或者调试上下文创建内存块,所以在创建一个内存监视器时不会被强制输入一个地址。另外,内存块结果(一个IMemoryBlockExtension)是动态的,允许调用者从任意地址查看内存。允许在表格渲染器中动态使用滚动条,允许用户在系统的任意可用地址查看内存。

下表总结了IMemoryBlockRetrievalExtensionIMemoryBlockRetrieval的区别:

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.

下一节详细讨论IMemoryBlockIMemoryBlockExtension的不同。

 

扩展IMemoryBlockIMemoryBlockExtension

一个内存块在调试模型中代表一个内存监视器。允许用户从调试模型中请求一块内存放入视图中。IMemoryBlock是一个非常简单的扩展接口。它设计的时候对目标系统做了少许的假定。IMemoryBlock不向用户提供扩展和更多的信息。

IMemoryBlockExtension是一个复杂的接口,它提供扩展和更多的目标系统信息。但是,用户在内存查看时需要提供其他一些更丰富的内容。

IMemoryBlock提供以下方法用于扩展:

     public long getStartAddress();
     public long getLength();
     public byte[]getBytes() throws DebugException;
     public void setValue(longoffset, byte[] bytes) throws DebugException;

固定长度的内存块

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()方法:

public boolean supportsValueModification() {    
        return fDebugTarget.getEngine().supportsValueModification(this);
    }

如果平台支持内存修改,这个方法会返回TRUE。否则返回FALSE,渲染器中的action就会不可用。

如果用户从表格渲染器中完成了内存的修改,渲染器会告知内存块使用setValues修改内存。然后内存块告诉调试引擎在指定的位置修改内存。如果出错,会抛出调试异常:

   public void setValue(BigInteger offset, byte[] bytes) throws DebugException {    
        try {
            // ask the engine to modify memory at specified address
         fDebugTarget.getEngine().setValue(fBaseAddress.add(offset), bytes);
         fireContentChangeEvent();
        } catch (RuntimeException e) {
            IStatus status = new Status(IStatus.ERROR, "example.debug.memoryview", 0,
                "Failed to edit.", e);
            DebugException exception = new DebugException(status);
         throw exception;
        }
    }

setValues完成后,内存块需要发出一个内容改变的调试事件。表格渲染器从内存块中监听修改事件然后更新。如果出问题,引擎抛出调试异常。

当用户删除一个内存块时,内存视图框架调用IMemoryBlockExtension.dispose() 方法。这时候调试器需要执行必要的清除工作。比如你的内存块是缓存数据,你需要在dispose调用时清空缓存。如果你的引擎正在某个确定位置监控内存,你需要通知它内存监视器已经删除,它需要停止监视。    

在我们的例子中,调试目标跟踪内存监视器。Dispose方法中给我们一个从调试目标中删除内存块的机会:

     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的模型展示SampleModelPresentationSampleModelPresesntation.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,渲染器将使用默认的前端颜色。

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

 

总结

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

使用表格格式显示内存监视器只是一种方式。如果表格渲染器不足以满足用户,你可以自己创建一个定制化的渲染类型。你可以随意扩展。

 

 原文地址:http://www.eclipse.org/articles/article.php?file=Article-MemoryView/index.html