#! /usr/bin/tclsh # # TCL script to scan disassembled programs against the "LEON3FT Stale Cache # Entry After Store with Data Tag Parity Error" errata, GRLIB-TN-0009. # # The following Cobham components are affected by GRLIB-TN-0009: # - GR712RC # - LEON3FT-RTAX (all versions) # - UT699 # - UT699E # - UT700 # # For information on how custom LEON3 systems may be affected, see # GRLIB-TN-0009. # # Copyright (C) 2016, Cobham Gaisler # # Usage: sparc-elf-objdump -d program | leon3ft-b2bst-scan.tcl # # Command line parameters: # -nonote: Do not print NOTE messages. # -noinfo: Do not print INFO messages. # -silent: Do not print NOTE or INFO messages. # # Return values: # 0: No potential error locations found. # 1: At least one potential error location found. # # Rev. history: # rev 1, MH, Initial revision, based on ut699scan.tcl. Add scanning for # store-store errata, reorganize comments # rev 2, MA, Restruction and clean-up, fixed Sequence B. # rev 3, MA, Added decoding of casa instruction, exit status and verbosity # parameters. # rev 4, MA, Reduced false warnings due to Store into Alternate space # instructions which can not trig the errata. Script renamed to # leon3ft-b2bst-scan.tcl. # # Note 1: Two modes are available with respect to call/ret (dslot_mode): # 1) ret, and call are assumed not to have stores in their delay slots. # The tool will warn if any such operations are found in the delay # slot of ret,call during the scan # 0) ret and call may have stores in their delay slots # The tool will not warn if any such operations are found in the # delay slot during the scan. Instead it will assume this # possibility exists when evaluating insns following a function call # ### CONFIGURATION set b2bst_seqa_scan 1; # enable/disable scanning for Sequence A set b2bst_seqb_scan 1; # enable/disable scanning for Sequence B # set dslot_mode 0; # assume store can be in delay slot of call,jmpl set dslot_mode 1; # do not allow store in delay slot of call,jmpl ### # Mask values for types of output messages set MSG_INFO 1; # Information on things to check manually. set MSG_NOTE 2; # Notices on branch following set msglevel [expr {$MSG_INFO | $MSG_NOTE}] set errata_desc "LEON3FT Stale Cache Entry After Store with Data Tag Parity Error" set util_rev 4 set util_date "20170215" set b2bst_seqa_desc "Sequence A" set b2bst_seqb_desc "Sequence B" set op_count 0 set lineskip_count 0 set line_count 0 set err_count 0 set fn_count 0 set fninst_count 0 proc puts_info {str} { global msglevel MSG_INFO if {$msglevel & $MSG_INFO} { puts "INFO: $str" } } proc puts_note {str} { global msglevel MSG_NOTE if {$msglevel & $MSG_NOTE} { puts "NOTE: $str" } } # ---------------------------------------------------------------------------- # Routine that decodes specified opcode and returns list of # 0 :optype Class of opcode # one of call,jmpl,branch,sethi,ld,st,atomic,alu,fpop,save,restore # 1 :target-reg Register modified by op # 2 :source-reg1 First source register (rs1) # 3 :source-reg2 Second source register (rs2) # 4 :store-reg Source register for store data # regs are numbered 0-31 integer regs, 32-63 FP regs # 5 :double-flag # ORed mask: 8) target-reg is DP, 4) sreg1 is DP, 2) sreg2 is DP, 1) store-reg is DP # 6 :immed Immediate operand # 7 :nnpc1 Next instructions nPC if branch not taken (normal case), normally nPC+4 # 8 :nnpc2 Next instruction's nPC if branch is taken, if not branch then nnpc2=nnpc1 # 9 :annul # ORed mask: next inst annulled if 2) branch not taken 1) branch taken # 10:bubbles Number of extra cycles spent in decode stage (bubbles inserted) proc decode_inst { opcode {pc "0x00000000"} {npc "0x00000004"}} { # Some "sta" instructions do not trig the errata. The related ASI:s are # defined here. set safeasi [list 0x02 0x03 0x04 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x13 0x14 0x15 0x18 0x19 0x1c 0x1d 0x1e] #Default values if no cond matches set opname "unknown" set destreg 0 set sreg1 0 set sreg2 0 set streg 0 set dblflag 0 set immed 0 if { [string is integer $npc] } { set nnpc1 [format "0x%08x" [ expr {$npc+4} ] ] } else { set nnpc1 "$npc+4" } set nnpc2 $nnpc1 set annul 0 set bubbles 0 #Decode set op1 [ expr {$opcode >> 30} ] if { $op1 == 1 } { set opname "call" set disp [ expr { ($opcode & 0x3fffffff)<<2 } ] set nnpc1 [ format "0x%08x" [ expr { $pc + $disp } ] ] set nnpc2 $nnpc1 } elseif { $op1 == 0 } { set op2 [ expr {($opcode >> 22) & 7} ] # NOTE: 2=Bicc, 6=FBcc, 7=CBcc if { $op2 == 2 || $op2 == 6 } { set opname "branch" set disp [expr { ($opcode & 0x3fffff) << 2 } ] if { $disp > 0x800000 } { set disp [expr {$disp - 0x1000000}] } # Todo check cond,annul, handle always or never set nnpc2 [ format "0x%08x" [ expr { $pc + $disp } ] ] set cond [ expr { ($opcode >> 25) & 15 } ] set annulbit [ expr { $opcode & 0x20000000 } ] if { $cond == 0 } { # Branch never set nnpc2 $nnpc1 if { $annulbit } { set annul 3 } } elseif { $cond == 8 } { # Branch always set nnpc1 $nnpc2 if { $annulbit } { set annul 3 } } else { # True cond branch if { $annulbit } { set annul 2 } } } elseif { $op2 == 4 } { set opname "sethi" set destreg [ expr { ($opcode >> 25) & 31 } ] set immed [ expr { ($opcode & 0x3fffff) << 10 } ] } } elseif { $op1 == 3 } { # SPARC V8 Table F-4 # set opname "mem" - changed to use ld/st/atomic instead set op3 [ expr { ($opcode >> 19) & 63 } ] set rs1 [ expr { (($opcode >> 14) & 31) } ] set rs2 [ expr { (($opcode ) & 31) } ] set rd [ expr { (($opcode >> 25) & 31) } ] set sreg1 $rs1 if { $opcode & 0x2000 } { set immed [ expr { $opcode & 0x1fff } ] if { $immed > 0xfff } { set immed [ expr { $immed-(0x2000) } ] } } else { set sreg2 $rs2 } if { (($op3 & 13) == 13) || ($op3 == 0x3c) } { # Atomic ldst/swap/casa set opname "atomic" set destreg $rd set streg $rd } else { # Adjust reg no if FP ld/st (op3[5]=1) if { ($op3 & 0x20) != 0 } { set rd [expr {32 + $rd} ] } # Check if read or write op3[2] if { ($op3 & 4) != 0 } { set opname "st" set streg $rd set allow_safe_sta true if { $allow_safe_sta && (($op3 & 0x3c) == 0x14) } { # op3 is 0x14, 0x15, 0x16 or 0x17 (sta, stba, stha or stda) # (if i = 0) set asi [expr { (($opcode >> 5) & 0xff) }] set safematch [lsearch -exact -integer $safeasi $asi] if {0 <= $safematch} { set opname "sta_safe" } } } else { set opname "ld" set destreg $rd } # Check size op3[1:0] if { ($op3 & 3) == 3 } { if { $opname eq "st" } { set dblflag 1 } else { set dblflag 8 } } } } else { set op3 [ expr { ($opcode >> 19) & 63 } ] if { $op3 == 0x34 || $op3 == 0x35 } { set opname "fpop" set opf [ expr { ($opcode >> 5) & 0x1ff } ] set rs1 [ expr { 32 + (($opcode >> 14) & 31) } ] set rs2 [ expr { 32 + (($opcode ) & 31) } ] set rd [ expr { 32 + (($opcode >> 25) & 31) } ] # opf[1:0] corresponds to operand size of rs2 # opf[7:6]=11 is conv op, opf[3:2] gives opsize of rd, rs1 unused # opf[7:6]=00 is single-operand op, rd same size as rs2, rs1 unused # opf[7:4]=0100 regular op, rs1,rd same size as rs2 # opf[7:4]=0101 comp op, rd unused, rs1 same size as rs2 # opf[7:4]=0110 conv+mul, opf[3:2] gives opsize of rd, rs1 same size as rs2 set sreg2 $rs2 if { ($opf & 3) > 1 } { set dblflag 2 } if { ($opf & 0xc0) == 0x40 } { set sreg1 $rs1 if { $dblflag & 2 } { set dblflag [ expr { $dblflag | 4 } ] } } if { ($opf & 0xc0) == 0xc0 || ($opf & 0xf0) == 0x60 } { set destreg $rd if { ($opf & 0xc) > 4 } { set dblflag [ expr { $dblflag | 8 } ] } } elseif { ($opf & 0xf0) != 0x50 } { set destreg $rd if { $dblflag & 2 } { set dblflag [ expr { $dblflag | 8 } ] } } } else { set opname "alu" set destreg [ expr { (($opcode >> 25) & 31) } ] set sreg1 [ expr { (($opcode >> 14) & 31) } ] if { $opcode & 0x2000 } { set immed [ expr { $opcode & 0x1fff } ] if { $immed > 0xfff } { set immed [ expr { $immed-(0x2000) } ] } } else { set sreg2 [ expr { (($opcode ) & 31) } ] } if { $op3 == 0x38 } { set opname "jmpl" set nnpc1 "jmpaddr" set nnpc2 "jmpaddr" set bubbles 2 } elseif { $op3 == 0x3c } { set opname "save" } elseif { $op3 == 0x3d } { set opname "restore" } } } #Return values return [list $opname $destreg $sreg1 $sreg2 $streg $dblflag $immed $nnpc1 $nnpc2 $annul $bubbles] } proc is_store { opcode } { set op1 [ expr { ($opcode >> 30) } ] set op3 [ expr { ($opcode >> 19) & 63 } ] return [expr {($op1 == 3) && (($op3 & 0xc) == 0x4)}] } # ------------------------------------------------------------------------------------- proc unpack { l args } { for { set i 0 } { $i < [llength $args] } { incr i } { uplevel 1 "set [lindex $args $i] [lindex $l $i]" } } # --------- Postprocessing on whole function # Number of instructions printed is starti + 2 proc print_trace { fnpc state fnarr seqstart {starti 4}} { upvar $fnarr fnops puts " # PC Opcode" set cnt [expr {$seqstart-$starti}] for { set x $starti } { $x > -2 } { set x [expr {$x-1}] } { incr cnt if { $cnt > 0 } { set cntv $cnt } else { set cntv " " } if { $x >= 0 } { set pc [lindex $state $x] } else { set pc $fnpc } if { $pc eq "?" || $pc eq "call" || $pc eq "ret" || $pc eq "dslot" } { puts " $cntv (other fn) $pc" } else { if { [string index $pc 0] eq "A" } { set pc [string range $pc 1 end] set annul " (annulled)" } else { set annul "" } if { [info exists fnops($pc)] } { set op $fnops($pc) } else { set op [list "0x???????? (outside func)"] } puts " $cntv $pc [join $op] $annul" } } puts "" } # Routine called by scan_wholefunc_main to check # instruction sequences described in GRLIB-TN-009, Issue 1.0 # # "Sequence A" # 1. store of word size or less (st / stb / sth / stf) # 2. any single instruction that is not a load or store # 3. any store instruction (st / stb / sth / stf / std / stdf) # # "Sequence B" # 1. store of double word size (std / stdf) # 2. any store instruction (st / stb / sth / stf / std / stdf) proc scan_b2bst { fnname fnpc fnop opdec fnarr state } { global b2bst_seqa_scan global b2bst_seqb_scan upvar $fnarr fnops global dslot_mode # Check latest 3 insn if they can be st/std/regular set pca [list $fnpc [lindex $state 0] [lindex $state 1]] set can_be_arr [list] for { set i 0 } { $i < 3 } { incr i } { set can_be [list 0 0 0] ; # st std regular set pcv [lindex $pca $i] if { $pcv eq "?" } { set can_be [list 1 1 1] } elseif { $pcv eq "dslot" } { set can_be [list [expr {1-$dslot_mode}] [expr {1-$dslot_mode}] 1] } elseif { [string index $pcv 0] eq "A" || $pcv eq "call" || $pcv eq "ret" } { set can_be [list 0 0 1] } else { set op $fnops($pcv) set ophex [lindex $op 0] set opdec [decode_inst $ophex] set optype [lindex $opdec 0] set opstorereg [lindex $opdec 4] set opdblflag [lindex $opdec 5] if { $optype eq "st" && ($opdblflag & 1) } { set can_be [list 0 1 0] } elseif { $optype eq "st" } { set can_be [list 1 0 0] } elseif { $optype ne "ld" && $optype ne "atomic" } { # Any single instruction that is not a load or store set can_be [list 0 0 1] } } lappend can_be_arr $can_be } # can_be_arr has at index # - at index 2: previous previous instruction # - at index 1: previous instruction # - at index 0: current instruction set pp_insn [lindex $can_be_arr 2] set p_insn [lindex $can_be_arr 1] set c_insn [lindex $can_be_arr 0] # Sequence A # 1. store of word size or less # 2. any single instruction that is not a load or store # 3. any store instruction if { ( $b2bst_seqa_scan && [lindex $pp_insn 0] && [lindex $p_insn 2] && ([lindex $c_insn 0] || [lindex $c_insn 1]) ) } { puts "WARNING: Possible sequence matching LEON3FT b2bst errata (sequence A) in function $fnname" print_trace $fnpc $state fnops 1 puts "" return 1 } # Sequence B # 1. any store instruction # 2. store of double word size if { ( $b2bst_seqb_scan && [lindex $p_insn 1] && ([lindex $c_insn 0] || [lindex $c_insn 1]) ) } { puts "WARNING: Possible sequence matching LEON3FT b2bst errata (sequence B) in function $fnname" print_trace $fnpc $state fnops 0 puts "" return 1 } return 0 } # Main recursive function doing scan # fnname - funcion name # fnpc, fnnpc, PC/nPC of current insn (8-digit hex, 0x prefix) prefix also with A if annulled, # fnarr - array/hash-table, see descr for scan_wholefunc # dpdist - number of SP FPOPs done minimum since last DP FPOP # state - List of 5 last instruction addresses (hex w 0x prefix), special values "?"=unknown "call"=call from calling proc # smap - Hash table used to track where we have been already proc scan_wholefunc_main { fnname fnpc fnnpc fnarr savelevel dpdist state smap lastoptype } { global dslot_mode upvar $fnarr fnops upvar $smap statemap set r 0 # puts "entering scan_wholefunc_main: PC:$fnpc nPC:$fnnpc dpdist:$dpdist savelevel:$savelevel state:$state" # puts "fnops exists [info exists fnops] [array exists fnops] [array names fnops]" while {1} { # puts "scan_wholefunc_main: PC:$fnpc nPC:$fnnpc dpdist:$dpdist savelevel:$savelevel state:$state" set statedesc [list $fnpc $fnnpc $savelevel $dpdist $state] if { [info exists statemap($statedesc)] } { # Already been here return $r } set statemap($statedesc) 1 set fnpca $fnpc set pcannul 0 if { [string index $fnpc 0] eq "A" } { set pcannul 1 set fnpc [string range $fnpc 1 end] set ophex "0x01000000" } else { if {![info exists fnops($fnpc)]} { puts_note "$fnname: Execution leaves function without return"; return $r } set op $fnops($fnpc) set ophex [lindex $op 0] set opname [lindex $op 1] } set opdec [ decode_inst $ophex $fnpc $fnnpc ] unpack $opdec optype treg sreg1 sreg2 streg dblflag immed nnpc1 nnpc2 annul bubbles set prev_savelevel $savelevel if { $optype eq "fpop" } { # update dpdist if { ($dblflag & 6) == 0 } { if { $dpdist < 2 } { incr dpdist } } else { set dpdist 0 } } elseif { $optype eq "save" } { if { $savelevel > 9 } { puts_note "$fnname: More than 10 deep saves (possibly regfile clear loop)" } else { incr savelevel } # puts "save at $fnpc, new savelevel: $savelevel" } elseif { $optype eq "restore" } { if { $savelevel < -9 } { puts_note "$fnname: More than 10 deep restores (possibly regfile clear loop)" } else { if { $savelevel == 0 } { puts_note "$fnname: More restore than save instructions" } set savelevel [ expr { $savelevel-1} ] } # puts "restore at $fnpc, new savelevel: $savelevel" } if { true && ($optype eq "st") } { if { [scan_b2bst $fnname $fnpc $op $opdec fnops $state] } { incr r } } # Handle jmpl and call # If in delay slot of jmpl: # If ret or retl, stop recursing further # If other jmpl, check savelevel if it will return or not # If in delay slot of call, set dpdist to 0 (called proc may have done DP operation) set is_call 0 if { ( $dslot_mode != 0 && ($lastoptype eq "branch" || $lastoptype eq "jmpl" || $lastoptype eq "call") ) } { if { $optype eq "st" } { puts "WARNING: $fnname: $opname in delay slot of branch/call/ret at $fnpc may cause errata" print_trace $fnpc $state fnops -1 1 puts "" incr r } } if { $lastoptype eq "jmpl" } { set linst [lindex $state 0] set q $fnops($linst) set qh [lindex $q 0] set qdec [ decode_inst $qh $linst $fnpc ] unpack $qdec qdtype qdtreg qdsreg1 qdsreg2 qdstreg qddblflag qdimmed qdnnpc1 qdnnpc2 qdannul if { $qdtreg==15 && $prev_savelevel==1 && $savelevel==1 } { # Function call via pointer incr is_call } elseif { $qdtreg==0 && $savelevel==0 } { # Return or tail recursion return $r } else { # Computed goto or some other non-standard construct puts_info "$fnname: Unable to trace jmpl at $linst - check manually" return $r } } elseif { $lastoptype eq "call" } { if { $savelevel==0 && $prev_savelevel==1 } { # call,restore tail recursion into other fn return $r } elseif { $savelevel==0 && $prev_savelevel==0 && $treg == 15 } { puts_note "$fnname: Leaf tail recursion construct at $fnpc" return $r } else { incr is_call } } # Update history for { set z 0 } { $z <= $bubbles } { incr z } { set state [concat $fnpca [lrange $state 0 4] ] } if { $is_call } { if { $optype eq "branch" || $optype eq "call" || $optype eq "jmpl" } { puts_info "$fnname: jump in delay slot of call - check manually" } # Setup state after call has returned set dpdist 0 set state [list "dslot" "ret" "ret" "ret" "?" ] set optype "?"; # for lastoptype set fnnpc [ format "0x%08x" [expr {$fnpc+4}]] set nnpc1 [ format "0x%08x" [expr {$fnpc+8}]] set nnpc2 $nnpc1 set annul 0 } # Handle annullment if { ($annul & 2) != 0 } { set npc1 "A$fnnpc" } else { set npc1 $fnnpc } if { ($annul & 1) != 0 } { set npc2 "A$fnnpc" } else { set npc2 $fnnpc } # Recurse if multiple nPC:s, tail recurse for last element if { $nnpc1 != $nnpc2 || $npc1 != $npc2 } { # puts "Recursion nnpc:$nnpc2!" set q [scan_wholefunc_main $fnname $npc1 $nnpc2 fnops $savelevel $dpdist $state statemap $optype] set r [expr {$r+$q}] } set fnpc $npc1 set fnnpc $nnpc1 set lastoptype $optype } } # Whole function scan for "LEON3FT Stale Cache After Store with Data Tag Parity Error" errata # fnname - function name, fnaddr - function addres # fnarr - name of array/hash-table containing operations # key to the array is the address as 8-digit hex number prefixed with 0x # value is a list containing 0:opcode, 1:mnemonic, 2:args proc scan_wholefunc { fnname fnaddr fnarr } { global fn_count fninst_count; upvar $fnarr fnops if { $fnname eq ".text" } { puts "WARNING: The disassembled binary appears to be stripped, the script can not scan stripped binaries" } # Does the fn not contain any store at all, then we can skip it set sid [array startsearch fnops] set found_st 0 while { !$found_st } { set op [array nextelement fnops $sid] if { $op eq "" } { array donesearch fnops $sid; break } set ophex [lindex $fnops($op) 0] # puts "$ophex" if { [is_store $ophex] } { incr found_st } } if { !$found_st } { # puts "No store in $fnname!" return 0 } # -- Function contains store, proceed with scanning # puts "Checking $fnname" set pc "0x$fnaddr" # puts "pc: $pc" set npc [format "0x%08x" [expr {$pc+4}]] array unset statemap array set statemap "" set ec [scan_wholefunc_main $fnname $pc $npc fnops 0 0 [list "dslot" "call" "?" "?" "?" "?"] statemap "?"] # Update fn_count/fninst_count incr fn_count array unset itagchk array set itagchk "" set sid [array startsearch statemap] while { [array anymore statemap $sid] } { set x [array nextelement statemap $sid] set iaddr [lindex $x 0] # puts "iaddr: $iaddr" if { [info exists fnops($iaddr)] && ![info exists itagchk($iaddr)]} { incr fninst_count set itagchk($iaddr) 1 } } array donesearch statemap $sid return $ec } # --------- Main routine if { ! [info exists sourcing] } { # Parse command line for {set i 0} {$i < $argc} {incr i} { set arg [lindex $argv $i] puts "arg=$arg" if {"-noinfo" eq $arg} { set msglevel [expr {$msglevel & ~($MSG_INFO)}] } if {"-nonote" eq $arg} { set msglevel [expr {$msglevel & ~($MSG_NOTE)}] } if {"-silent" eq $arg} { set msglevel 0 } } puts "\n$errata_desc errata scanning utility, rev $util_rev ($util_date)" puts "Searching objdump -d output on standard input for:" if {$b2bst_seqa_scan} { puts " - $b2bst_seqa_desc" } if {$b2bst_seqb_scan} { puts " - $b2bst_seqb_desc" } puts "" set fn_valid 0 while {! [eof stdin]} { set l [gets stdin] incr line_count # puts "line $l" # Remove comment after '!' set x [string first "!" "$l"] if { $x >= 0 } then { set l [string range "$l" 0 $x] } # check if entry point / function start set m [regexp {^ *([0-9A-Fa-f]+) <([^ ]+)>:} "$l" t1 addr symname] if { $m > 0 } then { # puts "Function name=$symname addr=$addr" if { $fn_valid } { # puts $curfn_ops(0x40000000) set q [scan_wholefunc $curfn $curfn_addr curfn_ops] # puts "q=$q err_count=$err_count" set err_count [ expr { $err_count + $q } ] } set curfn "$symname" set curfn_addr "$addr" array unset curfn_ops array set curfn_ops "" set fn_valid 0 } else { # Check if opcode set m [regexp {^ *([0-9A-Fa-f]+): *\t(.. .. .. ..) *\t([^ ]+)(.*)$} "$l" t1 addr hexcode opname opargs] if { $m > 0 } then { # puts "Opcode name=$opname args=$opargs addr=$addr" incr op_count set addr [format "%08x" 0x$addr] set hexcode [string map {" " ""} $hexcode] # puts "setting curfn_ops 0x$addr" set curfn_ops(0x$addr) [list "0x$hexcode" $opname $opargs "" ""] incr fn_valid } else { incr lineskip_count } } } if { $fn_valid } { set q [scan_wholefunc $curfn $curfn_addr curfn_ops] set err_count [ expr { $err_count + $q } ] } puts "\nObjdump lines processed: $line_count, lines skipped: $lineskip_count" puts "Functions scanned: ${fn_count}, reachable instruction count: ${fninst_count}" puts "Potential error locations found: $err_count\n" if {$err_count == 0} { exit 0 } else { exit 1 } }