The AVR disassembler

Trust is good, checks are better. An old proverb from the country I live in. You need to check in order to know your current situation. To be able to see if we're heading for the hall of fame, or for hell and damnation. So before I can start my real project for the AVR (my own kind of assembler) I need a tool to get from output right back to the input.
So I need a reverse engineering program for Atmel AVR object files. Such a product is generally referred to as a disassembler. It reads the object file (in Intel Hex) and converts all opcodes (short for Operation Codes, the raw numbers which are interpreted by the processor) back to human readable (and understandable) mnemonics. I started the project less than a week ago and by now I have a good working product. I am still beta testing, but the amount of errors is still very small.

AVRdis the disassembler

Below is the source code for the disassembler. This is a mature beta product, so there might still be some errors and minor inadequacies around. The source is, as usual, in Modula-2. You are free to port this high level language project to a lesser language, if you prefer to use C. But an error is easily made with C and it's lookalikes, especially when nibble-nine codes are to be decoded.

Since the source is in Modula-2 I can suffice to say that it needs little to no explanation. One remark however: I made this program with NEARLY ONLY qualified imports. So I do one IMPORT and next I couple a procedure and a library with the well known dot operator. See how far you get studying the source.

MODULE dis04;

(*     This software is released under the rules of the GNU GPL. Please feel free to improve this software, 
       if needed. You may even port this source to a lesser language like C. This software comes without any
       warranty of any kind.
       *)

(* version   disxx is a disassembler for Atmel AVR processors	*)
(*     01 : Try to get the framework running   	   	     			: Sept 20, 2006	*)
(*     02 : Make it adaptable	     	       	   		  		:  Oct 12, 2006	*)
(*     03 : Make it recognize bit constant names for ports	  		:  Oct 20, 2006	*)
(*     04 : Improved user messages and command line checking
       	    Added the support for the 'COMPILER/END' option
       	    Fixed the sts bug (parameters swapped) 	   			:  Oct 27, 2006 *)

FROM    MemPools	IMPORT	KillPool, MemPool, NewPool, PoolAllocate;

IMPORT  ASCII, Arguments, InOut, NumConv, Strings, SYSTEM, TextIO;


TYPE   Identifier 	 = ARRAY [0..63] OF CHAR;
       PortPtr	    	 = POINTER TO PortNode;
       NamePtr		 = POINTER TO NameNode;

       PortNode	 	 = RECORD
			      number	: CARDINAL;
			      Name	: Identifier;
			      next	: PortPtr
			   END;

       NameNode		 = RECORD
			      RegName	: Identifier;
			      BitNr	: CARDINAL;
			      BitName	: Identifier;
			      next	: NamePtr
			   END;


VAR    LstFile, InFile				: TextIO.File;
       storage		 			: Arguments.ArgTable;
       par1,
       Source, Target, Dest, path, option	: Identifier;
       PC, FlashRam, Eeprom, Sram, RAMstart,
       line, column				: CARDINAL;
       DecodeMode				: (Jump, ToMemory, FromMemory, None);
       DecodeNext, ok, BigFlash			: BOOLEAN;
       PortNames, BitNames			: MemPool;
       FirstPort				: PortPtr;
       FirstName				: NamePtr;


PROCEDURE UserMessage (nr    : CARDINAL);

BEGIN
   CASE  nr  OF
       0 : InOut.WriteCard (line, 5);	InOut.Write (':');     InOut.WriteCard (column, 5)
    |  1 : InOut.WriteString ("Syntax : AvrDis Infile [-t TargetCPU] [-v]")
    |  2 : InOut.WriteString ("AvrDis version 0.4, October 2006")
    |  3 : InOut.WriteString ("Could not open ");    InOut.WriteString (path)
    |  4 : InOut.WriteString ("Error in Configuration file. Aborting.")
    |  5 : InOut.WriteString ("Could not open source file. Aborting.")
    |  6 : InOut.WriteString ("No source file defined. Aborting.")
    |  7 : InOut.WriteString ("Could not create destination file.")
    |  8 : InOut.WriteString ("Error in Intel Hex file in line:column");	UserMessage (0)
    |  9 : InOut.WriteString ("Error in ");          InOut.WriteString (path)
    | 10 : InOut.WriteString ("Illegal IO port number; ignoring.")
    | 11 : InOut.WriteString ("Please specify a target processor.")
   ELSE
      InOut.WriteString ("Something weird happened while processing line");
      InOut.WriteCard (line, 5);
      InOut.WriteString (", column");
      InOut.WriteCard (column, 4)
   END;
   InOut.WriteLn
END UserMessage;


PROCEDURE StorePort (str   : Identifier; nr   : CARDINAL) : BOOLEAN;

VAR	  this, prev, new	   : PortPtr;
	  result   	   	   : INTEGER;

BEGIN
   this := FirstPort;
   prev := NIL;
   LOOP
      result := Strings.compare (this^.Name, str);
      IF  result = 0  THEN  RETURN FALSE  END;
      IF  result = 1  THEN  EXIT  END;
      prev := this;
      this := this^.next
   END;
   PoolAllocate (PortNames, new, SYSTEM.TSIZE (PortNode));
   new^.Name := str;
   new^.number := nr;
   new^.next := this;
   IF  prev = NIL  THEN
      FirstPort := new
   ELSE
      prev^.next := new
   END;
   RETURN TRUE
END StorePort;


PROCEDURE FindPort (n	   : CARDINAL; VAR str	  : Identifier);

VAR	  ThisOne    		: PortPtr;

BEGIN
   ThisOne := FirstPort;
   LOOP
      IF  ThisOne^.number = n  THEN
         str := ThisOne^.Name;
	 EXIT
      ELSE
         ThisOne := ThisOne^.next
      END;
      IF  Strings.StrEq (ThisOne^.Name, "|")  THEN
         str := "Reserved";
      	 EXIT
      END;
   END
