1
\$\begingroup\$

I've been able to build and run binaries successfully when I compile and link in one step. However if I try compiling but not linking with the -c option to avr-gcc, and then subsequently linking the resulting object files into a binary, the result is garbage, apparently due to a nonexistent interrupt table.

Is there something I must do at the link stage if I want to build from multiple intermediate object files?

Details: Say I have a simple program for an ATMega48P, like this one, which we can call ledblink.c:

#define F_CPU 16000000UL
#define F_ADC 48000

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

int main(void)
{
  DDRD = (1<<PD6);
  DDRB = (1<<PB0);
  TCCR0A = (1<<WGM01);  // CTC mode
  TCCR0B = (1<<CS01);   // divide by 8 prescaler
  TIMSK0 = (1 << OCIE0A);
  OCR0A = F_CPU / 8 / F_ADC;
  sei();
  while(1)
  {
    PORTB |= (1<<PB0);
    _delay_ms(100);
    PORTB &= ~(1<<PB0);
    _delay_ms(100);
  }
  return 0;
}

ISR(TIMER0_COMPA_vect) {
    PORTD |= (1<<PD6);
    _delay_us(5);
    PORTD &= ~(1<<PD6);
}

It should:

  • blink an LED on PB0 in the main loop, and simultaneously
  • output an (approximately) 48 kHz square wave on PD6, using timer0 and the ISR

If I build it like this:

avr-gcc -mmcu=atmega48p -Os -o ledblink ledblink.c
avr-objcopy -j .text -j .data -O ihex ledblink ledblink.hex
avrdude -c avrispmkII -p m48p -U flash:w:ledblink.hex

it works great. But if I build it like this:

avr-gcc -mmcu=atmega48p -Os   -c -o ledblink.o ledblink.c
avr-gcc   ledblink.o   -o ledblink
avr-objcopy -j .text -j .data -O ihex ledblink ledblink.hex
avrdude -c avrispmkII -p m48p -U flash:w:ledblink.hex

the LED on PB0 does not blink, nor does PD6 change.

The reason, I believe, is in the 2nd case the __vectors table, and some other things doesn't end up in the binary. I can see this with avr-objdump -d ledblink. When I compile and link in one step, as in the first case:

ledblink:     file format elf32-avr


Disassembly of section .text:

00000000 <__vectors>:
   0:   19 c0           rjmp    .+50        ; 0x34 <__ctors_end>
   2:   20 c0           rjmp    .+64        ; 0x44 <__bad_interrupt>
   4:   1f c0           rjmp    .+62        ; 0x44 <__bad_interrupt>
   6:   1e c0           rjmp    .+60        ; 0x44 <__bad_interrupt>
   8:   1d c0           rjmp    .+58        ; 0x44 <__bad_interrupt>
   a:   1c c0           rjmp    .+56        ; 0x44 <__bad_interrupt>
   c:   1b c0           rjmp    .+54        ; 0x44 <__bad_interrupt>
   e:   1a c0           rjmp    .+52        ; 0x44 <__bad_interrupt>
  10:   19 c0           rjmp    .+50        ; 0x44 <__bad_interrupt>
  12:   18 c0           rjmp    .+48        ; 0x44 <__bad_interrupt>
  14:   17 c0           rjmp    .+46        ; 0x44 <__bad_interrupt>
  16:   16 c0           rjmp    .+44        ; 0x44 <__bad_interrupt>
  18:   15 c0           rjmp    .+42        ; 0x44 <__bad_interrupt>
  1a:   14 c0           rjmp    .+40        ; 0x44 <__bad_interrupt>
  1c:   14 c0           rjmp    .+40        ; 0x46 <__vector_14>
  1e:   12 c0           rjmp    .+36        ; 0x44 <__bad_interrupt>
  20:   11 c0           rjmp    .+34        ; 0x44 <__bad_interrupt>
  22:   10 c0           rjmp    .+32        ; 0x44 <__bad_interrupt>
  24:   0f c0           rjmp    .+30        ; 0x44 <__bad_interrupt>
  26:   0e c0           rjmp    .+28        ; 0x44 <__bad_interrupt>
  28:   0d c0           rjmp    .+26        ; 0x44 <__bad_interrupt>
  2a:   0c c0           rjmp    .+24        ; 0x44 <__bad_interrupt>
  2c:   0b c0           rjmp    .+22        ; 0x44 <__bad_interrupt>
  2e:   0a c0           rjmp    .+20        ; 0x44 <__bad_interrupt>
  30:   09 c0           rjmp    .+18        ; 0x44 <__bad_interrupt>
  32:   08 c0           rjmp    .+16        ; 0x44 <__bad_interrupt>

