A minimal bare metal microcontroller program should contain at least this: an interrupt vector table. On SAMD and similar ARM, this is the boot area flash from address zero and beyond, which also contains the stack start address. As well as the address of the hardfault handler & friends, and of course every interrupt used, if any.
Usually this is solved by tool vendors in some specific way, through linker scripts or as part of the C run-time. If you aren't using a tool chain but just a "naked" compiler, you must manually generate the whole interrupt vector, fill it up with relevant information and make sure to download it into flash.
Additionally, in order to enable C programming, you must have a "C run-time" (CRT) library that sets up the various memory segments before it calls main(). On ELF-like linkers these are .bss
and .data
as a bare minimum. You also need to configure system clock, watchdog and other fundamental stuff early on, preferably from the reset vector. Some systems require MMU setup as well, but that shouldn't be necessary on Cortex M, from what I remember.
If you haven't got a clue about any these things, get a tool that does it for you.