Profiling support, tools and documentation.


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@8375 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/codecs.c b/apps/codecs.c
index d8ad714..88b2ea4 100644
--- a/apps/codecs.c
+++ b/apps/codecs.c
@@ -218,6 +218,12 @@
 
     /* new stuff at the end, sort into place next time
        the API gets incompatible */
+#ifdef RB_PROFILE
+    profile_thread,
+    profstop,
+    profile_func_enter,
+    profile_func_exit,
+#endif
 
 };
 
diff --git a/apps/codecs.h b/apps/codecs.h
index 320431f..70799f7 100644
--- a/apps/codecs.h
+++ b/apps/codecs.h
@@ -43,6 +43,9 @@
 #include "mpeg.h"
 #include "audio.h"
 #include "mp3_playback.h"
+#ifdef RB_PROFILE
+#include "profile.h"
+#endif
 #if (CONFIG_CODEC == SWCODEC)
 #include "dsp.h"
 #include "pcm_playback.h"
@@ -83,7 +86,7 @@
 #define CODEC_MAGIC 0x52434F44 /* RCOD */
 
 /* increase this every time the api struct changes */
-#define CODEC_API_VERSION 1
+#define CODEC_API_VERSION 2
 
 /* update this to latest version if a change to the api struct breaks
    backwards compatibility (and please take the opportunity to sort in any
@@ -289,6 +292,12 @@
 
     /* new stuff at the end, sort into place next time
        the API gets incompatible */     
+#ifdef RB_PROFILE
+    void (*profile_thread)(void);
+    void (*profstop)(void);
+    void (*profile_func_enter)(void *this_fn, void *call_site);
+    void (*profile_func_exit)(void *this_fn, void *call_site);
+#endif
 
 };
 
diff --git a/apps/codecs/Tremor/Makefile b/apps/codecs/Tremor/Makefile
index 7fd5de8..cec9797 100644
--- a/apps/codecs/Tremor/Makefile
+++ b/apps/codecs/Tremor/Makefile
@@ -16,7 +16,7 @@
 
 TREMOROPTS = -O2
 CFLAGS = $(GCCOPTS) $(TREMOROPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \
- -DMEM=${MEMORYSIZE}
+ -DMEM=${MEMORYSIZE} ${PROFILE_OPTS}
 
 # This sets up 'SRC' based on the files mentioned in SOURCES
 include $(TOOLSDIR)/makesrc.inc
diff --git a/apps/codecs/dumb/Makefile b/apps/codecs/dumb/Makefile
index fa647f3..d2a0452 100644
--- a/apps/codecs/dumb/Makefile
+++ b/apps/codecs/dumb/Makefile
@@ -178,7 +178,7 @@
 OFLAGS := -O2 -ffast-math -fomit-frame-pointer
 DBGFLAGS := -DDEBUGMODE=1 -g3
 
-CFLAGS_RELEASE := -Iinclude $(WFLAGS) $(OFLAGS)
+CFLAGS_RELEASE := -Iinclude $(WFLAGS) $(OFLAGS) $(PROFILE_OPTS)
 CFLAGS_DEBUG := -Iinclude $(WFLAGS) $(DBGFLAGS)
 
 LDFLAGS := -s
diff --git a/apps/codecs/lib/codeclib.c b/apps/codecs/lib/codeclib.c
index cad8f53..1f070e8 100644
--- a/apps/codecs/lib/codeclib.c
+++ b/apps/codecs/lib/codeclib.c
@@ -149,3 +149,14 @@
 {
     local_rb->qsort(base,nmemb,size,compar);
 }
+
+#ifdef RB_PROFILE
+void __cyg_profile_func_enter(void *this_fn, void *call_site) {
+    (void)call_site;
+    local_rb->profile_func_enter(this_fn, __builtin_return_address(1));
+}
+
+void __cyg_profile_func_exit(void *this_fn, void *call_site) {
+    local_rb->profile_func_exit(this_fn,call_site);
+}
+#endif
diff --git a/apps/codecs/lib/codeclib.h b/apps/codecs/lib/codeclib.h
index e112112..c2e7869 100644
--- a/apps/codecs/lib/codeclib.h
+++ b/apps/codecs/lib/codeclib.h
@@ -58,3 +58,9 @@
 int codec_init(struct codec_api* rb);
 void codec_set_replaygain(struct mp3entry* id3);
 
+#ifdef RB_PROFILE
+void __cyg_profile_func_enter(void *this_fn, void *call_site)
+    NO_PROF_ATTR ICODE_ATTR;
+void __cyg_profile_func_exit(void *this_fn, void *call_site)
+    NO_PROF_ATTR ICODE_ATTR;
+#endif
diff --git a/apps/codecs/libFLAC/Makefile b/apps/codecs/libFLAC/Makefile
index 4008479..d1e78bd 100644
--- a/apps/codecs/libFLAC/Makefile
+++ b/apps/codecs/libFLAC/Makefile
@@ -27,7 +27,7 @@
   FLACOPTS += --param large-function-insns=10000
 endif
 
-CFLAGS = $(GCCOPTS) $(FLACOPTS)\
+CFLAGS = $(GCCOPTS) $(PROFILE_OPTS) $(FLACOPTS)\
 $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) -DMEM=${MEMORYSIZE}
 
 # This sets up 'SRC' based on the files mentioned in SOURCES
diff --git a/apps/codecs/liba52/Makefile b/apps/codecs/liba52/Makefile
index 8e05017..ea3c01c 100644
--- a/apps/codecs/liba52/Makefile
+++ b/apps/codecs/liba52/Makefile
@@ -16,7 +16,7 @@
 
 A52OPTS = -O2
 CFLAGS = $(GCCOPTS) $(A52OPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \
- -DMEM=${MEMORYSIZE}
+ -DMEM=${MEMORYSIZE} $(PROFILE_OPTS)
 
 # This sets up 'SRC' based on the files mentioned in SOURCES
 include $(TOOLSDIR)/makesrc.inc
diff --git a/apps/codecs/libffmpegFLAC/Makefile b/apps/codecs/libffmpegFLAC/Makefile
index 60da5ae..7d9030e 100644
--- a/apps/codecs/libffmpegFLAC/Makefile
+++ b/apps/codecs/libffmpegFLAC/Makefile
@@ -16,7 +16,7 @@
 
 FLACOPTS = -O2
 CFLAGS = $(GCCOPTS) $(FLACOPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \
- -DMEM=${MEMORYSIZE}
+ -DMEM=${MEMORYSIZE} $(PROFILE_OPTS)
 
 # This sets up 'SRC' based on the files mentioned in SOURCES
 include $(TOOLSDIR)/makesrc.inc
diff --git a/apps/codecs/libm4a/Makefile b/apps/codecs/libm4a/Makefile
index 7f870c9..fcbc100 100644
--- a/apps/codecs/libm4a/Makefile
+++ b/apps/codecs/libm4a/Makefile
@@ -16,7 +16,7 @@
 
 M4AOPTS = -O3
 CFLAGS = $(GCCOPTS) $(M4AOPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \
- -DMEM=${MEMORYSIZE}
+ -DMEM=${MEMORYSIZE} $(PROFILE_OPTS)
 
 # This sets up 'SRC' based on the files mentioned in SOURCES
 include $(TOOLSDIR)/makesrc.inc
diff --git a/apps/codecs/libmad/Makefile b/apps/codecs/libmad/Makefile
index e2f2643..5eaf9f1 100644
--- a/apps/codecs/libmad/Makefile
+++ b/apps/codecs/libmad/Makefile
@@ -17,7 +17,7 @@
 # NOTE: FPM_ define has been moved to global.h
 MADOPTS = -DNDEBUG -O2
 CFLAGS = $(GCCOPTS) $(MADOPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \
- -DMEM=${MEMORYSIZE}
+ -DMEM=${MEMORYSIZE} $(PROFILE_OPTS)
 
 # This sets up 'SRC' based on the files mentioned in SOURCES
 include $(TOOLSDIR)/makesrc.inc
diff --git a/apps/codecs/libmusepack/Makefile b/apps/codecs/libmusepack/Makefile
index 8e65915..6bfa2f9 100644
--- a/apps/codecs/libmusepack/Makefile
+++ b/apps/codecs/libmusepack/Makefile
@@ -16,7 +16,7 @@
 
 MUSEPACKOPTS = -O2
 CFLAGS = $(GCCOPTS) $(MUSEPACKOPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \
- -DMEM=${MEMORYSIZE}
+ -DMEM=${MEMORYSIZE} $(PROFILE_OPTS)
 
 # This sets up 'SRC' based on the files mentioned in SOURCES
 include $(TOOLSDIR)/makesrc.inc
diff --git a/apps/codecs/libwavpack/Makefile b/apps/codecs/libwavpack/Makefile
index 3947879..eba67fa 100644
--- a/apps/codecs/libwavpack/Makefile
+++ b/apps/codecs/libwavpack/Makefile
@@ -16,7 +16,7 @@
 
 WAVPACKOPTS = -O2
 CFLAGS = $(GCCOPTS) $(WAVPACKOPTS) $(INCLUDES) $(TARGET) $(EXTRA_DEFINES) \
- -DMEM=${MEMORYSIZE}
+ -DMEM=${MEMORYSIZE} $(PROFILE_OPTS)
 
 # This sets up 'SRC' based on the files mentioned in SOURCES
 include $(TOOLSDIR)/makesrc.inc
diff --git a/apps/plugin.c b/apps/plugin.c
index 44eb0dc..b907604 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -363,6 +363,12 @@
 
     /* new stuff at the end, sort into place next time
        the API gets incompatible */
+#ifdef RB_PROFILE
+    profile_thread,
+    profstop,
+    profile_func_enter,
+    profile_func_exit,
+#endif
 
 };
 
diff --git a/apps/plugin.h b/apps/plugin.h
index 69a2a79..157831b 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -44,6 +44,9 @@
 #include "mpeg.h"
 #include "audio.h"
 #include "mp3_playback.h"
+#ifdef RB_PROFILE
+#include "profile.h"
+#endif
 #include "misc.h"
 #if (HWCODEC == SWCODEC)
 #include "pcm_playback.h"
@@ -93,7 +96,7 @@
 #define PLUGIN_MAGIC 0x526F634B /* RocK */
 
 /* increase this every time the api struct changes */
-#define PLUGIN_API_VERSION 1
+#define PLUGIN_API_VERSION 2
 
 /* update this to latest version if a change to the api struct breaks
    backwards compatibility (and please take the opportunity to sort in any 
@@ -424,6 +427,12 @@
 
     /* new stuff at the end, sort into place next time
        the API gets incompatible */     
+#ifdef RB_PROFILE
+    void (*profile_thread)(void);
+    void (*profstop)(void);
+    void (*profile_func_enter)(void *this_fn, void *call_site);
+    void (*profile_func_exit)(void *this_fn, void *call_site);
+#endif
 
 };
 
diff --git a/apps/plugins/lib/SOURCES b/apps/plugins/lib/SOURCES
index 4d4247e..0f6e13e 100644
--- a/apps/plugins/lib/SOURCES
+++ b/apps/plugins/lib/SOURCES
@@ -12,3 +12,6 @@
 #ifdef HAVE_LCD_CHARCELLS
 playergfx.c
 #endif
+#ifdef RB_PROFILE
+profile_plugin.c
+#endif
diff --git a/apps/plugins/lib/profile_plugin.c b/apps/plugins/lib/profile_plugin.c
new file mode 100644
index 0000000..3318476
--- /dev/null
+++ b/apps/plugins/lib/profile_plugin.c
@@ -0,0 +1,38 @@
+/***************************************************************************
+*             __________               __   ___.
+*   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+*   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+*   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+*   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+*                     \/            \/     \/    \/            \/
+* $Id$
+*
+* Passthrough routines for plugin profiling
+*
+* Copyright (C) 2006 Brandon Low
+*
+* All files in this archive are subject to the GNU General Public License.
+* See the file COPYING in the source tree root for full license agreement.
+*
+* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+* KIND, either express or implied.
+*
+****************************************************************************/
+
+#include "plugin.h"
+
+static struct plugin_api *local_rb = NULL; /* global api struct pointer */
+
+void profile_init(struct plugin_api* pa)
+{
+    local_rb = pa;
+}
+
+void __cyg_profile_func_enter(void *this_fn, void *call_site) {
+    (void)call_site;
+    local_rb->profile_func_enter(this_fn, __builtin_return_address(1));
+}
+
+void __cyg_profile_func_exit(void *this_fn, void *call_site) {
+    local_rb->profile_func_exit(this_fn,call_site);
+}
diff --git a/apps/plugins/lib/profile_plugin.h b/apps/plugins/lib/profile_plugin.h
new file mode 100644
index 0000000..71cff37
--- /dev/null
+++ b/apps/plugins/lib/profile_plugin.h
@@ -0,0 +1,35 @@
+/***************************************************************************
+*             __________               __   ___.
+*   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+*   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+*   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+*   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+*                     \/            \/     \/    \/            \/
+* $Id$
+*
+* Passthrough routines for plugin profiling.
+*
+* Copyright (C) 2005 Brandon Low
+*
+* All files in this archive are subject to the GNU General Public License.
+* See the file COPYING in the source tree root for full license agreement.
+*
+* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+* KIND, either express or implied.
+*
+****************************************************************************/
+
+#ifndef __PROFILE_PLUGIN_H__
+#define __PROFILE_PLUGIN_H__
+
+#include "plugin.h"
+
+void profile_init(struct plugin_api* pa);
+
+void __cyg_profile_func_enter(void *this_fn, void *call_site)
+    NO_PROF_ATTR ICODE_ATTR;
+void __cyg_profile_func_exit(void *this_fn, void *call_site)
+    NO_PROF_ATTR ICODE_ATTR;
+
+#endif /* __PROFILE_PLUGIN_H__ */
+
diff --git a/apps/plugins/wav2wv.c b/apps/plugins/wav2wv.c
index 20a4589..812ed17 100644
--- a/apps/plugins/wav2wv.c
+++ b/apps/plugins/wav2wv.c
@@ -17,6 +17,9 @@
  *
  ****************************************************************************/
 #include "plugin.h"
+#ifdef RB_PROFILE
+#include "lib/profile_plugin.h"
+#endif
 
 #include <codecs/libwavpack/wavpack.h>
 
@@ -289,6 +292,15 @@
 
 enum plugin_status plugin_start(struct plugin_api* api, void *parameter)
 {
+#ifdef RB_PROFILE
+    /* This doesn't start profiling or anything, it just gives the
+     * profiling functions that are compiled in someplace to call,
+     * this is needed here to let this compile with profiling support
+     * since it calls code from a codec that is compiled with profiling
+     * support */
+    profile_init(api);
+#endif
+
     rb = api;
 
     if (!parameter)
diff --git a/docs/FILES b/docs/FILES
index f1054ea..21f2faf 100644
--- a/docs/FILES
+++ b/docs/FILES
@@ -8,6 +8,7 @@
 FAQ
 FILES
 HISTORY
+LICENSES
 NODO
 README
 TECH
diff --git a/docs/LICENSES b/docs/LICENSES
new file mode 100644
index 0000000..652a276
--- /dev/null
+++ b/docs/LICENSES
@@ -0,0 +1,36 @@
+This file contains license for software imported into Rockbox but governed by
+a previous license.  Each section begins by identifying the code in Rockbox
+and the source from of those code, and is followed by the license text.
+
+*************************************************************************
+In: profile.c, profile_func_enter
+From: gcc - gmon.c, mcount
+*************************************************************************
+Copyright (c) 1991, 1998 The Regents of the University of California.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. [rescinded 22 July 1999]
+4. Neither the name of the University nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+@(#)gmon.c 5.3 (Berkeley) 5/22/91
diff --git a/docs/TECH b/docs/TECH
index 4532af3..b22a8c4 100644
--- a/docs/TECH
+++ b/docs/TECH
@@ -172,3 +172,29 @@
   controlled charging that Rockbox can't affect.)
 
   ...
+
+Profiling
+
+  Rockbox contains a profiling system which can be used to monitor call count
+  and time in function for a specific set of functions on a single thread.
+
+  To use this functionality:
+  1) Configure a developer build with profiling support.
+  2) Make sure that the functions of interest will be compiled with the
+     PROFILE_OPTS added to their CFLAGS
+  3) On the same thread as these functions will be run, surround the relevent
+     running time with calls to profile_thread and profstop.  (For codecs,
+     this can be done in the codec.c file for example)
+  4) Compile and run the code on the target, after the section to be profiled
+     exits (when profstop is called) a profile.out file will be written to
+     the player's root.
+  5) Use the tools/profile_reader/profile_reader.pl script to convert the 
+     profile.out into a human readable format.  This script requires the
+     relevent map files and object (or library) files created in the build.
+     (ex: ./profile_reader.pl profile.out vorbis.map libTremor.a 0)
+
+  There is also a profile_comparator.pl script which can compare two profile
+  runs as output by the above script to show percent change from optimization
+
+  profile_reader.pl requires a recent binutils that can automatically handle
+  target object files, or objdump in path to be the target-objdump.
diff --git a/firmware/SOURCES b/firmware/SOURCES
index 4fe5fc9..3a5f551 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -123,6 +123,9 @@
 #endif
 id3.c
 #ifndef SIMULATOR
+#ifdef RB_PROFILE
+profile.c
+#endif /* RB_PROFILE */
 hwcompat.c
 kernel.c
 rolo.c
diff --git a/firmware/export/config.h b/firmware/export/config.h
index 5e3bbeb..8df5966 100644
--- a/firmware/export/config.h
+++ b/firmware/export/config.h
@@ -174,6 +174,14 @@
 #define CODEC_SIZE 0
 #endif
 
+/* This attribute can be used to ensure that certain symbols are never profiled
+ * which can be important as profiling a function de-inlines it */
+#ifdef RB_PROFILE
+#define NO_PROF_ATTR __attribute__ ((no_instrument_function))
+#else
+#define NO_PROF_ATTR
+#endif
+
 /* IRAM usage */
 #if !defined(SIMULATOR) &&   /* Not for simulators */ \
     (((CONFIG_CPU == SH7034) && !defined(PLUGIN)) || /* SH1 archos: core only */ \
diff --git a/firmware/export/profile.h b/firmware/export/profile.h
new file mode 100644
index 0000000..cb75132
--- /dev/null
+++ b/firmware/export/profile.h
@@ -0,0 +1,80 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Profiling routines counts ticks and calls to each profiled function.
+ * 
+ * Copyright (C) 2005 by Brandon Low
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ ****************************************************************************/
+
+#ifndef _SYS_PROFILE_H
+#define _SYS_PROFILE_H 1
+
+#include <sys/types.h>
+
+/* PFD is Profiled Function Data */
+
+/* Indices are shorts which means that we use 4k of RAM */
+#define INDEX_BITS 11    /* What is a reasonable size for this? */
+#define INDEX_SIZE 2048  /* 2 ^ INDEX_BITS */
+#define INDEX_MASK 0x7FF /* lower INDEX_BITS 1 */
+
+/*
+ * In the current setup (pfd has 4 longs and 2 shorts) this uses 20k of RAM 
+ * for profiling, and allows for profiling sections of code with up-to 
+ * 1024 function caller->callee pairs
+ */
+#define NUMPFDS 1024
+
+struct pfd_struct {
+    void *self_pc;
+    unsigned long count;
+    unsigned long time;
+    unsigned short link;
+    struct pfd_struct *caller;
+};
+
+/* Possible states of profiling */
+#define PROF_ON 0x00
+#define PROF_BUSY 0x01
+#define PROF_ERROR 0x02
+#define PROF_OFF 0x03
+/* Masks for thread switches */
+#define PROF_OFF_THREAD 0x10
+#define PROF_ON_THREAD 0x0F
+
+extern int current_thread;
+
+/* Initialize and start profiling */
+void profstart(int current_thread)
+  NO_PROF_ATTR;
+
+/* Clean up and write profile data */
+void profstop (void)
+  NO_PROF_ATTR;
+
+/* Called every time a thread stops, we check if it's our thread and store
+ * temporary timing data if it is */
+void profile_thread_stopped(int current_thread)
+  NO_PROF_ATTR;
+/* Called when a thread starts, we check if it's our thread and resume timing */
+void profile_thread_started(int current_thread)
+  NO_PROF_ATTR;
+
+void profile_func_exit(void *this_fn, void *call_site) 
+  NO_PROF_ATTR ICODE_ATTR;
+void profile_func_enter(void *this_fn, void *call_site)
+  NO_PROF_ATTR ICODE_ATTR;
+
+#endif /*_SYS_PROFILE_H*/
diff --git a/firmware/export/thread.h b/firmware/export/thread.h
index fe1612d..da61d1a 100644
--- a/firmware/export/thread.h
+++ b/firmware/export/thread.h
@@ -37,5 +37,8 @@
 void wake_up_thread(void);
 void init_threads(void);
 int thread_stack_usage(int threadnum);
+#ifdef RB_PROFILE
+void profile_thread(void);
+#endif
 
 #endif
diff --git a/firmware/profile.c b/firmware/profile.c
new file mode 100644
index 0000000..8ad4651
--- /dev/null
+++ b/firmware/profile.c
@@ -0,0 +1,303 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Profiling routines counts ticks and calls to each profiled function.
+ * 
+ * Copyright (C) 2005 by Brandon Low
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************
+ *
+ * profile_func_enter() based on mcount found in gmon.c:
+ *
+ ***************************************************************************
+ * Copyright (c) 1991, 1998 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. [rescinded 22 July 1999]
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * @(#)gmon.c 5.3 (Berkeley) 5/22/91
+ */
+
+#include <file.h>
+#include <sprintf.h>
+#include <system.h>
+#include <string.h>
+#include <timer.h>
+#include "profile.h"
+
+static unsigned short profiling = PROF_OFF;
+static size_t recursion_level;
+static unsigned short indices[INDEX_SIZE];
+static struct pfd_struct pfds[NUMPFDS];
+/* This holds a pointer to the last pfd effected for time tracking */
+static struct pfd_struct *last_pfd;
+/* These are used to track the time when we've lost the CPU so it doesn't count
+ * against any of the profiled functions */
+static int profiling_thread = -1;
+
+/* internal function prototypes */
+static void profile_timer_tick(void);
+static void profile_timer_unregister(void);
+
+static void write_function_recursive(int fd, struct pfd_struct *pfd, int depth);
+
+/* Be careful to use the right one for the size of your variable */
+#define ADDQI_L(_var,_value) \
+    asm ("addq.l %[value],%[var];" \
+         : [var] "+g" (_var) \
+         : [value] "I" (_value) )
+
+void profile_thread_stopped(int current_thread) {
+    if (current_thread == profiling_thread) {
+        /* If profiling is busy or idle */
+        if (profiling < PROF_ERROR) {
+            /* Unregister the timer so that other threads aren't interrupted */
+            timer_unregister();
+        }
+        /* Make sure we don't waste time profiling when we're running the
+         * wrong thread */
+        profiling |= PROF_OFF_THREAD;
+    }
+}
+
+void profile_thread_started(int current_thread) {
+    if (current_thread == profiling_thread) {
+        /* Now we are allowed to profile again */
+        profiling &= PROF_ON_THREAD;
+        /* if profiling was busy or idle */
+        if (profiling < PROF_ERROR) {
+            /* After we de-mask, if profiling is active, reactivate the timer */
+            timer_register(0, profile_timer_unregister, 
+                    CPU_FREQ/10000, 0, profile_timer_tick);
+        }
+    }
+}
+
+static void profile_timer_tick(void) {
+    if (!profiling) {
+        register struct pfd_struct *my_last_pfd = last_pfd;
+        if (my_last_pfd) {
+            ADDQI_L(my_last_pfd->time,1);
+        }
+    }
+}
+
+static void profile_timer_unregister(void) {
+    profiling = PROF_ERROR;
+    profstop();
+}
+
+/* This function clears the links on top level linkers, and clears the needed
+ * parts of memory in the index array */
+void profstart(int current_thread) {
+    recursion_level = 0;
+    profiling_thread = current_thread;
+    last_pfd = (struct pfd_struct*)0;
+    pfds[0].link = 0;
+    pfds[0].self_pc = 0;
+    memset(&indices,0,INDEX_SIZE * sizeof(unsigned short));
+    timer_register(
+        0, profile_timer_unregister, CPU_FREQ/10000, 0, profile_timer_tick);
+    profiling = PROF_ON;
+}
+
+static void write_function_recursive(int fd, struct pfd_struct *pfd, int depth){
+    unsigned short link = pfd->link;
+    fdprintf(fd,"0x%08lX\t%08ld\t%08ld\t%04d\n", (size_t)pfd->self_pc, 
+            pfd->count, pfd->time, depth);
+    if (link > 0 && link < NUMPFDS) { 
+        write_function_recursive(fd, &pfds[link], depth++);
+    }
+}
+
+void profstop() {
+    int profiling_exit = profiling;
+    int fd = 0;
+    int i;
+    unsigned short current_index;
+    timer_unregister();
+    profiling = PROF_OFF;
+    fd = open("/profile.out", O_WRONLY|O_CREAT|O_TRUNC);
+    if (profiling_exit == PROF_ERROR) {
+        fdprintf(fd,"Profiling exited with an error.\n");
+        fdprintf(fd,"Overflow or timer stolen most likely.\n");
+    }
+    fdprintf(fd,"PROFILE_THREAD\tPFDS_USED\n");
+    fdprintf(fd,"%08d\t%08d\n", profiling_thread,
+        pfds[0].link);
+    fdprintf(fd,"FUNCTION_PC\tCALL_COUNT\tTICKS\t\tDEPTH\n");
+    for (i = 0; i < INDEX_SIZE; i++) {
+        current_index = indices[i];
+        if (current_index != 0) { 
+            write_function_recursive(fd, &pfds[current_index], 0);
+        }
+    }
+    fdprintf(fd,"DEBUG PROFILE DATA FOLLOWS\n");
+    fdprintf(fd,"INDEX\tLOCATION\tSELF_PC\t\tCOUNT\t\tTIME\t\tLINK\tCALLER\n");
+    for (i = 0; i < NUMPFDS; i++) {
+        struct pfd_struct *my_last_pfd = &pfds[i];
+        if (my_last_pfd->self_pc != 0) {
+            fdprintf(fd,
+                    "%04d\t0x%08lX\t0x%08lX\t0x%08lX\t0x%08lX\t%04d\t0x%08lX\n",
+                    i, (size_t)my_last_pfd, (size_t)my_last_pfd->self_pc,
+                    my_last_pfd->count, my_last_pfd->time, my_last_pfd->link,
+                    (size_t)my_last_pfd->caller);
+        }
+    }
+    fdprintf(fd,"INDEX_ADDRESS=INDEX\n");
+    for (i=0; i < INDEX_SIZE; i++) {
+        fdprintf(fd,"%08lX=%04d\n",(size_t)&indices[i],indices[i]);
+    }
+    close(fd);
+}
+
+void profile_func_exit(void *self_pc, void *call_site) {
+    (void)call_site;
+    (void)self_pc;
+    /* When we started timing, we set the time to the tick at that time
+     * less the time already used in function */
+    if (profiling) {
+        return;
+    }
+    profiling = PROF_BUSY;
+    {
+        register unsigned short my_recursion_level = recursion_level;
+        if (my_recursion_level) {
+            my_recursion_level--;
+            recursion_level = my_recursion_level;
+        } else {
+            /* This shouldn't be necessary, maybe exit could be called first */
+            register struct pfd_struct *my_last_pfd = last_pfd;
+            if (my_last_pfd) {
+                last_pfd = my_last_pfd->caller;
+            }
+        }
+    }
+    profiling = PROF_ON;
+}
+
+#define ALLOCATE_PFD(temp) \
+    temp = ++pfds[0].link;\
+    if (temp >= NUMPFDS) goto overflow; \
+    pfd = &pfds[temp];\
+    pfd->self_pc = self_pc; pfd->count = 1; pfd->time = 0
+
+void profile_func_enter(void *self_pc, void *from_pc) {
+    struct pfd_struct *pfd;
+    struct pfd_struct *prev_pfd;
+    unsigned short *pfd_index_pointer;
+    unsigned short pfd_index;
+
+    /* check that we are profiling and that we aren't recursively invoked
+     * this is equivalent to 'if (profiling != PROF_ON)' but it's faster */
+    if (profiling) {
+        return;
+    }
+    /* this is equivalent to 'profiling = PROF_BUSY;' but it's faster */
+    profiling = PROF_BUSY;
+    /* A check that the PC is in the code range here wouldn't hurt, but this is
+     * logically guaranteed to be a valid address unless the constants are
+     * breaking the rules.  */
+    pfd_index_pointer = &indices[((size_t)from_pc)&INDEX_MASK];
+    pfd_index = *pfd_index_pointer;
+    if (pfd_index == 0) {
+        /* new caller, allocate new storage */
+        ALLOCATE_PFD(pfd_index);
+        pfd->link = 0;
+        *pfd_index_pointer = pfd_index;
+        goto done;
+    }
+    pfd = &pfds[pfd_index];
+    if (pfd->self_pc == self_pc) {
+        /* only / most recent function called by this caller, usual case */
+        /* increment count, start timing and exit */
+        goto found;
+    }
+    /* collision, bad for performance, look down the list of functions called by
+     * colliding PCs */
+    for (; /* goto done */; ) {
+        pfd_index = pfd->link;
+        if (pfd_index == 0) {
+            /* no more previously called functions, allocate a new one */
+            ALLOCATE_PFD(pfd_index);
+            /* this function becomes the new head, link to the old head */
+            pfd->link = *pfd_index_pointer;
+            /* and set the index to point to this function */
+            *pfd_index_pointer = pfd_index;
+            /* start timing and exit */
+            goto done;
+        }
+        /* move along the chain */
+        prev_pfd = pfd;
+        pfd = &pfds[pfd_index];
+        if (pfd->self_pc == self_pc) {
+            /* found ourself */
+            /* Remove me from my old spot */
+            prev_pfd->link = pfd->link;
+            /* Link to the old head */
+            pfd->link = *pfd_index_pointer;
+            /* Make me head */
+            *pfd_index_pointer = pfd_index;
+            /* increment count, start timing and exit */
+            goto found;
+        }
+
+    }
+/* We've found a pfd, increment it */
+found:
+    ADDQI_L(pfd->count,1);
+/* We've (found or created) and updated our pfd, save it and start timing */
+done:
+    {
+        register struct pfd_struct *my_last_pfd = last_pfd;
+        if (pfd != my_last_pfd) {
+            /* If we are not recursing */
+            pfd->caller = my_last_pfd;
+            last_pfd = pfd;
+        } else {
+            ADDQI_L(recursion_level,1);
+        }
+    }
+    /* Start timing this function */
+    profiling = PROF_ON;
+    return;  /* normal return restores saved registers */
+
+overflow:
+    /* this is the same as 'profiling = PROF_ERROR' */
+    profiling = PROF_ERROR;
+    return;
+}
diff --git a/firmware/thread.c b/firmware/thread.c
index 2818014..d828211 100644
--- a/firmware/thread.c
+++ b/firmware/thread.c
@@ -78,6 +78,13 @@
 static inline void store_context(void* addr) __attribute__ ((always_inline));
 static inline void load_context(const void* addr) __attribute__ ((always_inline));
 
+#ifdef RB_PROFILE
+#include <profile.h>
+void profile_thread(void) {
+    profstart(current_thread);
+}
+#endif
+
 #if defined(CPU_ARM)
 /*--------------------------------------------------------------------------- 
  * Store non-volatile context.
@@ -245,6 +252,9 @@
  */
 void switch_thread(void)
 {
+#ifdef RB_PROFILE
+    profile_thread_stopped(current_thread);
+#endif
     int current;
     unsigned int *stackptr;
 
@@ -284,6 +294,9 @@
 
     current_thread = current;
     load_context(&thread_contexts[current]);
+#ifdef RB_PROFILE
+    profile_thread_started(current_thread);
+#endif
 }
 
 void sleep_thread(void)
diff --git a/tools/FILES b/tools/FILES
index 19d3a55..f4b1c6d 100644
--- a/tools/FILES
+++ b/tools/FILES
@@ -23,3 +23,4 @@
 ucl/src/*.[ch]
 ucl/src/Makefile
 ucl/include/ucl/*.h
+profile_reader/*.pl
diff --git a/tools/buildzip.pl b/tools/buildzip.pl
index eabf739..934b003 100755
--- a/tools/buildzip.pl
+++ b/tools/buildzip.pl
@@ -199,6 +199,7 @@
          "CUSTOM_CFG_FORMAT",
          "CUSTOM_WPS_FORMAT",
          "FAQ",
+         "LICENSES",
          "NODO",
          "TECH")) {
         `cp $ROOT/docs/$_ .rockbox/docs/$_.txt`;
diff --git a/tools/configure b/tools/configure
index 2cd855e..2bea201 100755
--- a/tools/configure
+++ b/tools/configure
@@ -240,7 +240,7 @@
   #
   echo ""
   echo "Enter your developer options (press enter when done)"
-  echo "(D)EBUG, (L)ogf, (S)imulator"
+  echo "(D)EBUG, (L)ogf, (S)imulator, (P)rofiling"
   cont=1
 
   while [ $cont = "1" ]; do
@@ -249,19 +249,29 @@
 
     case $option in
       [Dd])
-        echo "define DEBUG"
-        debug="-DDEBUG"
-        GCCOPTS="$GCCOPTS -g -DDEBUG"
+        if [ "yes" = "$profile" ]; then
+          echo "Debug is incompatible with profiling"
+        else
+          echo "define DEBUG"
+          use_debug="yes"
+        fi
         ;;
       [Ll])
-        logf="yes"
         echo "logf() support enabled"
-        use_logf="#define ROCKBOX_HAS_LOGF 1"
+        logf="yes"
         ;;
       [Ss])
         echo "Simulator build enabled"
         simulator="yes"
         ;;
+      [Pp])
+        if [ "yes" = "$use_debug" ]; then
+          echo "Profiling is incompatible with debug"
+        else
+          echo "Profiling support is enabled"
+          profile="yes"
+        fi
+        ;;
       *)
         echo "done"
         cont=0
@@ -269,11 +279,23 @@
     esac 
   done
 
+  if [ "yes" = "$use_debug" ]; then
+    debug="-DDEBUG"
+    GCCOPTS="$GCCOPTS -g -DDEBUG"
+  fi
+  if [ "yes" = "$logf" ]; then
+    use_logf="#define ROCKBOX_HAS_LOGF 1"
+  fi
   if [ "yes" = "$simulator" ]; then
     debug="-DDEBUG"
-    extradefines="-DSIMULATOR"
+    extradefines="$extradefines -DSIMULATOR"
     whichsim
   fi
+  if [ "yes" = "$profile" ]; then
+    extradefines="$extradefines -DRB_PROFILE"
+    PROFILE_OPTS="-finstrument-functions"
+    GCCOPTS="$GCCOPTS $GCCOPTIMIZE"
+  fi
 }
 
 whichsim () {
@@ -902,6 +924,7 @@
  -e "s,@FLASHFILE@,${flash},g" \
  -e "s,@PLUGINS@,${plugins},g" \
  -e "s,@CODECS@,${codecs},g" \
+ -e "s,@PROFILE_OPTS@,${PROFILE_OPTS},g" \
  -e "s,@GCCOPTS@,${GCCOPTS},g" \
  -e "s!@LDOPTS@!${LDOPTS}!g" \
  -e "s,@LOADADDRESS@,${loadaddress},g" \
@@ -952,6 +975,7 @@
 export DLLTOOL=@DLLTOOL@
 export DLLWRAP=@DLLWRAP@
 export RANLIB=@RANLIB@
+export PROFILE_OPTS=@PROFILE_OPTS@
 export GCCOPTS=@GCCOPTS@
 export LOADADDRESS=@LOADADDRESS@
 export SIMVER=@SIMVER@
diff --git a/tools/profile_reader/profile_comparator.pl b/tools/profile_reader/profile_comparator.pl
new file mode 100755
index 0000000..da5e300
--- /dev/null
+++ b/tools/profile_reader/profile_comparator.pl
@@ -0,0 +1,104 @@
+#!/usr/bin/perl
+sub error {
+  print("Error: @_\n");
+  exit(1);
+}
+sub usage {
+  if (@_) {
+    print STDERR ("Error: @_\n");
+  }
+  print STDERR ("USAGE:\n");
+  print STDERR ("$0 file1 file2 [showcalldiff]\n");
+  print STDERR
+    ("\tfile[12]      output from profile_reader.pl to compare\n");
+  print STDERR
+    ("\tshowcalldiff  show the percent change in calls instead of ticks\n");
+  exit(1);
+}
+if ($ARGV[0] =~ m/-(h|help|-help)/) {
+  usage();
+}
+if (@ARGV < 2) {
+  usage("Requires at least 2 arguments");
+}
+open(FILE1,shift) || error("Couldn't open file1");
+my @file1 = <FILE1>;
+close(FILE1);
+open(FILE2,shift) || error("Couldn't open file2");
+my @file2 = <FILE2>;
+close(FILE2);
+my $showcalldiff = shift;
+my %calls1;
+my %calls2;
+my @calls = (\%calls1,\%calls2);
+my $start = 0;
+my @files = (\@file1,\@file2);
+my @allcalls = (0,0);
+my @allticks = (0,0);
+for ( $i=0; $i <= $#files; $i++ ) {
+  my $file = $files[$i];
+  foreach $line(@$file) {
+    chomp($line);
+    if ( $line =~ m/By calls/ ) {
+      $start = 1;
+      next;
+    }
+    if ( $line =~ m/By ticks/ ) {
+      $start = 0;
+      last;
+    }
+    if ( $start == 1) {
+      my @line = split(/[[:space:]]+/,$line);
+      $allcalls[$i] += $line[1];
+      $allticks[$i] += $line[3];
+      $calls[$i]{$line[5]} = [($line[1],$line[3])];
+    }
+  }
+}
+printf("File one calls: %08ld, ticks: %08ld\n",$allcalls[0],$allticks[0]);
+printf("File two calls: %08ld, ticks: %08ld\n",$allcalls[1],$allticks[1]);
+printf("Percent change: %+7.2f%%, ticks: %+7.2f%%\n",
+        ($allcalls[1]-$allcalls[0])/$allcalls[0]*100,
+        ($allticks[1]-$allticks[0])/$allticks[0]*100);
+my @allkeys = keys(%calls1);
+push(@allkeys,keys(%calls2));
+my %u = ();
+my @keys = grep {defined} map {
+  if (exists $u{$_}) { undef; } else { $u{$_}=undef;$_; }
+} @allkeys;
+undef %u;
+my %byticks;
+my %bycalls;
+foreach $key(@keys) {
+  my $values1 = $calls1{$key};
+  my $values2 = $calls2{$key};
+  my $calldiff = @$values2[0]-@$values1[0];
+  my $totalcalls = @$values2[0]+@$values1[0];
+  my $tickdiff = @$values2[1]-@$values1[1];
+  my $totalticks = @$values2[1]+@$values1[1];
+  my $pdiff;
+  my $result;
+  if ($showcalldiff) {
+    $pdiff = $calldiff/(@$values1[0]>0?@$values1[0]:1)*100;
+    $result = sprintf("%+7.2f%%  Calls: %+09d  Symbol: %s$key\n", 
+        $pdiff, $calldiff,
+        (exists $calls1{$key} && exists $calls2{$key})?"":"LONE ");
+  } else {
+    $pdiff = $tickdiff/(@$values1[1]>0?@$values1[1]:1)*100;
+    $result = sprintf("%+7.2f%%  Ticks: %+09d  Symbol: %s$key\n", 
+        $pdiff, $tickdiff, 
+        (exists $calls1{$key} && exists $calls2{$key})?"":"LONE ");
+  }
+  $bycalls{sprintf("%08X$key",$totalcalls)} = $result;
+  $byticks{sprintf("%08X$key",$totalticks)} = $result;
+}
+my @calls = sort(keys(%bycalls));
+print("By calls\n");
+foreach $call(@calls) {
+  print($bycalls{$call});
+}
+my @ticks = sort(keys(%byticks));
+print("By ticks\n");
+foreach $tick(@ticks) {
+  print($byticks{$tick});
+}
diff --git a/tools/profile_reader/profile_reader.pl b/tools/profile_reader/profile_reader.pl
new file mode 100755
index 0000000..088ba71
--- /dev/null
+++ b/tools/profile_reader/profile_reader.pl
@@ -0,0 +1,236 @@
+#!/usr/bin/perl
+
+sub error {
+    print STDERR ("Error: @_\n");
+    exit(1);
+}
+
+sub warning {
+    print STDERR ("Warning: @_\n");
+}
+
+# string (filename.map)
+# return hash(string:hash(string:number))
+sub read_map {
+    open(MAP_FILE,$_[0]) || error("Couldn't open a map $_[0]");
+    my %retval;
+    while (<MAP_FILE>) {
+        chomp;
+        my @parts = split(/[[:space:]]+/);
+        if (@parts != 5) {
+            next;
+        }
+        if ($parts[1] =~ m/\.(text|data|rodata|bss|icode|idata|irodata|ibss)/) {
+            my $region = $parts[1];
+            my $number = $parts[2];
+            @parts = split(/\//,$parts[4]);
+            @parts = split(/[\(\)]/,$parts[$#parts]);
+            my $library = $retval{$parts[0]};
+            my %library =  %$library;
+            my $object = $parts[$#parts];
+            $library{$object . $region} = $number;
+            $retval{$parts[0]} = \%library;
+        }
+    }
+    close(MAP_FILE);
+    return %retval;
+}
+
+# string (filename.[ao]), hash(string:number)
+# return hash(number:string)
+sub read_library {
+    open(OBJECT_FILE,"objdump -t $_[0] |") || 
+        error("Couldn't pipe objdump for $_[0]");
+    my $library = $_[1];
+    my %library = %$library;
+    my %retval;
+    my $object;
+    while (<OBJECT_FILE>) {
+        chomp;
+        my @parts = split(/[[:space:]]+/);
+        if ($parts[0] =~ m/:$/) {
+            $object = $parts[0];
+            $object =~ s/:$//;
+            next;
+        }
+        if (@parts != 6) {
+            next;
+        }
+        if ($parts[0] eq "") {
+            next;
+        }
+        if ($parts[3] eq $parts[5]) {
+            next;
+        }
+        if ($parts[3] =~ m/\.(text|data|rodata|bss|icode|idata|irodata|ibss)/) {
+            my $region = $parts[3];
+            my $symbolOffset = hex("0x" . $parts[0]);
+            my $sectionOffset = hex($library{$object . $region});
+            my $location = $symbolOffset + $sectionOffset;
+            $retval{$location} = $parts[5] . "(" . $object . ")";
+        }
+    }
+    close(OBJECT_FILE);
+    return %retval;
+}
+
+# string (0xFFFFFFFF), hash(number:string)
+# return string
+sub get_name {
+    my $location = hex($_[0]);
+    my $offsets = $_[1];
+    my %offsets = %$offsets;
+    if (exists $offsets{$location}) {
+        return $offsets{$location};
+    } else {
+        my $retval = $_[0];
+        $retval =~ y/[A-Z]/a-z/;
+        warning("No symbol found for $retval");
+        return $retval;
+    }
+}
+
+# string (filename), hash(number:string)
+# return array(array(number,number,string))
+sub create_list {
+    open(PROFILE_FILE,$_[0]) || 
+        error("Could not open profile file: $profile_file");
+    my $offsets = $_[1];
+    my $started = 0;
+    my %pfds;
+#    my $totalCalls = 0;
+#    my $totalTicks = 0;
+#    my $pfds = 0;
+    while (<PROFILE_FILE>) {
+        if ($started == 0) {
+            if (m/^0x/) {
+                $started = 1;
+            } else {
+                next;
+            }
+        }
+        my @parts = split(/[[:space:]]+/);
+        if ($parts[0] =~ m/^0x/) {
+            my $callName = get_name($parts[0],$offsets);
+            my $calls = $parts[1];
+            my $ticks = $parts[2];
+            my @pfd = ($calls,$ticks,$callName);
+            if (exists $pfds{$callName}) {
+                my $old_pfd = $pfds{$callName};
+                $pfd[0]+=@$old_pfd[0];
+                $pfd[1]+=@$old_pfd[1];
+            }
+            $pfds{$callName} = \@pfd;
+#            $pfds++;
+#            $totalCalls+=$calls;
+#            $totalTicks+=$ticks;
+        } else {
+            last;
+        }
+
+    }
+    close(PROFILE_FILE);
+#    print("FUNCTIONS\tTOTAL_CALLS\tTOTAL_TICKS\n");
+#    printf("     %4d\t   %8d\t   %8d\n",$pfds,$totalCalls,$totalTicks);
+    return values(%pfds);
+}
+
+# array(array(number,number,string)), number (sort element)
+sub print_sorted {
+    my $pfds = $_[0];
+    my @pfds = @$pfds;
+    my $sort_index = $_[1];
+    my $percent = $_[2];
+    my %elements;
+    my $totalCalls = 0;
+    my $totalTicks = 0;
+    $pfds = 0;
+    foreach $element(@pfds) {
+        $elements{@$element[$sort_index] . @$element[2]} = $element;
+        $pfds++;
+        $totalCalls += @$element[0];
+        $totalTicks += @$element[1];
+    }
+    my @keys = sort(keys(%elements));
+    print("FUNCTIONS\tTOTAL_CALLS\tTOTAL_TICKS\n");
+    printf("     %4d\t   %8d\t   %8d\n",$pfds,$totalCalls,$totalTicks);
+    foreach $key(@keys) {
+        my $element = $elements{$key};
+        if ($percent) {
+            printf("Calls: %7.2f%% Ticks: %7.2f%% Symbol: %s\n",
+                    @$element[0]/$totalCalls*100,
+                    @$element[1]/$totalTicks*100,
+                    @$element[2]);
+        } else {
+            printf("Calls: %08d Ticks: %08d Symbol: %s\n",
+                    @$element);
+        }
+    }
+}
+
+# merges two hashes
+sub merge_hashes {
+    my $hash1 = $_[0];
+    my $hash2 = $_[1];
+    return (%$hash1,%$hash2);
+}
+
+sub usage {
+    if (@_) {
+        print STDERR ("Error: @_\n");
+    }
+    print STDERR ("USAGE:\n");
+    print STDERR ("$0 profile.out map obj[...] [map obj[...]...] sort[...]\n");
+    print STDERR 
+        ("\tprofile.out  output from the profiler, extension is .out\n");
+    print STDERR 
+        ("\tmap          map file, extension is .map\n");
+    print STDERR
+        ("\tobj          library or object file, extension is .a or .o\n");
+    print STDERR
+        ("\tformat       0-2[_p] 0: by calls, 1: by ticks, 2: by name\n");
+    print STDERR
+        ("\t             _p shows percents instead of counts\n");
+    print STDERR ("NOTES:\n");
+    print STDERR
+        ("\tmaps and objects come in sets, one map then many objects\n");
+    exit(1);
+}
+
+
+if ($ARGV[0] =~ m/-(h|help|-help)/) {
+    usage();
+}
+if (@ARGV < 2) {
+    usage("Requires at least 2 arguments");
+}
+if ($ARGV[0] !~ m/\.out$/) {
+    usage("Profile file must end in .out");
+}
+my $i = 1;
+my %symbols;
+{
+    my %map;
+    for (; $i < @ARGV; $i++) {
+        my $file = $ARGV[$i];
+        if ($file =~ m/\.map$/) {
+            %map = read_map($file);
+        } elsif ($file =~ m/\.[ao]$/) {
+            if (!%map) {
+                usage("No map file found before first object file");
+            }
+            my @parts = split(/\//,$file);
+            my %new_symbols = read_library($file,$map{$parts[$#parts]});
+            %symbols = merge_hashes(\%symbols,\%new_symbols);
+        } else {
+            last;
+        }
+    }
+}
+if (!%symbols) {
+    warning("No symbols found");
+}
+my @pfds = create_list($ARGV[0],\%symbols);
+for (; $i < @ARGV; $i++) {
+    print_sorted(\@pfds,split("_",$ARGV[$i]));
+}