what is 0 string of linker script? - ld

Im trying to create linker script and c runtime together
Ive done simple linker script(place all in one RAM section):
OUTPUT_FORMAT("elf64-tradlittlemips")
SEARCH_DIR(objs)
GROUP(-lc -lgcc)
STARTUP(mcrt.o)
PROVIDE( _vector_spacing = 0x00000001 );
PROVIDE( _ebase_address = 0xB5000000 );
PROVIDE( _stack = 0xB503FFFF );
PROVIDE( text_start = 0xB5000FA0 );
PROVIDE( data_start = 0xB500FA00 );
PROVIDE( bss_start = 0xB502EE00 );
PROVIDE( heap_start = 0xB5032C80 );
PROVIDE( _RESET_ADDR = 0xBFC00000 );
PROVIDE( _SRAM_ADDR_2_EX = 0xB5000000 );
PROVIDE( _GEN_EXCPT_ADDR = _ebase_address + 0x180 );
_SIMPLE_TLB_REFILL_EXCPT_ADDR = _ebase_address + 0;
_CACHE_ERR_EXCPT_ADDR = _ebase_address + 0x100;
_GEN_EXCPT_ADDR = _ebase_address + 0x180;
MEMORY
{
ram (rwx) : ORIGIN = 0xB5000000, LENGTH = 0x3FFFF
}
ENTRY(_startup)
SECTIONS
. = _text_start;
.text :
{
. = ALIGN(4);
KEEP(*(.simple_tlb_refill_vector))
. = _CACHE_ERR_EXCPT_ADDR;
KEEP(*(.cache_err_vector))
. = _GEN_EXCPT_ADDR;
KEEP(*(.gen_handler))
_startup = .;
KEEP(*(.init))
KEEP(*(.text))
*(.text)
*(.text.*)
}
. = data_start;
.data :
{
. = ALIGN(4);
_gp = . ;
KEEP(*(.data))
*(.data)
*(.data.*)
}
. = bss_start;
.bss :
{
. = ALIGN(4);
KEEP(*(.bss))
*(.bss)
*(.bss.*)
}
. = heap_start;
.heap :
{
. = ALIGN(4);
KEEP(*(.heap))
*(.heap)
*(.heap.*)
}
__stack = ORIGIN(ram) + LENGTH(ram);
}
And I also created simple c runtime
#include <mips/asm.h>
#include <mips/regdef.h>
#include <mips/mips64.h>
##include <mips.h>
# .set nomips16
##################################################################
# Entry point of the entire application
##################################################################
/* This file contains 32-bit assembly code */
.set nomips16
##################################################################
# Startup code
##################################################################
.section .init
.align 2
.set noreorder
.ent _startup
_startup: .global _startup
##################################################################
# Initialize CP0 registers
##################################################################
# Подготавливаем Cause Register
mfc0 $26, $13, 0
lui $27, 0x0080
or $26, $26, $27
mtc0 $26, $13, 0
mfc0 $26, $12, 1
ori $27, $0, 0x20
or $26, $26, $27
mtc0 $26, $12, 1
mfc0 $26, $15, 1
lui $27, 0x1500
or $26, $26, $27
mtc0 $27, $15, 1
mfc0 $26, $12, 0
lui $27, 0x0040
ori $27, $27, 0xFC06
nor $27, $0, $27
and $26, $26, $27
mtc0 $26, $12, 0
##################################################################
# EBASE configure
##################################################################
mfc0 $26, $15, 1
lui $27, 0x1500
or $26, $26, $27
mtc0 $26, $15, 1
##################################################################
# Initialize Stack Pointer
# _stack is initialized by the linker script to point to the
# starting location of the stack in RAM
##################################################################
dla sp,__stack
##################################################################
# Initialize Global Pointer
# _gp is initialized by the linker script to point to "middle"
# of the small variables region
##################################################################
dla gp,_gp
##################################################################
# Fill stack
# TODO - handle different stack lengths:
# mulitple of 4, 8, 16, or 32
##################################################################
dla t0,_stack_start
dla t1,_stack
b _stack_check
_stack_init:
sw zero,0x0(t0)
sw zero,0x4(t0)
sw zero,0x8(t0)
sw zero,0xc(t0)
addu t0,16
_stack_check:
bltu t0,t1,_stack_init
nop
##################################################################
# Clear uninitialized data sections
##################################################################
dla t0,_bss_begin
dla t1,_bss_end
b _bss_check
nop
_bss_init:
sw zero,0x0(t0)
sw zero,0x4(t0)
sw zero,0x8(t0)
sw zero,0xc(t0)
addu t0,16
_bss_check:
bltu t0,t1,_bss_init
nop
##################################################################
# Initializes data section
# Warning: _data_image_start, _data_image_end, _data_start
# must be aligned to word address (check linker script)!
##################################################################
init_data_section:
dla t0, _data_image_start
dla t1, _data_image_end
dla t2, _data_start
#init_data_loop:
# beq t0, t1, init_data_done
# nop
# lw t3, (t0)
# addiu t0, 4
# sw t3, (t2)
# b init_data_loop
# addiu t2, 4
#init_data_done:
# jr ra
# nop
.end _startup
##################################################################
# General Exception Vector Handler
# Jumps to _general_exception_context
##################################################################
.section .gen_handler
.set noreorder
.ent _gen_exception
_gen_exception:
/*0: dla k0,_general_exception_context
jr k0*/
nop
.end _gen_exception
##################################################################
# Simple TLB-Refill Exception Vector
# Jumps to _simple_tlb_refill_exception_context
##################################################################
.section .simple_tlb_refill_vector
.set noreorder
.ent simple_tlb_refill_vector
simple_tlb_refill_vector:
/* dla k0,_simple_tlb_refill_exception_context
jr k0*/
nop
.end simple_tlb_refill_vector
##################################################################
# Cache-Error Exception Vector Handler
# Jumps to _cache_err_exception_context
##################################################################
.section .cache_err_vector
.set noreorder
.ent _cache_err_vector
_cache_err_vector:
/*la k0,_cache_err_exception_context
jr k0*/
nop
.end _cache_err_vector
.section .text
.ent _main_entry
_main_entry:
##################################################################
# Call main
##################################################################
jal main
nop
.end _main_entry
And I use cyqwin64 + mips-mti-elf
But I have got the following warnings during make:
src/mcrt.S: Assembler messages:
src/mcrt.S:162: Warning: .ent or .aent not in text section
src/mcrt.S:168: Warning: .end not in text section
src/mcrt.S:177: Warning: .ent or .aent not in text section
src/mcrt.S:183: Warning: .end not in text section
src/mcrt.S:192: Warning: .ent or .aent not in text section
src/mcrt.S:198: Warning: .end not in text section
And one error during final buld(at following strings "$(APP).elf: $(TOBJS)
$(CC) $(LDFLAGS) $^ -o $#" of makefile):
c:/tmp/cygwin64/usr/local/mips-mti-elf/2017.10-08/bin/../lib/gcc/mips-mti-elf/6.3.0/../../../..
/mips-mti-elf/bin/ld.exe:script1.ld:0: syntax error
collect2.exe: error: ld returned 1 exit status
make: *** [Makefile:49: mipsoutput.elf] Error 1
I dont undestand what is 0 string of my script1.ld
And also I dont understand why these warnings appears
Please help!
Thanks in advance

Related

How to run an ASM file in VS Code

App: VS Code version 1.65.2
OS: Linux Mint 20.2 Cinnamon
I want to run the following code in Assembly:
; The input is a decimal number,
; Result is a hexadecimal number
section .text
; to make the printf out work the main 'method' is needed
global main
; for printing numbers out
extern printf
main:
; Find factorial for the initial value of ecx,
mov ecx, 5
; Copy the initial value of ecx to eax,
; eax is our factorial result
mov eax, ecx
loop:
; If the counter is less or equal to 1, finish
cmp ecx, 1
jle end
sub ecx, 1
mul ecx
jmp loop
end:
; Display the message and exit
push eax
push message
call printf
add esp, 8
ret
section .data
message: db "The result is = %08X", 10, 0
I have installed:
Cutting edge x86 and x86_64 assembly syntax highlighting.
Arm assembly syntax support for Visual Studio Code.
However, running the file in both extensions returns me the following:
Code language not supported or defined.

arithmetic calculations-assembly langugage

assembly language
**main.asm**
%include "lib.asm"
section .text
global _start
_start:
;
; YOUR CODE HERE!
;
; SAMPLE
mov rdi, 65526 ; going to print 65536
call print_num ; print the number
call print_lf ; move to next line
exit:
mov rax, 60 ; exit
mov rdi, 0 ; return code
syscall

Why does Test::LeakTrace say this Perl code is leaking memory?

Test::LeakTrace says code leaking. I don't understand.
I do not understand output of Test::LeakTrace, and it is too long for this post. Some leaks from Test system, but others? No.
This is the code.
use 5.26.0;
use warnings;
use Test::More;
use Test::LeakTrace;
sub spawn {
my %methods = #_;
state $spawned = 1;
my $object = bless {}, "Spawned::Class$spawned";
$spawned++;
while ( my ( $method, $value ) = each %methods ) {
no strict 'refs';
*{ join '::', ref($object), $method } = sub { $value };
}
return $object;
}
no_leaks_ok {
my $spawn = spawn( this => 2 );
is( $spawn->this, 2 );
}
'no leaks';
done_testing;
I get weird things like this:
# leaked SCALAR(0x7f9b41a069c0) from leak.pl line 11.
# 10: $spawned++;
# 11: while ( my ( $method, $value ) = each %methods ) {
# 12: no strict 'refs';
# SV = IV(0x7f9b41a069b0) at 0x7f9b41a069c0
# REFCNT = 1
# FLAGS = (IOK,pIOK)
# IV = 2
And this:
# leaked GLOB(0x7f9b411b22a0) from leak.pl line 9.
# 8: state $spawned = 1;
# 9: my $object = bless {}, "Spawned::Class$spawned";
# 10: $spawned++;
# SV = PVGV(0x7f9b41a29530) at 0x7f9b411b22a0
# REFCNT = 1
# FLAGS = (MULTI)
# NAME = "Class4::"
# NAMELEN = 8
# GvSTASH = 0x7f9b4081a0a8 "Spawned"
# FLAGS = 0x2
# GP = 0x7f9b40534720
# SV = 0x0
# REFCNT = 1
# IO = 0x0
# FORM = 0x0
# AV = 0x0
# HV = 0x7f9b41a24730
# CV = 0x0
# CVGEN = 0x0
# GPFLAGS = 0x0 ()
# LINE = 9
# FILE = "leak.pl"
# EGV = 0x7f9b411b22a0 "Class4::"
Nothing makes sense to me. Reference counts are 1.
Your code leaks. They are intentional leaks, but leaks nonetheless.
You create a package that is never freed.[1] In it you create a glob that is never freed. To this glob you assign a sub that is never freed. The sub captures a variable, so it's never freed.
The module is doing its job and telling you about this.
I encountered a few surprises confirming the above was what was happening. The rest of this answer identifies them and explains them.
I'll be using this program (a.pl):
use 5.010;
use Test::More tests => 1;
use Test::LeakTrace;
sub f {
state $spawned = 1;
my $object = bless {}, "Spawned::Class$spawned" if $ARGV[0] & 1;
$spawned++ if $ARGV[0] & 2;
delete $Spawned::{"Class".($spawned-1)."::"} if $ARGV[0] & 4;
}
If we do $spawned++; but not the bless:
$ perl a.pl 1
1..1
ok 1 - leaks 0 <= 0
Expected.
If we do the bless but not $spawned++;:
$ perl a.pl 2
1..1
ok 1 - leaks 0 <= 0
huh!? We created global symbols. Shouldn't those be considered leaks? So why did the OP produce leaks, then? I'll come back to this.
If we do both:
$ perl a.pl 3
1..1
not ok 1 - leaks 8 <= 0
# Failed test 'leaks 8 <= 0'
# at a.pl line 11.
# '8'
# <=
# '0'
#
# [snip]
huh?! Why is it suddenly mentioning the global symbols we created?! I mean, it's what we expect, but we expected it above too. I'll come back to this.
Finally, we'll also undo the changes we made.
$ perl a.pl 7
1..1
ok 1 - leaks 0 <= 0
As expected, if we release the additions we made to the global symbol table, it no longer reports any leaks.
Now let's address the questions I raised.
Imagine if you had done something like
state $cache = { };
You wouldn't want that hash to be reported as a leak even though it's never freed. To that end, Test::LeakTrace evaluates the test block twice, ignoring leaks from the first call.
Leaked SVs are SVs which are not released after the end of the scope they have been created. These SVs include global variables and internal caches. For example, if you call a method in a tracing block, perl might prepare a cache for the method. Thus, to trace true leaks, no_leaks_ok() and leaks_cmp_ok() executes a block more than once.
That's why perl a.pl 2 didn't result in any reported leaks.
But perl a.pl 3 and the OP's code (intentionally) leak every time they are called, not just the first. Test::LeakTrace has no way to know those leaks are intentional, so you get what I suppose you could call false positives.
When I say "never freed", I mean "never freed until global destruction". Everything gets freed then.

Passing an array reference to a perl callback using call_sv(), should I use newRV() or newRV_noinc()?

I have this XS code (XsTest.xs):
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
MODULE = My::XsTest PACKAGE = My::XsTest
PROTOTYPES: DISABLE
void
foo( callback )
SV *callback
PREINIT:
AV *array;
SSize_t array_len;
SV **sv_ptr;
SV *sv;
double value;
CODE:
if ( !SvROK(callback) ) {
croak("Not a reference!");
}
if ( SvTYPE(SvRV(callback)) != SVt_PVCV ) {
croak("Not a code reference!");
}
/* This array will go out of scope (and be freed) at the end of this XSUB
* due to the sv_2mortal()
*/
array = (AV *)sv_2mortal((SV *)newAV()); /* Line #28 */
/* NOTE: calling dSP is not necessary for an XSUB, since it has
* already been arranged for by xsubpp by calling dXSARGS
*/
printf( "Line #28: SvREFCNT(array) = %d\n", SvREFCNT(array));
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 1);
/* Should I use newRV_inc() or newRV_noinc() here? Or does it not
* matter?
* NOTE: XPUSHs mortalizes the RV (so we do not need to call sv_2mortal()
*/
XPUSHs((SV *)newRV_inc((SV *) array)); /* Line #41 */
printf( "Line #41: SvREFCNT(array) = %d\n", SvREFCNT(array));
PUTBACK;
call_sv(callback, G_VOID);
printf( "Line #45: SvREFCNT(array) = %d\n", SvREFCNT(array)); /* Line #45: */
array_len = av_top_index(array) + 1;
printf( "Array length: %ld\n", array_len );
if ( array_len != 1 ) {
croak( "Unexpected array size: %ld", array_len );
}
sv_ptr = av_fetch( array, 0, 0 );
sv = *sv_ptr;
if (SvTYPE(sv) >= SVt_PVAV) {
croak("Not a scalar value!");
}
value = SvNV(sv);
printf( "Returned value: %g\n", value);
FREETMPS; /* Line # 58 */
LEAVE;
printf( "Line #60: SvREFCNT(array) = %d\n", SvREFCNT(array));
I trying to figure out whether to use newRV_inc() or newRV_noinc() at line #41.
The Perl callback is defined in the test script p.pl:
use strict;
use warnings;
use ExtUtils::testlib;
use My::XsTest;
sub callback {
my ( $ar ) = #_;
$ar->[0] = 3.12;
}
My::XsTest::foo( \&callback );
The output from running p.pl is:
Line #28: SvREFCNT(array) = 1
Line #41: SvREFCNT(array) = 2
Line #45: SvREFCNT(array) = 2
Array length: 1
Returned value: 3.12
Line #60: SvREFCNT(array) = 2
As far as I can see, if I use newRV_inc():
The reference count of array is set to 1 at line #28 when calling newAV(),
then it is decreased to zero also at line #28 when calling sv_2mortal() on the same array,
at line #41 I create a reference using newRV_inc() and the reference count of array is increased back again from 0 to 1 (due to the _inc in newRV_inc()),
at line #58, the FREETMPS macro is called, but this does not affect (?) the refcount of array since it was created outside the SAVETMPS boundary we set up for the callback temporaries. On the other hand, the reference we pushed at line #41 is freed here (since it was made mortal), which causes it to relinquish its ownership of array and hence the reference count of array will be decreased to zero again.
at line #60, the XSUB exits and array will be freed (on a following call by the perl runop loop to FREETMPS) since it has reference count of zero. All the scalars in the array will also be freed at that point (?).
The problem with the above reasoning is that it does not agree with the output from SvREFCNT() as shown above. According to the output, the reference count of array is 2 (and not 1) at exit.
What is going on here?
Additional files to reproduce:
lib/My/XsTest.pm:
package My::XsTest;
use strict;
use warnings;
use Exporter qw(import);
our %EXPORT_TAGS = ( 'all' => [ qw( ) ] );
our #EXPORT_OK = ( #{ $EXPORT_TAGS{'all'} } );
our #EXPORT = qw( );
our $VERSION = 0.01;
require XSLoader;
XSLoader::load();
1;
Makefile.PL:
use 5.028001;
use strict;
use warnings;
use utf8;
use ExtUtils::MakeMaker 7.12; # for XSMULTI option
WriteMakefile(
NAME => 'My::XsTest',
VERSION_FROM => 'lib/My/XsTest.pm',
PREREQ_PM => { 'ExtUtils::MakeMaker' => '7.12' },
ABSTRACT_FROM => 'lib/My/XsTest.pm',
AUTHOR => 'Håkon Hægland <hakon.hagland#gmail.com>',
OPTIMIZE => '', # e.g., -O3 (for optimize), -g (for debugging)
XSMULTI => 0,
LICENSE => 'perl',
LIBS => [''], # e.g., '-lm'
DEFINE => '', # e.g., '-DHAVE_SOMETHING'
INC => '-I.', # e.g., '-I. -I/usr/include/other'
)
Compilation
To compile the module, run:
perl Makefile.PL
make
You should use newRV_inc().
Your actual problem is that you are creating a new RV which leaks. The fact that the RV is never freed means that the reference count on array is never decremented. You need to mortalise the return value of newRV_inc().
One other comment: the reference count of array is not reduced to zero when you mortalise it; it remains as 1. I'm not sure where you got that idea from. What actually happens is that when you call newAV(), you are given an AV with a reference count of one, which is 1 too high. Left as-is, it will leak. sv_2mortal() doesn't change array's ref count, but it does take ownership of one reference, which "corrects" the overall reference count and array will no longer leak.

