Question

I'm trying to write some assembly language for Arduino Duemilanove (AVR ATmega328P). Learning assembly language jointly in parallel with compiling and disassembling C code, I have got this:

(Compiled with AVR_GCC)

int main() {
  volatile int a = 0;
  while (1) {
    ++a;
  }
  return 0;
}

Which turns into

00000000 <__vectors>:
   0: 0c 94 34 00   jmp 0x68  ; 0x68 <__ctors_end>
   4: 0c 94 51 00   jmp 0xa2  ; 0xa2 <__bad_interrupt>
  ...
  64: 0c 94 51 00   jmp 0xa2  ; 0xa2 <__bad_interrupt>

00000068 <__ctors_end>:
  68: 11 24         eor r1, r1
  6a: 1f be         out 0x3f, r1  ; 63
  6c: cf ef         ldi r28, 0xFF ; 255
  6e: d8 e0         ldi r29, 0x08 ; 8
  70: de bf         out 0x3e, r29 ; 62
  72: cd bf         out 0x3d, r28 ; 61

00000074 <__do_copy_data>:
  74: 11 e0         ldi r17, 0x01 ; 1
  76: a0 e0         ldi r26, 0x00 ; 0
  78: b1 e0         ldi r27, 0x01 ; 1
  7a: e4 ec         ldi r30, 0xC4 ; 196
  7c: f0 e0         ldi r31, 0x00 ; 0
  7e: 02 c0         rjmp  .+4       ; 0x84 <__do_copy_data+0x10>
  80: 05 90         lpm r0, Z+
  82: 0d 92         st  X+, r0
  84: a0 30         cpi r26, 0x00 ; 0
  86: b1 07         cpc r27, r17
  88: d9 f7         brne  .-10      ; 0x80 <__do_copy_data+0xc>

0000008a <__do_clear_bss>:
  8a: 11 e0         ldi r17, 0x01 ; 1
  8c: a0 e0         ldi r26, 0x00 ; 0
  8e: b1 e0         ldi r27, 0x01 ; 1
  90: 01 c0         rjmp  .+2       ; 0x94 <.do_clear_bss_start>

00000092 <.do_clear_bss_loop>:
  92: 1d 92         st  X+, r1

00000094 <.do_clear_bss_start>:
  94: a0 30         cpi r26, 0x00 ; 0
  96: b1 07         cpc r27, r17
  98: e1 f7         brne  .-8       ; 0x92 <.do_clear_bss_loop>
  9a: 0e 94 53 00   call  0xa6  ; 0xa6 <main>
  9e: 0c 94 60 00   jmp 0xc0  ; 0xc0 <_exit>

000000a2 <__bad_interrupt>:
  a2: 0c 94 00 00   jmp 0 ; 0x0 <__vectors>

000000a6 <main>:
  a6: cf 93         push  r28
  a8: df 93         push  r29
  aa: 00 d0         rcall .+0       ; 0xac <main+0x6>
  ac: cd b7         in  r28, 0x3d ; 61
  ae: de b7         in  r29, 0x3e ; 62
  b0: 1a 82         std Y+2, r1 ; 0x02
  b2: 19 82         std Y+1, r1 ; 0x01
  b4: 89 81         ldd r24, Y+1  ; 0x01
  b6: 9a 81         ldd r25, Y+2  ; 0x02
  b8: 01 96         adiw  r24, 0x01 ; 1
  ba: 9a 83         std Y+2, r25  ; 0x02
  bc: 89 83         std Y+1, r24  ; 0x01
  be: fa cf         rjmp  .-12      ; 0xb4 <main+0xe>

000000c0 <_exit>:
  c0: f8 94         cli

000000c2 <__stop_program>:
  c2: ff cf         rjmp  .-2       ; 0xc2 <__stop_program>

I tried to understand a few things:

  1. What is the .-8 or alike syntax? (address 0x98 or 0xAA for instance.)
  2. Around lines with address 80 to 88 (end of __do_copy_data) there are some funny things. It seems to me that this loads all the program code into RAM, from address 0xC4. Why?
  3. In __do_clear_bss_start/loop, we clear all the work we have just done by setting bytes in the RAM to 0 (value of r1). Why? All this to finally call main. Any general explanations?
  4. Why doesn't disasembling show .bss, .rodata or other sections?
  5. Line 6a, why is SREG cleared? Isn't it set to what it should be after every instruction?
  6. Lines 6c and 6e: what do 0xFF and 0x08 correspond to? r28 and r29 are the stack pointer low and high.
  7. I played a bit and added a static global variable. Why do we store in RAM starting from 0x0100 and not 0x0000?
  8. At line 8a, why ldi r17, 1? We did that before (just a stupid remark). Or can something else alter r17?
  9. We start copying the program in flash to the RAM, starting at 0xC4 (.bss and other sections I guess), but the cpi/cpc of X with regard to 1 will make ALL the flash copied into all the RAM. Is it just by laziness of the compiler to not stop copying when .bss sections are done copying?
Was it helpful?

Solution