END FindPort;


PROCEDURE StoreName (port   : Identifier; bit   : CARDINAL; name   : Identifier);

VAR	  this, new, prev		: NamePtr;

BEGIN
   this := FirstName;
   prev := NIL;
   LOOP
      IF  Strings.compare (this^.RegName, port) >= 0  THEN  EXIT  END;
      prev := this;
      this := this^.next
   END;
   PoolAllocate (BitNames, new, SYSTEM.TSIZE (NameNode));
   WITH  new^  DO
      RegName := port;
      BitNr   := bit;
      BitName := name;
      next    := this
   END;
   IF  prev = NIL  THEN
      FirstName := new
   ELSE
      prev^.next := new
   END
END StoreName;


PROCEDURE FindBit (reg   : Identifier; bit   : CARDINAL; VAR  str    : Identifier);

VAR	  ThisOne 	   	: NamePtr;
	  result		: INTEGER;

BEGIN
   ThisOne := FirstName;
   LOOP
      result := Strings.compare (ThisOne^.RegName, reg);
      IF  result = 0  THEN
         LOOP
	    IF  bit = ThisOne^.BitNr  THEN
	       str := ThisOne^.BitName;
	       RETURN
	    ELSE
	       ThisOne := ThisOne^.next
	    END;
	    IF  Strings.StrEq (reg, ThisOne^.RegName) = FALSE  THEN  EXIT  END
	 END
      ELSIF  result = -1  THEN
         ThisOne := ThisOne^.next
      ELSE
         EXIT
      END
   END;
   str [0] := CHR (bit + ORD ('0'));
   str [1] := 0C
END FindBit;


PROCEDURE Configure () : BOOLEAN;

VAR  nr	       	            : CARDINAL;
     PortName, BitName	    : Identifier;

BEGIN
   InOut.WriteString ("Disassembling source ");		InOut.WriteString (Source);
   InOut.WriteString (" for target processor ");	InOut.WriteString (Target);
   InOut.WriteLn;
   path := "/usr/local/AVR/";
   Strings.Append (path, Target);
   TextIO.OpenInput (InFile, path);
   IF  TextIO.Done () = FALSE  THEN
      UserMessage (3);
      InOut.WriteString ("Choosing ATmega8515 instead.");     		InOut.WriteLn;
      InOut.WriteBf;
      path := "/usr/local/AVR/ATmega8515";
      TextIO.OpenInput (InFile, path);
      IF  TextIO.Done () = FALSE  THEN  UserMessage (3);  RETURN FALSE  END
   END;
   REPEAT
      TextIO.GetString (InFile, option);
      IF  TextIO.EOF (InFile) = TRUE  THEN  UserMessage (9);  RETURN FALSE  END
   UNTIL Strings.StrEq (option, 'BEGIN');
   LOOP
      IF  TextIO.EOF (InFile) = TRUE  THEN  UserMessage (9);  RETURN FALSE  END;
      TextIO.GetString (InFile, option);
      IF     Strings.StrEq (option, 'END')        = TRUE  THEN  EXIT
      ELSIF  Strings.StrEq (option, 'FLASH')      = TRUE  THEN  TextIO.GetCard (InFile, FlashRam)
      ELSIF  Strings.StrEq (option, 'EEPROM')     = TRUE  THEN  TextIO.GetCard (InFile, Eeprom)
      ELSIF  Strings.StrEq (option, 'SRAM') 	  = TRUE  THEN  TextIO.GetCard (InFile, Sram)
      ELSIF  Strings.StrEq (option, 'RAMSTART')	  = TRUE  THEN  TextIO.GetCard (InFile, RAMstart)
      ELSIF  Strings.StrEq (option, 'COMPILER')	  = TRUE  THEN
         REPEAT  TextIO.GetString (InFile, option)  UNTIL Strings.StrEq (option, 'END')
      ELSIF  Strings.StrEq (option, 'PORTS')	  = TRUE  THEN
         LOOP
	    TextIO.GetString (InFile, option);
	    IF  Strings.StrEq (option, 'END') = TRUE  THEN  EXIT  END;
	    NumConv.Str2Num (nr, 16, option, ok);
	    TextIO.GetString (InFile, PortName);
	    IF  NOT ok  THEN  
	       UserMessage (10)
	    ELSE
	       ok := StorePort (PortName, nr)
	    END
	 END
      ELSIF  Strings.StrEq (option, 'BITS')	  = TRUE  THEN
         LOOP
	    TextIO.GetString (InFile, PortName);
	    IF  Strings.StrEq (PortName, 'END') = TRUE  THEN  EXIT  END;
	    nr := 7;
	    LOOP
	       TextIO.GetString (InFile, BitName);
	       IF  BitName [0] # '-'  THEN  StoreName (PortName, nr, BitName)  END;
	       IF  nr = 0  THEN  EXIT  ELSE  DEC (nr)  END
	    END
	 END
      ELSE
         InOut.WriteString ('Invalid parameter in ');
	 InOut.WriteString (path);		  InOut.Write ('.');
	 InOut.WriteLn;
	 RETURN FALSE
      END
   END;
   TextIO.Close (InFile);
   IF  FlashRam > 4096  THEN  BigFlash := TRUE  ELSE  BigFlash := FALSE  END;
   RETURN TRUE
END Configure;


PROCEDURE ReadChar (VAR  ch  : CHAR);

BEGIN
   ch := option [column];
   INC (column);
END ReadChar;


PROCEDURE ReadHex (width   : CARDINAL) : CARDINAL;

VAR   val, n	  : CARDINAL;
      ch   	  : CHAR;

BEGIN
   n := 0;
   val := 0;
   LOOP
      val := val * 16;
      ReadChar (ch);
      IF  ch < 'A'  THEN
         INC (val, ORD (ch) - ORD ('0'))
      ELSE
         INC (val, ORD (ch) - ORD ('A') + 10) 
      END;
      INC (n);
      IF  n = width  THEN  EXIT  END
   END;
   RETURN val
