Прохождение кода AVR ассемблера "hello world"

Я пытаюсь написать некоторый язык сборки для Arduino Duemilanove (AVR схема ATmega328P). Изучая ассемблер совместно параллельно с компиляцией и разборкой кода C, я получил следующее:

(составлено с AVR_GCC)

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

превращается в

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>

Я попытался понять несколько вещей:

  1. что такое .-8 или синтаксис? (адрес 0x98 или 0xAA для пример.)
  2. вокруг строк с адресом 80 до 88 (конец __do_copy_data) есть некоторые забавные вещи. Мне кажется, что это загружает весь программный код в RAM, адрес 0xC4. Почему?
  3. в _ _ do _ clear_bss_start / loop мы очищаем всю работу, которую мы только что сделали, установив байты в ОЗУ на 0 (значение r1). Почему? Все это, наконец, называют main. Какие-нибудь общие объяснения?
  4. почему не disasembling показать .ОНБ. ,rodata или других секции?
  5. строка 6a, почему sreg очищен? Разве он не установлен на то, что он должен быть после каждой инструкции?
  6. строки 6c и 6e: чему соответствуют 0xFF и 0x08? R28 и r29-указатель стека низкий и высокий.
  7. я немного поиграл и добавил статическую глобальную переменную. Почему мы храним в ОЗУ, начиная с 0x0100, а не 0x0000?
  8. в строке 8а, зачем ldi r17, 1? Мы делали это раньше (просто глупое замечание). Или что-то еще может измениться? П17?
  9. мы начинаем копировать программу во flash в ОЗУ, начиная с 0xC4 (.BSS и другие разделы, я думаю), но cpi / cpc X относительно 1 сделает всю вспышку скопированной во всю ОЗУ. Это просто лень компилятора, чтобы не останавливать копирование, когда .БСС разделах копирования?

2 ответов


точка / точка используется в качестве ярлыка для указания адреса или местоположения этой инструкции или что-то относительно этого. .+8 означает отсюда плюс 8. Вы должны учитывать нюансы набора инструкций и / или ассемблера относительно набора инструкций. Как указывает дополнительная информация от ассемблера,-8 собирается do_clear_bss_loop который составляет восемь байтов назад, включая два байта для самой инструкции. В исходном коде, вероятно, просто была этикетка., brne do_clear_bss_loop.

вероятно копирование сегмента данных;.text в основном только для чтения. Это ваш код, и он хочет жить во flash на этой платформе. .data, однако, считывается / записывается и обычно инициализируется ненулевыми значениями. Таким образом, при отключении питания ваши начальные значения должны быть сохранены где-то, например, во flash, но перед запуском вашей реальной программы бутстрэпу нужно будет скопировать начальное значение .значения сегмента данных от flash до их фактического дома в оперативной памяти. Затем, когда программа запускается, она может читать и/или изменять эти значения по желанию.

например:

int x = 5;

main ()
{
    x = x + 1;
}

это значение 5 должно быть во вспышке, чтобы начать с включения питания только с помощью вспышки для хранения энергонезависимой информации. Но прежде чем вы сможете прочитать / записать местоположение памяти для x, вам нужно это в ОЗУ, поэтому некоторый код запуска копирует все .data sgement материал от вспышки до ОЗУ.

извините за это длинное объяснение чего-то это только догадка, глядя на ваш вопрос.

.bss переменные в вашей программе, которые инициализируются к нулю. С .data сегмент, если бы у нас было 100 предметов, нам понадобилось бы 100 вещей во вспышке. Но с .bss если у нас есть 100 пунктов, нужно только сказать кому-то, что есть 100 пунктов. Нам не нужно 100 нулей во flash, просто скомпилируйте / соберите его в код.

так

int x = 5;
int y;

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

x находится в .data и 5 должны быть в энергонезависимой место хранения. В г в .bss и только нужно быть обнуленным перед главным образом вызвано для того чтобы исполнить с стандартом C.

конечно, вы не можете использовать сами глобальные переменные, но могут быть и другие данные, которые каким-то образом с помощью .data и/или .bss сегменты и в результате код начальной загрузки подготавливает .data и .bss сегментов перед вызовом main() так что ваш опыт программирования на C, как и ожидалось.


