#!/usr/bin/perl -w
-# Copyright (c) 2003 University of Utah and the Flux Group.
+# Copyright (c) 2003-2009 University of Utah and the Flux Group.
# All rights reserved.
#
# Permission to use, copy, modify, distribute, and sell this software
# Author: John Regehr (regehr@cs.utah.edu)
# Revised by: Xuejun Yang on 01/10/2009
+# For more information:
+# http://docs.tinyos.net/index.php/Stack_Analysis
+
use strict;
use warnings;
use Getopt::Long;
# TODO:
-# tighten results by keeping track of atomic sections
-# support overriding the default heuristic for detecting atomic vectors
-# get rid of hard-coded non-terminating functions, just derive this
-# when no "ret" is executed
-# test for tightness / soundness using randprog
-# read config info from a file
-# chip parameters
-# libc information
-# recursion and interrupt info
-# support TOSThreads
-# support msp430
-# enumerate soundness requirements
-# stores to SP are direct and use "out"
-# no indirect stores to registers
-# no reentrant interrupts
-# outs to 0x3f are ending atomic blocks, seis are enabling interrupts for real
-# return instructions go back to their callers' successors
+#
+# support TOSThreads
+#
+# support msp430
+#
+# optionally don't do recursion checking
+#
+# print path to WC stack depth, perhaps graphically
+#
+# make it possible to specify chips individually
+#
+# make it possible to explicitly specify which interrupts are atomic
+# or not
+#
+# tighten results by keeping track of depths inside and out of
+# atomic sections
+#
+# print stack used at thread blocking points
+#
+# support overriding the default heuristic for detecting atomic
+# interrupts
+#
+# get rid of hard-coded non-terminating functions, just derive this
+# when no "ret" is executed
+#
+# test for tightness / soundness using randprog + Avrora
+#
+# read config info from a file
+# chip parameters
+# libc information
+# recursion and interrupt info
##########################################################################
# also look below for __prologue_saves__ and __epilogue_restores__
my %SPECIAL = (
+ "TinyThreadSchedulerP__switchThreads" => 10,
+
# these have icalls
#"__eewr_block" => 35,
#"__eerd_block" => 35,
if ($pair =~ /^([a-zA-Z0-9]+)\, ([a-zA-Z0-9\+]+)$/) {
return ($1, $2);
} else {
- die "FAIL: expected 'x, y' got '$pair'";
+ die "tos-ramsize FAIL: expected 'x, y' got '$pair'";
}
}
sub get_relative_address ($) {
(my $addr) = @_;
my $code = $args{$addr};
- die "FAIL" if (!($code =~ /.(\-?[0-9]+)/));
+ die "tos-ramsize FAIL" if (!($code =~ /.(\-?[0-9]+)/));
return 2+$1;
}
sub disassemble ($) {
(my $fn) = @_;
- open INF, "avr-objdump -zsD $fn |" or die "FAIL: can't open input file $fn";
+ open INF, "avr-objdump -zsD $fn |" or die "tos-ramsize FAIL: can't open input file $fn";
my $linenum = 0;
my $text_go = 0;
my $raw_text_go = 0;
if (!defined($raw_addr)) {
$raw_addr = $address;
} else {
- die "FAIL" if ($raw_addr != $address);
+ die "tos-ramsize FAIL" if ($raw_addr != $address);
}
($bytes =~ s/\s//g);
while (length($bytes)>0) {
- die "FAIL" if (length($bytes)==1);
+ die "tos-ramsize FAIL" if (length($bytes)==1);
my $b = substr $bytes, 0, 2;
$bytes = substr $bytes, 2, length($bytes)-2;
$raw_text{$raw_addr} = $b;
}
} else {
print "cannot parse raw text: '$line'\n";
- die "FAIL";
+ die "tos-ramsize FAIL";
}
}
}
# paranoid: don't ignore lines that look funny
- die "FAIL: can't understand '$line'";
+ die "tos-ramsize FAIL: can't understand '$line'";
}
}
sub jmp_call_target ($) {
(my $addr) = @_;
- die "FAIL" if ($insns{$addr} ne "jmp" && $insns{$addr} ne "call");
+ die "tos-ramsize FAIL" if ($insns{$addr} ne "jmp" && $insns{$addr} ne "call");
my $code = $args{$addr};
- die "FAIL" if (!($code =~ /0x([0-9a-f]+)/) && $code != 0);
+ die "tos-ramsize FAIL" if (!($code =~ /0x([0-9a-f]+)/) && $code != 0);
if (($code =~ /0x([0-9a-f]+)/)) {
return hex ($1);
} else {
# skip size depends on size of subsequent insn
if (is_skip ($addr)) {
- die "FAIL: $hex_addr" if (!defined($insn_size{$addr}));
+ die "tos-ramsize FAIL: $hex_addr" if (!defined($insn_size{$addr}));
my $next = $addr + $insn_size{$addr};
if (!defined($insn_size{$next})) {
return $next + 2;
}
}
- die "FAIL";
+ die "tos-ramsize FAIL";
}
sub match_branch ($$) {
# this is a strong sanity check-- if we've got something
# wrong it's highly unlikely that all jump table entries
# will point to actual instructions
- die "FAIL" if (!defined($insns{$targ}));
+ die "tos-ramsize FAIL" if (!defined($insns{$targ}));
push @targets, $targ;
}
# this is a strong sanity check-- if we've got something
# wrong it's highly unlikely that all jump table entries
# will point to actual instructions
- die "FAIL" if (!defined($insns{$targ}));
+ die "tos-ramsize FAIL" if (!defined($insns{$targ}));
push @targets, $targ;
}
if (defined($initial_stack_depth)) {
# FIXME: avr-gcc-412 initializes SP both in
# crt0 and in main(), we can believe the second one
- #die "FAIL: multiple initialization of SP?";
+ #die "tos-ramsize FAIL: multiple initialization of SP?";
}
- die "FAIL" if ($initial_depth < 0 || $initial_depth >= $RAM_SIZE{$platform});
+ die "tos-ramsize FAIL" if ($initial_depth < 0 || $initial_depth >= $RAM_SIZE{$platform});
$initial_stack_depth = $initial_depth;
replace_insn ($addr, $size, 0, "init_sp");
}
}
if ($insn eq "ijmp") {
- $diehere{$addr} = "cannot process raw ijmp at $hex_addr";
+ $diehere{$addr} = "cannot process raw indirect jump at $hex_addr";
} elsif ($insn eq "ret" || $insn eq "reti") {
# no control flow from here in our model
} elsif (is_branch ($addr) || is_skip ($addr) || is_jmp ($addr)) {
}
} elsif ($insn eq "jump_table") {
my $listref = $jump_lists{$addr};
- die "FAIL" if (!defined($listref));
+ die "tos-ramsize FAIL" if (!defined($listref));
@succ = @{$listref};
} elsif (is_fallthrough ($addr)) {
push @succ, ($addr + $insn_size{$addr});
my $target;
if (is_direct_call ($addr)) {
$target = get_target ($addr);
- die "FAIL" if (!defined($target));
+ die "tos-ramsize FAIL" if (!defined($target));
} else {
my $target_func = $ICALL_TARGETS{$last_label};
if (defined($target_func)) {
$target = $label_to_addr{$target_func};
- die "FAIL" if (!defined($target));
+ die "tos-ramsize FAIL" if (!defined($target));
} else {
- $diehere{$addr} = "cannot process raw icall at $hex_addr";
+ $diehere{$addr} = "cannot process raw indirect call at $hex_addr";
}
}
if (defined($target)) {
sub compute_global_size($) {
(my $fn) = @_;
- open INF, "avr-objdump -h $fn |" or die "FAIL: can't open input file $fn";
- my $data_size;
- my $bss_size;
+ open INF, "avr-objdump -h $fn |" or die "tos-ramsize FAIL: can't open input file $fn";
+ my $data_size = 0;
+ my $bss_size = 0;
while (my $line = <INF>) {
chomp $line;
}
}
close INF;
- die "FAIL" if (!defined($data_size));
- die "FAIL" if (!defined($bss_size));
return ($data_size, $bss_size);
}
my $lref = pop (@worklist);
(my $addr, my $old_depth) = @{$lref};
- die "FAIL" if (!defined $addr);
+ die "tos-ramsize FAIL" if (!defined $addr);
my $hex_addr = sprintf "%x", $addr;
if (!defined($insns{$addr})) {
- die "FAIL: no instruction at address $hex_addr";
+ die "tos-ramsize FAIL: no instruction at address $hex_addr";
}
my $insn = $insns{$addr};
my $xxx = $diehere{$addr};
if (defined ($xxx)) {
- die "FAIL: $xxx";
+ die "tos-ramsize FAIL: $xxx";
}
$visited{$addr} = 1;
}
my $se = $stack_effect{$addr};
- die "FAIL: no stack effect for $insn" if (!defined($se));
+ die "tos-ramsize FAIL: no stack effect for $insn" if (!defined($se));
my $new_depth;
if ($se == $ZERO_STACK) {
$new_depth = 0;
# something is very wrong
if ($new_depth > $RAM_SIZE{$platform}) {
- printf "FAIL: stack depth exceeds RAM size at %x\n", $hex_addr;
+ printf "tos-ramsize FAIL: stack depth exceeds RAM size at %x\n", $hex_addr;
die;
}
# require balanced stack #1
if ($insn eq "reti") {
- die "FAIL" if ($vec_type ne "intr");
- die "FAIL" if ($new_depth != 0);
+ die "tos-ramsize FAIL" if ($vec_type ne "intr");
+ die "tos-ramsize FAIL" if ($new_depth != 0);
next;
}
# require balanced stack #2
if ($insn eq "ret") {
- die "FAIL" if ($vec_type ne "func");
- die "FAIL -- unbalanced stack on return from $func_name" if ($new_depth != 0);
+ die "tos-ramsize FAIL" if ($vec_type ne "func");
+ die "tos-ramsize FAIL -- unbalanced stack on return from $func_name" if ($new_depth != 0);
next;
}
}
my $min_path = $INFINITY;
my $min_func;
+ if ($verbosity > 2) {
+ print "self-path lengths in the callgraph:\n";
+ }
foreach my $z (@func_list) {
- if ($path{$z}{$z} < $min_path) {
- $min_path = $path{$z}{$z};
+ my $len = $path{$z}{$z};
+ if ($verbosity > 2) {
+ print " $addr_to_label{$z} $len\n";
+ }
+ if ($len < $min_path) {
+ $min_path = $len;
$min_func = $z;
}
}
if ($min_path != $INFINITY) {
- print "there is a recursive loop of length $min_path\n";
+ print "cannot estimate stack depth due to recursive loop of length $min_path:\n";
my $f = $min_func;
for (my $i=$min_path-1; $i>0; $i--) {
my @next_list = keys (%{$path{$f}});
my $found;
foreach my $n (@next_list) {
- if ($path{$n}{$min_func} == $i) {
+ if ($path{$n}{$min_func} == $i &&
+ $path{$n}{$n} == $min_path) {
$found = $n;
}
}
- die "FAIL graph algo bug" if (!$found);
- print " $addr_to_label{$f} $f -> $addr_to_label{$found} $found\n";
+ die "tos-ramsize FAIL graph algo bug" if (!$found);
+ printf " %s @ %x -> %s @ %x\n", $addr_to_label{$f}, $f, $addr_to_label{$found}, $found;
$f = $found;
}
- print " $addr_to_label{$f} $f -> $addr_to_label{$min_func} $min_func\n";
- die "FAIL";
+ printf " %s @ %x -> %s @ %x\n", $addr_to_label{$f}, $f, $addr_to_label{$min_func}, $min_func;
+
+ die "tos-ramsize FAIL";
}
}
$|=1;
my $result = GetOptions ("verbosity=i" => \$verbosity);
-die "FAIL" if (!$result);
+die "tos-ramsize FAIL" if (!$result);
if (scalar(@ARGV) != 2) {
die "usage: ramsize.pl [-verbosity 0-9] mica2|micaz|iris avr_file.elf";
}
$platform = $ARGV[0];
-die "FAIL: unknown platform '$platform'" if (!defined($RAM_SIZE{$platform}));
+die "tos-ramsize FAIL: unknown platform '$platform'" if (!defined($RAM_SIZE{$platform}));
my $file = $ARGV[1];
-die "FAIL: '$file' not found" if (!(-f $file));
+die "tos-ramsize FAIL: '$file' not found" if (!(-f $file));
+
+if ($verbosity > 1) {
+ print "analyzing elf file '$file' for platform '$platform'\n";
+}
disassemble ($file);
insn_stack_effects();
my $free_mem = $RAM_SIZE{$platform} - $ramused;
if ($verbosity > 2) {
- print "\n";
foreach my $addr (sort bynum keys %insns) {
if (!$visited{$addr}) {
my $l = $addr_to_label{$addr};
}
if ($verbosity > 0) {
- print "\n";
print "BSS segment size is ${bss_size}, data segment size is ${data_size}\n";
}
print "The upper bound on stack size is ${total_depth}\n";