END ReadHex;


PROCEDURE GetRegs (word   : CARDINAL; VAR  str : Identifier);

VAR  reg1, reg2	  	  : CARDINAL;
     ok	   		  : BOOLEAN;
     substr		  : ARRAY [0..3] OF CHAR;

BEGIN
   word := word MOD 1024;		(*  throw away all rubbish  *)
   str:= 'R';
   reg2 := word MOD 16;
   IF  9 IN BITSET (word)  THEN  INC (reg2, 16)  END;
   reg1 := (word DIV 16) MOD 32;
   NumConv.Num2Str (reg1, 10, substr, ok);
   Strings.Append (str, substr);
   Strings.Append (str, ', R');
   NumConv.Num2Str (reg2, 10, substr, ok);
   Strings.Append (str, substr)
END GetRegs;


PROCEDURE DumpToken (word	: CARDINAL);

VAR   byte1, byte2  		: CARDINAL;
      char1, char2		: CHAR;
      str    			: ARRAY [0..7] OF CHAR;

BEGIN
   IF  word = 0A0DH  THEN
      Strings.Assign (str, '   Cr/Lf')
   ELSE
      Strings.Assign (str, '      ');
      byte1 := word DIV 256;
      byte2 := word MOD 256;
      IF  (byte1 > 31) AND (byte1 < 127)  THEN  char1 := CHR (byte1)  ELSE  char1 := '.'  END;
      IF  (byte2 > 31) AND (byte2 < 127)  THEN  char2 := CHR (byte2)  ELSE  char2 := '.'  END;
      str [6] := char2;
      str [7] := char1
   END;
   TextIO.PutString (LstFile, str)
END DumpToken;


PROCEDURE Decode (value	  : CARDINAL);

VAR	  mnem, par2, par3      		: Identifier;
	  undef    	    	      		: BOOLEAN;
	  const, nibble, sub, subb, reg1, reg2	: CARDINAL;
	  jump	 	      	    	  	: CARDINAL;

