summaryrefslogtreecommitdiff
path: root/lib/compiler/disassembler.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compiler/disassembler.tcl')
-rwxr-xr-xlib/compiler/disassembler.tcl664
1 files changed, 664 insertions, 0 deletions
diff --git a/lib/compiler/disassembler.tcl b/lib/compiler/disassembler.tcl
new file mode 100755
index 0000000..b74fedf
--- /dev/null
+++ b/lib/compiler/disassembler.tcl
@@ -0,0 +1,664 @@
+#!/usr/bin/tclsh
+# Part of MCU 8051 IDE ( http://mcu8051ide.sf.net )
+
+############################################################################
+# Copyright (C) 2007-2009 by Martin Ošmera #
+# martin.osmera@gmail.com #
+# #
+# This program is free software; you can redistribute it and#or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation; either version 2 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program; if not, write to the #
+# Free Software Foundation, Inc., #
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #
+############################################################################
+
+# --------------------------------------------------------------------------
+# Disassembler 8051
+#
+# DESCRIPTION:
+# Converts data from Intel HEX 8 fromat to assembler 51.
+#
+# SUMMARY:
+# * Generated code doesn't contain any compiler directives except 'DB', 'ORG' and 'END'.
+# * Unrecognized opcodes are traslated to 'DB opcode'
+# * Labels referencing to "nowhere" are traslated as 'label EQU address'
+# * Input code must be absolutely clear so it mustn't contain any
+# ambiguity and all addresses must be in icremental order
+# * Lines in hex code which don't start with colon ':' are ignored
+#
+# USAGE:
+# disassembler::compile $hex_data ;# -> asm code
+#
+# --------------------------------------------------------------------------
+
+namespace eval disassembler {
+
+ variable tmp_asm {} ;# Tempotary variable for code procedure: 'final_stage'
+ variable hex_data {} ;# Raw input data
+ variable hex {} ;# Adjusted input data, list: {addr hex0 hex1 hex2 ...}
+ variable lineNum {} ;# Number of line currently beeing parsed
+ variable error_count {} ;# Number of errors raised during decompilation
+ variable warning_count {} ;# Number of warnings occured
+ variable label_idx {} ;# Label index
+ variable final_lbls 0 ;# Number of final labels
+ variable label ;# Array of tempotary labels, label(int) -> addr
+ variable asm {} ;# Resulting source code
+
+
+ # ----------------------------------------------------------------
+ # GENERAL PURPOSE PROCEDURES
+ # ----------------------------------------------------------------
+
+ ## Initiate decompilation
+ # @parm string data - Input IHEX8 code
+ # @return string - output asm code or {}
+ proc compile {data} {
+
+ variable hex_data ;# Raw input
+ variable hex ;# Adjusted input data, list: {addr hex0 hex1 hex2 ...}
+ variable lineNum ;# Number of line currently beeing parsed
+ variable error_count ;# Number of errors raised during decompilation
+ variable warning_count ;# Number of warnings occured
+ variable asm ;# Resulting source code
+
+ set error_count 0 ;# reset errors count
+ set warning_count 0 ;# reset errors count
+ set hex {} ;# clear hex data used for futher prosessing
+ set hex_data $data ;# set input data
+
+ # Adjust input data
+ regexp -all {\r\n?} hex_data "\n" hex_data
+ set hex_data [split $hex_data "\n"]
+
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} [::Compiler::msgc {SN}][mc "Initializing disassembler ..."]
+
+ # Verify input code validity and set variable 'hex'
+ adjust_code
+
+ # Exit if the code does not seem to be valid
+ if {$error_count != 0} {
+ if {${::Compiler::Settings::NOCOLOR}} {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} \
+ [::Compiler::msgc {EN}][mc "Disassembly FAILED ..."]
+ } {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} \
+ [::Compiler::msgc {EN}][mc "\033\[31;1mDisassembly FAILED\033\[m ..."]
+ }
+ return {}
+ }
+
+ if {${Compiler::Settings::ABORT_VARIABLE}} {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} [::Compiler::msgc {EN}][mc "Aborted"]
+ free_resources
+ return {}
+ }
+
+ # Convert processor code into asm code
+ decompile_code
+
+ if {${Compiler::Settings::ABORT_VARIABLE}} {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} [::Compiler::msgc {EN}[mc "Aborted"]
+ free_resources
+ return {}
+ }
+
+ # Create labels in resulting code
+ parse_labels
+
+ if {${Compiler::Settings::ABORT_VARIABLE}} {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} [::Compiler::msgc {EN}][mc "Aborted"]
+ free_resources
+ return {}
+ }
+
+ # Final stage
+ final_stage
+
+ if {${Compiler::Settings::ABORT_VARIABLE}} {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} [::Compiler::msgc {EN}][mc "Aborted"]
+ free_resources
+ return {}
+ }
+
+ # Free memory used during decompilation
+ free_resources
+
+ if {${Compiler::Settings::ABORT_VARIABLE}} {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} [::Compiler::msgc {EN}][mc "Aborted"]
+ free_resources
+ return {}
+ }
+
+ if {${::Compiler::Settings::NOCOLOR}} {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} \
+ [::Compiler::msgc {SN}][mc "Disassembly complete"]
+ } {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} \
+ [mc "\033\[32;1mDisassembly complete\033\[m"]
+ }
+
+ # Return resulting source code
+ return $asm
+ }
+
+
+ # ----------------------------------------------------------------
+ # INTERNAL AUXILIARY PROCEDURES
+ # ----------------------------------------------------------------
+
+ ## Verify input code validity and set variable 'hex'
+ # @return void
+ proc adjust_code {} {
+ variable hex ;# Adjusted input data
+ variable lineNum ;# Number of line currently beeing parsed
+ variable hex_data ;# Raw input (hex data)
+
+ set pointer -1 ;# Program address pointer
+ set lineNum 0 ;# Line number
+
+ foreach line $hex_data {
+
+ if {[expr {$lineNum % 10}] == 0} ${Compiler::Settings::UPDATE_COMMAND}
+
+ incr lineNum ;# line number
+
+ # Skip comments
+ if {[string index $line 0] != {:}} {continue}
+
+ # Check for valid characters
+ set line [string range $line 1 end]
+ if {![regexp {^[0-9A-Fa-f]*$} $line]} {
+ Error $lineNum [mc "Invalid line (line contain not allowed characters)"]
+ continue
+ }
+
+ # Check for odd number of characters
+ set len [string length $line]
+ if {[expr {$len % 2}] != 0} {
+ Error $lineNum [mc "Line do not contain odd number of chars"]
+ continue
+ }
+
+ # Check for valid checksum
+ set check [string range $line {end-1} end]
+ set new_check [::IHexTools::getCheckSum [string range $line 0 {end-2}]]
+ if {$check != $new_check} {
+ Error $lineNum [mc "Bad checksum, given: %s ; computed: %s" $check $new_check]
+ continue
+ }
+
+ # Check for correct record type
+ set type [string range $line 6 7]
+ if {$type == {01}} {
+ break
+ } elseif {$type != {00}} {
+ Error $lineNum [mc "Unknown record type number `%s' (Intel HEX 8 can contain only 00 and 01)" $type]
+ }
+
+ # Check valid line length
+ set len [string range $line 0 1]
+ set len [expr "0x$len"]
+ set data [string range $line 8 {end-2}]
+ if {$len != ([string length $data] / 2)} {
+ Error $lineNum [mc "Length field do not corespond true data length"]
+ continue
+ }
+
+ # Check for valid incremental addressing without any ambiguity
+ set addr_hex [string range $line 2 5]
+ set addr [expr "0x$addr_hex"]
+ if {$addr <= $pointer} {
+ Error $lineNum [mc "Unexpected address -- code is not well formated"]
+ continue
+ } elseif {$addr > ($pointer + 1)} {
+ set pointer $addr
+ } {
+ incr pointer $len
+ }
+
+ ## Convert line into this form:
+ # {
+ # {addr hex hex hex ...}
+ # ...
+ # }
+ set len [expr {($len * 2) - 1}]
+ set line {}
+ for {set i 0} {$i <= $len} {incr i} {
+ append line { }
+ append line [string index $data $i]
+ incr i
+ append line [string index $data $i]
+ }
+ lappend hex [string toupper "${addr_hex}${line}"]
+ }
+ set hex_data {} ;# delete input data
+ }
+
+ ## Convert processor code into source code
+ # @return void
+ proc decompile_code {} {
+
+ variable hex ;# Adjusted input data, list: {{addr hex0 hex1 hex2 ...} ...}
+ variable asm ;# Resulting source code
+ variable label ;# Array of tempotary labels, label(int) -> addr
+ variable label_idx ;# Number of usages of code memory addressing
+
+ set pointer 0 ;# reset code memory pointer
+ set label_idx -1 ;# set label counter
+ set asm {} ;# reset asm
+ set idx 0
+
+ set trailing_data [list] ;# data remained after parsing last line
+ set trailing_data_length 0 ;# length od trailing_data
+
+ foreach line $hex {
+
+ if {[expr {$idx % 10}] == 0} ${Compiler::Settings::UPDATE_COMMAND}
+
+ incr idx
+
+ set addr [lindex $line 0] ;# address field (hex)
+ set line [lreplace $line 0 0] ;# data fields (hex)
+ set addr_dec [expr "0x$addr"] ;# decimal value of address
+
+ ## If requested address overlaping expected address then
+ # adjust pointer and write trailing data by DB directive
+ if {$addr_dec > ($pointer + 1 + $trailing_data_length) || ($pointer == 0)} {
+ if {$trailing_data_length} {
+ # Write trailing data
+ foreach opcode $trailing_data {
+ append asm "_$pointer {DB 0${opcode}h {}} "
+ incr pointer
+ }
+ # Reset trailing data
+ set trailing_data_length 0
+ set trailing_data [list]
+ }
+
+ # Adjust pointer
+ set pointer $addr_dec
+ append asm "{} {} {} {ORG [HEX $addr]h {}} "
+ }
+
+ # Number of data fields
+ set len [llength $line]
+
+ # Append trailing data from last parsing to the current line
+ if {$trailing_data_length} {
+ # append
+ incr len $trailing_data_length
+ set line [concat $trailing_data $line]
+ # reset
+ set trailing_data_length 0
+ set trailing_data [list]
+ }
+
+ # Translate opcodes to source code
+ set instruction_skipped 0
+ set remaining_bytes $len
+ incr len -1
+ for {set idx 0} {$idx <= $len} {incr idx} {
+
+ set opcode [lindex $line $idx] ;# current opcode
+ # Search for he given opcode
+ if {[lsearch ${CompilerConsts::defined_OPCODE} $opcode] == -1} {
+ # opcode not found -> write opcode directly to source code
+ append asm "_$pointer {DB 0${opcode}h {}} "
+ set length 1
+ } else {
+ # opcode found -> resolve it's definition
+ set def $CompilerConsts::Opcode($opcode)
+
+ set instruction [lindex $def 0] ;# Instruction name
+ set opr_types [lindex $def 1] ;# Oprand types
+ set length [lindex $def 2] ;# Instruction length
+ set mask_opr [lindex $def 3] ;# Opreand mask
+ set operands {} ;# reset operand values
+
+ ## If remaining code on this line has insufficient length to
+ # make valid instruction then continue to next line and
+ # append remainder of current line to the next line
+ if {$length > $remaining_bytes} {
+ set trailing_data_length $remaining_bytes
+ set trailing_data [lrange $line $idx end]
+
+ break
+ }
+
+ # Resolve operands
+ set opr {}
+ foreach type $opr_types {
+
+ if {[lsearch ${CompilerConsts::FixedOperands} [string tolower $type]] != -1} {
+ # Fixed operand -> only copy
+ set opr $type
+ } {
+ # Get operand value
+ incr idx
+
+ if {$idx > $len} {
+ append asm "_$pointer {DB 0${opcode}h {}} "
+ set instruction_skipped 1
+ set length 1
+ incr idx -1
+ break
+ }
+ switch -- $type {
+ {imm8} { ;# Immediate addressing 8 bit
+ set opr "#[HEX [lindex $line $idx]]h"
+ }
+ {imm16} { ;# Immmediate addressing 16 bit
+ set opr "#[HEX [lindex $line $idx]]"
+ incr idx
+
+ if {$idx > $len} {
+ append asm "_$pointer {DB 0${opcode}h {}} "
+ set instruction_skipped 1
+ set length 1
+ incr idx -2
+ break
+ }
+ append opr "[lindex $line $idx]h"
+ }
+ {bit} { ;# Direct addressing bit
+ set opr "[HEX [lindex $line $idx]]h"
+ set tmp_opr [string range $opr 0 {end-1}]
+ set tmp_opr [expr "0x$tmp_opr"]
+ foreach item ${CompilerConsts::MapOfSFRBitArea} {
+ if {[expr "0x[lindex $item 1]"] == $tmp_opr} {
+ set opr [lindex $item 0]
+ break
+ }
+ }
+ }
+ {/bit} { ;# Direct inverted addressing bit
+ set opr "/[HEX [lindex $line $idx]]h"
+ set tmp_opr [string range $opr 1 {end-1}]
+ set tmp_opr [expr "0x$tmp_opr"]
+ foreach item ${CompilerConsts::MapOfSFRBitArea} {
+ if {[expr "0x[lindex $item 1]"] == $tmp_opr} {
+ set opr [lindex $item 0]
+ break
+ }
+ }
+ }
+ {data} { ;# Direct addressing
+ set opr "[HEX [lindex $line $idx]]h"
+ set tmp_opr [string range $opr 0 {end-1}]
+ set tmp_opr [expr "0x$tmp_opr"]
+ foreach item ${CompilerConsts::MapOfSFRArea} {
+ if {[expr "0x[lindex $item 1]"] == $tmp_opr} {
+ set opr [lindex $item 0]
+ break
+ }
+ }
+ }
+ {code8} { ;# Immediate addressing code memory, 8 bit
+ incr label_idx
+ set opr "lbl${label_idx}-"
+
+ set label($label_idx) [expr "0x[lindex $line $idx]"]
+
+ if {$label($label_idx) > 127} {
+ incr label($label_idx) -256
+ }
+
+ incr label($label_idx) $pointer
+ incr label($label_idx) $length
+
+ if {$label($label_idx) > 0x0FFFF || $label($label_idx) < 0} {
+ set label($label_idx) [expr {$label($label_idx) & 0x0FFFF}]
+ Warning "Code address overflow, instruction: $instruction"
+ } elseif {$label($label_idx) == $pointer} {
+ unset label($label_idx)
+ incr label_idx -1
+ set opr {$}
+ }
+ }
+ {code11} { ;# Immediate addressing code memory, 11 bit
+ incr label_idx
+ set opr "lbl${label_idx}-"
+ set label($label_idx) "$mask_opr[lindex $line $idx]"
+ set label($label_idx) [expr "0x$label($label_idx)"]
+
+ if {$label($label_idx) == $pointer} {
+ unset label($label_idx)
+ incr label_idx -1
+ set opr {$}
+ }
+ }
+ {code16} { ;# Immediate addressing code memory, 16 bit
+ incr label_idx
+ set opr "lbl${label_idx}-"
+ set label($label_idx) [lindex $line $idx]
+ incr idx
+
+ if {$idx > $len} {
+ append asm "_$pointer {DB 0${opcode}h {}} "
+ set length 1
+ incr idx -2
+ set instruction_skipped 1
+ break
+ }
+ append label($label_idx) [lindex $line $idx]
+ set label($label_idx) [expr "0x$label($label_idx)"]
+
+ if {$label($label_idx) == $pointer} {
+ unset label($label_idx)
+ incr label_idx -1
+ set opr {$}
+ }
+ }
+ }
+ }
+ # Resulting operand value to operand list
+ lappend operands $opr
+ }
+
+ if {$instruction_skipped} {
+ set instruction_skipped 0
+ incr pointer $length
+ incr remaining_bytes -$length
+ continue
+ }
+
+ # Append line to source code list
+ append asm "_$pointer {$instruction {$operands} {}} "
+ }
+ # Increment program address pointer
+ incr pointer $length
+ incr remaining_bytes -$length
+ }
+ }
+ }
+
+ ## Create labels in resulting code
+ # Replace tempotary labels references by theirs final forms
+ # and add appropriate labels to lines (label: mov A, #56q)
+ proc parse_labels {} {
+
+ variable asm ;# Resulting source code
+ variable label ;# Array of tempotary labels, label(int) -> addr
+ variable label_idx ;# Label index
+ variable final_lbls ;# Number of final labels
+
+ set addrs {} ;# List of set addresses
+ set equ_block {} ;# Reset block of declarations of unresoved labels
+
+ # Replace each tempotary label with final label
+ set lbl_idx -1
+ for {set i 0} {$i <= $label_idx} {incr i} {
+ set idx [lsearch $addrs $label($i)]
+ if {$idx != -1} {
+ # Reuse an existing label
+ regsub -all "lbl${i}-" $asm "label$idx" asm
+ } {
+ # Realy a new label
+ lappend addrs $label($i)
+ incr lbl_idx
+ regsub -all "lbl${i}-" $asm "label$lbl_idx" asm
+ }
+ }
+ set final_lbls $lbl_idx
+
+ # Write final labels to the source code
+ set i 0
+ foreach addr $addrs {
+ set idx [lsearch $asm "_$addr"]
+ if {$idx == -1} {
+ # Not found
+ append equ_block "{} {CODE [HEX [format %X $addr]]h label$i} "
+ } {
+ # Found
+ incr idx
+ lset asm [list $idx 2] "label$i"
+ }
+ incr i
+ }
+
+ # Append block of declarations of unresolved labels to resulting source code
+ append equ_block $asm
+ set asm $equ_block
+ }
+
+ ## Final stage
+ # list -> plain text
+ proc final_stage {} {
+ variable asm ;# Resulting code
+ variable tmp_asm ;# Tempotary variable only this procedure
+ variable final_lbls ;# Number of final labels
+
+ set tmp_asm {} ;# reset tempotary code
+ set len [llength $asm] ;# length of source code list
+ incr len -1
+
+ # Rewrite the source code
+ for {set i 1} {$i <= $len} {incr i 2} {
+
+ if {[expr {$len % 5}] == 0} ${Compiler::Settings::UPDATE_COMMAND}
+
+ set line [lindex $asm $i] ;# Get line
+
+ # Empty line
+ if {$line == {}} {
+ append tmp_asm "\n"
+ continue
+
+ # Not an empty line
+ } {
+ set label [lindex $line 2] ;# label
+ set instr [lindex $line 0] ;# instruction
+ set oprs [lindex $line 1] ;# oprands
+
+ if {$label != {} && $instr != {CODE}} {
+ append label {:}
+ if {[string length $label] > 7} {
+ append tmp_asm "$label\n"
+ set label {}
+ }
+ }
+ if {$instr == {CODE}} {
+ if {$final_lbls > 999 && [string length $label] < 8} {
+ append label "\t"
+ }
+ }
+ }
+
+ # Write line
+ append label "\t" $instr "\t" [join $oprs {, }]
+ append tmp_asm [string trimright $label] "\n"
+ }
+
+ # Append 'END' directive at the end
+ append tmp_asm "\n\tEND"
+ set asm $tmp_asm
+ }
+
+ ## Free memory used during processing
+ proc free_resources {} {
+ variable tmp_asm ;# Tempotary variable for procedure: 'final_stage'
+ variable hex_data ;# Raw input data
+ variable hex ;# Adjusted input data, list: {addr hex0 hex1 hex2 ...}
+ variable lineNum ;# Number of line currently beeing parsed
+ variable error_count ;# Number of errors raised during decompilation
+ variable label_idx ;# Label index
+ variable label ;# Array of tempotary labels, label(int) -> addr
+ variable asm ;# Resulting source code
+
+ set tmp_asm {}
+ set hex_data {}
+ set hex {}
+ set lineNum {}
+ set error_count {}
+ set label_idx {}
+ catch {array unset label}
+ }
+
+ ## Adjust hexadecimal number
+ # note: resulting number starts with digit
+ # @parm String number - input
+ # @return String - result
+ proc HEX {number} {
+
+ set number [string trimleft $number 0]
+ if {$number == {}} {return 0}
+
+ if {[regexp {^[a-fA-F]} $number]} {
+ return "0$number"
+ }
+
+ return $number
+ }
+
+ ## Report warning message
+ # @parm Int LineNumber - Number of line where it occured
+ # @parm String ErrorInfo - Text of the warning
+ # @return void
+ proc Warning {ErrorInfo} {
+ variable idx ;# Current position in asm list
+ variable warning_count ;# Number of warnings occured
+
+ # Increment warning counter
+ incr warning_count
+
+ # Report the warning
+ if {${::Compiler::Settings::WARNING_LEVEL} < 2} {
+ if {${::Compiler::Settings::NOCOLOR}} {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} [::Compiler::msgc {WN}][mc "Warning: %s" $ErrorInfo]
+ } {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} [mc "\033\[33mWarning\033\[m: %s" $ErrorInfo]
+ }
+ }
+ }
+
+ ## Error
+ # @parm Int lineNumber - number of line, where the error occured
+ # @parm String info - error string
+ proc Error {lineNumber info} {
+ variable error_count
+ incr error_count
+ if {$lineNumber != {}} {
+ if {${::Compiler::Settings::NOCOLOR}} {
+ set lineNumber [mc " at line %s" $lineNumber]
+ } {
+ set lineNumber [mc " at line \033\[31;1;4m%s\033\[m" $lineNumber]
+ }
+ }
+ if {${::Compiler::Settings::WARNING_LEVEL} < 3} {
+ if {${::Compiler::Settings::NOCOLOR}} {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} [::Compiler::msgc {EL}][mc "Error%s: %s" $lineNumber $info]
+ } {
+ ${Compiler::Settings::TEXT_OUPUT_COMMAND} [mc "\033\[31;1mError%s\033\[m: %s" $lineNumber $info]
+ }
+ }
+ }
+}