March 6, 2026

Adventures with memcpy and the RP2040 - Julian's example

Julian started with a short bit of C code and built a uf2 file using the pico SDK. He then disassembled the uf2 file and found our mystery while studying it. Here is the code he started with
#include 
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "pico/binary_info.h"

const uint LED_PIN = 25;

int main() {

	bi_decl(bi_program_description("This is a test binary."));
	bi_decl(bi_1pin_with_name(LED_PIN, "On-board LED"));

	stdio_init_all();

	gpio_init(LED_PIN);
	gpio_set_dir(LED_PIN, GPIO_OUT);
	while (1) {
		gpio_put(LED_PIN, 0);
		sleep_ms(250);
		gpio_put(LED_PIN, 1);
		puts("Hello World\n");
		sleep_ms(1000);
	}
}
This certainly looks simple and straightforward, and there is not a call to memcpy to be seen anywhere. However, when you look at the disassembly, you find:
10001d98 <__wrap___aeabi_memcpy>:
10001d98:   4b01        ldr r3, [pc, #4]    @ (10001da0 <__wrap___aeabi_memcpy+0x8>)
10001d9a:   685b        ldr r3, [r3, #4]
10001d9c:   4718        bx  r3
10001d9e:   0000        .short  0x0000
10001da0:   20001060    .word   0x20001060

20001060:   0000534d    .word   0x0000534d
20001064:   0000434d    .word   0x0000434d
20001068:   00003453    .word   0x00003453
2000106c:   00003443    .word   0x00003443
The function "__wrap___eabi_memcpy" is called from several library init routines. One for example sets up the vector table.

If you study the code above you will see that it first loads 0x20001060 into r3 (the base address for the 4 entry table shown). Then it fetches the table entry at offset 4 in that table, namely 0x434d. It then attempts a bx using register r3, and the crazy part is that this would seem to branch into ROM, but in fact the ROM ends at 0x4000 and this is a reserved and unmapped address. We would thus expect this to generate a HardFault.

Also note the value. If we take 0x434d as ascii (and undo the byte swapping that the disassembler did) we get 0x4d, 0x43, i.e. "M", "C".

If you jump ahead to the next page, you will find this is the lookup code for _memcpy in the bootrom table. That certainly makes sense. What doesn't make sense is trying to use the bx instruction to jump there.

Now if this did cause a HardFault (and we are pretty sure it will), we might expect to find a HardFault handler set up to take note of attempted calls to this range and do the lookup.

In any case, this is truly weird coding. And we have not yet found such a HardFault handler.


Have any comments? Questions? Drop me a line!

Tom's software pages / tom@mmto.org