BEGIN
   TextIO.PutHex (LstFile, PC, 8);		TextIO.PutHex (LstFile, value, 8);
   DumpToken (value);
   undef := FALSE;
   TextIO.PutString (LstFile, '        ');
   nibble := value DIV 4096;
   CASE  nibble  OF
        0 : sub := (value DIV 1024) MOD 4;
	    CASE  sub  OF
	       0 : subb := (value DIV 256) MOD 4;
	           CASE  subb  OF
		      0 : IF  value = 0  THEN
		             mnem := 'nop'
			  ELSE
			     mnem := 'Error';
			     undef := TRUE
			  END

	            | 1 : mnem := 'movw    ';
	                  reg1 := ((value DIV 16) MOD 16) * 2;		reg2 := (value MOD 16) * 2;

	     	    | 2 : mnem := 'muls    ';
	                  reg1 := ((value DIV 16) MOD 16) + 16;		reg2 := (value MOD 16) + 16;

	     	    | 3 : const := ((value DIV 8) MOD 2) + ((value DIV 128) MOD 2) * 2;
	       	      	  reg1 := ((value DIV 16) MOD 8) + 16;		reg2 := (value MOD 8) + 16;
		   	  CASE  const  OF
		    	     0 : mnem := 'mulsu   '
			   | 1 : mnem := 'fmul    '
			   | 2 : mnem := 'fmuls   '
			   | 3 : mnem := 'fmulsu  '
		   	  END
	           END;
	    	   IF  (value > 0) AND (undef = FALSE)  THEN
	       	      Strings.Append (mnem, 'R');
	       	      NumConv.Num2Str (reg1, 10, par1, ok);	Strings.Append (par1, ', R');
	       	      Strings.Append (mnem, par1);
	       	      NumConv.Num2Str (reg2, 10, par1, ok);	Strings.Append (mnem, par1)
	    	   END

	     | 1 : mnem := 'cpc     ';			GetRegs (value, par1);
	       	   Strings.Append (mnem, par1)

	     | 2 : mnem := 'sbc     ';	      		GetRegs (value, par1);
		   Strings.Append (mnem, par1)

	     | 3 : reg1 := value MOD 16;
	           IF  9 IN BITSET (value)  THEN  INC (reg1, 16)  END;
		   reg2 := (value DIV 16) MOD 32;
		   IF  reg1 = reg2  THEN
		      mnem := 'lsl     R';
		      NumConv.Num2Str (reg1, 10, par1, ok)
		   ELSE
		      mnem := 'add     ';		GetRegs (value, par1)
		   END;
		   Strings.Append (mnem, par1)
	    END

     |  1 : sub := (value DIV 1024) MOD 4;	(* isolate bits 11 and 12  *)
     	    CASE  sub  OF
	       0 : mnem := 'cpse    ';			GetRegs (value, par1)
	     | 1 : mnem := 'cp      ';			GetRegs (value, par1)
	     | 2 : mnem := 'sub     ';			GetRegs (value, par1)
	     | 3 : reg1 := value MOD 16;
	           IF  9 IN BITSET (value)  THEN  INC (reg1, 16)  END;
		   reg2 := (value DIV 16) MOD 32;
		   IF  reg1 = reg2  THEN
		      mnem := 'rol     R';		NumConv.Num2Str (reg1, 10, par1, ok)
		   ELSE
		      mnem := 'adc     ';		GetRegs (value, par1)
		   END;
	    END;
	    Strings.Append (mnem, par1)

     |  2 : sub := (value DIV 1024) MOD 4;	(* isolate bits 11 and 12  *)
     	    CASE  sub  OF
	       0 : reg1 := value MOD 16;
	           IF  9 IN BITSET (value)  THEN  INC (reg1, 16)  END;
		   reg2 := (value DIV 16) MOD 32;
		   IF  reg1 = reg2  THEN
		      mnem := 'tst     R';		NumConv.Num2Str (reg1, 10, par1, ok)
		   ELSE
		      mnem := 'and     ';		GetRegs (value, par1)
		   END
	     | 1 : reg1 := value MOD 16;
	           IF  9 IN BITSET (value)  THEN  INC (reg1, 16)  END;
		   reg2 := (value DIV 16) MOD 32;
		   IF  reg1 = reg2  THEN
		      mnem := 'clr     R';		NumConv.Num2Str (reg1, 10, par1, ok)
		   ELSE
		      mnem := 'eor     ';		GetRegs (value, par1)
		   END
	     | 2 : mnem := 'or      ';			GetRegs (value, par1)
	     | 3 : mnem := 'mov     ';			GetRegs (value, par1)
	    END;
	    Strings.Append (mnem, par1)

     |  3 : mnem := 'cpi     R';
     	    reg1 := ((value DIV 16) MOD 16) + 16;
	    const := 16 * ((value DIV 256) MOD 16) + (value MOD 16);
	    NumConv.Num2Str (reg1, 10, par1, ok);
	    Strings.Append (mnem, par1);
	    Strings.Append (mnem, ', ');
	    NumConv.Num2Str (const, 10, par1, ok);
	    Strings.Append (mnem, par1)

     |  4 : mnem := 'sbci    R';
     	    reg1 := ((value DIV 16) MOD 16) + 16;
	    const := 16 * ((value DIV 256) MOD 16) + (value MOD 16);
	    NumConv.Num2Str (reg1, 10, par1, ok);
	    Strings.Append (mnem, par1);
	    Strings.Append (mnem, ', ');
	    NumConv.Num2Str (const, 10, par1, ok);
	    Strings.Append (mnem, par1)

     |  5 : mnem := 'subi    R';
     	    reg1 := ((value DIV 16) MOD 16) + 16;
	    const := 16 * ((value DIV 256) MOD 16) + (value MOD 16);
	    NumConv.Num2Str (reg1, 10, par1, ok);
	    Strings.Append (mnem, par1);
	    Strings.Append (mnem, ', ');
	    NumConv.Num2Str (const, 10, par1, ok);
	    Strings.Append (mnem, par1)

     |  6 : mnem := 'ori     R';
     	    reg1 := ((value DIV 16) MOD 16) + 16;
	    const := 16 * ((value DIV 256) MOD 16) + (value MOD 16);
	    NumConv.Num2Str (reg1, 10, par1, ok);
	    Strings.Append (mnem, par1);
	    Strings.Append (mnem, ', $');
	    NumConv.Num2Str (const, 16, par1, ok);
	    IF  const < 16  THEN  Strings.Append (mnem, '0')  END;
	    Strings.Append (mnem, par1)

     |  7 : mnem := 'andi    R';
     	    reg1 := ((value DIV 16) MOD 16) + 16;
	    const := 16 * ((value DIV 256) MOD 16) + (value MOD 16);
	    NumConv.Num2Str (reg1, 10, par1, ok);
	    Strings.Append (mnem, par1);
	    Strings.Append (mnem, ', $');
	    NumConv.Num2Str (const, 16, par1, ok);
	    IF  const < 16  THEN  Strings.Append (mnem, '0')  END;
	    Strings.Append (mnem, par1)

     |  9 : sub := (value DIV 512) MOD 8;
     	    CASE  sub  OF
	       0 : subb := value MOD 16;
	       	   reg1 := (value DIV 16) MOD 32;
		   NumConv.Num2Str (reg1, 10, par1, ok);
	           CASE  subb  OF
		       0 : mnem := 'lds     R';
			   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', ');
			   DecodeMode := FromMemory;
			   DecodeNext := FALSE
		    |  1 : mnem := 'ld      R';
		       	   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', Z+')
		    |  2 : mnem := 'ld      R';
		       	   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', -Z')
		    |  4 : mnem := 'lpm     R';
		       	   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', Z')
		    |  5 : mnem := 'lpm     R';
		       	   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', Z+')
		    |  6 : mnem := 'elpm    R';
		       	   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', Z+')
		    |  7 : mnem := 'elpm    R';
		       	   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', Z')
		    |  9 : mnem := 'ld      R';
		       	   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', Y+')
		    | 10 : mnem := 'ld      R';
		       	   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', -Y')
		    | 12 : mnem := 'ld      R';
		       	   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', X')
		    | 13 : mnem := 'ld      R';
		       	   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', X+')
		    | 14 : mnem := 'ld      R';
		       	   Strings.Append (mnem, par1);
			   Strings.Append (mnem, ', -X')
		    | 15 : mnem := 'pop     R';
		      	   Strings.Append (mnem, par1)
		   ELSE
		      mnem := 'Error'
		   END

	     | 1 : subb := value MOD 16;
	       	   reg1 := (value DIV 16) MOD 32;
		   NumConv.Num2Str (reg1, 10, par1, ok);
	           CASE  subb  OF
		       0 : mnem := 'sts     ';
			   DecodeMode := ToMemory;
			   DecodeNext := FALSE
		    |  1 : mnem := 'st      Z+, R';
		       	   Strings.Append (mnem, par1);
		    |  2 : mnem := 'st      -Z, R';
		       	   Strings.Append (mnem, par1);
		    |  9 : mnem := 'st      Y+, R';
		       	   Strings.Append (mnem, par1);
		    | 10 : mnem := 'st      -Y, R';
		       	   Strings.Append (mnem, par1);
		    | 12 : mnem := 'st      X, R';
		       	   Strings.Append (mnem, par1);
		    | 13 : mnem := 'st      X+, R';
		       	   Strings.Append (mnem, par1);
		    | 14 : mnem := 'st      -X, R';
		       	   Strings.Append (mnem, par1);
		    | 15 : mnem := 'push    R';
		      	   Strings.Append (mnem, par1)
		   ELSE
		      mnem := 'Error'
		   END

	     | 2 : subb := value MOD 16;
	       	   reg1 := (value DIV 16) MOD 32;
		   NumConv.Num2Str (reg1, 10, par1, ok);
	           CASE  subb  OF
		       0 : mnem := 'com     R';
		       	   Strings.Append (mnem, par1);
		    |  1 : mnem := 'neg     R';
		       	   Strings.Append (mnem, par1);
		    |  2 : mnem := 'swap    R';
		       	   Strings.Append (mnem, par1);
		    |  3 : mnem := 'inc     R';
		       	   Strings.Append (mnem, par1);
		    |  5 : mnem := 'asr     R';
		       	   Strings.Append (mnem, par1);
		    |  6 : mnem := 'lsr     R';
		       	   Strings.Append (mnem, par1);
		    |  7 : mnem := 'ror     R';
		       	   Strings.Append (mnem, par1);
		    |  8 : const := (value DIV 16) MOD 32;
		       	   CASE  const  OF
			       0 : mnem := 'sec'
			    |  1 : mnem := 'sez'
			    |  2 : mnem := 'sen'
			    |  3 : mnem := 'sev'
			    |  4 : mnem := 'ses'
			    |  5 : mnem := 'seh'
			    |  6 : mnem := 'set'
			    |  7 : mnem := 'sei'
			    |  8 : mnem := 'clc'
			    |  9 : mnem := 'clz'
			    | 10 : mnem := 'cln'
			    | 11 : mnem := 'clv'
			    | 12 : mnem := 'cls'
			    | 13 : mnem := 'clh'
			    | 14 : mnem := 'clt'
			    | 15 : mnem := 'cli'
			    | 16 : mnem := 'ret'
			    | 17 : mnem := 'reti'
			    | 24 : mnem := 'sleep'
			    | 25 : mnem := 'break'
			    | 26 : mnem := 'wdr'
			    | 28 : mnem := 'lpm     R0, Z'
			    | 29 : mnem := 'elpm    R0'
			    | 30 : mnem := 'spm'
			   ELSE
			      mnem := 'Error'
			   END
		    |  9 : const := (value DIV 16) MOD 32;
		       	   CASE  const  OF
			       0 : mnem := 'ijmp'
			    |  1 : mnem := 'eijmp'
			    | 16 : mnem := 'icall'
			    | 17 : mnem := 'eicall'
			   ELSE
			      mnem := 'Error'
			   END
		    | 10 : mnem := 'dec     R';
		       	   Strings.Append (mnem, par1);
		    | 12,
		      13 : mnem := 'jmp     $';
		           const := ((value DIV 16) MOD 32) + (value MOD 2);
			   NumConv.Num2Str (const, 16, par1, ok);
			   Strings.Append (mnem, par1);
			   DecodeMode := Jump;
			   DecodeNext := FALSE
		    | 14,
		      15 : mnem := 'call    $';
		           const := ((value DIV 16) MOD 32) + (value MOD 2);
			   NumConv.Num2Str (const, 16, par1, ok);
			   Strings.Append (mnem, par1);
			   DecodeMode := Jump;
			   DecodeNext := FALSE
		   ELSE
		      mnem := 'Error'
		   END

	     | 3 : reg1 := (((value DIV 16) MOD 4) * 2) + 25;
		   NumConv.Num2Str (reg1, 10, par1, ok);
		   const := 16 * ((value DIV 64) MOD 4) + (value MOD 16);
		   NumConv.Num2Str (const, 10, par2, ok);
		   IF  8 IN BITSET (value)  THEN
		      mnem := 'sbiw    R'
		   ELSE
		      mnem := 'adiw    R'
		   END;
		   Strings.Append (mnem, par1);		DEC (par1 [1]);
		   Strings.Append (mnem, ':');		Strings.Append (mnem, par1);
		   Strings.Append (mnem, ', ');		Strings.Append (mnem, par2)

	     | 4 : const := value MOD 8;
		   reg1 := (value DIV 8) MOD 32;
		   FindPort (reg1, par1);    		FindBit (par1, const, par2);
		   IF  8 IN BITSET (value)  THEN
		      mnem := 'sbic    '
		   ELSE
		      mnem := 'cbi     '
		   END;
		   Strings.Append (mnem, par1);		Strings.Append (mnem, ', ');
		   Strings.Append (mnem, par2)

	     | 5 : const := value MOD 8;
		   reg1 := (value DIV 8) MOD 32;
		   FindPort (reg1, par1);    		FindBit (par1, const, par2);
		   IF  8 IN BITSET (value)  THEN
		      mnem := 'sbis    '
		   ELSE
		      mnem := 'sbi     '
		   END;
		   Strings.Append (mnem, par1);		Strings.Append (mnem, ', ');
		   Strings.Append (mnem, par2)

	     | 6, 
	       7 : mnem := 'mul     ';
	       	   GetRegs (value, par1);
		   Strings.Append (mnem, par1)
	    END;

     |  8,
       10 : sub := ((value DIV 8) MOD 2) + 2 * ((value DIV 512) MOD 2);
            const := (value MOD 8) + 8 * ((value DIV 1024) MOD 4) + 32 * ((value DIV 8192) MOD 2);
	    reg1 := (value DIV 16) MOD 32;
	    NumConv.Num2Str (reg1, 10, par1, ok);
	    NumConv.Num2Str (const, 10, par2, ok);
	    IF  const = 0  THEN
               CASE  sub  OF
	          0 : mnem := 'ld      R';
		      Strings.Append (mnem, par1);	Strings.Append (mnem, ', Z')
	        | 1 : mnem := 'ld      R';
		      Strings.Append (mnem, par1);	Strings.Append (mnem, ', Y')
	     	| 2 : mnem := 'st      Z, R';  	  	Strings.Append (mnem, par1)
	     	| 3 : mnem := 'st      Y, R';		Strings.Append (mnem, par1)
	       END
	    ELSE
               CASE  sub  OF
	          0 : mnem := 'ldd     R';
		      Strings.Append (mnem, par1);
		      Strings.Append (mnem, ', Z + ');
		      Strings.Append (mnem, par2)
	        | 1 : mnem := 'ldd     R';
		      Strings.Append (mnem, par1);
		      Strings.Append (mnem, ', Y + ');
		      Strings.Append (mnem, par2)
	     	| 2 : mnem := 'std     Z + ';
		      Strings.Append (mnem, par2);
		      Strings.Append (mnem, ', R');
		      Strings.Append (mnem, par1)
	     	| 3 : mnem := 'std     Y + ';
		      Strings.Append (mnem, par2);
		      Strings.Append (mnem, ', R');
		      Strings.Append (mnem, par1)
	       END
	    END

     | 11 : reg1 := (value DIV 16) MOD 32;
       	    reg2 := 16 * ((value DIV 512) MOD 4) + (value MOD 16);
	    IF  11 IN BITSET (value)  THEN
       	       mnem := 'out     ';
	       FindPort (reg2, par1);
	       Strings.Append (mnem, par1); 	    	Strings.Append (mnem, ', R');
	       NumConv.Num2Str (reg1, 10, par1, ok);
	       Strings.Append (mnem, par1)
	    ELSE
       	       mnem := 'in      R';
	       NumConv.Num2Str (reg1, 10, par1, ok);
	       Strings.Append (mnem, par1); 	    	Strings.Append (mnem, ', ');
	       FindPort (reg2, par1);
	       Strings.Append (mnem, par1)
	    END

     | 12 : mnem := 'rjmp    $';
       	    const := value MOD 4096;
	    IF  const > 2047  THEN
	       const := PC + const - 4095
	    ELSE
	       const := PC + const + 1
	    END;
	    NumConv.Num2Str (const, 16, par1, ok);
	    Strings.Append (mnem, par1)

     | 13 : mnem := 'rcall   $';
       	    const := value MOD 4096;
	    IF  const > 2047  THEN
	       const := PC + const - 4095
	    ELSE
	       const := PC + const + 1
	    END;
	    NumConv.Num2Str (const, 16, par1, ok);
      	    Strings.Append (mnem, par1)

     | 14 : const := (value MOD 16) + 16 * ((value DIV 256) MOD 16);
	    reg1 := ((value DIV 16) MOD 16) + 16;
	    IF  const = 0FFH  THEN
	       mnem := 'ser     R';			NumConv.Num2Str (reg1, 10, par1, ok)
	    ELSE
	       mnem := 'ldi     R';			NumConv.Num2Str (reg1, 10, par1, ok);
	       Strings.Append (mnem, par1);	    	Strings.Append (mnem, ', $');
	       NumConv.Num2Str (const, 16, par1, ok)
	    END;
	    Strings.Append (mnem, par1)

     | 15 : sub := (value DIV 1024) MOD 4;
       	    subb := value MOD 8;
	    CASE  sub  OF
	       0 : jump := (value DIV 8) MOD 128;
	       	   IF  jump > 63  THEN
		      jump := PC + jump - 127
		   ELSE
		      jump := PC + jump + 1
		   END;
	           CASE  subb  OF
		      0 : mnem := 'brcs'
		    | 1 : mnem := 'breq'
		    | 2 : mnem := 'brmi'
		    | 3 : mnem := 'brvs'
		    | 4 : mnem := 'brlt'
		    | 5 : mnem := 'brhs'
		    | 6 : mnem := 'brts'
		    | 7 : mnem := 'brie'
		   END;
		   Strings.Append (mnem, '    $');	NumConv.Num2Str (jump, 16, par1, ok);
		   Strings.Append (mnem, par1)

	     | 1 : jump := (value DIV 8) MOD 128;
	       	   IF  jump > 63  THEN
		      jump := PC - 127 + jump
		   ELSE
		      jump := PC + jump + 1
		   END;
	           CASE  subb  OF
		      0 : mnem := 'brcc'
		    | 1 : mnem := 'brne'
		    | 2 : mnem := 'brpl'
		    | 3 : mnem := 'brvc'
		    | 4 : mnem := 'brge'
		    | 5 : mnem := 'brhc'
		    | 6 : mnem := 'brtc'
		    | 7 : mnem := 'brid'
		   END;
		   Strings.Append (mnem, '    $');	NumConv.Num2Str (jump, 16, par1, ok);
		   Strings.Append (mnem, par1)

	     | 2 : subb := ((value DIV 8) MOD 2) + 2 * ((value DIV 512) MOD 2);
		   reg1 := (value DIV 16) MOD 32;
		   NumConv.Num2Str (reg1, 10, par1, ok);
		   const := value MOD 8;
		   NumConv.Num2Str (const, 10, par2, ok);
		   CASE  subb  OF
		      0 : mnem := 'bld     R'
		    | 2 : mnem := 'bst     R'
		   ELSE
		      mnem := 'Error'
		   END;
		   IF  Strings.StrEq (mnem, 'Error') = FALSE  THEN
		      Strings.Append (mnem, par1);	Strings.Append (mnem, ', bit ');
		      Strings.Append (mnem, par2)
		   END
		   
	     | 3 : subb := ((value DIV 8) MOD 2) + 2 * ((value DIV 512) MOD 2);
		   reg1 := (value DIV 16) MOD 32;
		   NumConv.Num2Str (reg1, 10, par1, ok);
		   const := value MOD 8;
		   NumConv.Num2Str (const, 10, par2, ok);
		   CASE  subb  OF
		      0 : mnem := 'sbrc    R'
		    | 2 : mnem := 'sbrs    R'
		   ELSE
		      mnem := 'Error'
		   END;
		   IF  Strings.StrEq (mnem, 'Error') = FALSE  THEN
		      Strings.Append (mnem, par1);	Strings.Append (mnem, ', bit ');
		      Strings.Append (mnem, par2)
		   END
	    END
   ELSE
      mnem := 'Something fishy!'
   END;
   TextIO.PutString (LstFile, mnem);
   IF  DecodeNext = TRUE  THEN   TextIO.PutLn (LstFile)   END;
   TextIO.PutBf (LstFile)
