Determining ARM Cortex M3 RAM Size at run time

Right, I finally figured this out with the help of users on ST's forum.

First, you need to enable the BusFault IRQ:

SCB->SHCSR |= SCB_SHCSR_BUSFAULTENA;

Then, you need to define a BusFault handler which will increment the program counter by 2 in order to skip over the offending instruction (taking a gamble that it is in fact a 2-byte instruction) :

__attribute__ ((naked)) void BusFault_Handler(void) {
  /* NAKED function so we can be sure that SP is correct when we
   * run our asm code below */

  // DO NOT clear the busfault active flag - it causes a hard fault!

  /* Instead, we must increase the value of the PC, so that when we
   * return, we don't return to the same instruction.
   *
   * Registers are stacked as follows: r0,r1,r2,r3,r12,lr,pc,xPSR
   * http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0337e/Babedgea.html
   *
   * So we want PC - the 6th down * 4 bytes = 24
   *
   * Then we add 2 - which IS DANGEROUS because we're assuming that the op
   * is 2 bytes, but it COULD be 4.
   */
  __asm__(
      "ldr r0, [sp, #24]\n"  // load the PC
      "add r0, #2\n"         // increase by 2 - dangerous, see above
      "str r0, [sp, #24]\n"  // save the PC back
      "bx lr\n"              // Return (function is naked so we must do this explicitly)
  );
}

And now - FINALLY - we can try and read from an arbitrary memory location. If it's wrong, the BusFault handler gets called, but we skip over the read or write instruction as if it wasn't there.

This means that it's relatively easy to write to a memory location and then read back - and if you get the same thing, you know it's valid (you just need to make sure that your code isn't fooled by having both str and ldr as no-ops).