Is it normal that two different versions of perl produce different results?

I am trying to perform some stack analysis on an MCU following the steps described here. The site links then to a Perl script that I launch as a post-build operation by meanings of a simple batch file.
The IDEA based on Eclipse uses the Perl executable at the path:
C:\..\S32DS_ARM_v2018.R1\utils\msys32\usr\bin\perl.exe
perl.exe -v gives:
This is perl 5, version 22, subversion 1 (v5.22.1) built for i686-msys-thread-multi-64int
The OS (windows) has a perl installation at
C:\Perl64\bin\perl.exe
perl.exe -v gives:
This is perl 5, version 24, subversion 3 (v5.24.3) built for MSWin32-x64-multi-thread
(with 1 registered patch, see perl -V for more detail)
I can confirm that avstak.pl (the perl script I am referring some lines above) produces different results with the former or the latter.
WHY this happens, is out of my area of expertise at the moment.
What I would like to understand is
Understand why this is happening;
Understand which perl provides the right outputs (pretty sure I suppose the 5.24.3 is the correct one);
Learning how to prevent this issue if I am going to use perl in future.
Thanks and best regards,
L.
Edit: the outcome of the script with the two different perl versions (reduced output for readability):
This one is result_5.22.1
Func Cost Frame Height
------------------------------------------------------------------------
> I2C_MasterGetTransferStatus 292 292 1
> FLEXIO_I2C_DRV_MasterStartTransfer 236 236 1
> CLOCK_DRV_Init 172 172 1
> CLOCK_SYS_SetConfiguration 172 172 1
> EDMA_DRV_ConfigScatterGatherTransfer 132 132 1
> CLOCK_SYS_SetSystemClockConfig 76 76 1
> FLEXIO_I2C_DRV_MasterInit 60 60 1
> EDMA_DRV_ConfigSingleBlockTransfer 60 60 1
> main 52 52 1
> LPI2C_DRV_MasterSetBaudRate 52 52 1
> LPI2C_DRV_MasterStartDmaTransfer 52 52 1
> FLEXIO_DRV_InitDriver 52 52 1
> I2C_MasterInit 44 44 1
> LPI2C_DRV_SlaveStartDmaTransfer 44 44 1
> CLOCK_SYS_UpdateConfiguration 44 44 1
> CLOCK_DRV_SetClockSource 44 44 1
> LPI2C_DRV_SlaveInit 44 44 1
> EDMA_DRV_Init 44 44 1
> EDMA_DRV_Deinit 36 36 1
> CLOCK_SYS_ConfigureSOSC 36 36 1
> CLOCK_SYS_ConfigureFIRC 36 36 1
vs
result_5.24.3
Func Cost Frame Height
------------------------------------------------------------------------
> main 536 52 9
I2C_MasterSendDataBlocking 484 28 8
> I2C_MasterReceiveDataBlocking 484 28 8
> I2C_MasterReceiveData 468 20 7
> I2C_MasterSendData 468 20 7
FLEXIO_I2C_DRV_MasterReceiveDataBlocking 456 28 7
FLEXIO_I2C_DRV_MasterSendDataBlocking 456 28 7
FLEXIO_I2C_DRV_MasterSendData 448 20 5
FLEXIO_I2C_DRV_MasterReceiveData 448 20 5
FLEXIO_I2C_DRV_MasterStartTransfer 428 236 4
> I2C_MasterGetTransferStatus 408 292 6
CLOCK_SYS_UpdateConfiguration 336 44 6
CLOCK_SYS_SetConfiguration 292 172 5
> CLOCK_DRV_Init 292 172 5
LPI2C_DRV_MasterReceiveDataBlocking 256 20 7
> I2C_SlaveReceiveDataBlocking 252 12 8
> I2C_SlaveSendDataBlocking 252 12 8
As you can see the hight number in the first version doesn't increase (and it should).
Cost and frame suffer the same issue I suppose.
the script is here:
#!/usr/bin/perl -w
# avstack.pl: AVR stack checker
# Copyright (C) 2013 Daniel Beer <dlbeer#gmail.com>
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all
# copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
# PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
# Usage
# -----
#
# This script requires that you compile your code with -fstack-usage.
# This results in GCC generating a .su file for each .o file. Once you
# have these, do:
#
# ./avstack.pl <object files>
#
# This will disassemble .o files to construct a call graph, and read
# frame size information from .su. The call graph is traced to find, for
# each function:
#
# - Call height: the maximum call height of any callee, plus 1
# (defined to be 1 for any function which has no callees).
#
# - Inherited frame: the maximum *inherited* frame of any callee, plus
# the GCC-calculated frame size of the function in question.
#
# Using these two pieces of information, we calculate a cost (estimated
# peak stack usage) for calling the function. Functions are then listed
# on stdout in decreasing order of cost.
#
# Functions which are recursive are marked with an 'R' to the left of
# them. Their cost is calculated for a single level of recursion.
#
# The peak stack usage of your entire program can usually be estimated
# as the stack cost of "main", plus the maximum stack cost of any
# interrupt handler which might execute.
use strict;
# Configuration: set these as appropriate for your architecture/project.
my $objdump = "arm-none-eabi-objdump";
my $call_cost = 4;
# First, we need to read all object and corresponding .su files. We're
# gathering a mapping of functions to callees and functions to frame
# sizes. We're just parsing at this stage -- callee name resolution
# comes later.
my %frame_size; # "func#file" -> size
my %call_graph; # "func#file" -> {callees}
my %addresses; # "addr#file" -> "func#file"
my %global_name; # "func" -> "func#file"
my %ambiguous; # "func" -> 1
foreach (#ARGV) {
# Disassemble this object file to obtain a callees. Sources in the
# call graph are named "func#file". Targets in the call graph are
# named either "offset#file" or "funcname". We also keep a list of
# the addresses and names of each function we encounter.
my $objfile = $_;
my $source;
open(DISASSEMBLY, "$objdump -dr $objfile|") ||
die "Can't disassemble $objfile";
while (<DISASSEMBLY>) {
chomp;
if (/^([0-9a-fA-F]+) <(.*)>:/) {
my $a = $1;
my $name = $2;
$source = "$name\#$objfile";
$call_graph{$source} = {};
$ambiguous{$name} = 1 if defined($global_name{$name});
$global_name{$name} = "$name\#$objfile";
$a =~ s/^0*//;
$addresses{"$a\#$objfile"} = "$name\#$objfile";
}
if (/: R_[A-Za-z0-9_]+_CALL[ \t]+(.*)/) {
my $t = $1;
if ($t eq ".text") {
$t = "\#$objfile";
} elsif ($t =~ /^\.text\+0x(.*)$/) {
$t = "$1\#$objfile";
}
$call_graph{$source}->{$t} = 1;
}
}
close(DISASSEMBLY);
# Extract frame sizes from the corresponding .su file.
if ($objfile =~ /^(.*).o$/) {
my $sufile = "$1.su";
open(SUFILE, "<$sufile") || die "Can't open $sufile";
while (<SUFILE>) {
$frame_size{"$1\#$objfile"} = $2 + $call_cost
if /^.*:([^\t ]+)[ \t]+([0-9]+)/;
}
close(SUFILE);
}
}
# In this step, we enumerate each list of callees in the call graph and
# try to resolve the symbols. We omit ones we can't resolve, but keep a
# set of them anyway.
my %unresolved;
foreach (keys %call_graph) {
my $from = $_;
my $callees = $call_graph{$from};
my %resolved;
foreach (keys %$callees) {
my $t = $_;
if (defined($addresses{$t})) {
$resolved{$addresses{$t}} = 1;
} elsif (defined($global_name{$t})) {
$resolved{$global_name{$t}} = 1;
warn "Ambiguous resolution: $t" if defined ($ambiguous{$t});
} elsif (defined($call_graph{$t})) {
$resolved{$t} = 1;
} else {
$unresolved{$t} = 1;
}
}
$call_graph{$from} = \%resolved;
}
# Create fake edges and nodes to account for dynamic behaviour.
$call_graph{"INTERRUPT"} = {};
foreach (keys %call_graph) {
$call_graph{"INTERRUPT"}->{$_} = 1 if /^__vector_/;
}
# Trace the call graph and calculate, for each function:
#
# - inherited frames: maximum inherited frame of callees, plus own
# frame size.
# - height: maximum height of callees, plus one.
# - recursion: is the function called recursively (including indirect
# recursion)?
my %has_caller;
my %visited;
my %total_cost;
my %call_depth;
sub trace {
my $f = shift;
if ($visited{$f}) {
$visited{$f} = "R" if $visited{$f} eq "?";
return;
}
$visited{$f} = "?";
my $max_depth = 0;
my $max_frame = 0;
my $targets = $call_graph{$f} || die "Unknown function: $f";
if (defined($targets)) {
foreach (keys %$targets) {
my $t = $_;
$has_caller{$t} = 1;
trace($t);
my $is = $total_cost{$t};
my $d = $call_depth{$t};
$max_frame = $is if $is > $max_frame;
$max_depth = $d if $d > $max_depth;
}
}
$call_depth{$f} = $max_depth + 1;
$total_cost{$f} = $max_frame + ($frame_size{$f} || 0);
$visited{$f} = " " if $visited{$f} eq "?";
}
foreach (keys %call_graph) { trace $_; }
# Now, print results in a nice table.
printf " %-30s %8s %8s %8s\n",
"Func", "Cost", "Frame", "Height";
print "------------------------------------";
print "------------------------------------\n";
my $max_iv = 0;
my $main = 0;
foreach (sort { $total_cost{$b} <=> $total_cost{$a} } keys %visited) {
my $name = $_;
if (/^(.*)#(.*)$/) {
$name = $1 unless $ambiguous{$name};
}
my $tag = $visited{$_};
my $cost = $total_cost{$_};
$name = $_ if $ambiguous{$name};
$tag = ">" unless $has_caller{$_};
if (/^__vector_/) {
$max_iv = $cost if $cost > $max_iv;
} elsif (/^main#/) {
$main = $cost;
}
if ($ambiguous{$name}) { $name = $_; }
printf "%s %-30s %8d %8d %8d\n", $tag, $name, $cost,
$frame_size{$_} || 0, $call_depth{$_};
}
print "\n";
print "Peak execution estimate (main + worst-case IV):\n";
printf " main = %d, worst IV = %d, total = %d\n",
$total_cost{$global_name{"main"}},
$total_cost{"INTERRUPT"},
$total_cost{$global_name{"main"}} + $total_cost{"INTERRUPT"};
print "\n";
print "The following functions were not resolved:\n";
foreach (keys %unresolved) { print " $_\n"; }
Edit2:
As Amon suggested to check, subsequent iterations of the script on the same dataset doesn't produce the same output. Values (cost/frame/height) are always the same but the order in which the functions are reported is different.