mbed memory usage

Inspired by this thread.

Since mbed's startup code is quite standard, it's possible to parse it with a little work. I made a Python script which looks for the scatter region table and calculates some values from it.

 

# script to print memory usage stats for binaries produced by mbed online compiler
# Version 1.0, 2010-01-23
# Author: Igor Skochinsky

import sys, struct

RAM_START  = 0x10000000
RAM_SIZE   = 0x00008000 # 32K
FLASH_SIZE = 0x00080000 # 512K

# read a 4-byte dword (little-endian) from file
def getDword(f, off):
  f.seek(off)
  data = f.read(4)
  if len(data) != 4:
    print "Trying to read ouside the file!"
    return 0
  return struct.unpack("<I", data)[0]

class RegionEntry:
  def __init__(self, f, off):
    f.seek(off)
    data = f.read(4*4)
    if len(data) != 4*4:
      print "Trying to read ouside the file!"
      return
    values = struct.unpack("<IIII", data)
    self.source = values[0]
    self.dest   = values[1]
    self.size   = values[2]
    self.func   = values[3]

def show_stats(f):
  sp = getDword(f, 0)
  if sp != RAM_START + RAM_SIZE:
    print "Warning: stack pointer (%08X) is different from expected value (%08X)" % (sp, RAM_START + RAM_SIZE)
  entry = getDword(f, 4)
  print "Entry point address: %08X" % entry
  if entry & 1 != 1:
    print "Doesn't look like a Cortex-M3 binary"
    return
  entry &= ~1
  f.seek(entry)
  startup = f.read(8)
  # ldr r0, =HWInit
  # blx r0
  # ldr r0, =__main
  # bx  r0
  if startup != "\x06\x48\x80\x47\x06\x48\x00\x47":
    print "Wrong startup code, probably not an mbed 1768 binary"
    return
  __main = getDword(f, entry + 0x20) & ~1
  f.seek(__main)
  if f.read(4) != "\x00\xF0\x02\xF8":
    print "Wrong startup code, probably not an mbed 1768 binary"
    return
  #fetch scatter region table pointers 
  tblptrs = __main+0x34
  tblstart = getDword(f, tblptrs)   + tblptrs
  tblend   = getDword(f, tblptrs+4) + tblptrs
  tblsize = tblend - tblstart
  tblentries = tblsize / 16
  if tblsize % 16 != 0 or tblentries == 0 or tblentries > 2:
    print "Wrong size of scatter region table (%d entries)" % tblentries
    return
  entry1 = RegionEntry(f, tblstart)
  # first entry sets up RW data
  # so its src address will be code + RO data size
  code_rodata = entry1.source
  # size will be the size of RW data
  rwdata = entry1.size
  if tblentries == 2:
    # second entry sets up ZI data
    # its "source" will be the total flash size, but we'll just use the file size
    entry2 = RegionEntry(f, tblstart+16)
    zidata = entry2.size
  else:
    # no ZI data
    zidata = 0
  usedram = rwdata + zidata
  f.seek(0, 2)
  usedflash = f.tell()

  print "Code + RO data = %-7d (0x%05x)" % (code_rodata, code_rodata)
  print "RW data        = %-7d (0x%05x)" % (rwdata, rwdata)
  print "ZI data        = %-7d (0x%05x)" % (zidata, zidata)  
  print "Used flash     = %-7d (0x%05x)" % (usedflash, usedflash)
  print "Free flash     = %-7d (0x%05x)" % (FLASH_SIZE - usedflash, FLASH_SIZE - usedflash)
  print "Used RAM       = %-7d (0x%05x)" % (usedram, usedram)
  print "Free RAM       = %-7d (0x%05x)" % (RAM_SIZE - usedram, RAM_SIZE - usedram)

print "mbed 1768 binary stats script 1.0"
if len(sys.argv) >= 2:
  inf = open(sys.argv[1],"rb")
  show_stats(inf)
else:
  print "Usage: binstats.py file.bin"

Save it as binstats.py and pass the binary filename as the parameter. If you need to install it, get Python 2.x, not 3.x.

Please note that the reported sizes are before any C/C++ startup code is executed, so any RAM used by CRT (e.g. malloc structures) or global objects is not included. Also, the stack occupies the top of the RAM and overwriting it is not a good idea.

Example stats for some programs:

FFT.bin:
Entry point address: 00000215
Code + RO data = 43568 (0x0aa30)
RW data = 240 (0x000f0)
ZI data = 380 (0x0017c)
Used flash = 43808 (0x0ab20)
Free flash = 480480 (0x754e0)
Used RAM = 620 (0x0026c)
Free RAM = 32148 (0x07d94)