Я понимаю, что это поздний ответ. Тем не менее, я все еще думаю, что может быть интересно получить подробный ответ по пунктам на все вопросы.

  1. Что такое .-8 или синтаксис? (адрес 0x98 или как 0xaa например.)

это означает: "вернитесь назад на 8 байт отсюда". Остерегайтесь, что счетчик программы уже был увеличен на длину инструкции (2 байта), таким образом brne .-8 переместит вас 6 байт (не 8) до самой инструкции brne. В том же духе rcall .+0 будет толкать счетчик программы в стек без изменения потока программы. Это трюк, предназначенный только для резервирования двух байтов пространства стека в одной инструкции.

  1. вокруг строк с адресом 80 до 88 (конец __do_copy_data) есть некоторые забавные вещи. Мне кажется, что это загружает весь программный код в ОЗУ, с адреса 0xC4. Почему?

нет, ничего не копируется, это пустой цикл. В строках 84-88 есть тест, который выходит из цикла, когда указатель X (r27:r26) равен 0x0100. Поскольку X инициализируется 0x0100, это не будет циклическим вообще.

этот цикл предназначен для копирования раздела данных из flash в RAM. Это в основном что-то вроде этого:

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

но в вашей программе есть пустой раздел данных (DATA_SIZE == 0 в приведенном выше псевдокоде).

кроме того, вы должны отметить, что ваш программа заканчивается по адресу 0x00c3, таким образом, указатель Z инициализируется в точку сразу после программный код. Здесь должны быть начальные значения инициализированных переменных.

  1. в _ _ do _ clear_bss_start / loop мы очищаем всю работу, которую мы только что сделали, установив байты в ОЗУ на 0 (значение r1). Почему? Все это, наконец, называют main. Какие-нибудь общие объяснения?

нет, ничего не будет перезаписанный. Этот цикл очищает BSS, который обычно приходит сразу после раздела данных, без перекрытия. Псевдокод:

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

здесь BSS_START == DATA_START + DATA_SIZE. Это также пустой цикл в вашей программе, потому что у вас есть пустой bss.

  1. почему не disasembling показать .ОНБ. ,родата или другие разделы?

, потому что objdump -d только разбирает разделы, которые должны содержать код.

  1. строка 6a, почему sreg очищен? Разве он не установлен на то, что он должен быть после каждой инструкции?

большинство инструкций только alter некоторые биты SREG. Кроме того, это очищает бит global interrupt enable.

  1. строки 6c и 6e: чему соответствуют 0xFF и 0x08? R28 и r29-указатель стека низкий и высокий.

указатель стека загружен 0x08ff, который является последним местоположением ОЗУ в ATmega328P. Стек будет расти вниз оттуда.

  1. я немного поиграл и добавил статическую глобальную переменную. Почему мы храним в ОЗУ, начиная с 0x0100, а не 0x0000?

ОЗУ находится в 0x0100-0x08ff на 328P. Под этим адресом у вас есть некоторые регистры, сопоставленные с памятью (регистры CPU и регистры ввода-вывода). Проверка техническое описание подробнее в разделе " 8.3 SRAM Data Память."

  1. в строке 8а, зачем ldi r17, 1? Мы делали это раньше (просто глупое замечание). Или что-то еще может изменить r17?

строка 8a бесполезна. Это здесь из-за того, как компоновщик строит программу, склеивая разные части: __do_copy_data и __do_clear_bss являются независимыми подпрограммами, они не полагаются на то, что осталось в регистрах.

  1. мы начинаем копировать программу в flash памяти в ОЗУ, начиная с 0xC4 (.BSS и другие разделы, я думаю), но cpi / cpc X относительно 1 сделает всю вспышку скопированной во всю ОЗУ. Это просто лень компилятора, чтобы не останавливать копирование, когда .БСС разделах копирования?

вы неправильно поняли эту часть кода. Инструкции cpi, cpc и brne будут цикл только до тех пор, пока X отличается от r17:0x00 (т. е. 0x0100, так как r17 = 1). C. f. псевдо-коды выше.