00000034 <__ctors_end>:
  34:   11 24           eor r1, r1
  36:   1f be           out 0x3f, r1    ; 63
  38:   cf ef           ldi r28, 0xFF   ; 255
  3a:   d2 e0           ldi r29, 0x02   ; 2
  3c:   de bf           out 0x3e, r29   ; 62
  3e:   cd bf           out 0x3d, r28   ; 61
  40:   14 d0           rcall   .+40        ; 0x6a <main>
  42:   34 c0           rjmp    .+104       ; 0xac <_exit>

00000044 <__bad_interrupt>:
  44:   dd cf           rjmp    .-70        ; 0x0 <__vectors>

00000046 <__vector_14>:
  46:   1f 92           push    r1
  48:   0f 92           push    r0
  4a:   0f b6           in  r0, 0x3f    ; 63
  4c:   0f 92           push    r0
  4e:   11 24           eor r1, r1
  50:   8f 93           push    r24
  52:   5e 9a           sbi 0x0b, 6 ; 11
  54:   8a e1           ldi r24, 0x1A   ; 26
  56:   8a 95           dec r24
  58:   f1 f7           brne    .-4         ; 0x56 <__vector_14+0x10>
  5a:   00 c0           rjmp    .+0         ; 0x5c <__vector_14+0x16>
  5c:   5e 98           cbi 0x0b, 6 ; 11
  5e:   8f 91           pop r24
  60:   0f 90           pop r0
  62:   0f be           out 0x3f, r0    ; 63
  64:   0f 90           pop r0
  66:   1f 90           pop r1
  68:   18 95           reti

0000006a <main>:
  6a:   80 e4           ldi r24, 0x40   ; 64
  6c:   8a b9           out 0x0a, r24   ; 10
  6e:   81 e0           ldi r24, 0x01   ; 1
  70:   84 b9           out 0x04, r24   ; 4
  72:   82 e0           ldi r24, 0x02   ; 2
  74:   84 bd           out 0x24, r24   ; 36
  76:   85 bd           out 0x25, r24   ; 37
  78:   80 93 6e 00     sts 0x006E, r24 ; 0x80006e <__EEPROM_REGION_LENGTH__+0x7f006e>
  7c:   89 e2           ldi r24, 0x29   ; 41
  7e:   87 bd           out 0x27, r24   ; 39
  80:   78 94           sei
  82:   28 9a           sbi 0x05, 0 ; 5
  84:   2f ef           ldi r18, 0xFF   ; 255
  86:   81 ee           ldi r24, 0xE1   ; 225
  88:   94 e0           ldi r25, 0x04   ; 4
  8a:   21 50           subi    r18, 0x01   ; 1
  8c:   80 40           sbci    r24, 0x00   ; 0
  8e:   90 40           sbci    r25, 0x00   ; 0
  90:   e1 f7           brne    .-8         ; 0x8a <main+0x20>
  92:   00 c0           rjmp    .+0         ; 0x94 <main+0x2a>
  94:   00 00           nop
  96:   28 98           cbi 0x05, 0 ; 5
  98:   2f ef           ldi r18, 0xFF   ; 255
  9a:   81 ee           ldi r24, 0xE1   ; 225
  9c:   94 e0           ldi r25, 0x04   ; 4
  9e:   21 50           subi    r18, 0x01   ; 1
  a0:   80 40           sbci    r24, 0x00   ; 0
  a2:   90 40           sbci    r25, 0x00   ; 0
  a4:   e1 f7           brne    .-8         ; 0x9e <main+0x34>
  a6:   00 c0           rjmp    .+0         ; 0xa8 <main+0x3e>
  a8:   00 00           nop
  aa:   eb cf           rjmp    .-42        ; 0x82 <main+0x18>

000000ac <_exit>:
  ac:   f8 94           cli

000000ae <__stop_program>:
  ae:   ff cf           rjmp    .-2         ; 0xae <__stop_program>

But compiling to an object file, then linking in a second step I get:

ledblink:     file format elf32-avr


Disassembly of section .text:

00000000 <__vector_14>:
   0:   1f 92           push    r1
   2:   0f 92           push    r0
   4:   0f b6           in  r0, 0x3f    ; 63
   6:   0f 92           push    r0
   8:   11 24           eor r1, r1
   a:   8f 93           push    r24
   c:   5e 9a           sbi 0x0b, 6 ; 11
   e:   8a e1           ldi r24, 0x1A   ; 26
  10:   8a 95           dec r24
  12:   f1 f7           brne    .-4         ; 0x10 <__zero_reg__+0xf>
  14:   00 c0           rjmp    .+0         ; 0x16 <__zero_reg__+0x15>
  16:   5e 98           cbi 0x0b, 6 ; 11
  18:   8f 91           pop r24
  1a:   0f 90           pop r0
  1c:   0f be           out 0x3f, r0    ; 63
  1e:   0f 90           pop r0
  20:   1f 90           pop r1
  22:   18 95           reti

00000024 <main>:
  24:   80 e4           ldi r24, 0x40   ; 64
  26:   8a b9           out 0x0a, r24   ; 10
  28:   81 e0           ldi r24, 0x01   ; 1
  2a:   84 b9           out 0x04, r24   ; 4
  2c:   82 e0           ldi r24, 0x02   ; 2
  2e:   84 bd           out 0x24, r24   ; 36
  30:   85 bd           out 0x25, r24   ; 37
  32:   80 93 6e 00     sts 0x006E, r24 ; 0x80006e <_edata+0xe>
  36:   89 e2           ldi r24, 0x29   ; 41
  38:   87 bd           out 0x27, r24   ; 39
  3a:   78 94           sei
  3c:   28 9a           sbi 0x05, 0 ; 5
  3e:   2f ef           ldi r18, 0xFF   ; 255
  40:   81 ee           ldi r24, 0xE1   ; 225
  42:   94 e0           ldi r25, 0x04   ; 4
  44:   21 50           subi    r18, 0x01   ; 1
  46:   80 40           sbci    r24, 0x00   ; 0
  48:   90 40           sbci    r25, 0x00   ; 0
  4a:   e1 f7           brne    .-8         ; 0x44 <__SREG__+0x5>
  4c:   00 c0           rjmp    .+0         ; 0x4e <__SREG__+0xf>
  4e:   00 00           nop
  50:   28 98           cbi 0x05, 0 ; 5
  52:   2f ef           ldi r18, 0xFF   ; 255
  54:   81 ee           ldi r24, 0xE1   ; 225
  56:   94 e0           ldi r25, 0x04   ; 4
  58:   21 50           subi    r18, 0x01   ; 1
  5a:   80 40           sbci    r24, 0x00   ; 0
  5c:   90 40           sbci    r25, 0x00   ; 0
  5e:   e1 f7           brne    .-8         ; 0x58 <__SREG__+0x19>
  60:   00 c0           rjmp    .+0         ; 0x62 <__SREG__+0x23>
  62:   00 00           nop
  64:   eb cf           rjmp    .-42        ; 0x3c <main+0x18>

Note how __vectors is missing, and at address 0 there is only the ISR. I've never actually read the datasheet in enough detail to confirm, but I guess this means the MCU starts execution at some garbage address, so I get garbage behavior.

How can I build a working hex file from a set of intermediate object files?

\$\endgroup\$
2
  • \$\begingroup\$ One thing to note is that you're depending on the toolchain to provide an appropriate vector table and linker map for the hardware, because you do not provide your own. When you compile and link in a single step, the knowledge of which AVR you are using would make it to whatever selects the built in library pieces that provide those. But when you link discretely there's no way for the information as to which are appropriate to get through - that wouldn't be the object from your singular compilation unit. I'm guessing you need to then explicitly specify them to the linker... \$\endgroup\$ May 4, 2020 at 2:59
  • \$\begingroup\$ This suggests about what I was suspecting with more detail: the -m flag to the compiler also ultimately tells it which libc and libm to link by setting the appropriate linker path: avrfreaks.net/comment/1900311#comment-1900311 hopefully a useful direction to look though not yet quite an "answer" \$\endgroup\$ May 4, 2020 at 3:17

2 Answers 2

3
\$\begingroup\$

The -mmcu=atmega48p is required at the link step as well as the compile step. So this:

avr-gcc ledblink.o -o ledblink

should become this:

avr-gcc -mmcu=atmega48p ledblink.o -o ledblink