The dot/period is used as a shortcut to indicate this instruction's address or location or something relative to that. .+8 means from here plus 8. You have to account for the nuances of the instruction set and/or assembler relative to the instruction set. As the additional information from the assembler indicates, the .-8 is going to do_clear_bss_loop which is eight bytes back including the two bytes for the instruction itself. The original code probably just had the label in there, brne do_clear_bss_loop.

It is likely copying the data segment; .text is basically read-only. It is your code and it wants to live in flash on this platform. .data, though, is read/write and usually initialized to non-zero values. So with the power off, your initial values need to be preserved somewhere, in flash for example, but before you start your real program the bootstrap will need to copy the initial .data segment values from flash to their actual home in RAM. Then as the program runs, it can read and/or modify those values as desired.

For example:

int x = 5;

main ()
{
    x = x + 1;
}

That value 5 has to be in flash in order to start from power up only using flash to hold non-volatile information. But before you can read/write the memory location for x you need it in RAM, so some startup code copies all of the .data sgement stuff from flash to RAM.

Sorry for that long explanation for something that is only a guess looking at your question.

.bss are variables in your program that are initialized to zero. With the .data segment, if we had 100 items we would need 100 things in flash. But with .bss if we have 100 items we only need to tell someone that there are 100 items. We don't need 100 zeros in flash, just compile/assemble it into the code.

So

int x = 5;
int y;

int main ()
{
    while(1)
    {
        y = y + x + 1;
    }
}

x is in .data and the 5 needs to be in non-volatile storage. The y is in .bss and only needs to be zeroed before main is called to comply with the C standard.

Granted, you may not be using global variables yourself, but there may be other data that is in some way using the .data and/or .bss segments and as a result the bootstrap code prepares the .data and .bss segments before calling main() so that your C programming experience is as expected.

OTHER TIPS

I realize this is a late answer. However, I still think it may be interesting to have a detailed point-by-point answer to all the questions.

  1. What is the .-8 or alike syntax? (address 0x98 or 0xAA for instance.)

It means: "jump back 8 bytes from here". Beware that the program counter has already been incremented by the length of the instruction (2 bytes), thus brne .-8 will move you 6 bytes (not 8) prior to the brne instruction itself. In the same vein, rcall .+0 will push the program counter to the stack without altering the program flow. This is a trick only intended to reserve two bytes of stack space in a single instruction.

  1. Around lines with address 80 to 88 (end of __do_copy_data) there are some funny things. It seems to me that this loads all the program code into RAM, from address 0xC4. Why?

No, nothing is copied, this is an empty loop. On lines 84 to 88 there is a test that exits the loop when the pointer X (r27:r26) equals 0x0100. Since X is initialized to 0x0100, this will not loop at all.

This loop is intended to copy the data section from flash to RAM. It does basically something like this:

X = DATA_START;  // RAM address
Z = 0x00C4;      // Flash address
while (X != DATA_START + DATA_SIZE)
    ram[X++] = flash[Z++];

but your program happens to have an empty data section (DATA_SIZE == 0 in the above pseudo-code).

Also, you should note that your program ends at address 0x00c3, thus the Z pointer is initialized to point right after the program code. This is where the initial values of the initialized variables are supposed to be.

  1. In __do_clear_bss_start/loop, we clear all the work we have just done by setting bytes in the RAM to 0 (value of r1). Why? All this to finally call main. Any general explanations?

No, nothing will be overwritten. This loop clears the BSS, which normally comes right after the data section, with no overlap. Pseudocode:

X = BSS_START;
while (X != BSS_START + BSS_SIZE)
    ram[X++] = 0;

where BSS_START == DATA_START + DATA_SIZE. This is also an empty loop in your program because you have an empty bss.

  1. Why doesn't disasembling show .bss, .rodata or other sections?

Because objdump -d only disassembles the sections expected to hold code.

  1. Line 6a, why is SREG cleared? Isn't it set to what it should be after every instruction?

Most instructions only alter some bits of SREG. Also, this clears the global interrupt enable bit.

  1. Lines 6c and 6e: what do 0xFF and 0x08 correspond to? r28 and r29 are the stack pointer low and high.

The stack pointer is loaded with 0x08ff, which is the last RAM location in the ATmega328P. The stack will grow downwards from there.

  1. I played a bit and added a static global variable. Why do we store in RAM starting from 0x0100 and not 0x0000?

RAM is at 0x0100–0x08ff on the 328P. Below this address you have some memory-mapped registers (the CPU registers and the I/O registers). Check the datasheet for details, section "8.3 SRAM Data Memory".

  1. At line 8a, why ldi r17, 1? We did that before (just a stupid remark). Or can something else alter r17?

Line 8a is useless. It is here because of the way the linker builds the program by gluing together different pieces: __do_copy_data and __do_clear_bss are independent routines, they do not rely on whatever the other left in the registers.

  1. We start copying the program in flash to the RAM, starting at 0xC4 (.bss and other sections I guess), but the cpi/cpc of X with regard to 1 will make ALL the flash copied into all the RAM. Is it just by laziness of the compiler to not stop copying when .bss sections are done copying?

You misunderstood this part of the code. The cpi, cpc and brne instructions will loop only as long as X is different from r17:0x00 (i.e. 0x0100, since r17 = 1). C.f. the pseudo-codes above.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top