relocation truncated to fit error explained

There seems to be a chronic problem where making some minor change to their code (like just changing the length of a string) then their code won’t compile/upload. This is not an arduino bug but a problem with the linker.

For a quick fix, put this in your code in the global variable area before your setup() routine. You may have to try a few different array sizes, and for people running on Tiny machines, feel free to try small numbers like 50. In some cases, just 2 bytes will be enough to fix the problem.

#include <avr/pgmspace.h>
const char pad[500] PROGMEM = { 0 };

The rest of this gets fairly technical, sorry about that.

Here’s what’s going on. The first 224 bytes of flash memory on a Mega2560 are the interrupt vector table. When an interrupt occurs (like a timer expires or there’s a byte waiting in some receive buffer), this table tells the processor what code to execute. This example doesn’t use many interrupts, so unused vectors get sent
to the routine bad_interrupt(). Here’s a few lines from the vector table.

0000 <__vectors>:
   0:    0c 94 08 08   jmp   0x1010  ; 0x1010 <__ctors_end>
   4:    0c 94 12 08   jmp   0x1024  ; 0x1024 <__bad_interrupt>
   8:    0c 94 12 08   jmp   0x1024  ; 0x1024 <__bad_interrupt>
   c:    0c 94 12 08   jmp   0x1024  ; 0x1024 <__bad_interrupt>
  10:    0c 94 12 08   jmp   0x1024  ; 0x1024 <__bad_interrupt>
  14:    0c 94 12 08   jmp   0x1024  ; 0x1024 <__bad_interrupt>
  18:    0c 94 12 08   jmp   0x1024  ; 0x1024 <__bad_interrupt>
  1c:    0c 94 12 08   jmp   0x1024  ; 0x1024 <__bad_interrupt>
  20:    0c 94 12 08   jmp   0x1024  ; 0x1024 <__bad_interrupt>
  24:    ff c7         rjmp  .+4094  ; 0x1024 <__bad_interrupt>
  26:    00 00         nop
  28:    fd c7         rjmp  .+4090  ; 0x1024 <__bad_interrupt>
  2a:    00 00         nop
  2c:    fb c7         rjmp  .+4086  ; 0x1024 <__bad_interrupt>
  2e:    00 00         nop
  30:    f9 c7         rjmp  .+4082  ; 0x1024 <__bad_interrupt>

A few things to notice

  • The bad_interrupt routine is located at address 0x1024
  • The first few vectors use a direct jump to 0x1024
  • The last vectors use a relative jump to 0x1024

The use of both jmp and rjmp is an artifact of the -mrelax compiler flag. Amongst other things, it tells the compiler to use rjmp instructions when the destination is close enough (which is +/- 4k). Otherwise, the compiler should use jmp. This isn’t a bad thing, rjmp instructions run 1 clock faster and use 2 bytes less data.

Without -mrelax, the compiler uses only jmp instructions in the vector table and the problem goes away. BTW, for our purposes, --relax is the same as -mrelax and is turned on by default on the larger processors.

The problem is that the linker is getting jammed up somehow. In the above example, when the bad_interrupt routine is located at address 0x1028, the vector should at address 0x24 should turn into a jmp but the linker can’t do it for some reason. Instead, it leaves the instruction as a rjmp with a relative offset of +4098. As the allowed range is 4096, the offset would get truncated to +2, which is a serious error.

The reason why “pad[500] PROGMEM = { 0 };” should work is it will allocate a chunk of flash memory between the vector table and moves bad_interrupt() far enough away from the vector table that the linker isn’t even tempted to use a rjmp instruction.

In searching the web, this appears to be a chronic problem with all sorts of solutions that sometimes work. Popular are using more/less PSTR(“Hello World”) constructs and various -lm -lc options. I suspect these things are just jiggling around subroutine addresses and by blind luck they fall in places that work.

Below is the code I used to isolate this bug. This was run on arduino 1.0.5 with a Mega2560 board. The code doesn’t have any useful function, it just irritates the compiler bug.

#include <avr/pgmspace.h>

// 3580 works
// 3582 fails
// 3594 fails
// 3596 works
char pad[3582] PROGMEM = { 0 };

byte i;
void setup() {
  pinMode(13, OUTPUT);
}

void loop() {
  digitalWrite(13,pad[i++]);
}

In the above example, when the bad_interrupt routine is located at address 0x1028, the vector should at address 0x24 should turn into a jmp but the linker can't do it for some reason.

It all made sense up to the comma in this sentence. Something got dropped/added in the serial communication between your brain and your fingers, I think.

Some days it's hard to believe this is my native tongue.

The problem is that the linker is getting jammed up somehow. In the above example, when the bad_interrupt routine is located at address 0x1028, the vector at address 0x24 should turn into a jmp but the linker can't do it for some reason. Instead, it leaves the instruction as a rjmp with a relative offset of +4098. As the allowed range is 4096, the offset would get truncated to +2, which is a serious error.

Here is a different way to describe it.

As the location of bad_interrupt() moves to higher addresses (0x1024, 0x1028, 0x102a) the transition from jmp to rjmp in the vector table should track accordingly (0x20, 0x24, 0x28). For some unknown reason the linker gets jammed up and cannot make address 0x24 into a jmp instruction but leaves it as a rjmp with a relative offset of +4098. As the allowed range is 4096, the offset would get truncated to +2, which is a serious error.

I don't think the second description is any better, just different.

Thanks for pointing out my com error.

andersom:
You may have to try a few different array sizes, and for people running on Tiny machines, feel free to try small numbers like 50.

Do you mean an "ATtiny processor"?

Do you mean an "ATtiny processor"?

Yes, I did mean ATtiny, but it's true with all processors.

With my Mega2560, when I finally tracked down what was going on, I removed 4 characters from a PSTR ("string") and the problem went away. I've been using the char pad[500] PROGMEM construct because I don't need the 500 bytes and with smaller values around 50, I still occasionally bump into the bug. When I do my final compile, I'll optimize a better value.

andersom:

Do you mean an "ATtiny processor"?

Yes, I did mean ATtiny, but it's true with all processors.

The solution for ATtiny processors is much simpler; just use a newer linker...
http://forum.arduino.cc/index.php?topic=116674.0

Agreed, a working linker is way better than a workaround. I've poked around for a few minutes, couldn't find a Linux version of the patch, I'd really like to try it on the little test program posted in this thread. Can someone out there do a quick test for me and share the results?