This option results in the linker reading the device spec file (/usr/lib/gcc/avr/5.4.0/device-specs/specs-atmega48p on my Debian system) which contains all kinds of settings, but in particular:

*avrlibc_startfile:
        crtatmega48p.o%s

This object file contains __vectors as well as some of the other missing parts:

/usr/lib/avr/lib/avr4/crtatmega48p.o:     file format elf32-avr


Disassembly of section .text:

00000000 <__bad_interrupt>:
   0:   00 c0           rjmp    .+0         ; 0x2 <__bad_interrupt+0x2>

Disassembly of section .vectors:

00000000 <__vectors>:
   0:   00 c0           rjmp    .+0         ; 0x2 <__vectors+0x2>
   2:   00 c0           rjmp    .+0         ; 0x4 <__vectors+0x4>
   4:   00 c0           rjmp    .+0         ; 0x6 <__vectors+0x6>
   6:   00 c0           rjmp    .+0         ; 0x8 <__vectors+0x8>
   8:   00 c0           rjmp    .+0         ; 0xa <__vectors+0xa>
   a:   00 c0           rjmp    .+0         ; 0xc <__vectors+0xc>
   c:   00 c0           rjmp    .+0         ; 0xe <__vectors+0xe>
   e:   00 c0           rjmp    .+0         ; 0x10 <__vectors+0x10>
  10:   00 c0           rjmp    .+0         ; 0x12 <__vectors+0x12>
  12:   00 c0           rjmp    .+0         ; 0x14 <__vectors+0x14>
  14:   00 c0           rjmp    .+0         ; 0x16 <__vectors+0x16>
  16:   00 c0           rjmp    .+0         ; 0x18 <__vectors+0x18>
  18:   00 c0           rjmp    .+0         ; 0x1a <__vectors+0x1a>
  1a:   00 c0           rjmp    .+0         ; 0x1c <__vectors+0x1c>
  1c:   00 c0           rjmp    .+0         ; 0x1e <__vectors+0x1e>
  1e:   00 c0           rjmp    .+0         ; 0x20 <__vectors+0x20>
  20:   00 c0           rjmp    .+0         ; 0x22 <__vectors+0x22>
  22:   00 c0           rjmp    .+0         ; 0x24 <__vectors+0x24>
  24:   00 c0           rjmp    .+0         ; 0x26 <__vectors+0x26>
  26:   00 c0           rjmp    .+0         ; 0x28 <__vectors+0x28>
  28:   00 c0           rjmp    .+0         ; 0x2a <__vectors+0x2a>
  2a:   00 c0           rjmp    .+0         ; 0x2c <__vectors+0x2c>
  2c:   00 c0           rjmp    .+0         ; 0x2e <__vectors+0x2e>
  2e:   00 c0           rjmp    .+0         ; 0x30 <__vectors+0x30>
  30:   00 c0           rjmp    .+0         ; 0x32 <__vectors+0x32>
  32:   00 c0           rjmp    .+0         ; 0x34 <__FUSE_REGION_LENGTH__+0x31>

Disassembly of section .init2:

00000000 <.init2>:
   0:   11 24           eor r1, r1
   2:   1f be           out 0x3f, r1    ; 63
   4:   c0 e0           ldi r28, 0x00   ; 0
   6:   d0 e0           ldi r29, 0x00   ; 0
   8:   de bf           out 0x3e, r29   ; 62
   a:   cd bf           out 0x3d, r28   ; 61

Disassembly of section .init9:

00000000 <.init9>:
   0:   00 d0           rcall   .+0         ; 0x2 <.init9+0x2>
   2:   00 c0           rjmp    .+0         ; 0x4 <__FUSE_REGION_LENGTH__+0x1>

There are also linker scripts in /usr/lib/avr/lib/ldscripts which instruct the linker to do things like put the .vectors section (from crtatmega48p.o) into the .text section so they end up in the hex file, and so on. Without the -mmcu option, the linker will default to avr2 which is just going to be wrong for most cases.

\$\endgroup\$
1
\$\begingroup\$

When linking, at least you should provide for which AVR you are linking, so the linker knows to include the correct startup library. See the avr-gcc example projects that have multiple source files or a makefile. Basically, when linking, give the same cpu string that you already use when compiling.

\$\endgroup\$
1
  • \$\begingroup\$ I believe this is the correct answer, but it would be more useful if it was more specific and self-contained. \$\endgroup\$
    – Phil Frost
    May 4, 2020 at 4:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.