<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های Sirous</title>
        <link>https://virgool.io/feed/@sorisori</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-06-10 12:52:02</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/21118/avatar/avatar.png?height=120&amp;width=120</url>
            <title>Sirous</title>
            <link>https://virgool.io/@sorisori</link>
        </image>

                    <item>
                <title>اجرای برنامه روی ماشین cortex-m3 با qemu</title>
                <link>https://virgool.io/@sorisori/%D8%A7%D8%AC%D8%B1%D8%A7%DB%8C-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D8%B1%D9%88%DB%8C-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-cortex-m3-%D8%A8%D8%A7-qemu-biy3j4f0sq4x</link>
                <description>تو پست قبل گفتم که چجوری می‌تونیم ماشین خودمونو به 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(&quot;vectors&quot;)))= {
    (unsigned int *)    STACK_TOP,                      // 0 Top of Stack
    (unsigned int *)    startup,                        // 1 Reset Handler
};
void startup()
{
    unsigned int * bss_start_p = &amp;_BSS_START; 
    unsigned int * bss_end_p = &amp;_BSS_END;
    while(bss_start_p != bss_end_p)
    {
        *bss_start_p = 0;
        bss_start_p++;
    }
    unsigned int * data_rom_start_p = &amp;_DATA_ROM_START;
    unsigned int * data_ram_start_p = &amp;_DATA_RAM_START;
    unsigned int * data_ram_end_p = &amp;_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:.</description>
                <category>Sirous</category>
                <author>Sirous</author>
                <pubDate>Mon, 12 Aug 2019 23:36:45 +0430</pubDate>
            </item>
                    <item>
                <title>اضافه کردن ماشین جدید به qemu</title>
                <link>https://virgool.io/@sorisori/%D8%A7%D8%B6%D8%A7%D9%81%D9%87-%DA%A9%D8%B1%D8%AF%D9%86-%D9%85%D8%A7%D8%B4%DB%8C%D9%86-%D8%AC%D8%AF%DB%8C%D8%AF-%D8%A8%D9%87-qemu-cmmabm8vc8wb</link>
                <description>اولش بگم اینا همه برداشت‌های منه و ممکنه درست نباشه! یکی از شبیه‌سازهای کامپیوتر خیلی پرکاربرد qemu (مخفف Quick Emulator) هستش. به کمک kvm توی خیلی ‌جا ها استفاده می‌شه. به صورت پیشفرض یه قسمتیش به TCG وضیفه اجرای دستورالعمل‌های پردازنده‌ها رو داره ولی kvm کمک می‌کنه بهش که به جای TCG از تکنولوژی‌های مجازی سازی اینتل و AMD (VT-X و AMD-V) استفاده کنه و بقیه بخشا رو خود qemu شبیه سازی می‌کنه (مثل pci، usb و ...). یکی از قابلیت‌هاش شبیه سازی پردازنده‌های مبتنی بر هسته‌های cortex-m3 و cortex-m4 هستش ولی به جز Texas Instruments بقیه سازنده‌ها، میکروکنترلر‌های خودشونو به کد‌های qemu اضافه نکردن. توی این نوشته و چند تا بعدیش من می‌خوام سعی کنم میکروکنترلر خودمو به qemu اضافه کنم. منظورم از میکروکنترلر خودم اینه که سعی می‌کنم خودم چند تا پریفرال میکرو مثل gpio، uart و ... رو بنویسیم.داکیومنتی برای qemu توی اینترنت پیدا نمیشه (یا حداقل من پیدا نکردم). برای این کار یا باید از بقیه کد بقیه دستگاه‌ها استفاده کرد یا از سایتایی مثل stackoverflow استفاده کرد.اگر کد‌های دستگاهی مثل zynq رو ببینیم آخرش این شکلیه:static void zynq_machine_init(MachineClass *mc)
{
    mc-&gt;desc = &quot;Xilinx Zynq Platform Baseboard for Cortex-A9&quot;;
    mc-&gt;init = zynq_init;
    mc-&gt;max_cpus = 1;
    mc-&gt;no_sdcard = 1;
    mc-&gt;ignore_memory_transaction_failures = true;
    mc-&gt;default_cpu_type = ARM_CPU_TYPE_NAME(&quot;cortex-a9&quot;);
}
DEFINE_MACHINE(&quot;xilinx-zynq-a9&quot;, zynq_machine_init)این که اون DEFINE_MACHINE چجوری کار می‌کنه رو توی نوشته‌های بعدی شاید توضیح بدم. اما نکته مهم اینه که اون mc-&gt;init تابعیه که باید ماشین رو اینتیت کنه برای ماشین من میشه یه همچین چیزی:static void myMachineClassInit(MachineClass *mc)
{
    mc-&gt;desc = &quot;This is my machine!&quot;;
    mc-&gt;init = myMachineInit;
    mc-&gt;ignore_memory_transaction_failures = true;
    mc-&gt;default_cpu_type = ARM_CPU_TYPE_NAME(&quot;cortex-m3&quot;);
}
DEFINE_MACHINE(&quot;My Machine&quot;, myMachineClassInit)تابع myMachineInit هم توی این پست فقط نوع پردازنده و فلش و رم میکرو رو مشخص می‌کنیم. توی qemu پردازنده و پریفرالا و ... همه یه «نوع» هستند. در واقع ماشین هم یه نوع که اون DEFINE_MACHINE کارش اضافه کردنش به کد اصلی هستش. تابع myMachineInit هم این شکلی میشه:static void myMachineInit(MachineState *ms)
{
     DeviceState* nvic;
     MemoryRegion *sram = g_new(MemoryRegion, 1);
     MemoryRegion *flash = g_new(MemoryRegion, 1);
     MemoryRegion *system_memory = get_system_memory();
     memory_region_init_ram(flash, NULL, &quot;my_machine.flash&quot;, MY_MACHINE_FLASH_SIZE, &amp;error_fatal);
     memory_region_set_readonly(flash, true);
     memory_region_add_subregion(system_memory, 0, flash);
     memory_region_init_ram(sram, NULL, &quot;my_machine.sram&quot;, MY_MACHIN_SRAM1_SIZE, &amp;error_fatal);
     memory_region_add_subregion(system_memory, 0x20000000, sram);
     nvic = qdev_create(NULL, TYPE_ARMV7M);
     qdev_prop_set_uint32(nvic, &quot;num-irq&quot;, NUM_IRQ_LINES);
     qdev_prop_set_string(nvic, &quot;cpu-type&quot;, ms-&gt;cpu_type);
     qdev_prop_set_bit(nvic, &quot;enable-bitband&quot;, true);
     object_property_set_link(OBJECT(nvic), OBJECT(get_system_memory()), &quot;memory&quot;, &amp;error_abort);
     qdev_init_nofail(nvic);
     armv7m_load_kernel(ARM_CPU(first_cpu), ms-&gt;kernel_filename, MY_MACHINE_FLASH_SIZE);
}کد بالا تقریبا همه چیش معلومه. اول حافظه اصلی و زیر بخشاش (فلش و رم رو تعریف می‌کنه). بعد پارامتر‌های هسته‌ رو تنظیم می‌کنه. در آخر هم فایل برنامه رو توی فلش لود می‌کنه. این فایلو تو این آدرس کاملشو می‌تونید ببینید. حالا باید این فایلو به لیست فایلایی که بیلد میشن اضافه کنیم. سیستم بیلد qemu مثل سیستم بیلد کرنل لینوکسه. کاری که باید کرد اینه که این فایلو به hw/arm/Makefile.objs اضافه کرد. این جوری‌ (اسم فایلو میذاریم hw/arm/my_machine.c) هستش:obj-$(CONFIG_MY_MACHINE) += my_machine.o بعدش باید تو hw/arm/kconfig این خط‌ها رو اضافه کنیم تا بدونه این فایل به چه فایل‌های دیگه‌ای نیاز داره:config MY_MACHINE
    bool
    select ARM_V7M
    select CMSDK_APB_WATCHDOGآخر هم باید بگیم می‌خوایم این فایلا تو کدوم برنامه‌هایی که ساخته می‌شن وجود داشته باشن. چون من اینو از روی کد‌های دستگاه‌های تگزاس بوده منم توی همون برنامه‌ای که اونا ساخته می‌شدن این دستگاه رو اضافه می‌کنم. برای میکرو‌های تگزاس توی فایل arm-softmmu هستش.برای این کار فایل default-configs/arm-softmmu.mak این خطو اضافه می‌کنیم:CONFIG_MY_MACHINE=yحالا باید کد رو دوباره بیلد کنیم. اینم نتیجه:تغییراتی که توی این نوشته دادم توی این کامیت معلومن. توی نوشته بعدی سعی می‌کنم پریفرالUART به این دستگاه اضافه کنم.</description>
                <category>Sirous</category>
                <author>Sirous</author>
                <pubDate>Wed, 03 Jul 2019 11:34:50 +0430</pubDate>
            </item>
                    <item>
                <title>hello world بدون libc</title>
                <link>https://virgool.io/@sorisori/hello-world-%D8%A8%D8%AF%D9%88%D9%86-libc-oc4oxnui6gwn</link>
                <description>خیلی‌ها ممکنه علاقع داشته باشن بدونن اگر توی یه برنامه رو از کتاب‌خونه‌های استاندارد استفاده نکنیم چی میشه؟برای این که یه برنامه رو با gcc بدون libc کامپایل کنیم، باید از این دستور استفاده کنیم:‍‍gcc main.cpp  -nostdlib -m32وقتی این جوری کامپایل کنیم دیگه لزومی به وجود تابع main نیست و به جاش برنامه از start_ شروع میشه.می‌دونیم که برای چاپ hello world باید msg رو توی stdout بنویسیم. stdout برای هر برنامه‌ای فایل شماره ۱ه. پس باید این سیستم‌کال رو اجرا کنیم:  write(1, msg, sizeof(msg) - 1);آخر تابع start  هم باید سیستم‌کال exit رو اجرا کنیم:‍‍exit(0);ولی ما نمی‌خوایم از این تابع‌ها هم استفاده کنیم D: به خاطر همین اونا رو هم خودمون می‌نویسیم. توی لینوکس همه سیستم‌کال‌ها توسط اینتراپت شماره 0x80 اجرا می‌شن در واقع هندلر اینتراپت ۸۰ توی کرنل کارش اجرا کردن سیستم‌کال‌هاست. برای اجرای سیستم‌کال‌ها باید آرگمان‌های مورد نیازشونو رو توی رجیستر‌های پردازنده قرار بدیم و بعدشم دستور int $0x80 رو اجرا کنیم. نکته دیگه اینه که برنامه در انتها باید سیستم‌کال exit رو هم اجرا کنه. تو جدول زیر هم میشه چند تا سیستم‌کال خیلی پرکاربرد و به همراه شمارشون و رجیستر‌های ارگمان‌هاشون رو دید(منبع: https://www.tutorialspoint.com/assembly_programming/assembly_system_calls.htm):همون‌طور که توی جدول معلومه شماره سیستم‌کال باید توی رجیستر eax باشه برای write این شماره 4ه و برای exit این شماره 1ه. برای سیستم‌کال exit اول باید مقدار 1 رو توی رجیستر eax بذاریم و بعدشم 0 رو توی ebx بذاریم. در انتها باید درخواست اینتراپت 0x80 رو بدیم. پس exit(0) این طوری میشه:asm(&quot;movl $1,%eax;&quot;
        &quot;xorl %ebx,%ebx;&quot;
         &quot;int $0x80&quot;); برای write هم باید 4(شماره سیستم‌کال از جدول بالا) رو توی eax بذاریم، بعدش ebx باید 1 باشه (شماره فایلی که همیشه مربوط به stdoutه)، آدرس بافر (اینجا msg) رو توی ecx و طول بافر رو توی edx. دو تا تغییر آخر نیاز به اکستنشن‌های اسمبلی gcc دارن. برای استفاده از اکستنشنا باید نتیجه‌هایی که می‌خوایم با متغییرا جایگزین بشن رو به ترتیب با %0، %1، %2 و ... مشخص کنیم و برای رجیسترا هم یه % اضافه بذاریم و بعد از دستور باید به ترتیب لیست متغییرایی که باید نتیجه در اون‌ها قرار بگیره ،لیست متغیرایی که باید از مقدارشون استفاده بشه و لیست رجیسترایی که نباید از اون‌ها استفاده بشه رو مشخص کنیم:asm(&quot;movl %0, %%ecx&quot;::&quot;r&quot;(msg):); 
asm(&quot;movl %0, %%edx&quot;::&quot;r&quot;(sizeof(msg)-1):&quot;%ecx&quot;);توی خط اول داریم می‌گیم msg رو بذار توی ecx. اون دو تا : هم برای اینه که لازم نبوده چیزی رو از رجیسترا بذاریم توی متغیرا. بعد : سوم هم،  رجیسترایی که نمی‌خوایم مقدارشون تغییر کنه رو می‌نویسیم که اینجا خالیه. توی خط دوم هم طول msg رو می‌ذاریم توی edx این جا چون مقدار ecx برامون مهمه و نمی‌خوایم تغییر کنه بعد : سوم قرارش می‌دیم.اخرش فایل main.cpp این شکلی میشه:extern&quot;C&quot; void _start()
{
    const char msg[] = &quot;helloworld!&quot;;
    asm(&quot;movl %0, %%ecx&quot;::&quot;r&quot;(msg):);
    asm(&quot;movl %0, %%edx&quot;::&quot;r&quot;(sizeof(msg)-1):&quot;%ecx&quot;);
    asm(&quot;movl $4, %eax;&quot;
            &quot;movl $1, %ebx;&quot;
            &quot;int $0x80&quot;);
    asm(&quot;movl $1, %eax;&quot;
            &quot;xorl %ebx, %ebx;&quot;
            &quot;int $0x80&quot;);
} و اینم از نتجه:</description>
                <category>Sirous</category>
                <author>Sirous</author>
                <pubDate>Thu, 27 Jun 2019 21:45:29 +0430</pubDate>
            </item>
            </channel>
</rss>