تو پست قبل گفتم که چجوری میتونیم ماشین خودمونو به QEMU اضافه کنیم. اونجا گفتیم که ماشینمون یه پردازنده ARM Cortex-m3، یه فلش به اندازه 512KB که از آدرس 0x00000000 شروع میشه و یه رم به اندازه 64KB که از آدرس 0x20000000 شروع میشه داره. توی این پست میخوایم ببینیم که حالا باهاش چیکار میشه کرد. هدف اینه یه برنامه ساده رو کامپایل کنیم براش و بتونیم با gdb دیباگش کنیم چون ماشینمون هیچ پریفرالی برای صحبت با بیرون نداره!
تولچین arm رو نصب میکنیم:
# apt-get install binutils-arm-none-eabi
فایل startup.c رو این شکلی درست میکنیم:
#define STACK_TOP 0x20005000 extern unsigned int _BSS_START; extern unsigned int _BSS_END; extern unsigned int _DATA_ROM_START; extern unsigned int _DATA_RAM_START; extern unsigned int _DATA_RAM_END; void startup(); void main(); unsigned int * myvectors[] __attribute__ ((section("vectors")))= { (unsigned int *) STACK_TOP, // 0 Top of Stack (unsigned int *) startup, // 1 Reset Handler }; void startup() { unsigned int * bss_start_p = &_BSS_START; unsigned int * bss_end_p = &_BSS_END; while(bss_start_p != bss_end_p) { *bss_start_p = 0; bss_start_p++; } unsigned int * data_rom_start_p = &_DATA_ROM_START; unsigned int * data_ram_start_p = &_DATA_RAM_START; unsigned int * data_ram_end_p = &_DATA_RAM_END; while(data_ram_start_p != data_ram_end_p) { *data_ram_start_p = *data_rom_start_p; data_ram_start_p++; data_rom_start_p++; } main(); while (1) ; }
این فایل وکتور پردازنده رو توخودش داره. اون آریهی myvectors در قسمت اول فلش توسط لینکر قرار میگیره. ۱۶ کلمه اول وکتور cortex-m3 اینتراپتای اصلی پردازنده رو مشخص میکنن. البته اولی اینتراپتی رو مشخص نمی کنه و آدرس ابتدای استک رو مشخص میکنه. دومی هم اینتراپتیه که موقع شروع به کار پردازنده اجرا میشه که ما مقدارشو برابر آدرس تابع startup قرار میدیم اینجا بقیه رو هم فعلا بیخیال شدیم و فرض کردیم اتفاق نمیافتن چون باعث میشد این فایل خیلی طولانی بشه. تو تابع startup هم متغییرهایی که مقدار اولیه دارن رو مقدار دهی میکنیم و متغییرهایی که مقدار صفر دارن رو صفر میکنیم و بعدش هم تابع main رو اجرا میکنیم. تابع main هم این شکلی فرض میکنیم توی فایل main.c:
static const int a = 7; static int b = 8; static int sum; int main() { sum = a + b; return 0; }
این دوتا فایل رو با این دستورا کامپایل میکنیم:
$ arm-none-eabi-gcc -O0 -c -g -mcpu=cortex-m3 -mthumb -o main.o main.c $ arm-none-eabi-gcc -O0 -c -g -mcpu=cortex-m3 -mthumb -o startup.o startup.c
فایل لینکر اسکریپت(my_machine.ld) رو هم با توجه به آدرس فلش و رم و اندازههاشون اینجوری درست میکنیم:
SECTIONS { . = 0x0; /* From 0x00000000 */ .text : { *(vectors) /* Vector table */ *(.text) /* Program code */ } .rodata : { *(.rodata) /* Read only data */ } _DATA_ROM_START = .; . = 0x00080000; . = 0x20000000; /* From 0x20000000 */ _DATA_RAM_START = .; .data : AT(_DATA_ROM_START) { *(.data) /* Data memory */ } _DATA_RAM_END = .; _BSS_START = .; /* Indicates where BSS section starts in RAM *; .bss : { *(.bss) /* Zero-filled run time allocate data memory */ } _BSS_END = .; /* Indicates where BSS section ends in RAM */ . = 0x20010000; }
و برنامه رو لینک میکنیم:
$ arm-none-eabi-ld -Tmy_machine.ld -o main.elf startup.o main.o $ arm-none-eabi-objcopy -O binary main.elf main.bin
خط دوم فایل elf رو تبدیل به فایل .bin که قابل استفاده برای qemu باشه میکنه. قبل از اینکه بریم سراغ qemu بذارید چندتا قسمت دیساسمبلی فایل تولید شده رو ببینیم:
$ arm-none-eabi-objdump -S main.elf
قسمتی از نتیجه توی این عکسه که مربوط به متغییرهای با مقدار اولیه صفره:
توجه کنید که آدرس 0x5c و 0x60 سر و ته سکشن BSS (قسمتی از RAM که متغییرهای بدون مقدار اولیه قرار داردن) رو نشون میدن.
الان باید بریم سراغ qemu:
$ qemu/build/arm-softmmu/qemu-system-arm -machine my-machine -S -s -nographic -kernel ~/test/arm1/main.bin
این جا اول ماشین خودمونو برای اجرا انتخاب کردیم. -s برای اجرای gdb هستش. -S هم برای اینه که پردازنده ابتدای کار متوقف باشه و چیزی رو اجرا نکنه. وقتی وارد gdb شدیم، c رو بزنیم برنامه اجرا میشه.
بعد از شروع برنامه وقتی مقدار حافظههای 0x5c و 0x60 رو میبینیم میفهمیم که طول BSS چهار بایته و شروعشم 0x20000004ه (چون متغیر sum یک متغیر int بود ۴ بایت شده) و وقتی مقدار 0x20000004 رو پرینت کنیم میبینیم که ۱۵ شده (۷+۸). توی cortex-m3 رجیستر R15 پروگرم کانتره که این جا 0x00000080ه دلیلش اینه من تو کد خودم آخر تابع main یه ;while (1) گذاشته بودم که اگر نتیجه دیساسمبلی رو ببینید اینجوری شده:
واضحه که ;while(1) هی به آدرس خودش که 0x80ه جامپ میکنه به خاطر همین بود که R15 هم 0x80 بود.
واضحه اینا رو همه رو از تو اینترنت پیدا کردم و با توجه به اینکه بسیار بعیده کسی به این قسمت برسه حال ندارم مرجعها رو بذارم D:.