leon3ft-b2bst-scan.tcl
681 lines
| 24.5 KiB
| application/x-tcl
|
TclLexer
/ sparc / leon3ft-b2bst-scan.tcl
paul
|
r369 | #! /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 | ||||
} | ||||
} | ||||