END Decode;


PROCEDURE ProcessLine;

VAR    checksum, words, n, type, code		: CARDINAL;
       par2	 	   	 		: Identifier;
       ch	 	   			: CHAR;

BEGIN
   LOOP
      TextIO.GetString (InFile, option);
      IF  TextIO.EOF (InFile) = TRUE  THEN  EXIT  END;
      INC (line);
      column := 0;
      ReadChar (ch);
      IF  ch = ':'  THEN
         words := ReadHex (2) DIV 2;
	 n := ReadHex (4);    	    		PC := n DIV 2;
	 type := ReadHex (2);
	 IF  type = 1  THEN  RETURN  END;
	 IF  type = 2  THEN  PC := ReadHex (2) + 256 * ReadHex (2)  END;
	 IF  type = 0  THEN
	    FOR  n := 1  TO  words  DO
	       code := ReadHex (2) + 256 * ReadHex (2);
	       IF  DecodeNext = TRUE  THEN
	          Decode (code)
	       ELSE
		  IF  DecodeMode = Jump  THEN
		     NumConv.Num2Str (code, 16, par1, ok)
	          ELSIF  (DecodeMode = FromMemory) OR (DecodeMode = ToMemory)  THEN
		     IF  DecodeMode = ToMemory  THEN  par2 := par1  END;
		     IF  code < 32  THEN
		        TextIO.PutString (LstFile, 'R');
			NumConv.Num2Str (code, 10, par1, ok)
		     ELSIF  code < 96  THEN
		        FindPort (code - 32, par1)
		     ELSIF  code < RAMstart  THEN
		        FindPort (code + 256, par1)
		     ELSE
		        TextIO.PutString (LstFile, '[$');
			NumConv.Num2Str (code, 16, par1, ok);
			Strings.Append (par1, ']')
		     END
		  END;
		  TextIO.PutString (LstFile, par1);
		  IF  DecodeMode = ToMemory  THEN
		     TextIO.PutString (LstFile, ", R");		TextIO.PutString (LstFile, par2)
		  END;
		  TextIO.PutLn (LstFile);
		  TextIO.PutHex (LstFile, PC, 8);		TextIO.PutHex (LstFile, code, 8);
		  DumpToken (code);	      			TextIO.PutLn (LstFile);
		  DecodeNext := TRUE;
		  DecodeMode := None
	       END;
	       INC (PC)
	    END
	 END
      ELSE
         UserMessage (8)
      END
   END
END ProcessLine;


PROCEDURE OpenFiles;

BEGIN
   TextIO.OpenInput (InFile, Source);
   IF  NOT TextIO.Done ()  THEN
      UserMessage (5);
      HALT
   END;
   Dest := Source;			Strings.Append (Dest, '.lst');
   TextIO.OpenOutput (LstFile, Dest);
   IF  NOT TextIO.Done ()  THEN
      UserMessage (7);
      HALT
   END;
   TextIO.PutString (LstFile, "Disassembly listing");
   TextIO.PutLn (LstFile);    		   	     	TextIO.PutLn (LstFile);
   TextIO.PutString (LstFile, "Source : ");		TextIO.PutString (LstFile, Source);
   TextIO.PutString (LstFile, " disassembling for target processor ");
   TextIO.PutString (LstFile, Target);
   TextIO.PutLn (LstFile);				TextIO.PutLn (LstFile);
   TextIO.PutString (LstFile, " address  opcode    ASCII    mnemonics");
   TextIO.PutLn (LstFile);
   TextIO.PutLn (LstFile)
END OpenFiles;


PROCEDURE CloseFiles;

BEGIN
   TextIO.Close (InFile);
   TextIO.Close (LstFile);
   KillPool (PortNames);
   KillPool (BitNames)
