1) складений двійковий файл записується на пром / флеш так. USB, serial, i2c, jtag тощо, залежать від пристрою щодо того, що підтримується цим пристроєм, незалежно від розуміння процесу завантаження.
2) Це, як правило, не стосується мікроконтролера, головним випадком використання є вказівки в режимі rom / flash та дані в операційній пам’яті. Незалежно від архітектури. для немікроконтролера, вашого ПК, ноутбука, вашого сервера, програма копіюється з енергонезалежного (дискового) в таран, а потім запустіть звідти. Деякі мікроконтролери дозволяють також використовувати таран, навіть ті, які заявляють, що вони є голодними, навіть якщо це, мабуть, порушує визначення. Немає нічого про гарвард, що не заважає вам зіставити барана в сторону інструкцій, вам просто потрібно мати механізм, щоб отримати вказівки там після підключення живлення (що порушує визначення, але системи гарвардів повинні зробити це, щоб бути корисним іншим ніж як мікроконтролери).
3) роду.
Кожен процесор "завантажується" детермінованим, як задумано, способом. Найпоширеніший спосіб - це векторна таблиця, де адреса перших інструкцій, що запускаються після включення, перебуває у векторі скидання, адреса, яку апаратне зчитування потім використовує цю адресу, щоб почати працювати. Інший загальний спосіб - почати виконання процесора без векторної таблиці за деякою відомою адресою. Іноді в мікросхемі будуть "ремінці", деякі шпильки, які ви можете зв'язати високими або низькими, перш ніж випускати скидання, які логіка використовує для завантаження різними способами. Ви повинні відокремити сам процесор, ядро процесора від решти системи. Зрозумійте, як працює процесор, а потім зрозумійте, що дизайнери мікросхем / систем мають декодери адрес установки навколо зовнішньої сторони процесора, щоб частина частини адресного простору cpus спілкувалася спалахом, а деякі - з оперативними та деякі з периферійними пристроями (uart, i2c, spi, gpio тощо). Ви можете взяти те саме ядро процесора, якщо хочете, і обгорнути його по-іншому. Це те, що ви отримуєте, купуючи щось на базі руки або мипу. рука і миші роблять процесорні ядра, які чіп люди купують і обертають своїми руками навколо, з різних причин вони не роблять ці речі сумісними від марки до марки. Ось чому рідко можна задати загальне питання про руку, якщо мова йде про що-небудь поза ядром.
Мікроконтролер намагається бути системою на мікросхемі, тому його енергонезалежна пам’ять (flash / rom), летюча (sram) та cpu - це одна і та ж мікросхема разом із сумішшю периферійних пристроїв. Але чіп розроблений внутрішньо таким чином, що спалах відображається в адресному просторі процесора, що відповідає характеристикам завантаження цього процесора. Якщо, наприклад, у процесора є вектор скидання за адресою 0xFFFC, тоді повинен бути flash / rom, який відповідає на цю адресу, яку ми можемо запрограмувати через 1), а також достатньо flash / rom в адресному просторі для корисних програм. Дизайнер мікросхем може вибрати, що 0x1000 байт спалаху починається від 0xF000, щоб задовольнити ці вимоги. І, можливо, вони ставлять якусь кількість оперативної пам'яті за нижньою адресою, а може, і 0x0000, а периферійні пристрої десь посередині.
Інша архітектура процесора може почати виконувати за адресою нуль, тому їм потрібно буде робити навпаки, розмістити спалах, щоб він відповідав діапазону адрес навколо нуля. наприклад, 0x0000 до 0x0FFF. а потім покласти десь барана в іншому місці.
Дизайнери чіпів знають, як завантажуються процесорні системи, і вони помістили туди енергонезалежне сховище (flash / rom). Тоді саме люди з програмного забезпечення повинні писати завантажувальний код, щоб відповідати загальновідомій поведінці цього процесора. Ви повинні розмістити адресу вектора скидання у векторі скидання, а ваш код завантаження за адресою, визначеною у векторі скидання. Тут дуже допоможе вам ланцюжок інструментів. Іноді, esp з ідентифікаторами точок і клацань, або іншими пісочницями, вони можуть зробити для вас більшу частину роботи, все, що ви робите, - це викликати apis мовою високого рівня (C).
Але, однак це робиться, програма, завантажена у flash / rom, повинна відповідати поведінці завантажуваного процесора з жорстким проводом. Перед частиною C у програмі main () та після, якщо ви використовуєте main як вхідну точку, деякі речі потрібно зробити. Програміст змінного струму припускає, що коли оголошують змінну з початковим значенням, вони очікують, що вона справді спрацює. Добре, що змінні, крім const, є в операційній пам’яті, але якщо у вас є початкове значення, то початкове значення повинно бути в енергонезалежному барі. Отже, це сегмент .data, і завантажувальний файл C потребує копіювання матеріалів .data з флеш-пам'яті (там, де зазвичай це визначається для вас інструментальною ланцюжком). Глобальні змінні, які ви заявляєте без початкового значення, вважаються нульовими до запуску вашої програми, хоча ви не повинні цього вважати і, на щастя, деякі компілятори починають попереджати про неініціалізовані змінні. Цей сегмент. Знову інструментальна мережа тут вам дуже допомагає. І нарешті, мінімальний мінімум - вам потрібно встановити покажчик стека, оскільки програми C очікують мати локальні змінні та викликати інші функції. Тоді, можливо, робиться якийсь інший специфічний матеріал для чіпів, або ми залишаємо, щоб решта специфічних чіпів траплялася в C. не потрібно зберігати в енергонезалежній пам'яті, але вихідна адреса і скільки робить. Знову інструментальна мережа тут вам дуже допомагає. І нарешті, мінімальний мінімум - вам потрібно встановити покажчик стека, оскільки програми C очікують мати локальні змінні та викликати інші функції. Тоді, можливо, робиться якийсь інший специфічний матеріал для чіпів, або ми залишаємо, щоб решта специфічних чіпів траплялася в C. не потрібно зберігати в енергонезалежній пам'яті, але вихідна адреса і скільки робить. Знову інструментальна мережа тут вам дуже допомагає. І нарешті, мінімальний мінімум - вам потрібно встановити покажчик стека, оскільки програми C очікують мати локальні змінні та викликати інші функції. Тоді, можливо, робиться якийсь інший специфічний матеріал для чіпів, або ми залишаємо, щоб решта специфічних чіпів траплялася в C.
Ядра серії cortex-m з руки зроблять щось для вас, вказівник стека знаходиться у векторній таблиці, є вектор скидання, який вказує на код, який слід запустити після скидання, так що крім того, що вам потрібно зробити щоб генерувати векторну таблицю (для якої ви зазвичай використовуєте ASM), ви можете перейти до чистого C без asm. тепер ви не отримуєте свої .data скопійовані ні ваш .bss нульовий, тому вам доведеться це робити самостійно, якщо ви хочете спробувати пройти без asm на чомусь на базі cortex-m. Більшою особливістю є не вектор скидання, а переривання векторів, де апаратне забезпечення дотримується зброї, рекомендованої C викликом виклику, і зберігає регістри для вас, і використовує правильне повернення для цього вектора, так що вам не доведеться обертати правильну зону навколо кожного обробника ( або мати конкретні директиви для вашої цілі, щоб ланцюжок інструментів обертав її для вас).
Наприклад, для мікросхем часто використовуються мікроконтролери в системах, що базуються на акумуляторах, тому низька потужність, тому деякі виходять із скидання, коли більшість периферійних пристроїв вимкнено, і вам потрібно ввімкнути кожну з цих підсистем, щоб ви могли їх використовувати . Uarts, gpios тощо. Часто використовується тактова частота з низьким бажанням прямо від кристала або внутрішнього генератора. І ваш дизайн системи може показати, що вам потрібен швидший годинник, тому ви ініціалізуєте це. Ваш годинник може бути занадто швидким для спалаху чи оперативної пам’яті, тому, можливо, вам знадобилося змінити стан очікування перед тим, як підняти годинник. Можливо, потрібно налаштувати uart, usb чи інші інтерфейси. тоді ваша заява може зробити своє.
Настільний комп'ютер, ноутбук, сервер та мікроконтролер не відрізняються тим, як вони завантажуються / працюють. За винятком того, що вони знаходяться в основному не на одній мікросхемі. Програма bios часто знаходиться на окремому флеш-пам’яті / rom від процесора. Хоча останнім часом x86 cpus залучає все більше і більше того, що раніше використовували чіпи підтримки в той же пакет (pcie-контролери тощо), але у вас все ще є більшість ваших оперативної пам’яті та чім-оф, але це все-таки система, і вона все ще працює точно те саме на високому рівні. Процес завантаження процесора добре відомий, дизайнери плати розміщують flash / rom у адресному просторі, де завантажується процесор. що програма (частина BIOS на x86 pc) виконує всі згадані вище речі, вона запускає різні периферійні пристрої, ініціалізує драма, перераховує шини pcie тощо. Зазвичай користувач може налаштовуватися на основі біологічних налаштувань або того, що ми звикли називати налаштуваннями cmos, тому що в той час саме використовувались технології. Неважливо, є налаштування користувача, які ви можете перейти та змінити, щоб повідомити код завантаження bios, як змінювати те, що він робить.
різні люди будуть використовувати різну термінологію. чіп-чоботи, це перший код, який працює. іноді називають завантажувальним. завантажувач із завантажувачем слів часто означає, що якщо ви нічого не робите, щоб заважати, це завантажувальний засіб, який переносить вас із загального завантаження в щось більше, вашу програму чи операційну систему. але частина завантажувача означає, що ви можете перервати процес завантаження, а потім, можливо, завантажити інші тестові програми. якщо ви коли-небудь використовували uboot, наприклад, у вбудованій системі Linux, ви можете натиснути клавішу і зупинити нормальну завантаження, тоді ви можете завантажити тестове ядро в операційний і завантажувати його замість того, яке знаходиться у спалах, або ви можете завантажити свій власні програми, або ви можете завантажити нове ядро, потім завантажувач завантажить його у спалах, щоб наступного разу при запуску він запустив новий матеріал.
Що стосується самого процесора, то основного процесора, який не знає оперативної пам'яті від спалаху з периферійних пристроїв. Немає поняття завантажувач, операційна система, додаток. Це просто послідовність інструкцій, які подаються на процесор, який потрібно виконати. Це програмні терміни, щоб відрізняти різні завдання програмування одне від одного. Поняття програмного забезпечення одне від одного.
Деякі мікроконтролери мають окремий завантажувач, наданий постачальником мікросхем в окремій спалаху або окремій області спалаху, яку ви, можливо, не зможете змінити. У цьому випадку часто є шпилька або набір штифтів (я називаю їх ремінцями), що якщо ви пов’язуєте їх високими або низькими, перш ніж скинути скидання, ви говорите логіці та / або тому завантажувачу, що робити, наприклад, одна комбінація ременів може скажіть мікросхемі запустити цей завантажувач і чекати на uart, щоб дані були запрограмовані у спалах. Встановіть ремінці іншим способом, і ваша програма завантажується не завантажувачем постачальників чіпів, що дозволяє програмувати чіп на місцях або відновлюватись після збоїв у програмі. Іноді це просто чиста логіка, яка дозволяє програмувати спалах. Це досить часто в ці дні,
Причиною, по якій у більшості мікроконтролерів є набагато більше спалаху, ніж оперативної пам'яті, є те, що основним випадком використання є запуск програми безпосередньо з спалаху, і достатньо лише оперативної пам'яті для покриття стеку та змінних. Хоча в деяких випадках ви можете запускати програми з оперативної пам’яті, які вам потрібно скласти правильно і зберігати у спалах, а потім скопіювати перед викликом.
EDIT
спалахи
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
notmain.c
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
return(0);
}
flash.ld
MEMORY
{
bob : ORIGIN = 0x00000000, LENGTH = 0x1000
ted : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.rodata : { *(.rodata*) } > bob
.bss : { *(.bss*) } > ted
.data : { *(.bss*) } > ted AT > bob
}
Отже, це приклад для cortex-m0, а cortex-ms працюють так само, наскільки це стосується цього прикладу. У цьому прикладі конкретний чіп має спалах додатка за адресою 0x00000000 в адресному просторі руки та оперативної пам’яті в 0x20000000.
Спосіб завантаження cortex-m - 32-бітове слово за адресою 0x0000 - це адреса для ініціалізації вказівника стека. Мені не потрібно багато стека для цього прикладу, так що 0x20001000 буде достатньо, очевидно, що має бути оперативна пам'ять під цією адресою (спосіб натискання на руку, спочатку віднімає потім натискає, тому якщо ви встановите 0x20001000, перший елемент стека знаходиться за адресою 0x2000FFFC вам не доведеться використовувати 0x2000FFFC). 32-бітове слово за адресою 0x0004 - це адреса обробника скидання, в основному перший код, який запускається після скидання. Потім є більше оброблювачів переривань і подій, які є специфічними для цього ядра і мікросхема cortex, можливо, аж 128 або 256, якщо ви їх не використовуєте, то вам не потрібно налаштовувати таблицю для них, я підкинув декілька для демонстрації цілей.
Мені не потрібно мати справу з .data ні .bss у цьому прикладі, тому що я знаю, що в цих сегментах нічого немає, дивлячись на код. Якби вони були, я би розібрався з цим, і буде за секунду.
Таким чином, стек - це налаштування, перевірка, .data подбали, перевірка, .bss, перевірка, так що завантажувальний файл C виконаний, може розгалужуватися до функції введення для C. Оскільки деякі компілятори додадуть додаткові непотрібні, якщо вони побачать функцію main () і на шляху до main, я не використовую точну назву, я тут не використовував notmain () як мою точку входу C. Таким чином, обробник скидання викликає notmain (), тоді, якщо / коли notmain () повертається, він переходить до зависання, що є просто нескінченним циклом, можливо, погано названим.
Я твердо вірю в оволодіння інструментами, багато хто не робить, але те, що ви знайдете, це те, що кожен розробник голого металу робить свою власну справу через майже повну свободу, а не віддалено настільки обмежений, як ви робите програми чи веб-сторінки . Вони знову роблять свою справу. Я вважаю за краще мати свій код завантажувального коду та сценарій посилання. Інші покладаються на інструментальну ланцюжок або грають у пісочниці постачальників, де більшу частину роботи робить хтось інший (і якщо щось зламаєш, ти потрапляєш у світ боляче, а з голим металом речі ламаються часто і драматично).
Тому я збираю, збираю та зв'язую з інструментами gnu:
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
То як же завантажувач знає, де речі. Тому що компілятор зробив роботу. У першому випадку асемблер генерував код для flash.s, і тим самим знає, де мітки (мітки - це просто адреси, як імена функцій, імена змінних тощо), тому мені не довелося рахувати байти та заповнювати вектор таблиці вручну, я використовував назву мітки, і асемблер зробив це для мене. Тепер ви запитуєте, якщо скиданням є адреса 0x14, чому асемблер поставив 0x15 у векторну таблицю. Ну це cortex-m, і він завантажується і працює лише в режимі великого пальця. За допомогою ARM, коли ви підключаєтесь до адреси, якщо розгалуження до режиму великого пальця, потрібно встановити lsbit, якщо режим руки потім скинути. Тож вам завжди потрібен цей набір бітів. Я знаю інструменти і, поставивши .thumb_func перед міткою, якщо ця мітка використовується такою, як є у векторній таблиці, або для розгалуження на будь-яку іншу. Мережа інструментів знає встановити lsbit. Отже, вона має тут 0x14 | 1 = 0x15. Аналогічно і для підвішування. Тепер розбиральник не показує 0x1D для виклику notmain (), але не хвилюйтесь, що інструменти правильно побудували інструкцію.
Тепер, коли код у notmain, ці локальні змінні не використовуються, вони є мертвим кодом. Компілятор навіть коментує цей факт, кажучи, що y встановлено, але не використовується.
Зверніть увагу на адресний простір, всі вони починаються з адреси 0x0000 і йдуть звідти, щоб векторна таблиця була правильно розміщена, .text або програмний простір також правильно розміщені, як я отримав flash.s перед кодом notmain.c знаючи інструменти, поширена помилка - це не виправдатися, а врізатися і сильно спалити. ІМО вам потрібно розібрати, щоб переконатися, що речі розміщені прямо перед першим завантаженням. Після того, як ви знайдете речі в потрібному місці, вам обов’язково потрібно перевіряти кожен раз. Просто для нових проектів або якщо вони висять.
Тепер те, що дивує деяких людей, - це те, що немає жодної причини очікувати, що будь-який два компілятори будуть отримувати однаковий вихід з одного входу. Або навіть той самий компілятор з різними налаштуваннями. Використовуючи clang, компілятор llvm, я отримую ці два виходи з оптимізацією та без неї
llvm / clang оптимізовано
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
не оптимізований
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: b082 sub sp, #8
1e: 2001 movs r0, #1
20: 9001 str r0, [sp, #4]
22: 2002 movs r0, #2
24: 9000 str r0, [sp, #0]
26: 2000 movs r0, #0
28: b002 add sp, #8
2a: 4770 bx lr
так що це брехня, компілятор оптимізував додавання, але він виділив два елементи в стеку для змінних, оскільки це локальні змінні, вони знаходяться в операційній пам’яті, але в стеці не за фіксованими адресами, з глобаліками побачать, що це зміни. Але компілятор зрозумів, що він може обчислити y під час компіляції, і немає підстав обчислювати його під час виконання, тому він просто розмістив 1 у просторі стеку, виділеному для x, і 2 для простору стека, виділеного для y. компілятор "виділяє" цей простір внутрішніми таблицями, декларую стек плюс 0 для змінної y та стек плюс 4 для змінної x. компілятор може робити все, що завгодно, поки код, який він реалізує, відповідає стандарту C або очікуванням програміста C. Немає причини, чому компілятор повинен залишати х у стеці + 4 протягом тривалості функції,
Якщо я додаю функцію манекен в асемблері
.thumb_func
.globl dummy
dummy:
bx lr
а потім зателефонуйте
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
dummy(y);
return(0);
}
вихід змінюється
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f804 bl 20 <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <dummy>:
1c: 4770 bx lr
...
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
Тепер, коли ми вклали функції, функції notmain необхідно зберегти свою зворотну адресу, щоб вона могла зменшити зворотну адресу для вкладеного виклику. це тому, що рука використовує реєстр для повернення, якби він використовував стек, як, скажімо, x86 або якийсь інший добре ... він би все одно використовував стек, але інакше. Тепер ви запитуєте, чому він натиснув r4? Ну, не так давно конвенція виклику змінилася, щоб стек вирівнювався на 64-бітових (два слова) межі замість 32-бітових, меж одного слова. Тому їм потрібно щось натиснути, щоб стек вирівнювався, тому компілятор довільно вибрав r4 чомусь, неважливо, чому. Перейти на r4 було б помилкою, хоча згідно з умовою виклику для цієї мети, ми не робимо clobber r4 під час виклику функції, ми можемо клобувати r0 через r3. r0 - повернене значення. Можливо, це робить оптимізацію хвоста,
Але ми бачимо, що математика x і y оптимізована до твердо кодованого значення 2, яке передається функції фіктивного (манекен був спеціально закодований в окремому файлі, в цьому випадку asm, щоб компілятор не оптимізував функцію виклику повністю, якби у мене була фіктивна функція, яка просто поверталася в C у notmain.c, оптимізатор видалив би виклик функції x, y та манекен, оскільки всі вони мертвий / непотрібний код).
Також зауважте, що через те, що код flash.s збільшився, головне не є, і ланцюжок інструментів подбав про виправлення всіх адрес для нас, тому нам не потрібно робити це вручну.
неоптимізований стук для довідки
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: b082 sub sp, #8
26: 2001 movs r0, #1
28: 9001 str r0, [sp, #4]
2a: 2002 movs r0, #2
2c: 9000 str r0, [sp, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: b002 add sp, #8
36: bd80 pop {r7, pc}
оптимізований клакс
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 2002 movs r0, #2
26: f7ff fff9 bl 1c <dummy>
2a: 2000 movs r0, #0
2c: bd80 pop {r7, pc}
цей автор компілятора вирішив використовувати r7 як фіктивну змінну для вирівнювання стека, також він створює покажчик кадру за допомогою r7, хоча у кадрі стека немає нічого. в основному інструкція могла бути оптимізована. але він використовував поп, щоб повернути не три інструкції, це, мабуть, було на мене, я думаю, що я можу отримати gcc, щоб зробити це за допомогою правильних параметрів командного рядка (із зазначенням процесора).
це здебільшого має відповісти на всі ваші запитання
void dummy ( unsigned int );
unsigned int x=1;
unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
Зараз у мене є глобалісти. тож вони йдуть у будь-які .data чи .bss, якщо вони не оптимізуються.
перед тим, як ми подивимось на кінцевий вихід, давайте подивимось на проміжний об'єкт
00000000 <notmain>:
0: b510 push {r4, lr}
2: 4b05 ldr r3, [pc, #20] ; (18 <notmain+0x18>)
4: 6818 ldr r0, [r3, #0]
6: 4b05 ldr r3, [pc, #20] ; (1c <notmain+0x1c>)
8: 3001 adds r0, #1
a: 6018 str r0, [r3, #0]
c: f7ff fffe bl 0 <dummy>
10: 2000 movs r0, #0
12: bc10 pop {r4}
14: bc02 pop {r1}
16: 4708 bx r1
...
Disassembly of section .data:
00000000 <x>:
0: 00000001 andeq r0, r0, r1
тепер у цій інформації відсутня інформація, але вона дає уявлення про те, що відбувається, лінкер - це той, який бере об’єкти і пов'язує їх разом з інформацією, наданою ним (у цьому випадку flash.ld), яка повідомляє, де .text і. дані і так. компілятор не знає таких речей, він може зосередитися лише на представленому коді, будь-який зовнішній він повинен залишити отвір для того, щоб лінкер заповнив з'єднання. Будь-які дані, які він має, залишають спосіб з'єднати ці речі разом, тому адреси для всіх нульові, засновані тут просто тому, що компілятор і цей розбиральник не знають. тут не відображається інша інформація, яку лінкер використовує для розміщення речей. код тут є позицією досить незалежною, щоб лінкер міг зробити свою роботу.
Тоді ми бачимо принаймні демонтаж зв'язаного виводу
00000020 <notmain>:
20: b510 push {r4, lr}
22: 4b05 ldr r3, [pc, #20] ; (38 <notmain+0x18>)
24: 6818 ldr r0, [r3, #0]
26: 4b05 ldr r3, [pc, #20] ; (3c <notmain+0x1c>)
28: 3001 adds r0, #1
2a: 6018 str r0, [r3, #0]
2c: f7ff fff6 bl 1c <dummy>
30: 2000 movs r0, #0
32: bc10 pop {r4}
34: bc02 pop {r1}
36: 4708 bx r1
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
Disassembly of section .bss:
20000000 <y>:
20000000: 00000000 andeq r0, r0, r0
Disassembly of section .data:
20000004 <x>:
20000004: 00000001 andeq r0, r0, r1
компілятор в основному запитував дві 32-бітні змінні в операційному режимі. Один є в .bss, тому що я не ініціалізував його, тому передбачається, що він init як нуль. інше - .дані, тому що я ініціалізував це при оголошенні.
Тепер, оскільки це глобальні змінні, передбачається, що інші функції можуть змінювати їх. компілятор не робить припущень щодо того, коли notmain можна викликати, тому він не може оптимізувати те, що він може бачити, y = x + 1 математика, тому він повинен виконувати цей час виконання. Він повинен прочитати з оперативної пам'яті дві змінні, додати їх і зберегти назад.
Тепер чітко цей код не працює. Чому? тому що мій завантажувальний пристрій, як показано тут, не готує оперативної пам’яті до виклику notmain, тому те, що сміття було в 0x20000000 та 0x20000004, коли чіп прокинувся - це те, що буде використано для y та x.
Не збираюся цього показувати. Ви можете прочитати мої ще більш звиті бурення на .data та .bss, і чому я ніколи не потребую їх у своєму голому металевому коді, але якщо ви вважаєте, що вам потрібно і хочете освоїти інструменти, а не сподіватися, що хтось зробив це правильно? .
https://github.com/dwelch67/raspberrypi/tree/master/bssdata
сценарії лінкерів і завантажувальні програми дещо специфічні для компілятора, тому все, що ви дізнаєтесь про одну версію одного компілятора, може бути перекинуто на наступну версію або з іншим компілятором, але ще одна причина, чому я не вкладаю багато зусиль у підготовку .data та .bss просто щоб бути таким ледачим:
unsigned int x=1;
Я б набагато скоріше це зробив
unsigned int x;
...
x = 1;
і нехай компілятор помістить його в .text для мене. Іноді таким чином економить спалах, іноді більше горять. Це, безумовно, набагато простіше програмувати і переносити з версії інструментарію або одного компілятора до іншого. Набагато надійніший, менше схильний до помилок. Так, не відповідає стандарту С.
тепер що робити, якщо ми робимо ці статичні глобалі?
void dummy ( unsigned int );
static unsigned int x=1;
static unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
добре
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
очевидно, що ці змінні не можуть бути змінені іншим кодом, тому компілятор тепер може під час компіляції оптимізувати мертвий код, як це було раніше.
неоптимізований
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 4804 ldr r0, [pc, #16] ; (38 <notmain+0x18>)
26: 6800 ldr r0, [r0, #0]
28: 1c40 adds r0, r0, #1
2a: 4904 ldr r1, [pc, #16] ; (3c <notmain+0x1c>)
2c: 6008 str r0, [r1, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: bd80 pop {r7, pc}
36: 46c0 nop ; (mov r8, r8)
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
Цей компілятор, який використовував стек для місцевих жителів, тепер використовує ram для глобальних мереж, і цей код, як написано, порушено, тому що я не обробляв .data і .bss належним чином.
і одне останнє, що ми не можемо побачити при розбиранні.
:1000000000100020150000001B0000001B00000075
:100010001B00000000F004F8FFE7FEE77047000057
:1000200080B500AF04480068401C04490860FFF731
:10003000F5FF002080BDC046040000200000002025
:08004000E0FFFF7F010000005A
:0400480078563412A0
:00000001FF
Я змінив x, щоб бути попередньо init з 0x12345678. У моєму скрипті лінкера (це для gnu ld) є ця теда на bob річ. що повідомляє лінкеру, що я хочу, щоб остаточне місце знаходилось у адресному просторі Тед, але зберігайте його у двійковому у адресному просторі Тед, і хтось перемістить його за вас. І ми можемо бачити, що це сталося. це формат Intel hex. і ми можемо бачити 0x12345678
:0400480078563412A0
знаходиться у адресному просторі спалаху двійкового файлу.
readelf також це показує
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x010040 0x00000040 0x00000040 0x00008 0x00008 R 0x4
LOAD 0x010000 0x00000000 0x00000000 0x00048 0x00048 R E 0x10000
LOAD 0x020004 0x20000004 0x00000048 0x00004 0x00004 RW 0x10000
LOAD 0x030000 0x20000000 0x20000000 0x00000 0x00004 RW 0x10000
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
рядок LOAD, де віртуальна адреса 0x20000004, а фізична - 0x48