Transition through the AVR assembler "hello world" code

I am trying to write an assembly language for Arduino Duemilanove (AVR ATmega328P ). While learning assembly language along with compiling and disassembling C code, I got the following:

(Compiled with AVR_GCC )

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

What 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:

  • What is.-8 syntax or similar? (e.g. address 0x98 or 0xAA.)
  • There are some funny things around the lines with addresses from 80 to 88 (end of __do_copy_data). It seems to me that it loads all the program code into RAM , from the address 0xC4. Why?
  • In the __do_clear_bss_start / loop, we clear all the work we just did by setting the bytes in RAM to 0 (value r1). What for? All of this will finally call main . Any general explanations?
  • Why not unmount show.bss, .rodata, or other sections?
  • Line 6a, why is SREG cleared? Isn't he tuned to what should be after each instruction?
  • Lines 6c and 6e: what correspond to 0xFF and 0x08? r28 and r29 are the stack pointer, low and high.
  • I played a little and added a static global variable. Why do we store in RAM starting with 0x0100, and not 0x0000?
  • On line 8a, why ldi r17, 1 ? We did this before (just a stupid remark). Or can something else change r17?
  • We start copying the program into flash memory in RAM, starting with 0xC4 (.bss and other sections, I think), but cpi / cpc X with respect to 1 will make ALL flash memory copied to the entire RAM. Is it just too lazy for the compiler not to stop copying when .bss sections finish copying?
+7
source share
2 answers

A point / period is used as a shortcut to indicate this address or location of the instruction or something relative to it .. + 8 means plus 8. Here you must take into account the nuances of the instruction set and / or assembler relative to the instruction set. As additional information from the assembler indicates. -8 goes to do_clear_bss_loop , which contains eight bytes, including two bytes for the instruction itself. In the source code, there was probably only a shortcut brne do_clear_bss_loop .

Most likely copying a data segment; .text is mostly read-only. This is your code, and he wants to live on a flash drive on this platform. .data , however, is read / written and is usually initialized with non-zero values. Thus, when you turn off the power, your initial values ​​should be stored somewhere, for example, in flash memory, but before you start your real program, bootstrap will need to copy the initial values ​​of the .data segment from flash to their actual home in RAM Then, when the program starts, it can read and / or change these values ​​as desired.

For example:

 int x = 5; main () { x = x + 1; } 

This value of 5 must be in the flash in order to start by turning on the power using only flash memory to store non-volatile information. But before you can read / write the memory location for x, you need this in RAM, so some startup code copies all the .data sgement elements from the flash memory to RAM.

Sorry for this long explanation of something that is just 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 elements, 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 do not need 100 zeros in the flash, just compile / copy them into code.

So,

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

x is in .data , and 5 is in non-volatile memory. Y is in .bss and needs to be reset only before main is invoked according to the C standard.

Of course, you cannot use global variables yourself, but there may be other data that is somehow used in the .data and / or .bss segments, and as a result, the bootstrap code prepares .data and .bss before calling main() , so that your C programming experience is as expected.

+3
source

I understand that this is a late answer. However, I still think it might be interesting to have a detailed step-by-step answer to all the questions.

  • What is.-8 syntax or similar? (e.g. address 0x98 or 0xAA.)

This means: "jump from here 8 bytes." Remember that the program counter has already been increased by the length of the instruction (2 bytes), so brne .-8 move you 6 bytes (not 8) to the brne command itself. In the same vein, rcall .+0 will call the program counter rcall .+0 stack without changing the program stream. This is a trick designed only to reserve two bytes of stack space in one command.

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

No, nothing is copied, this is an empty loop. Lines 84 through 88 have a test that exits the loop when the X pointer (r27: r26) is 0x0100. Since X is initialized to 0x0100, this will not loop completely.

This loop is for copying a section of data from flash memory to RAM. This basically happens like this:

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

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

In addition, you should note that your program ends at 0x00c3, so the Z pointer is initialized to indicate immediately after the program code. Here should be the initial values ​​of the initialized variables.

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

No, nothing will be overwritten. This loop clears the BSS, which usually arrives immediately after the data partition without overlapping. 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 not unmount show.bss, .rodata, or other sections?

Because objdump -d only parses sections that expect code to be saved.

  1. Line 6a, why is SREG cleared? Isn't he tuned to what should be after each instruction?

Most instructions change only some of the SREG bits. In addition, it clears the enable global interrupt bit.

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

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

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

RAM is at 0x0100-0x08ff at 328P. Below this address you have memory mapping registers (CPU registers and I / O registers). For more details see the technical description , section "8.3 SRAM data memory".

  1. On line 8a, why ldi r17, 1 ? We did this before (just a stupid remark). Or can something else change r17?

Line 8a is useless. It is here that, due to the way the linker creates the program by gluing the different parts __do_copy_data : __do_copy_data and __do_clear_bss are independent procedures, they do not rely on what remains in the registers.

  1. We start copying the program into flash memory in RAM, starting with 0xC4 (.bss and other sections, I think), but cpi / cpc X with respect to 1 will make ALL flash memory copied to the entire RAM. Is it just too lazy for the compiler not to stop copying when .bss sections finish copying?

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

+3
source

All Articles