END CloseFiles;


PROCEDURE Init;

VAR   count, i    	: SHORTCARD;

BEGIN
   NewPool (PortNames);
   NewPool (BitNames);
   PoolAllocate (PortNames, FirstPort, SYSTEM.TSIZE (PortNode));
   WITH  FirstPort^  DO
      Name := "|";
      number := 5000;
      next := NIL
   END;
   PoolAllocate (BitNames, FirstName, SYSTEM.TSIZE (NameNode));
   WITH  FirstName^  DO
      RegName := "|";
      BitNr := 50000;
      BitName := "|";
      next := NIL
   END;
   line := 0;				column := 1;			PC := 0;
   RAMstart := 96;
   Strings.EmptyString (Source);       	Target := "ATmega8515";
   Arguments.GetArgs (count, storage);
   IF  count = 1  THEN
      UserMessage (2);
      UserMessage (1);
      HALT
   END;
   i := 1;
   LOOP
      IF  i >= count  THEN  EXIT  END;
      Strings.Assign (option, storage^ [i]^);
      IF  Strings.StrEq (option, '-t')  THEN
         INC (i);
	 IF  i = count  THEN
	    UserMessage (11);		InOut.WriteLn;
	    UserMessage (1); 		UserMessage (2);
	    HALT
	 END;
         Strings.Assign (Target, storage^ [i]^)
      ELSIF  Strings.StrEq (option, '-v')  THEN
         UserMessage (2)
      ELSE
         Strings.Assign (Source, option)
      END;
      INC (i)
   END;
   IF  Source [0] = 0C  THEN
      UserMessage (6);
      HALT
   END;
   DecodeNext := TRUE
END Init;


