آموزش Linker Script و نقشه حافظه در STM32 | قسمت 28 آموزش امبدد C

قسمت 28
embedded C 28
مشاهده سایر جلسات آموزش
15 بازدید
۱۴۰۴-۰۹-۱۸
7 دقیقه
  • نویسنده: Alireza Abbasi
  • درباره نویسنده: ---

بخش‌های غیراستاندارد

در مباحث قبلی، به بررسی بخش‌های حافظه استاندارد که توسط زنجیره ابزار GNU تولید می‌شوند، پرداختیم. تراشه‌های STM32 از یک بخش سفارشی به نام .isr_vector استفاده می‌کنند. این بخش باید اولین داده‌ای باشد که در حافظه فلش برنامه‌ریزی می‌شود، چرا که سخت‌افزار ARM از این بخش حافظه برای مدیریت وقفه‌ها و سایر عملکردهای مرتبط با سخت‌افزار استفاده می‌کند. جدول 11-1 که از راهنمای STM32F030x4 آورده شده است، جزئیات مربوط به بردار وقفه را شرح می‌دهد.

موقعیت اولویت نوع اولویت نام اختصاری توضیحات آدرس
رزرو شده 0x00000000
-3 ثابت Reset بازنشانی 0x00000004
-2 ثابت NMI وقفه غیر قابل ماسک 0x00000008
-1 ثابت Hard تمام کلاس‌های خطا 0x0000000c
3 قابل‌تنظیم SVC تماس سیستمی از طریق دستورالعمل SWI 0x0000002c
5 قابل‌تنظیم PendSV درخواست قابل تعلیق برای خدمات سیستمی 0x00000038
6 قابل‌تنظیم SysTick تایمر تیک سیستم 0x0000003c
0 7 قابل‌تنظیم WWDG وقفه window watchdog 0x00000040
1 رزرو شده     0x00000044
2 9 قابل‌تنظیم RTC وقفه‌های RTC (ترکیب خطوط EXTI 17، 19 و 20) 0x00000048

مستندات بردار وقفه (خلاصه)

فایل فریم‌ور STM با نام startup_stm32f030x8.s که یک فایل زبان اسمبلی است، کد مربوط به تعریف این جدول (جدول بردار وقفه) را در خود جای‌داده است. در اینجا بخشی از این فایل آمده است:

خط اول به لینکر می‌گوید که جدول را در بخشی به نام .isr_vector قرار دهد. این بخش وابستگی شدیدی به سخت‌افزار دارد، تعریف دقیقی دارد و باید به طور مطلق در جای درست قرار گیرد. در غیر این صورت سیستم کار نخواهد کرد.

کد بالا، یک آرایه به نام g_pfnVectors تعریف می‌کند که شامل موارد زیر است:

  • آدرس پشته اولیه (initial stack)
  • آدرس مدیریت ریست (reset handler)
  • آدرس مدیریت وقفه غیر قابل ماسک (NMI handler)
  • سایر بردارهای وقفه، همان‌طور که در جدول بالا شرح داده شد.

نحوه مدیریت این کد توسط لینکر را در بخش بعدی بررسی خواهیم کرد.

فرایند لینک‌کردن

کامپایلر و اسمبلر مجموعه‌ای از فایل‌های آبجکت (object files) تولید می‌کنند که کد و داده را به بخش‌های زیر تقسیم می‌کنند:

  • <name>: کد و داده فقط خواندنی
  • rodata: داده فقط خواندنی
  • Data: داده مقداردهی شده اولیه
  • Bss: داده‌ای که در ابتدا به صفر مقداردهی شده (تعریفی کمی متفاوت از مدل حافظه ایده‌آل C)
  • COMMON: داده مقداردهی‌نشده
  • .isr_vector: روتین‌های مدیریت وقفه و ریست که باید در مکان خاصی قرار بگیرند

