Search code examples
linkerarmcortex-m

How does the ARM linker know where the exception table stops?


I have come across something very similar to the following when looking for basic, bare-metal Cortex-M3 programming information (referred to in a great answer right here, that I'll attempt to locate later).

/* vectors.s */
.cpu cortex-m3
.thumb

.word   0x20002000  /* stack top address */
.word   _start      /* 1 Reset */
.word   hang        /* 2 NMI */
.word   hang        /* 3 HardFault */
.word   hang        /* 4 MemManage */
.word   hang        /* 5 BusFault */
.word   hang        /* 6 UsageFault */
.word   hang        /* 7 RESERVED */
.word   hang        /* 8 RESERVED */
.word   hang        /* 9 RESERVED*/
.word   hang        /* 10 RESERVED */
.word   hang        /* 11 SVCall */
.word   hang        /* 12 Debug Monitor */
.word   hang        /* 13 RESERVED */
.word   hang        /* 14 PendSV */
.word   hang        /* 15 SysTick */
.word   hang        /* 16 External Interrupt(0) */
.word   hang        /* 17 External Interrupt(1) */
.word   hang        /* 18 External Interrupt(2) */
.word   hang        /* 19 ...   */

.thumb_func
.global _start
_start:
    bl notmain
    b hang

.thumb_func
hang:   b .

This makes sense to me, I understand what it does, but what does not make sense to me is how the linker (or CPU, I'm not sure which...) knows where the exception table ends and actual code begins. It seems to me from the Cortex-M3 documentation that there can be an arbitrary number of external interrupts in the table.

How does this work, and what should I have read to learn?


Solution

  • That is my code, and it is the programmer not the linker that needs to know what is going on. The cortex-m's have different vector tables and they can be quite lengthy, many many individual interrupt vectors. The above was just a quicky from one flavor one day. If you never will use anything more than the reset vector do you need to burn more memory locations creating an entire table? probably not. You might want to make one deep enough to cover undefined exceptions and aborts and such, so you can trap them with a controlled hang. If you only need reset and one interrupt but that interrupt is pretty deep in the table, you can make a huge table to cover both or if brave enough put some code between them to recover the lost space.

    To learn/read more you need to go to the TRM, technical reference manual, for the specific cortex-m cores cortex-m0, cortex-m3 and cortex-m4 as well as possibly the ARM ARM for the armv7-m (the cortex-m's are all of the family armv7-m). ARM ARM means ARM Architectural Reference Manual, which talks generically about the whole family but doesnt cover core specific details to any great length, you need an ARM ARM and the right TRM usually when programming for an ARM (if you need to get into anything low level like asm or specific registers). All can be found at the arm website infocenter.arm.com along the left look for Architecture or look for Cortex-M series processors.

    These cores are new enough they probably dont have more than the original rev number. For some older cores it is best to get the TRM for the specific core. Take the ARM11 Mpcore for example if the vendor used rev 1.0 (r1p0) of the core despite the obsolete markings on the manual you want to at least try to use the rev 1.0 manual over the rev 2.0 manual if there are differences. The vendors dont always tell you which rev they bought/used so this can be a problem when sorting out how to program the thing or what errata applies to you and what doesnt. (Linux is FULL of mistakes related to ARM because of not understanding this, errata applied wrong in general or applied to the wrong core, instructions used on the wrong core or at the wrong time, etc).

    I probably wrote that handler for a cortex-m3 when i first got one and then cut and pasted here and there not bothering to fix/change it.

    The core (CPU, logic) definitely knows how many interrupts are supported, and knows the complete make up of the vector table. There might be control signals on the edge of the core that might change these kinds of things, and the vendor may have tied those one way or made them programmable, etc. No matter what the logic for that core definitely knows what the vector table looks like it is up to the programmer to make the code match the hardware as needed. This will all be described in the TRM for that core.

    EDIT

    Hardcoded in the logic will be an address or an offset for each of these events or interrupts. So when the interrupt or event happens it performs a memory read from that address.

    This is just a bank of flash memory, you feed it an address flick a read strobe and some data comes out. Those bits dont mean anything until you put them in a context. You can cause a memory cycle at address 0x10 if you create some code that does a load instruction at that address, or you can branch to that address and a fetch cycle reads that location hoping to find an instruction, and if an event has that hardcoded address and a read happens at that address it is hoping to find an address for a handler. but it is just a memory. Just like a file on a file system, its just bytes on the disk. If it is a jpeg then a particular byte might be a pixel, if the file on the disk is a program then a byte at an offset might be an instruction. its just a byte.

    These addresses are generated directly from the logic. These days logic is written using programming languages (usually verilog or vhdl or some higher level language that produces verilog or vhdl as an output). No different than if you were to write a program in your favorite language you might choose to literally hardcode some address

    x = (unsigned int *)0x1234;
    

    or you might choose to use a structure or an array or some sort of other programming style, but once compiled it still ends up producing some fixed address or offset:

    unsigned int vector_table[256];
    ...
    handler_address = vector_table[interrupt_base+interrupt_number];
    ...
    

    So as a programmer, at this low level, you have to know if and when the hardware is going to read one of these addresses and why. if you never use interrupts because you never enable any interrupts then those memory locations that might normally hold interrupt handler addresses, are now memory locations you can use for anything you want. if as you see in many, almost all, of my examples I only ever need the reset vector, the rest I dont use. I might accidentally hit an undefined instruction handler or data abort if I accidentally perform an unaligned access, but I dont worry about it enough to place a handler there, usually. I am going to crash/hang anyway so I will sort that problem out when I get there, cross that bridge when I get to it. So I am usually content with the bare minimum for a cortex-m the stack address and reset address:

    .cpu cortex-m3
    .thumb
    
    .word   0x20002000  /* stack top address */
    .word   _start      /* 1 Reset */
    
    .thumb_func
    .global _start
    _start:
        bl notmain
        b hang
    
    .thumb_func
    hang:   b .
    

    And yes, absolutely I have placed a two word instruction bl notmain at the memory location for an NMI. if an NMI were to occur, then the cpu would read that location, assume it was an address for a handler, try to fetch the instruction at that address, which will have who knows what. It might know before even fetching that it is an invalid address and cause a data abort, which in the above case would be yet another address to somewhere, and it might turn into an infinite loop of data aborts. Or if by chance one of those instructions happens to appear like an address in our program then basically it will jump right into the middle of the program, which quite often will end up in some sort of crash. what is a crash? really? the cpu doing its job reading bytes from memory and interpreting them as instructions if those instructions tell it to do unaligned accesses or those bytes are not valid instructions or cause you to read invalid memory address you go back into the vector table. Or you might be so lucky that the messed up addresses being written or read by jumping in the middle of the code space cause a flash bank to be erased, or a byte to spit out a uart, or a gpio input port to be changed into an output port (if really lucky you make it try to drive against a ground or something and you melt down the chip).

    If I were to start seeing weird things (and hopefully not hear or smell or see the chip melting down) I might toss in a few dozen entries in the vector table that point to a hang, or point to a handler that spits something out a port or turns on a gpio/led. if my weirdness now becomes this led coming on or the uart output in the handler then I have to sort out what event is happening, or if I think about the last few changes to my application I may realize I had an unaligned access or a branch into the weeds, etc.

    It goes back to that saying, "The computer doesnt do what you want it to do, it does what you told it to do". You put the bytes there and it interpreted them for what they were, if you didnt put the right bytes in the right place (vector addresses to the right place, instructions that do the right thing, and the right data in the right place) it can/will crash.