BEGIN
   Init;
   IF  Configure () = FALSE  THEN
      UserMessage (4);
      HALT
   END;
   UserMessage (2);
   OpenFiles;
   ProcessLine;
   CloseFiles
END dis04.
   
Compiling:
    jan@beryllium:~/modula/AVR$ mocka
    Mocka 0608m
    >> i dis04
    >> p dis04
    .. Compiling Program Module dis04 I/0013 II/0013
    .. Linking dis04
    >> q
    jan@beryllium:~/modula/AVR$
   
You can download the executable and the support files in the download section.

You need to be a follower of the Penguin to be able to use AVRdis. If you still prefer to follow the richest man in the world (self acclaimed) you will probably still be using TOS (The Other System) and you have bad luck. Sorry: if you are serious with the AVR you need to be a Follower of the Penguin.

An example

Here follows an example disassembly of a rather big file I found on the internet (source and hex files so I can compare). The complete file is too big to show here but I took out some highlights, showing what avrdis already can do. The ASCII dump makes it easy to spot data area's. An example is in the bottom section of the list. Also pay attention to the smart port name and bit position scanner.

Disassembly listing

Source : Hebb disassembling for target processor ATmega8515

 address  opcode    ASCII    mnemonics

       0    C00C      ..        rjmp    $D
       1    9518      ..        reti
       2    9518      ..        reti

       C    9518      ..        reti
       D    94F8      ..        cli
       E    E6E0      ..        ldi     R30, $60
       F    E0F0      ..        ldi     R31, $0
      10    E6C0      ..        ldi     R28, $60
      11    E0D2      ..        ldi     R29, $2
      12    2700      .'        clr     R16
      13    9301      ..        st      Z+, R16
      14    17EC      ..        cp      R30, R28
      15    F7E9      ..        brne    $13
      16    17FD      ..        cp      R31, R29
      17    F7D9      ..        brne    $13
      18    E50F      ..        ldi     R16, $5F
      19    E012      ..        ldi     R17, $2
      1A    BF0D      ..        out     SPL, R16
      1B    BF1E      ..        out     SPH, R17
      1C    2700      .'        clr     R16
      1D    BB0B      ..        out     PORTA, R16
      1E    BB0A      ..        out     DDRA, R16
      1F    BB08      ..        out     PORTB, R16
      20    EF0F      ..        ser     R16

      2B    E800      ..        ldi     R16, $80
      2C    2E20       .        mov     R2, R16
      2D    2E30      0.        mov     R3, R16
      2E    E001      ..        ldi     R16, $1
      2F    E010      ..        ldi     R17, $0
      30    D52C      ,.        rcall   $55D
      31    2F62      b/        mov     R22, R18
      32    706F      op        andi    R22, $0F
      33    2F72      r/        mov     R23, R18
      34    9572      r.        swap    R23

     475    9120       .        lds     R18, [$F3]
     476      F3      ..
     477    9523      #.        inc     R18
     478    7021      !p        andi    R18, $01
     479    9320       .        sts     [$F3], R18
     47A      F3      ..
     47B    E003      ..        ldi     R16, $3
     47C    E010      ..        ldi     R17, $0
     47D    D0E6      ..        rcall   $564
     47E    CC00      ..        rjmp    $7F

     5F3    B61F      ..        in      R1, SREG
     5F4    B19C      ..        in      R25, UDR
     5F5    995B      [.        sbic    UCSRA, DOR
     5F6    C012      ..        rjmp    $609
     5F7    995C      \.        sbic    UCSRA, FE
     5F8    C012      ..        rjmp    $60B

     9E9     A0D   Cr/Lf        sbc     R0, R29
     9EA    6E6F      on        ori     R22, $EF
     9EB    7420       t        andi    R18, $40
     9EC    6568      he        ori     R22, $58
     9ED    7420       t        andi    R18, $40
     9EE    7265      er        andi    R22, $25
     9EF    696D      mi        ori     R22, $9D
     9F0    616E      na        ori     R22, $1E
     9F1    206C      l         and     R6, R12
     9F2    6E61      an        ori     R22, $E1
     9F3    2064      d         and     R6, R4
     9F4    7270      pr        andi    R23, $20
     9F5    7365      es        andi    R22, $35
     9F6    2073      s         and     R7, R3
     9F7    4E45      EN        sbci    R20, 229
     9F8    4554      TE        sbci    R21, 84
     9F9    2052      R         and     R5, R2
   

Manual and requisites

This version of AVRdis (which is named 'dis04' after it has been compiled) is a development version, but it will do the job none the less. If you get the tar.gz file, commence as follows:

  1. make sure you are root
  2. copy the file to the '/usr/local' directory
  3. unpack it: 'gzip -d AVRdis.tar.gz'
  4. untar it: 'tar xf AVRdis.tar'
  5. move the executable to /usr/local/bin : 'mv dis04 bin/AVRdis'
  6. move the sourcefile to a convenient place
  7. the AT* files should be put in the correct directory. If it isn't, make sure the files are in '/usr/local/AVR'
Now, if you have a project to disassemble, I strongly recommend to do that in a separate directory. The disassembler will create one output file and it will not ask for confirmation to overwrite existing ones. So, please be smart and do as follows (supposing the file is called Thingy.hex): Now, the output file is created in a temporary directory and it will not overwrite other files with the same names. Also: you can keep your project directory tidy.

AVRdis will now do it's best to take apart the opcodes and make a nice workingfile for you which can be used to reconstruct the lost source file. The '.lst' file contains all data plus some extra, to make life easier on the disassembling human:

  1. the address of the opcode
  2. the actual opcode, derived from the hex file.
  3. the ASCII representation of the opcode
  4. the most likely mnemonics
What follows is a process of stepwise refinements. By now, you have a file that is quite well readable by a programmer. And a programmer now knows what to do with it.

Page created on 20 September 2006 and