DOGLCDDemo.bin:
Entry point address: 00000255
Code + RO data = 36128 (0x08d20)
RW data = 2528 (0x009e0)
ZI data = 1720 (0x006b8)
Used flash = 36616 (0x08f08)
Free flash = 487672 (0x770f8)
Used RAM = 4248 (0x01098)
Free RAM = 28520 (0x06f68)


6 comments

06 Feb 2010

Hi Igor

I compiled the EthernetTester from this notebook and ran the script with the following outcome:

mbed 1768 binary stats script 1.0
Entry point address: 00000261
Wrong size of scatter region table (3 entries)

Is this due to two libraries being linked (mbed and lwip)?

Thanks
Daniel

07 Feb 2010

Hi Igor,

I ran the script on a program with three libraries (FATFileSystem, mbed and lwip) and got exactly the same result as Daniel.

Paul

26 Jul 2013

For what it's worth, I had the same issue with the script mentioned above, and dug into it because I wanted to know whether I had a RAM usage problem or not, and just how much I really had left if there wasn't really a problem.

As mentioned in other places, the MBED compiler report of RAM usage is not correct if the AHB SRAM memory banks are used, which apparently the EthernetInterface does. The LCP1768 has two 16K AHB SRAM banks in addition to the 32K bank in the CPU. So while MBED docs show the 1768 has only 32K of RAM, the spec sheets say it has 64K.

When the AHB SRAM banks are used, there are extra table entries in the bin file, which is why the above script bombed out when used on a program that uses EthernetInterface.

I modified the script to report usage by RAM block (CPU, AHB0, and AHB1), and the modified code is pasted below. Example results with this modified script appear as follows:

mbed 1768 binary stats script 2.0

Entry point address: 000002C5
Region 1:     source=0x000129C8, dest=0x100000C8, size=     720, func= 256
Region 2:     source=0x00012A08, dest=0x2007C000, size=   16384, func= 256
Region 3:     source=0x00012A8C, dest=0x20080000, size=   11220, func= 256
Region 4:     source=0x00012A08, dest=0x10000398, size=   10348, func= 316


Used flash     = 76520   (0x12ae8)
Free flash     = 447768  (0x6d518)

CPU  RAM Used  = 11068   (0x02b3c)
CPU  Free RAM  = 21700   (0x054c4)

AHB0 RAM Used  = 16384   (0x04000)
AHB0 Free RAM  = 0       (0x00000)

AHB1 RAM Used  = 11220   (0x02bd4)
AHB1 Free RAM  = 5164    (0x0142c)

 

The modified script follows:

 

# script to print memory usage stats for binaries produced by mbed online compiler
# Version 1.0, 2013-07-26 Author: Igor Skochinsky
# Version 2.0, 2013-07-26 Modifications by John VanLaanen
# Based on an the oroginal script by Igor Skochinsky copied from: http://mbed.org/users/igorsk/notebook/mbed-memory-usage/

import sys, struct

# Memory map for the LCP1768
RAM_START  = 0x10000000
RAM_SIZE   = 0x00008000 # 32K
RAM_END = RAM_START + RAM_SIZE - 1

AHB0_START = 0x2007c000
AHB0_SIZE = 0x4000      # 16K
AHB0_END = AHB0_START + AHB0_SIZE - 1

AHB1_START = 0x20080000
AHB1_SIZE = 0x4000      # 16K
AHB1_END = AHB1_START + AHB1_SIZE - 1

FLASH_SIZE = 0x00080000 # 512K

