Visualize Assembly using DGML

Starting from Visual Studio 2010 Ultimate there is a cool feature called DGML (Directed Graph Markup Language).

I wrote a small script to convert the disassembled code from WinDBG into a DGML.

In order to use it, simply type the following commands under a debug session:

.shell -o LoadLibraryA.dgml -ci "uf kernel32!LoadLibraryA" cscript.exe /nologo dasm2dgml.js

A DGML file will be generated with the given name, and here is what it looks like:

Here is the source code:


 var EBB = [];

var hypertext=function(s){
  var r=[],L=s.length;
  for(var i=0;i<L;i++){
    var c=s.charAt(i);
    switch(c){
      case '"':r.push('&quot;');break;
      case '&':r.push('&amp;');break;
      case '<':r.push('&lt;');break;
      case '>':r.push('&gt;');break;
      default:r.push(c);}}
  return r.join('');
};

var map=function(f,v){var L=v.length,r=[];for(var i=0;i<L;i++)r.push(f(v[i]));return r;};

(function(){
  var blk;

  var CExtendedBasicBlock = function(name, previous, next){
    this.Address = '';
    this.Code = [];
    this.Name = name;
    this.Previous = previous;
    this.Next = next;
  };

  while(true)
  {
    if(WScript.StdIn.AtEndOfStream)
      break;
    var strSourceLine = WScript.StdIn.ReadLine().replace(/(^\s+)|(\s+$)/g, '');
    if(!strSourceLine)
      continue;
    if(strSourceLine.match(/.*:$/))
    {
      blk = new CExtendedBasicBlock(strSourceLine.slice(0, -1));
      EBB.push(blk);
    }
    else
    {
      blk.Address = blk.Address || strSourceLine.match(/^[^\s]+/)[0];
      blk.Code.push(strSourceLine.replace(/[^\s]*\s+/, '').replace(/[^\s]*\s+/, ''));
    }
  }
})();

EBB = EBB.sort(function(x, y){ return x.Address == y.Address ? 0 : x.Address > y.Address ? 1 : -1; });
for(var i = 1; i < EBB.length; i++)
{
  EBB[i].Previous = EBB[i - 1];
  EBB[i].Previous.Next = EBB[i];
}

WScript.Echo('<DirectedGraph Background="#FFFFFF" GraphDirection="TopToBottom" xmlns="https://schemas.microsoft.com/vs/2009/dgml">');
WScript.Echo('  <Nodes>');
map(function(blk){
  var content = hypertext(blk.Name + ' (' + blk.Address + ')') + '&#xD;&#xA;';
  map(function(instruction){
    content += '&#xD;&#xA;' + hypertext(instruction);
  }, blk.Code);
  WScript.Echo('    <Node Id="' + hypertext(blk.Name) + '" Label="' + content + '" />');
}, EBB);
WScript.Echo('  </Nodes>');
WScript.Echo('  <Links>');
map(function(blk){
  map(function(instruction){
    map(function(x){
      var idx = instruction.indexOf(x.Name);
      idx = idx >= 0 ? instruction.charAt(idx + x.Name.length) : -1;
      if(idx == '' || idx == ' ')
        WScript.Echo('    <Link Source="' + hypertext(blk.Name) + '" Target="' + hypertext(x.Name) + '" />');
    }, EBB);
  }, blk.Code);
  if(blk.Next && !(blk.Code[blk.Code.length - 1].match(/^[^\s]+/)[0] in {jmp: 0, ret: 0}))
    WScript.Echo('    <Link Category="FallThrough" Source="' + hypertext(blk.Name) + '" Target="' + hypertext(blk.Next.Name) + '" />');
}, EBB);
WScript.Echo('  </Links>');
WScript.Echo('  <Styles>');
WScript.Echo('    <Style TargetType="Node">');
WScript.Echo('      <Setter Property="FontFamily" Value="Consolas" />');
WScript.Echo('      <Setter Property="FontSize" Value="11" />');
WScript.Echo('      <Setter Property="Background" Value="White" />');
WScript.Echo('      <Setter Property="NodeRadius" Value="2" />');
WScript.Echo('    </Style>');
WScript.Echo('    <Style TargetType="Link">');
WScript.Echo('        <Condition Expression="HasCategory(\'FallThrough\')" />');
WScript.Echo('        <Setter Property="Background" Value="Red" />');
WScript.Echo('        <Setter Property="Stroke" Value="Red" />');
WScript.Echo('    </Style>');
WScript.Echo('  </Styles>');
WScript.Echo('</DirectedGraph>');

Notes:

  1. This script cannot generate 100% accurate control flow diagram, you will have to do further analysis (e.g. jmp eax).
  2. I haven't got a chance to test under WOA (ARM32), so I leave it as a homework for our readers.

Enjoy:)