لینکر توسط اسکریپتی به نام LinkerScript.ld کنترل می‌شود که بخشی از هر پروژه STM32 Workbench است. این اسکریپت به لینکر می‌گوید که حافظه سیستم از دو بخش تشکیل شده است:

  • فلش (Flash): از آدرس 0x8000000 شروع می‌شود، با طول 64 کیلوبایت
  • رم (RAM): از آدرس 0x20000000 شروع می‌شود، با طول 8 کیلوبایت

وظیفه لینکر گرفتن داده‌ها از فایل‌های آبجکت و جای‌گذاری آنها با مراحل زیر در حافظه است:

  • قراردادن بخش .isr_vector در ابتدای فلش.
  • قراردادن تمام داده‌های بخش‌های .text.* در فلش.
  • قراردادن بخش .rodata در فلش.
  • قراردادن بخش .data در رم، بااین‌حال مقدار دهنده‌های اولیه بخش .data در فلش قرار می‌گیرند (این موضوع را بیشتر بررسی خواهیم‌ کرد).
  • قراردادن بخش .bss در رم.
  • در نهایت، بارگذاری بخش COMMON در رم.

⚠️توجه

یک‌سری مراحل دیگر هم در اسکریپت لینک وجود دارد که هنگام جابه‌جایی بین حالت برنامه‌نویسی Thumb و ARM استفاده می‌شوند و همچنین برای رسیدگی به سازنده‌ها (constructors) و مخرب‌ها (destructors) در C++. ما در اینجا از هیچ‌یک از این قابلیت‌ها استفاده نمی‌کنیم.

بخش .data بخش دردسرساز ماجراست. این اعلان را در نظر بگیر:

int initializedGlobal = 1234;

لینکر برای initializedGlobal در RAM فضا تخصیص می‌دهد. مقدار اولیه‌ (1234) در فلش قرار می‌گیرد. در زمان راه‌اندازی (startup)، مقادیر اولیه به‌صورت یک بلوک از فلش به RAM کپی می‌شوند تا بخش .data مقداردهی اولیه شود.

نمادهای تعریف‌شده توسط لینکر

پس از ریست، کدی که در فایل startup_stm32f030x8.S قرار دارد اجرا می‌شود و مراحل زیر را انجام می‌دهد:

  • _sidata: شروع مقدار دهنده‌های اولیه بخش .data در فلش
  • _sdata: شروع بخش .data در رم
  • _edata: پایان بخش .data در رم
  • _sbss: شروع بخش‌های .bss و COMMON در رم
  • _ebss: پایان بخش‌های .bss و COMMON در رم
  • _estack: آخرین آدرس رم

هنگام راه‌اندازی مجدد، کد موجود در فایل startup_stm32f030x8.S اجرا می‌شود و مراحل زیر را انجام می‌دهد:

  • وقتی میکروکنترلر ریست می‌شود، کد داخل فایل S اجرا می‌شود و این مراحل را انجام می‌دهد:
  • مقدار رجیستر استک را با آدرس _estack تنظیم می‌کند؛ یعنی پشته از آن نقطه شروع می‌شود و روبه‌پایین رشد می‌کند.
  • محتوایی را که از آدرس _sidata در فلش شروع می‌شود، به محدوده‌ی RAM بین _sdata و _edata کپی می‌کند. این همان مقداردهی اولیه‌ی متغیرهای بخش .data است.
  • محدوده‌ی حافظه‌ی بین _sbss و _ebss را صفر می‌کند تا بخش .bss آماده شود.
  • تابع SystemInit را اجرا می‌کند تا تنظیمات اولیه‌ی سخت‌افزار STM32 انجام شود.
  • تابع __libc_init_array را اجرا می‌کند تا بخش‌های لازم برای کتابخانه‌ی C مقداردهی اولیه شوند.
  • تابع main را اجرا می‌کند تا برنامه‌ی اصلی شروع شود.
  • و در آخر، وارد یک حلقه‌ی بی‌نهایت می‌شود تا اجرای برنامه ادامه پیدا کند.
شاید برای شما مفید باشد:
کار با تراشه F1C100S – قسمت دوم – مقدمه ای بر buildroot

پیوند و جابه‌جایی فایل‌های شیء (آبجکت)

دو نوع فایل object وجود دارد: absolute و relocatable.