# read a 4-byte dword (little-endian) from file
def getDword(f, off):
  f.seek(off)
  data = f.read(4)
  if len(data) != 4:
    print "Trying to read ouside the file!"
    return 0
  return struct.unpack("= RAM_START) and (entryn.dest <= RAM_END):
        ram_used += entryn.size
    elif (entryn.dest >= AHB0_START) and (entryn.dest <= AHB0_END):
        ahb0_used += entryn.size
    elif (entryn.dest >= AHB1_START) and (entryn.dest <= AHB1_END):
        ahb1_used += entryn.size
    else:
        print("Region %d in an unknown memory location" % ii)
        
    print('Region %d:     source=0x%08X, dest=0x%08X, size=%8d, func=%4d' % \
                (ii+1, entryn.source, entryn.dest, entryn.size, entryn.func) )

  f.seek(0, 2)
  usedflash = f.tell()
  print("\n")
  print "Used flash     = %-7d (0x%05x)" % (usedflash, usedflash)
  print "Free flash     = %-7d (0x%05x)\n" % (FLASH_SIZE - usedflash, FLASH_SIZE - usedflash)
  print "CPU  RAM Used  = %-7d (0x%05x)" % (ram_used, ram_used)
  print "CPU  Free RAM  = %-7d (0x%05x)\n" % (RAM_SIZE - ram_used, RAM_SIZE - ram_used)
  print "AHB0 RAM Used  = %-7d (0x%05x)" % (ahb0_used, ahb0_used)
  print "AHB0 Free RAM  = %-7d (0x%05x)\n" % (AHB0_SIZE - ahb0_used, AHB0_SIZE - ahb0_used)
  print "AHB1 RAM Used  = %-7d (0x%05x)" % (ahb1_used, ahb1_used)
  print "AHB1 Free RAM  = %-7d (0x%05x)" % (AHB1_SIZE - ahb1_used, AHB1_SIZE - ahb1_used)

print "mbed 1768 binary stats script 2.0\n"
if len(sys.argv) >= 2:
  inf = open(sys.argv[1],"rb")
  show_stats(inf)
else:
  print "Usage: MBED_binstats.py file.bin"

 

 

26 Jul 2013

Something happened with the previous post and the script got corrupted. I tried it again and the same thing happened. If anyone wants the modified script, please contact me at johnvanlaanen@gmail.com

26 Jul 2013

user John VanLaanen wrote:

Something happened with the previous post and the script got corrupted. I tried it again and the same thing happened. If anyone wants the modified script, please contact me at johnvanlaanen@gmail.com

Could you share it on pastebin or any similar service available, just paste the link here, that would be much better than privately shared through an email.

Regards,

0xc0170

11 Jan 2016 . Edited: 11 Jan 2016
John VanLaanen sent me his script which I have modified to work with Python 3. {{{ <> # script to print memory usage stats for binaries produced by mbed online compiler # Version 1.0, 2013-07-26 Author: Igor Skochinsky # Version 2.0, 2013-07-26 Modifications by John VanLaanen # Version 2.1, 2016-01-11 Modifications by David Pairman # Based on an the oroginal script by Igor Skochinsky copied from: http://mbed.org/users/igorsk/notebook/mbed-memory-usage/ import sys, struct # Memory map for the LCP1768 RAM_START = 0x10000000 RAM_SIZE = 0x00008000 # 32K RAM_END = RAM_START + RAM_SIZE - 1 AHB0_START = 0x2007c000 AHB0_SIZE = 0x4000 # 16K AHB0_END = AHB0_START + AHB0_SIZE - 1 AHB1_START = 0x20080000 AHB1_SIZE = 0x4000 # 16K AHB1_END = AHB1_START + AHB1_SIZE - 1 FLASH_SIZE = 0x00080000 # 512K # read a 4-byte dword (little-endian) from file def getDword(f, off): f.seek(off) data = f.read(4) if len(data) != 4: print("Trying to read ouside the file!") return 0 return struct.unpack("= RAM_START) and (entryn.dest <= RAM_END): ram_used += entryn.size elif (entryn.dest >= AHB0_START) and (entryn.dest <= AHB0_END): ahb0_used += entryn.size elif (entryn.dest >= AHB1_START) and (entryn.dest <= AHB1_END): ahb1_used += entryn.size else: print("Region %d in an unknown memory location" % ii) print('Region %d: source=0x%08X, dest=0x%08X, size=%8d, func=%4d' % \ (ii+1, entryn.source, entryn.dest, entryn.size, entryn.func) ) f.seek(0, 2) usedflash = f.tell() print("\n") print("Used flash = %-7d (0x%05x)" % (usedflash, usedflash)) print("Free flash = %-7d (0x%05x)\n" % (FLASH_SIZE - usedflash, FLASH_SIZE - usedflash)) print("CPU RAM Used = %-7d (0x%05x)" % (ram_used, ram_used)) print("CPU Free RAM = %-7d (0x%05x)\n" % (RAM_SIZE - ram_used, RAM_SIZE - ram_used)) print("AHB0 RAM Used = %-7d (0x%05x)" % (ahb0_used, ahb0_used)) print("AHB0 Free RAM = %-7d (0x%05x)\n" % (AHB0_SIZE - ahb0_used, AHB0_SIZE - ahb0_used)) print("AHB1 RAM Used = %-7d (0x%05x)" % (ahb1_used, ahb1_used)) print("AHB1 Free RAM = %-7d (0x%05x)" % (AHB1_SIZE - ahb1_used, AHB1_SIZE - ahb1_used)) print("mbed 1768 binary stats script 2.0\n") if len(sys.argv) >= 2: inf = open(sys.argv[1],"rb") show_stats(inf) else: print("Usage: MBED_binstats.py file.bin") <> }}} \\linebreak My apologies - I can't seem to get the "code" markup working as per [[https://developer.mbed.org/cookbook/Wiki-Syntax]]!!

You need to log in to post a comment