یک فایل absolute همه چیز را بر اساس یک آدرس ثابت (absolute) تعریف می‌کند. یعنی مثلاً سمبل main در آدرس 0x7B0 قرار دارد و لینکِر یا هیچ ابزار دیگری نمی‌تواند آن را به آدرس دیگری منتقل کند.

اما یک فایل object از نوع relocatable طوری طراحی شده که محل قرارگیری داده‌ها و کدهایش قابل‌جابه‌جایی (relocate) باشد. مثلاً فایل main.c وقتی کامپایل می‌شود، فایل main.o تولید می‌کند. اگر اسمبلی این فایل را نگاه کنیم، می‌بینیم سمبل main در آدرس 0000 تعریف شده است:

این نماد به بخشی که در آن قرار دارد (یعنی text.main) وابسته است. ازآنجایی‌که فایل شیء (object file) قابل‌جابه‌جایی است، text.main می‌تواند در هر جایی از حافظه قرار گیرد. در این مورد، لینکر (Linker) تصمیم گرفته است آن را در حافظه فلش با آدرس 0x00000000080007b0 قرار دهد. (این مقدار را با استفاده از نقشه لینکر که در بخش بعدی توضیح داده می‌شود، پیدا کردیم). ازآنجایی‌که main در ابتدای این بخش قرار دارد، مقدار 0x00000000080007b0 را به خود می‌گیرد.

به‌عنوان بخشی از فرایند، لینکر فایل‌های شیء قابل‌جابه‌جایی را گرفته و آن‌ها را در یک مکان خاصی از حافظه قرار می‌دهد. نتیجه، یک فایل برنامه با آدرس‌های مطلق برای هر فایل شیء است.

همچنین لینکر فایل‌های شیء را به هم پیوند می‌دهد. برای مثال، فایل startup_stm32f030x8.S تابع main را فراخوانی می‌کند. مشکل این کد است که نمی‌داند تابع main کجا قرار دارد. تابع main در ماژول دیگری (main.o) تعریف شده است، بنابراین در زمان لینک، لینکر متوجه می‌شود که startup_stm32f030x8.S نیاز دارد بداند تابع main در کجا تعریف شده است پس یک عملیات لینک بین فراخوانی main در startup_stm32f030x8.S و آدرس مطلق main (0x7B0) انجام می‌دهد.

یک کتابخانه مجموعه‌ای از فایل‌های شیء (object) است که با فرمت بایگانی (شبیه به .zip، ولی ساده‌تر) ذخیره می‌شوند. اسکریپت لینکر به لینکر دستور می‌دهد کتابخانه‌های libc.a، libm.a و libgcc.a را در برنامه نهایی قرار دهد. به‌عنوان‌مثال، کتابخانه‌ی libm.a شامل فایل‌های شیء زیر است:

هنگام پردازش یک کتابخانه، لینکر فقط فایل‌هایی از شیء را بارگذاری می‌کند که حاوی نماد موردنیاز برنامه شما باشند. به‌عنوان‌مثال، اگر برنامه شما از تابع سینوس (sin) استفاده کند، لینکر فایل شیء s_sin.o که این تابع را تعریف می‌کند، در برنامه نهایی قرار خواهد داد. اما اگر از تابع سینوس استفاده نکنید، لینکر متوجه می‌شود که به کد موجود در s_sin.o نیازی ندارید و در نتیجه آن را در برنامه نهایی قرار نمی‌دهد.

نقشه لینکر (Linker Map)

هنگام بارگذاری داده‌ها توسط لینکر در برنامه، یک فایل نقشه به نام Debug/output.map ایجاد می‌شود. این فایل شامل اطلاعاتی در مورد محل قرارگیری کد و داده‌های ما است.

فایل نقشه بسیار جامع است و حاوی اطلاعات مفید و همچنین جزئیات کم‌اهمیت‌تری است. به‌عنوان‌مثال، این فایل، پیکربندی حافظه‌ی ما را نشان می‌دهد که انواع مختلف حافظه و موقعیت آن‌ها را برای پردازنده‌ی ما مشخص می‌کند:

پیکربندی حافظه:

نام نشانی شروع (Origin) طول (Length) ویژگی‌ها (Attributes)
FLASH 0x0000000008000000 0x0000000000010000 خواندنی (r) و اجرایی (x)
RAM 0x0000000020000000 0x0000000000002000 خواندنی (r)، نوشتنی (w)، و اجرایی (x)
پیش‌فرض (default) 0x0000000000000000 0xffffffffffffffff

در این مورد، تراشه‌ی ما دارای حافظه‌ی FLASH است که می‌توان از آن برای خواندن (r) و اجرای کد (x) استفاده کرد. این حافظه از آدرس 0x8000000 شروع شده و به اندازه‌ی 0x10000 بایت ادامه دارد. بخش RAM از آدرس 0x20000000 شروع شده و فقط به اندازه‌ی 0x2000 بایت ادامه دارد. این بخش حافظه قابل خواندن (r)، نوشتن (w) و اجرا (x) است.

همان‌طور که قبلاً ذکر شد، بخش .isr_vector اولین بخشی است که بارگذاری می‌شود. نقشه لینکر محل قرارگیری این بخش را به ما نشان می‌دهد:

آدرس 0x8000000 شروع حافظه‌ی FLASH است. سخت‌افزار انتظار دارد که بردار وقفه (interrupt vector) در این آدرس قرار داشته باشد. اطلاعات دیگری که می‌بینم طول این بخش 0xc0 بایت است.

شاید برای شما مفید باشد:
طرح یک مثال کاربردی جهت درک بهتر موضوع میکروبلیز در FPGA | آموزش میکروبلیز قسمت اول

نماد اصلی (main) در فایل src/main.o تعریف شده است. این نماد بخشی از سگمنت .text.main بوده و در آدرس 0x0000000008000138 قرار دارد:

همچنین این بخش شامل قطعه‌ای از کد (0x60 بایت) است که باتوجه‌به خالی بودن کد ۱۱-۳، حجیم به نظر می‌رسد.

همچنین می‌توانیم ببینیم که متغیرهای سراسری ما کجا قرار دارند. برای مثال، در اینجا موقعیت برای uninitializedGlobal آمده است:

فایل linker map آدرس مطلق (absolute address) همه‌ی متغیرها و تابع‌های موجود در برنامه را نشان می‌دهد. این به چه دردی می‌خورد؟ وقتی در شرایط واقعی (بدون JTAG debugger) در حال دیباگ‌کردن هستیم، معمولاً فقط آدرس‌های مطلق در دسترس ما هستند؛ بنابراین اگر برنامه دچار یک خطای جدی شود و در کنسول دیباگ فقط این را ببینید:

می‌فهمید که خطا 20 بایت بعد از شروع تابع main رخ داده است.

ما با برد STM خودمان از یک دیباگر خارجی استفاده کرده‌ایم. این سیستم شامل یک کامپیوتر میزبان که دیباگر روی آن اجرا می‌شود، یک ماژول JTAG، و یک دستگاه هدف (target) است. دیباگر روی کامپیوتر میزبان به کد منبع و جدول سمبل‌ها (symbol table) که توسط لینکِر تولید می‌شود دسترسی دارد. وقتی خطایی در آدرس 0x8000158 تشخیص دهد، می‌تواند در جدول سمبل‌ها نگاه کند، ببیند که این آدرس دقیقاً 20 بایت بعد از شروع برنامه یا تابع مربوطه است، تشخیص بدهد خطا در کدام خط کد رخ‌داده، و سپس یک فلش قرمز بزرگ در فایل سورس نشان دهد تا محل دقیق خطا را مشخص کند.

برخی از سیستم‌ها دارای یک دیباگر داخلی هستند که خود دیباگر و تمام فایل‌های موردنیاز آن روی سیستم هدف قرار دارند. برخی از این دیباگرهای داخلی امکان ریختن حافظه بر اساس آدرس‌های مطلق را فراهم می‌کنند. این دیباگرها کوچک و ساده هستند، همچنین در عیب‌یابی میدانی می‌توانند به طرز شگفت‌انگیزی مفید باشند.

فرض کنید چنین دیباگری دارید و می‌خواهید مقدار متغیر سراسری «uninitializedGlobal» را بدانید. یک دیباگر ساده هیچ‌چیزی در مورد نام‌های نماد نمی‌داند. این دیباگر حافظه را بر اساس آدرس تخلیه می‌کند و کار دیگری انجام نمی‌دهد.

از طرف دیگر، شما از نام‌های نماد اطلاع دارید همچنین نقشه‌ی لینکر (linker map) را در اختیار دارید، بنابراین می‌توانید به دیباگر بگویید یک مقدار ۴ بایتی را در موقعیت 0x20000464 نمایش دهد:

این نوع دیباگ‌کردن خیلی ابتدایی و سخت است، اما گاهی در سیستم‌های امبدد تنها راهی است که می‌شود از آن استفاده کرد.

شاید بپرسید چرا ما به دیباگر نمی‌گوییم که متغیر uninitializedGlobal کجاست تا کار راحت‌تر شود. مشکل اینجاست که جدول سمبل‌ها (symbol table) حجم زیادی از حافظه را اشغال می‌کند، درحالی‌که ما محدودیت حافظه داریم. علاوه بر این، داشتن جدول سمبل‌ها داخل خود سیستم یک ریسک امنیتی است. (یک هکر عاشق این است که بداند آدرس تابع passwordCheckingFunction کجاست!)

اطلاعات
15
0
0
اشتراک و حمایت
profile نویسنده: Alireza Abbasi متخصص الکترونیک

ویراستار: MasoudHD
مقالات بیشتر

slide

پالت | بازار خرید و فروش قطعات الکترونیک

قطعات اضافه و بدون استفاده همیشه یکی از سرباره‌‌های شرکتها و طراحان حوزه برق و الکترونیک بوده و هست. پالت سامانه‌ای است که بصورت تخصصی اجازه خرید و فروش قطعات مازاد الکترونیک را فراهم می‌کند. فروش در پالت
family

آیسی | موتور جستجوی قطعات الکترونیک

سامانه آی سی سیسوگ (Isee) قابلیتی جدید و کاربردی از سیسوگ است. در این سامانه سعی شده است که جستجو، انتخاب و خرید مناسب تر قطعات برای کاربران تسهیل شود. جستجو در آیسی
family

سیسوگ‌شاپ | فروشگاه محصولات Quectel

فروشگاه سیسوگ مجموعه ای متمرکز بر تکنولوژی های مبتنی بر IOT و ماژول های M2M نظیر GSM، GPS، LTE، NB-IOT، WiFi، BT و ... جایی که با تعامل فنی و سازنده، بهترین راهکارها انتخاب می شوند. برو به فروشگاه سیسوگ
family

سیسوگ فروم | محلی برای پاسخ پرسش‌های شما

دغدغه همیشگی فعالان تخصصی هر حوزه وجود بستری برای گفتگو و پرسش و پاسخ است. سیسوگ فروم یک انجمن آنلاین است که بصورت تخصصی امکان بحث، گفتگو و پرسش و پاسخ در حوزه الکترونیک را فراهم می‌کند. پرسش در سیسوگ فرم
family

سیکار | اولین مرجع متن باز ECU در ایران

بررسی و ارائه اطلاعات مربوط به ECU (واحد کنترل الکترونیکی) و نرم‌افزارهای متن باز مرتبط با آن برو به سیکار
become a writer
نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
become a writer
نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله

خانواده سیسوگ

سیسوگ‌شاپ

فروشگاه محصولات Quectel

پالت
سیسوگ فروم

محلی برای پاسخ پرسش‌های شما

سیسوگ جابز
سیسوگ
سیسوگ فروم
سی‌کار

اولین مرجع متن باز ECU در ایران

سیسوگ مگ
آی‌سی

موتور جستجوی قطعات الکترونیکی

سیسوگ آکادمی
پالت

بازار خرید و فروش قطعات الکترونیک

دیدگاه ها

become a writer
نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
become a writer
نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله