چیه ماجرا؟

امروز ۱۵ اسفنده و روز هفتم جنگ. طبعاً مثل هر آدم دیگه‌ای من هم تجربه‌ای مرکب از حس‌های مختلف رو دارم تجربه می‌کنم و برای سرگرم کردن خودم، یه پروژه تعریف کردم. تو این پست می‌بینم که آرم چقد قشنگه و مثل اون اسمبلی X86 رجیسترهای عجق وجق و اینستراکشنای قوری ووری نداره. و قراره با اسمبلی آرم یه برنامه بنویسیم که یه آی‌پی تو فرمت CIDR (123.12.23.13/31) بگیره و بهمون بگه نت‌مسکش، نتورک‌آدرس، برودکست‌آدرس و اولین و آخرین آدرس‌های قابل استفاده‌ی رنج چیان.

ماجرای اسمبلی آرم رو هم با فایل‌هایی که اینجا و اینجا و اینجا هست پی می‌گیرم.

نت‌مسک و ماجراهاش

کل ماجرا رو میشه تو این کد جاوااسکریپت داشت:

 1class IP {  
 2    constructor(ip, mask) {  
 3        this.ip = ip;
 4        this.mask = mask;
 5
 6        this.parts = ip.split(".").map(e => parseInt(e.trim()));
 7    }  
 8  
 9    netmaskAddr() {  
10        const octets = [0, 0, 0, 0];
11  
12        for (let i = 0; i < this.mask; i++) {  
13            octets[Math.floor(i / 8)] += Math.pow(2, 7 - (i % 8));  
14        }  
15  
16        return new IP(octets.join("."));  
17    }  
18  
19    subnetAddr() {  
20        const octets = [0, 0, 0, 0];  
21  
22        for (let i = 0; i < this.mask; i++) {  
23            octets[Math.floor(i / 8)] += Math.pow(2, 7 - (i % 8));  
24        }  
25  
26        for (let i = 0; i < 4; i++) {  
27            octets[i] &= this.parts[i];  
28        }  
29  
30        return new IP(octets.join("."));  
31    }  
32  
33    broadcastAddr() {  
34        const octets = [0, 0, 0, 0];  
35  
36        for (let i = this.mask; i < 32; i++) {  
37            octets[Math.floor(i / 8)] += Math.pow(2, 7 - (i % 8));  
38        }  
39  
40        for (let i = 0; i < 4; i++) {  
41            octets[i] |= this.parts[i];  
42        }  
43  
44        return new IP(octets.join("."));  
45    }  
46  
47    firstAddress() {  
48        const subnet = this.subnetAddr();  
49        subnet.parts[3]++;  
50  
51        return new IP(subnet.parts.join("."));  
52    }  
53  
54    lastAddress() {  
55        const broadcast = this.broadcastAddr();  
56        broadcast.parts[3]--;  
57  
58        return new IP(broadcast.parts.join("."));  
59    }  
60  
61    toString() {  
62        let s = this.ip;  
63        if (this.mask) {  
64            s += "/" + this.mask;  
65        }  
66
67        return s;  
68    }  
69}

کل ماجرای نت‌مسک چیزی نداره. نت‌مسک میاد ۳۲بیت آیپی رو به دو بخش تقسیم می‌کنه: نتورک و هاست. اون عدد که بعد از اسلش میاد می‌گه چند بیت مال نتورکه و باقی چیزی که می‌مونه می‌شه بیت‌های هاست. یچی مثل کد اموال تو شرکت‌ها مثلاً: ما-چپ-۱۱ میشه گروه مالی، سمت چپ ساختمون، مال شماره‌ی ۱۱. گروه مالی و سمت چپ میشه قسمت نتورک و ۱۱ هم میگه اونجا کی کجاست. پس:

  • خود نت‌مسک می‌شه 0xFFFFFFFF شیفت چپ به اندازه‌ی ۳۲ منهای مسک.
  • نتورک‌آدرس می‌شه & منطقی نت‌مسک با آیپی
  • برود‌کست می‌شه نقیض نت‌مسک و یای | لاجیکال با آیپی
  • در نهایت هم اولین آدرس قابل استفاده می‌شه آخرین اکتت نتورک‌آدرس + ۱
  • و آخرین آدرس قابل استفاده هم می‌شه اکتت آخر برودکست‌آدرس منهای یک.

قراره چطور پیش بریم؟

ماجرا احتمالاً خیلی سخت نباشه در عمل ولی برای چیزی که هنوز هیچ دیدی نداریم چه اتفاقی قراره تو مسیر برامون بیفته بهتر بشکنیمش. چیزی که الان دارم بهش فکر می‌کنم اینه:

  1. یه تیکه کد که یچی چاپ کنه
  2. یه تیکه کد که بتونه یه عدد بخونه از ورودی و اون رو چاپ کنه
  3. یه تیکه کد که بتونه روی یه اکتت جمع بزنه
  4. یه آی‌پی CIDR رو بخونه و تبدیلش کنه به یه صحیح ۳۲بیتی و بایت ماسک
  5. نت‌مسک رو بسازه
  6. چهارتا آیپی رو با استفاده از ماسک بسازه
  7. چاپ کنیم حاصل رو

پیاده‌سازی

یه تیکه کد که یچی چاپ کنه

 1.global _start  
 2  
 3.section .data  
 4  msg: .ascii "Hello, World\n"  
 5  msg_len= . - msg  
 6  
 7.section .text  
 8_start:  
 9  mov w8, #64       ; w8 <- 64
10  mov w0, #1        ; w0 <- 1
11  ldr w1, =msg      ; w1 <-(from memory)- *(msg)
12  ldr w2, =msg_len  ; w2 <- msg_len = 13
13  svc #0  
14  
15  mov w8, #93  
16  mov w0, #0  
17  svc #0

احتمالاً اگه از قبل هیچ برخوردی با اسمبلی نداشته باشیم، قسمت استارت ماجرا یذره گنگ می‌زنه.

سیس‌کال‌ها

تو این کد ما سیس‌کال write رو صدا زدیم، بهش گفتیم داخل خروجی استاندارد مسیجی که بهت میگیم رو که طولش هم فلان‌قدره بنویس. بعدش هم سیس‌کال exit رو صدا زدیم و گفتیم با کد صفر خارج شو. اینجاست که برای من زیبایی اسمبلی آرم نسبت به دوستاش خودش رو نشون میده. رجیسترها مثل آدمی‌زاد بنابر سایز یه حرف گرفتن (در اینجا w که میشه اول word که همون ۳۲بیته تو ادبیات مرتبط و اون یکی که x هست که از extended word گرفته شده و میشه ۶۴ بیته) و از صفر تا ۳۱ شماره‌گذاری کردن. راحت! خبری از eax و اینا نیست (من یدوره حداقل یک ماهه تلاش کردم اونا رو حفظ کنم و الان هم یادم نمیاد کی کجا بود و چی بود).

برای اینکه چیزی که در بالا گفتیم رو ترجمه کنیم، کافیه بدونیم که همیشه تو ورد ۸ شماره‌ی سیس‌کال میاد که این داکیومنت (از این سایت) زیبا و قشنگ ماجرا رو روشن می‌کنه (ستون اول شماره‌ی سیس‌کال‌هاست، یا ستون w8 که مقدار هگز همونه) و وردهای صفر تا پنج هم آرگومان‌های صفر تا پنج سیس‌کال رو مشخص می‌کنن. برای مثال امضای تابع write اینه:

1write(unsigned int fd, const char *buf, size_t count)

پس ما تو ورد هشت مقدار ۶۴ رو گذاشتیم که میشه سیس‌کال رایت، تو ورد صفر مقدار ۱ رو گذاشتیم یعنی فد خروجی استاندارد، تو ورد یک آدرس پیام رو لود کردیم از مموری و تو ورد دو هم طول رشته رو گذاشتیم. با svc (که مخفف Supervisor Callعه) هم که سیس‌کال رو کال کردیم. همین اتفاق برای سیس‌کال exit هم افتاده.

سکشن دیتا

یه توضیح کوچیک هم در مورد بخش data اینکه طبعاً سکشن دیتا برای تعریف کردن داده‌ها (متغیرها) استفاده میشه. در کد بالا ما اومدیم یه تیکه از مموری رو گرفتیم و رشته‌مون رو گذاشتیم داخلش و برای اینکه بعداً بتونیم بهش دسترسی داشته باشیم، باید آدرسش رو برچسب بزنیم. msg یه برچسبه برای مکانی از مموری (بر چسب‌ها همیشه با : تعریف میشن) و بعد از جنریت شدن اون رشته یچی میشه مثل ۱۲۰۰ (یه آدرس از مموری). تو خط بعد = همون عملگر اساینمنته که تو زبان‌های دیگه هست (فقط در اینطور موقعیتی) و میاد میگه مقدار msg - . رو به این ثابت (با اغماض) اساین کن (چون ثابته دیگه آدرسی در مموری هم نداره که بخواد با : برچسب بزنیم بهش). نقطه هم موقعیت مموریه در اونجایی که هست. یعنی بعد از رشته‌ی ۱۳ حرفی‌ای که داریم، نقطه به آدرس ۱۲۱۳ اشاره می‌کنه. حالا تفریق این دوتا آدرس میشه طول رشته و اون مقدار به msg_len اساین میشه.

و حالا که تا اینجا اومدیم، بعد از اسم‌گذاری رجیسترها که خیلی راحت‌تر از اسمبلی X86عه، به تفاوت دوم این دوتا معماری هم رسیدیم. داخل معماری آرم، فقط میشه روی چیزهایی محاسبات و چک و تیک داشت که داخل رجیسترها باشن و از سر همین با Load and Store خیلی داخل این معماری روبرو می‌شیم (LDR در واقع مخفف LoaD Register هست و در کنارش STR که میشه STore Register).

ذخیره/بازیابی و ماجرای آدرس‌دهی

برای فهمیدن LDR و STR باید اول دقت کنیم که مموری رو میشه به شکل یه آرایه از بایت‌ها دید که اندیس اون آرایه‌ها میشه آدرس مموری. تو آرم۶۴ این آدرس‌ها ۶۴ بیتن. طبعاً وقتی یه بخشی از حافظه رو رزرو می‌کنیم آدرس‌های اون بخش هم پشت سر هم میان و میشه از جمع و تفریق روی آدرس‌ها استفاده کرد. مثل کاری که برای گرفتن طول رشته در کد بالا کردیم (رشته در واقع آرایه‌ای از بایت‌هاست). یا مثلاً میشه اگه آدرس یه المان آرایه رو داشت المان بعدیش رو گرفت و از اینجور کارها (داخل سی کار با آرایه‌ها عملاً همین بود).

شکل کلی استفاده از LDR و STR اینجوریه:

1ldr Rn, addressing_mode
2str Rn, addressing_mode

که در اون R یا ‍Xعه که باعث میشه روی ۶۴ بیت از حافظه ذخیره انجام بشه یا خونده بشه و یا Wعه که باعث میشه این اتفاق روی ۳۲ بیت از حافظه باشه (در واقع W و X هر دو یه رجیسترن و ما ۳۲ یا ۶۴ بیت از اونها رو استفاده می‌کنیم). addressing mode هم شیوه‌ای هست که دستورمون استفاده می‌کنه تا آدرسی از مموری که نیاز هست رو پیدا کنه. برای محاسبه‌ی آدرس میشه از شیوه‌های زیر استفاده کرد (شیوه‌ها برای هر دو دستور کار می‌کنن ولی برای سادگی و عدم تکرار فقط ldr رو میاریم):

  • پایه: در این شیوه آدرس در یه رجیستر ذخیره شده و ما از اون آدرس استفاده می‌کنیم و محتوا رو ذخیره یا لود می‌کنیم.

    1ldr x1, [x0] // x1 <- *(x0)
    
  • پایه جمع با آفست: اگه آدرس در رجیستر ذخیره شده باشه و ما بخوایم چندتا آفست اینور اونورش رو بخونیم. آفست رو در محاسبه‌ی طول رشته در بالا گفتیم که چیه.

    1ldr x1, [x0, #1]    // x1 <- *(x0 + 1)
    2ldr x1, [x0, #-16]  // x1 <- *(x0 - 16)
    

    در این شیوه یه سری حالت‌ها (مثل پردازش آرایه یا پردازش روی استک که جلوتر می‌بینیم) هست که نیازه محتوای یه آدرس رو در آفست‌های منظم بخونیم (مثلاً برای یه آرایه یدونه یدونه بریم جلو). راه ساده‌ست، هر بار که لود می‌کنیم رجیستر آفست رو یکی افزایش می‌دیم و دوباره می‌خونیم و افزایش می‌دیم تاااا کار تموم شه. برای اینکه هر بار با یه دستور جدا آفست رو جلو عقب نکنیم، آرم دوتا میانبر در اختیار گذاشته:

    • آدرس‌دهی پیشا-اندیس (pre-index addressing): در این حالت اول اندیس رو افزایش/کاهش می‌ده و بعد حافظه در آدرس جدید رو لود/ذخیره می‌کنه. شیوه‌ی استفاده‌اش اینطوریه:
      1ldr x1, [x0, #1]! // x0 = x0+1 and then x1 <- *(x0)
      
      این حالت بیشتر وقتی به کار میاد که می‌خوایم یه چیزی رو تو آرایه یا استک ذخیره کنیم و اگه روی همون اندیس فعلی ذخیره کنیم، مقدار فعلی از بین می‌ره، پس قبل از ذخیره اندیس رو افزایش میدیم.
    • آدرس‌دهی پسا-اندیس (post-index addressing): در این حالت اول مقدار حافظه رو در آدرس فعلی لود/ذخیره می‌کنه و بعد از خوندن/ذخیره، آدرس رو افزایش/کاهش میده:
      1ldr x1, [x0], #1 // x1 <- *(x0) and then x0=x0+1
      
      یه مثال از کاربرد این حالت هم وقتاییه که می‌خوایم اول مقدار آرایه یا استک در اندیس فعلی رو بخونیم/ذخیره کنیم و بعد اندیس رو کاهش/افزایش بدیم.
  • پایه جمع با رجیستر آفست: اگه آدرس در رجیستر ذخیره شده باشه و آفستی که می‌خوایم بخونیم هم در یه رجیستر ذخیره شده.

    1ldr x2, [x0, x1] // x2 <- *(x0 + x1)
    
  • و در نهایت خوندن یه متغیر:

    1.section .data
    2my_var: .space 16
    3
    4.section .text
    5_start:
    6ldr x0, my_var // x0 <- &my_var (address of my_var)
    7ldr x1, [x0]   // x1 <- *(x0)   (value stored in my_var)
    

    همونطور که مشخصه اینطور می‌تونیم مموری رو وصل کنیم به رجیسترها و عملاً از اینجا میشه برنامه‌ی واقعی نوشت. ما بالاتر از همین شیوه استفاده کردیم برای خوندن رشته‌ای که ساختیم ولی اونجا شیوه‌ی لودمون با = بود. در واقع = اینجا یه شبه‌عملگره و میاد کار رو می‌سپره به خود کامپایلر و اکسپنشن رو خود کامپایلر انجام میده (تو زمینه همین کارایی که ما کردیم رو بسته به اینکه اون آدرس چی توش باشه و چقد باشه و اینا انجام میده).

    علت این اتفاق هم که مشخصه. هر دستور آرم۶۴ دقیقاً ۳۲ بیته و اگه فایل ISA که در بالا بود رو ببینید، قالب ۳۲ بیتی هر دستور و کاربرد هر بیت داخلش مشخصه. از اونور ما گفتیم که آدرس‌ها داخل آرم۶۴ دقیقاً ۶۴ بیتن و خیلی واضحه که نمیشه هیچ جوره مستقیم یه مقدار ۶۴ بیتی رو توی ۳۲ بیت جا داد.

  • خیلی وقت‌ها هم نیازه که دوتا رجیستر رو لود/ذخیره کرد. برای این کار هم آرم دستورات ldp و stp رو معرفی کرده که addressing modeش مثل بالاست ولی دوبار کار ذخیره/بازیابی رو با آفست ۸ انجام میده:

    1ldp x0, x1, [x2]        // x0 <- *(x2) and x2 <- *(x2+8)
    2stp x0, x1, [x2, #-16]! // x2' = x2-16, then *(x2') = x0 and *(x2'+8) = x1
    3  					   //                   *(x2-16) = x0 and *(x2-8) = x1
    

برای پایان این بخش کافیه کد رو کامپایل و لینکش کنیم و نتیجه رو ببینیم:

1meysam@ubuntu:~/www/test/arm/netmask$ as -o main.o main.s
2meysam@ubuntu:~/www/test/arm/netmask$ ld -o main main.o
3meysam@ubuntu:~/www/test/arm/netmask$ ./main
4Hello, World
5meysam@ubuntu:~/www/test/arm/netmask$ echo $?
60

و چون تو پست داکر از صفر: پروسه هم strace رو دیده بودیم، باحاله که خروجی رو برای فایلمون ببینیم:

1meysam@ubuntu:~/www/test/arm/netmask$ strace -f ./main
2execve("./main", ["./main"], 0xffffda37aea8 /* 25 vars */) = 0
3write(1, "Hello, World\n", 13Hello, World
4)          = 13
5exit(0)                                 = ?
6+++ exited with 0 +++

:)). کوچولوووو.

یه تیکه کد که بتونه یه عدد بخونه از ورودی و اون رو چاپ کنه

اول یه کد بنویسیم که یه عدد رو چاپ کنه.

 1.global _start  
 2  
 3.section .text  
 4_start:  
 5  mov w8, #64  
 6  mov w0, #1  
 7  mov w1, #5  
 8  mov w2, #1  
 9  svc #0  
10  
11  mov w8, #93  
12  mov w0, #0  
13  svc #0

داخل رجیستر و۱ مقدار ۵ رو گذاشتیم و طولش رو ۱ ست کردیم و انتظار داریم که چاپش کنه:

1meysam@ubuntu:~/www/test/arm/netmask$ as -o main.o main.s
2meysam@ubuntu:~/www/test/arm/netmask$ ld -o main main.o
3meysam@ubuntu:~/www/test/arm/netmask$ ./main
4meysam@ubuntu:~/www/test/arm/netmask$

ولی نکرد. و خب عجیب هم نیست. اگه به پارامتر دوم write برگردیم ‍const char *buf از ما انتظار تایپ پوینتر کاراکتر رو داره. ولی چیزی که ما بهش دادیم مثل یه آدرس تو ناکجا آباده:

1execve("./main", ["./main"], 0xffffcdab8bf0 /* 25 vars */) = 0
2write(1, 0x5, 1)                        = -1 EFAULT (Bad address)
3exit(0)                                 = ?
4+++ exited with 0 +++

پس اول نیازه مقدار ۵ رو داخل یه خونه‌ی حافظه ذخیره کنیم و پوینتر اون رو به سیس‌کالمون پاس بدیم:

 1.global _start  
 2  
 3.section .data  
 4  buf: .space 1  
 5  
 6.section .text  
 7_start:  
 8  mov w1, #5  
 9  ldr x0, =buf  
10  str w1, [x0]  
11  
12  mov w8, #64  
13  mov w0, #1  
14  ldr w1, =buf  
15  mov w2, #1  
16  svc #0  
17  
18  mov w8, #93  
19  mov w0, #0  
20  svc #0

و رانش:

1meysam@ubuntu:~/www/test/arm/netmask$ strace ./main
2execve("./main", ["./main"], 0xffffcc078380 /* 25 vars */) = 0
3write(1, "\5", 1)                       = 1
4exit(0)                                 = ?
5+++ exited with 0 +++

به جای ۵، 5\ رفته برای چاپ. باز اگه به سیس‌کال رایت برگردیم و پارامتر دوم، به تایپ char می‌رسیم. تایپ کاراکتر در واقع همون تایپ بایته که با استاندارد اسکی تفسیر می‌شه. پس عدد ۵ اون چیزی نیست که معادل ۵ اسکی باشه. اگه از man 7 ascii استفاده کنیم، می‌بینیم که کد اسکی ۵ در واقع ۵۳ هست:

 1  30 40 50 60 70 80 90 100 110 120
 2  ---------------------------------
 30: (     2  <  F  P  Z  d   n   x
 41: )     3  =  G  Q  [  e   o   y
 52: *     4  >  H  R  \  f   p   z
 63: !  +  5  ?  I  S  ]  g   q   {
 74: "  ,  6  @  J  T  ^  h   r   |
 85: #  -  7  A  K  U  _  i   s   }
 96: $  .  8  B  L  V  `  j   t   ~
107: %  /  9  C  M  W  a  k   u  DEL
118: &  0  :  D  N  X  b  l   v
129: '  1  ;  E  O  Y  c  m   w

پس با تغییر مقدار ۵ به ۵۳ به چیزی که می‌خوایم می‌رسیم:

1meysam@ubuntu:~/www/test/arm/netmask$ strace ./main
2execve("./main", ["./main"], 0xffffdb1e6ba0 /* 25 vars */) = 0
3write(1, "5", 15)                        = 1
4exit(0)                                 = ?
5+++ exited with 0 +++
6meysam@ubuntu:~/www/test/arm/netmask$ ./main
75

تقریباً کار این زیربخش دیگه تمومه. کافیه سیس‌کال read رو پیدا کنیم و ببینیم چی می‌خواد، مقدار رو با اون بخونیم و بریزیم داخل بافری که ساختیم و همون بافر رو هم چاپ کنیم. شماره‌ی سیس‌کال ۶۳ هست و امضای سیس‌کال هم اینطوریه:

1read(unsigned int fd, char *buf, size_t count)

تنها چیزی که فرق می‌کنه نسبت به راهی که تا اینجا اومدیم اینه که فد رو باید بذاریم صفر که میشه ورودی استاندارد.

 1.global _start  
 2  
 3.section .data  
 4  buf: .space 1  
 5  
 6.section .text  
 7_start:  
 8  mov w8, #63  
 9  mov w0, #0  
10  ldr w1, =buf  
11  mov w2, #1  
12  svc #0  
13  
14  mov w8, #64  
15  mov w0, #1  
16  ldr w1, =buf  
17  mov w2, #1  
18  svc #0  
19  
20  mov w8, #93  
21  mov w0, #0  
22  svc #0

و اجراش:

1meysam@ubuntu:~/www/test/arm/netmask$ strace ./main
2execve("./main", ["./main"], 0xffffc44ae9c0 /* 25 vars */) = 0
3read(0, 4
4"4", 1)                         = 1
5write(1, "4", 14)                        = 1
6exit(0)                                 = ?
7+++ exited with 0 +++

یه تیکه کد که بتونه روی یه اکتت جمع بزنه

برای شروع از کد مرحله‌ی قبل استفاده می‌کنیم و یک اکتت می‌گیریم و چاپش می‌کنیم. برای این منظور کافیه ۳ بایت رزرو کنیم (هر کاراکتر ۱ بایت)، ۳تا کاراکتر از ورودی بخونیم و همون رو چاپش کنیم:

 1.global _start  
 2  
 3.section .data  
 4  octet: .space 3
 5  
 6.section .text  
 7_start:  
 8  mov w8, #63  
 9  mov w0, #0  
10  ldr w1, =octet  
11  mov w2, #3  
12  svc #0  
13  
14  mov w8, #64  
15  mov w0, #1  
16  ldr w1, =octet  
17  mov w2, #3  
18  svc #0  
19  
20  mov w8, #93  
21  mov w0, #0  
22  svc #0

و اجراش:

1meysam@ubuntu:~/www/test/arm/netmask$ as -o main.o main.s && ld -o main main.o && ./main
2123
3123

از اسکی به بایت

در ادامه برای اینکه بتونیم جمع بزنیم به همون مشکلی می‌خوریم که موقع چاپ ۵ داشتیم. بافر از تایپ کاراکتره و اسکی ولی ما عدد می‌خوایم. اگه فرض کنیم که قرارمون اینه که همیشه عدد بخونیم، با توجه به جدول اسکی که بالاتر بود متوجه می‌شیم که فاصله‌ی هر کاراکتر عددی از کاراکتر صفر، همون عدده، پس کافیه از هر کاراکتر مقدار اسکی صفر رو کم کنیم.

پس در ادامه اول روی رشته تلاش می‌کنیم هر کاراکتر رو بگیریم:

 1.global _start  
 2  
 3.section .data  
 4  octet: .space 3  
 5  
 6.section .text  
 7_start:  
 8  mov w8, #63  
 9  mov w0, #0  
10  ldr w1, =octet  
11  mov w2, #3  
12  svc #0  
13  
14  ldr x3, =octet  
15  mov w4, #3  
16  bl str_to_num  ; branch with link (the next line address will be saved on LR or x30 register and after calling,  
17                 ; the main branch continue to execute from there.  
18  
19  mov w8, #93  
20  mov w0, #0  
21  svc #0  
22  
23str_to_num:  
24  ldrb w5, [x3]      ; w5 <- loadTheFirstByte(x3)
25  
26  mov w8, #64  
27  mov w0, #1  
28  mov w1, w5  
29  mov w2, #1  
30  svc #0  
31  
32  add x3, x3, #1     ; x3 <- x3 + 1 (x3 stores the address of a memory location, so by incrementing it, we go to the  
33                     ; next char)  
34  
35  subs w4, w4, #1    ; w4 <- w4 - 1 and set flags  
36  
37  bne str_to_num     ; branch to the str_to_num label again if the sat flags are not zero  
38  
39  ret

قبل از اجرای برنامه، چندتا چیز جدید داخل کد داریم. اول اینکه اینجا اولین جاییه که داخل اسمبلی ما تابع (str_to_num) تعریف کردیم. تابع، داخل اسمبلی چیزی نیست جز یه برچسب که حاوی آدرسیه که شروع تابع اونجاست. برچسب رو داخل سی و پاسکال و گو هم دیدیم. چیزیه که با goto میشه مسیر اجرای برنامه رو به سمتش برد. اینجا به جای goto از b (مخفف branch) استفاده می‌کنیم.

جریانِ برنامه

برنچ گرفتن رو کلاً می‌شه به دو دسته تقسیم کرد: برنچ گرفتن غیرشرطی و برنچ گرفتن شرطی. برنچ غیرشرطی از اسمش مشخصه که فقط می‌گیم برو به اون برچسب. برای این منظور از b و br و bl استفاده می‌کنیم.‍ b که فقط جریان برنامه رو می‌بره به یه برچسب مشخص، br میاد برای رفتن به جای مشخص از رجیستر آدرس رو می‌خونه، و bl (مخفف branch with link) میاد وقتی تابع می‌خواد کال شه، آدرس دستور بعدی رو در رجیستر LR ذخیره می‌کنه. اینطور وقتی که ما در آخر ret رو صدا زدیم، کد میاد به جایی که صدا زده شده بوده و ادامه‌ی مسیر رو پی می‌گیره (در واقع ret همون ‍br LR هست).

برنچ‌های شرطی که در واقع مثالش در کد بالا، bne هست میاد بر اساس شرطی که چک کردیم تصمیم می‌گیره بره به یه مسیر یا نره. برای فهمیدن این شکل برنچ گرفتن، خط قبل برنچ گرفتن در کد بالا رو اگه ببینیم تابع subs کال شده. اون s در واقع state هست و برای فهمیدنش یچی دیگه رو باید بفهمیم :)).

حالت پردازشگر

آرم۶۴ یه رجیستر سیستمی به اسم PSTATE (که تو نسخه‌های قبلی سیستمی نبوده و اسمش PSR مخفف Program Status Register بوده) داره که مخفف Processor State هست و یه سری اطلاعات مشخص رو در خودش نگه می‌داره. به صورت عادی نمی‌شه به این رجیستر دسترسی داشت و با دستورهای خاص mrs (برای خوندن) و msr (برای نوشتن) می‌شه مقادیر چندتا viewطور از این رجیستر رو خوند.

ویوی NZCV

همینطور که تا اینجا دیدیم، داخل اسمبلی خیلی خبری از معجزه و ژینگول‌بازی و اینا نیست. یکی از این موقعیت‌ها که شاید در زبان‌های دیگه خیلی اصن متوجهش هم نباشیم اینه که به صورت مستقیم نمیشه فهمید جمع، تفریق و ترکیب دوتا رجیستر کی صفر شد، کی منفی شد و کی آورفلو داد. دستورهای مرتبط با این اعمال میان کارشون رو می‌کنن و می‌رن و برای وقتی که برای ما مهمه که آیا این اتفاق‌ها افتاده یا نه، PSTATE میاد استیت رو برامون نگه می‌داره. ماجرا هم به این صورته که وقتی یه سری توابع مشخص روی رجیسترها کال میشن، بر اساس مقدار حاصل، بیت‌های ۲۸ تا ۳۱ PSTATE بروز میشن و این بیت‌ها هر کدوم میشن یه فلگ که با اون می‌شه سوالی که داشتیم رو پاسخ داد.

فلگریشهشماره‌ی بیتتوضیح
NNegative۳۱حاصل منفی بوده یا نه (مقدار بیت ۳۱ یا ۶۱ بسته به سایز رجیستر)
ZZero۳۰حاصل صفر بوده یا نه
CCarry۲۹سرریز/قرض گرفتن در حالت بدون علامت رخ داده/نداده (صفر) یا نداده/داده (یک)
VoVerflow۲۸سرریز در حالت علامت‌دار رخ داده (یک) یا نه (صفر)

یه نکته که رو باید بهش توجه کنیم اینه که داخل رجیستری ما رجیستر با/بدون علامت (برای اعداد صحیح طبعاً) نداریم. رجیستر ۳۲ یا ۶۴ بایتی داریم و وقت چک کردن، خودمون باید منظور خودمون از اون رجیستر رو بدونیم و بر مبنای اون چک‌مون رو انجام بدیم. با این وصف، دوتا ردیف اول که نکته‌ی خاصی نداره واقعیت. می‌مونه آورفلوها. برای در آوردن ته آورفلوها باید یچی رو قبلش بررسی کنیم.

مکمل ۲

گفتیم که داخل اسمبلی، رجیسترهامون تایپ علامت‌دار یا بدون علامت ندارن و صرفاً یه مشت صفر و یک رو ذخیره می‌کنن. تفسیر ما از صفر و یک‌هاست که می‌تونه اونا رو علامت‌دار یا بدون علامت کنه.

تایپ بدون علامت ساده‌ست. مقدار صفر رو ذخیره می‌کنه تا $2^n - 1$. برای نمایش علامت می‌تونیم بیت آخر رو بذارم برای علامت و اینطور از $-2^n -1$ تا $2^n -1$ رو می‌تونیم نشون بدیم ولی دوتا مشکل وجود داره، اول اینکه اینطور دوتا صفر داریم (همه بیتا صفر و همه بیتا صفر به جز بیت آخر) و دوم اینکه یک‌پارچگی محاسباتی بین سایزهای ۳۲ و ۶۴ از بین میره: برای نمایش اعداد منفی در سایزهای ۳۲ و ۶۴ باید کار اضافه انجام داد. این شد که ملت اومدن از یه مفهوم جبری استفاده کردن: حلقه.

حلقه

حلقه یا ring یه ساختار جبریه که الان کاری به همه‌ی خاصیت‌هاش نداریم به جز سه‌تا: بسته بودن نسبت به عملگر جمع (یعنی حاصل جمع دوتا عدد داخل حلقه، دوباره داخل همون حلقه می‌افته)، وجود عنصر یکتای همانی جمع (که میشه همون صفرمون) و وجود عنصر وارون جمع (که میشه المانی که اگه با المان دیگه جمع شه میشه عنصر همانی). از بین همه‌ی حلقه‌ها هم یکی هست که الان به کار ما میاد: حلقه‌ی اعداد صحیح با پیمانه‌ی $2^n$.

پیمانه همون باقیمانده به زبان ریاضی‌دان‌هاست :)). احتمالاً از ریاضی دبیرستان یادتون باشه هم‌نهشتی رو. می‌گفتیم دوتا عدد به پیمانه‌ی ایکس هم‌نهشتن اگه تفریق دوتا عدد بر ایکس بخش‌پذیر باشه. و بعدش هم رابطه‌ی افراز تعریف می‌کردیم و می‌گفتیم رابطه‌ی هم‌نهشتی میاد کلاس‌های هم‌ارزی تعریف می‌کنه. همه‌ی اینا رو گفتم که بگم مجموعه‌ی همه‌ی کلاس‌های هم‌ارزی با یک پیمانه، تشکیل یک حلقه میده که بهش میگن حلقه‌ی اعداد صحیح با پیمانه‌ی ایکس. پس عنصر همانی جمع یکتاست توش و عنصر وارون جمع هم وجود داره و یکتاست.

حالا اگه فرض کنیم تعداد بیت‌هامون ۳عه، حلقه‌ی اعداد صحیح با پیمانه‌ی سه ما رابطه‌اش این شکلی میشه

$$a - b ≡ 2^3$$

که این یعنی ما صفر داریم داخل این مجموعه و برای عنصر داخلش هم یک عنصر یکتا داریم که اگه باهاش جمع شه میشه صفر. از رابطه‌ی بالا ما چندتا نتیجه می‌گیریم، اول اینکه صفر با $2^3$ هم‌ارزه. دومین نتیجه‌ای که می‌تونیم بگیریم اینه که ۷ با منفی ۱ هم‌ارزه (۷ منهای منهای ۱ میشه هشت که باقیمانده‌اش بر هشت صفره). در واقع اگه دقت کنیم هر عددی رو میشه بر مبنای هم‌ارزی، به دو شکل داخل این حلقه وصف کرد. حالت مثبت و حالت منفی. ۶ با منفی ۲ هم‌ارزه، ۵ با منفی ۳ و این کار رو میشه تا ۴ انجام داد. به بیان دیگه، با بیان هم‌ارزی به پیمانه‌ی ۳، ما دو صورت از اعداد صحیح رو می‌تونیم نشون بدیم:

$$\{0,1,2,3,4,5,6,7\}$$

و

$$\{-4,-3,-2,-1,0,1,2,3\}$$

به جای هر عدد بزرگتر یا مساوی چهار در مجموعه‌ی اول، هم‌ارزش رو گذاشتیم تو مجموعه‌ی دوم. نمایش بیتی هر کدوم رو داشته باشیم، اینطور چیزی میشه:

10        000
21        001
32        010
43        011
54 or -4: 100
65 or -3: 101
76 or -2: 110
87 or -1: 111

و اینطور ما تونستیم مجموعه‌ی اعداد صحیح علامت‌دار رو بسازیم (به پیمانه‌ی ۳). به بیان دیگه اگه $x$ داخل مجموعه‌ی اعداد صفر تا $2^n - 1$ تعریف شده باشه، فرمول مجموعه‌ی علامت‌دار متناظر باهاش این میشه:

$$ signed_n(x) = \begin{cases} x, & x < 2^{n-1} \\ x - 2^n, & x \ge 2^{n-1} \end{cases} $$

تنها مشکلی که فرمول بالا برای محاسبه داخل کامپیوتر داره اینه که وقتی ما با n بیت کار می‌کنیم، مقدار $2^n$ رو نداریم و بزرگترین مقداری که داریم، $2^n -1$ هست که برای درست شدن فرمول باید حالت دوم فرمول رو با یک جمع و تفریق کنیم، پس:

$$ 2^n - x = (2^n -1 + 1) - x = (2^n - 1) - x + 1 $$

از طرفی چون جمع هر عدد و نقیضش میشه عددی که همه‌ی بیت‌هاش یک هستن:

$$ 2^n - 1 = 111...1 = x + \sim x \Rightarrow \sim x = (2^n - 1) - x $$

دقت کنیم در فرمول بالا، چون عنصر وارون جمعی رو داخل حلقه داریم، پس:

$$ x + y \equiv 0 \pmod{2^n} \Rightarrow y \equiv -x \pmod{2^n}\Rightarrow y \equiv 2^n - x \pmod{2^n} $$

این یعنی ما یه راه عملی برای ساختن عدد منفی هم پیدا کردیم، نات عدد رو با یک جمع می‌کنیم و میشه عدد منفی‌مون:

$$ comp^2_n(x) = \begin{cases} x, & x < 2^{n-1} \\ \sim x + 1, & x \ge 2^{n-1} \end{cases} $$

به این میگن نمایش مکمل ۲ی عدد. در نهایت ما تونستیم نمایش عدد منفی رو خود عدد منفی رو داخل این حلقه پیدا کنیم و چون حلقه‌ست و بسته‌ست، پس بدون اینکه نیاز به چیز دیگه‌ای باشه، جمع هم مثل تفریق داخل این حلقه تعریف می‌شه و نیاز نیست برای تفریق کار خاص دیگه‌ای بکنیم، مثلاً سه منهای یک (سه + منفی یک) رو در نظر بگیریم:

1 3: 011
2-1: 111
3-------
4   1010

که اگه اون بیت آخری رو حذف کنیم (در واقع اینجا آورفلوی علامت‌دار رخ داده و فلگ V روشن میشه) نتیجه می‌شه 010 که همون ۲ هست، چیزی که از این تفریق انتظار داشتیم.

توابع حالت‌ساز

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

  • ADDS, SUBS, ANDS
  • CMN, CMP, TST

دسته‌ی اول که روشنن چی هستن. جمع و تعریف و اند می‌کنن و بر اساس مقداری که به دست میاد در نهایت، فلگ‌ها رو هم ست می‌کنن.

دسته‌ی دوم هم در واقع یه میانبری هستن برای دسته‌ی اول:

1CMN x1, x2 // <- ADDS xzr, x1, x2 <- xzr = x1+x2
2CMP x1, x2 // <- SUBS xzr, x1, x2 <- xzr = x1-x2
3TST x1, x2 // <- ANDS xzr, x1, x2 <- xzr = x1&x2

اون xzr در واقع رجیستر x31 هست که مثل dev/null/ لینوکس عمل می‌کنه: نوشتن توش فایده نداره و هر موقع هم ازش بخونی مقدار صفر بر می‌گردونه.

تنها نکته‌ای که می‌مونه اینه که چرا وقتی V داریم، C هم داریم اگه دوتاشون برای آورفلو هستن. فرض کنیم در حال ۳۲ بیت می‌خوایم 0xFFFFFFFF + 1 رو انجام بدیم. در حالت بدون علامت مقدار 0x100000000 میشه که ۱ در بیت ۳۳ قرار داره و بنابراین مقدار C یک می‌شه، ولی اگه همین جمع رو در حال علامت‌دار انجام بدیم عدد 0xFFFFFFFF همون منفی یک هست و حاصل جمع صفر می‌شه پس آورفلویی رخ نداده و V صفر خواهد بود.

تغییر شرطی جریان برنامه

حالا اگه برگردیم به جایی که بودیم می‌تونیم برنچ گرفتن‌های شرطی رو داشته باشیم:

حالتمخففشرط معادلتایپ فرضی رجیسترهاتوضیح بر اساس NZCV
beqbranch if equalx0 == x1Z == 1
bnebranch if not equalx0 != x1Z == 0
bgebranch if greater than or equalx0 >= x1علامت‌دارN == V
bgtbranch if greater thanx0 > x1علامت‌دارZ == 0 & N == V
blebranch if less than or equalx0 <= x1علامت‌دارZ == 1 | N != V
bltbranch if less thanx0 < x1علامت‌دارN != V
bhibranch if higherx0 >= x1بدون علامتC == 1 & Z == 0
bhsbranch if higher or samex0 > x1بدون علامتC == 1
blobranch if lowerx0 <= x1بدون علامتC == 0
blsbranch if lower or samex0 < x1بدون علامتC == 0 | Z == 1

اجرای اسکی به بایت

حالا در نهایت برای اینکه ببینیم کدی که در بالا نوشتیم درست بوده یا نه، داخل سیس‌کال write به مقدار بافر خروجی توجه می‌کنیم:

1meysam@ubuntu:~/www/test/arm/netmask$ strace ./main
2execve("./main", ["./main"], 0xffffd8c5cc70 /* 25 vars */) = 0
3read(0, 123
4"123", 3)                       = 3
5write(1, 0x31, 1)                       = -1 EFAULT (Bad address)
6write(1, 0x32, 1)                       = -1 EFAULT (Bad address)
7write(1, 0x33, 1)                       = -1 EFAULT (Bad address)
8exit(0)                                 = ?
9+++ exited with 0 +++

سه تا مقدار 0x31، 0x32 و 0x33 همون مقادیر هگز اسکی ۱ و ۲ و ۳ هستن. پس میشه تبدیلشون کنیم به مقدار عددی:

 1.global _start  
 2  
 3.section .data  
 4  octet: .space 3  
 5  
 6.section .text  
 7_start:  
 8  mov w8, #63  
 9  mov w0, #0  
10  ldr w1, =octet  
11  mov w2, #3  
12  svc #0  
13  
14  ldr x3, =octet  
15  mov w4, #3  
16  bl str_to_num
17  
18  mov w8, #93  
19  mov w0, #0  
20  svc #0  
21  
22str_to_num:  
23  ldrb w5, [x3]
24  sub w5, w5, #0x30  ; w5 <- w5 - 48 (48 or 0x30 is ascii code of '0')  
25  
26  mov w8, #64  
27  mov w0, #1  
28  mov w1, w5  
29  mov w2, #1  
30  svc #0  
31  
32  add x3, x3, #1
33  
34  subs w4, w4, #1
35  
36  bne str_to_num
37  
38  ret

و در نهایت می‌مونه ذخیره کردن هر عدد. برای ذخیره می‌تونیم ورد صفر رو صفر کنیم. هر بار مقدار قبلیش رو در ۱۰ ضرب کنیم و با رقمی که ساختیمش جمع کنیم:

 1.global _start  
 2  
 3.section .data  
 4  octet: .space 3
 5  
 6.section .text  
 7_start:  
 8  mov w8, #63  
 9  mov w0, #0  
10  ldr w1, =octet  
11  mov w2, #3  
12  svc #0  
13  
14  ldr x3, =octet  
15  mov w4, #3  
16  mov w0, #0  
17  bl str_to_num
18  
19  mov w8, #64  
20  mov w0, #1  
21  mov w1, w7  
22  mov w2, #1  
23  svc #0  
24  
25  mov w8, #93  
26  mov w0, #0  
27  svc #0  
28  
29str_to_num:  
30  ldrb w5, [x3]  
31  sub w5, w5, #0x30  
32  
33  mov w8, #10  
34  mul w0, w0, w8  
35  add w0, w0, w5  
36  
37  add x3, x3, #1  
38  
39  subs w4, w4, #1  
40  
41  bne str_to_num  
42  
43  ret

و رانش که از پارامتر دوم write قراره مقداری که داریم رو بفهمیم چقدر بوده:

1meysam@ubuntu:~/www/test/arm/netmask$ strace ./main
2execve("./main", ["./main"], 0xffffc8a20af0 /* 25 vars */) = 0
3read(0, 128
4"128", 3)                       = 3
5write(1, 0x1, 1)                        = -1 EFAULT (Bad address)
6write(1, 0xc, 1)                        = -1 EFAULT (Bad address)
7write(1, 0x80, 1)                       = -1 EFAULT (Bad address)
8exit(0)                                 = ?
9+++ exited with 0 +++

و جمع زدنش هم سخت نیست دیگه:

 1.global _start  
 2  
 3.section .data  
 4  octet: .space 3  
 5  
 6.section .text  
 7_start:  
 8  mov w8, #63  
 9  mov w0, #0  
10  ldr w1, =octet  
11  mov w2, #3  
12  svc #0  
13  
14  ldr x3, =octet  
15  mov w4, #3  
16  mov w0, #0  
17  bl str_to_num  
18  mov w7, w0 // store result of calling function  
19  
20  add w7, w7, 10 // to end this subsection  
21  
22  mov w8, #64  
23  mov w0, #1  
24  mov w1, w7  
25  mov w2, #1  
26  svc #0  
27  
28  mov w8, #93  
29  mov w0, #0  
30  svc #0  
31  
32str_to_num:  
33  ldrb w5, [x3]  
34  sub w5, w5, #0x30  
35  
36  mov w8, #10  
37  mul w0, w0, w8  
38  add w0, w0, w5  
39  
40  add x3, x3, #1  
41  
42  subs w4, w4, #1  
43  
44  bne str_to_num  
45  
46  ret

و اجراش:

1meysam@ubuntu:~/www/test/arm/netmask$ strace ./main
2execve("./main", ["./main"], 0xfffffa2898a0 /* 25 vars */) = 0
3read(0, 128
4"128", 3)                       = 3
5write(1, 0x8a, 1)                       = -1 EFAULT (Bad address)
6exit(0)                                 = ?
7+++ exited with 0 +++

که ۱۲۸ در واقع مقدار عددیش در نمایش مبنای ۱۶ میشه 0x8a. به نظر این تیکه هم تمومه.

یه آی‌پی CIDR رو بخونه و تبدیلش کنه به یه صحیح ۳۲بیتی و بایت ماسک

ما الان با کد بالا فناوری تبدیل اکتت به مسک رو داریم :)). اینجا می‌رسیم به خوندن یه آی‌پی به فرمت 123.123.123.123/12 و طبعاً شرطی‌ها در برنامه‌نویسی. باید یه رشته‌ی ماکزیمم ۱۸ کاراکتری رو بخونیم. هر وقت به نقطه رسیدیم تا اونجا رو یه اوکتت در نظر بگیریم، تبدیلش کنیم به عدد، بذاریمش تو جای خودش در یه ورد (اکتت اول میشه ۸ بیت چپ ورد، اکتت دوم میشه ۸ بیت دوم و الی آخر) و در نهایت اگه به اسلش رسیدیم، ماکزیمم دوتا کاراکتر بعدش رو تبدیل کنیم به عدد و بذاریم داخل بایت مسک.

خوندن رو که قبلاً رفتیم:

 1.global _start  
 2  
 3.section .data  
 4  cidrIP: .space 18  
 5  
 6.section .text  
 7_start:  
 8  mov w8, #63  
 9  mov w0, #0  
10  ldr w1, =cidrIP  
11  mov w2, #18  
12  svc #0  
13  
14  mov w8, #64  
15  mov w0, #1  
16  ldr w1, =cidrIP  
17  mov w2, #18  
18  svc #0  
19  
20  mov w8, #93  
21  mov w0, #0  
22  svc #0

و اجراش:

1meysam@ubuntu:~/www/test/arm/netmask$ ./main
2123.123.123.123/12
3123.123.123.123/12

حالا باید تابعی داشته باشیم که به هر نقطه که رسید تیکه کنه ماجرا رو، یچی شبیه

1s.split(".")

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

  • اگه عدد بود ذخیره‌ش کنیم یجایی
  • اگه رسیدیم به نقطه، عددی که ذخیره کردیم رو تو جای مناسبی داخل ورد قرار بدیم
  • اگه به / رسیدیم یعنی آی‌پی تموم شده و به مسک رسیدیم
  • اگه به کاراکتر نال یا n\ رسیدیم یعنی آخر رشته‌ست و مسک رو ذخیره کنیم و بره

طبعاً قرار نیست اینا رو پشت سر هم بنویسیم بره و نیاز داریم برای اینکه کدمون حداقلی از خوانایی رو داشته باشه، تابع تعریف کنیم. و خب اینجا قراره چندتا تابع درون هم کال شن و هی برن اینور اونور.

مسئولیت حفظ رجیسترها با کیه؟

نکته‌ای که اینجا هست اینه که بالاتر گفتیم که هر بار که برنچ با لینک می‌گیریم آدرس خط بعدی در LR ذخیره می‌شه و اگه ret رو کال کنیم تو تابع دوم، بر می‌گردیم به ادامه‌ی اجرای کد از جایی که تابع رو صدا زده بودیم. مشکل الان اینجاست که اگه چندبار برنچ با لینک بگیریم، هر بار رجیستر LR بروز می‌شه و جایی که بودیم گم می‌شه.

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

رجیسترنقشمسئولیت حفظ
x0 تا x7آرگومان تابع / نتیجهکسی که کال می‌کنه
x8پوینتر به نتیجه / سیس‌کالکسی که کال می‌کنه
x9 تا x15ذخیره‌سازی موقتکسی که کال می‌کنه
x19 تا x28استفاده‌ی عمومیکسی که کال شده
x29 (FP)Frame Pointerکسی که کال شده
x30 (LR)Link Registerکسی که کال شده
SPStack Pointerکسی که کال شده

رجیسترهای ۱۶ و ۱۷ و ۱۸ رو نفهمیدم چیا هستن و نیاوردم تو این لیست :)).

در نهایت منظور از ستون سوم اینه که

  • اگه داخل تابع اول قراره تابع دوم کال شه و تابع اول به رجیسترهای x0 تا x15 نیازه داره، این وظیفه‌ی خود تابع اوله که مقادیر اونا رو ذخیره کنه و تابع دوم وظیفه‌ای نداره که به این مقادیر دست نزنه.
  • اگه داخل تابع اول قراره تابع دوم کال شه و تابع دوم نیاز داره به رجیسترهای x19 تا x30 و همینطور اشاره‌گر استک، وظیفه‌اشه که مقادیر این رجیسترها رو حفظ کنه و بعد تغییرشون بده و کارش هم که تموم شد و خواست به تابع اول برگرده، مقادیر اولیه رو بذاره سر جاش و بعد برگرده.

مسئله‌ی استک

در ادامه یه تیکه کد می‌نویسیم که دو سطح از کال کردن تابع داشته باشیم که به مشکل از دست دادن LR بخوریم و بر اساس جدول بالا و چیزایی که تا حالا دیدیم، حلش کنیم. این کد همون کارو می‌کنه:

 1.global _start  
 2  
 3.section .text  
 4_start:  
 5  bl f1  
 6  
 7  mov x8, #93  
 8  mov x0, #0  
 9  svc #0  
10  
11f1:  
12  bl f2  
13  ret  
14  
15f2:  
16  ret

و اگه رانش کنیم می‌بینیم که همینطور وایمیسه و نگاهمون می‌کنه. برای اینکه بفهمیم چرا اینطور می‌شه با gdb میریم برای دیباگش:

 1meysam@ubuntu:~/www/test/arm/netmask$ as -g -o ctc.o ctc.s && ld -o ctc ctc.o
 2meysam@ubuntu:~/www/test/arm/netmask$ gdb ./ctc
 3Reading symbols from ./ctc...
 4(gdb) break _start
 5Breakpoint 1 at 0x400078: file ctc.s, line 5.
 6(gdb) disas /r _start
 7Dump of assembler code for function _start:
 8   0x0000000000400078 <+0>:	94000004	bl	0x400088 <f1>
 9   0x000000000040007c <+4>:	d2800ba8	mov	x8, #0x5d                  	// #93
10   0x0000000000400080 <+8>:	d2800000	mov	x0, #0x0                   	// #0
11   0x0000000000400084 <+12>:	d4000001	svc	#0x0
12End of assembler dump.
13(gdb) disas /r f1
14Dump of assembler code for function f1:
15   0x0000000000400088 <+0>:	94000002	bl	0x400090 <f2>
16   0x000000000040008c <+4>:	d65f03c0	ret
17End of assembler dump.
18(gdb) disas /r f2
19Dump of assembler code for function f2:
20   0x0000000000400090 <+0>:	d65f03c0	ret
21End of assembler dump.
22(gdb) run
23Breakpoint 1, _start () at ctc.s:5
245	  bl f1
25(gdb) info registers x30
26x30            0x0                 0
27(gdb) s
28f1 () at ctc.s:12
2912	  bl f2
30(gdb) info registers x30
31x30            0x40007c            4194428
32(gdb) s
33f2 () at ctc.s:16
3416	  ret
35(gdb) info registers x30
36x30            0x40008c            4194444
37(gdb) s
38f1 () at ctc.s:13
3913	  ret
40(gdb) info registers x30
41x30            0x40008c            4194444
42(gdb) s
43^C
44Program received signal SIGINT, Interrupt.
45f1 () at ctc.s:13
4613	  ret
47(gdb) info registers x30
48x30            0x40008c            4194444

بعد از اینکه گیر کرد با کنترل+سی می‌زنیم تو سرش. تو تیکه‌ی اول دی‌اسمبل هر تیکه و در آخر هم آمار رجیستر x30 که همون LR هست رو میشه دید. در ادامه برای اشاره به آدرس از دو رقم آخر استفاده می‌کنیم. با ران کردن برنامه، تابع start_ کال می‌شه و می‌رسه به خط ۷۸ و تابع f1 رو کال می‌کنه، مقدار x30 میشه 7c (یعنی خطی که بعد از برنچ‌لیک داخل start_ هست). تابع f1 تابع f2 رو کال می‌کنه و مقدارش x30 میشه 8c که میشه خط ret در تابع f1. تابع f2 کال می‌شه و می‌رسه به ret خودش و بر می‌گرده به آدرس 8c. آدرس 8c همون خط ret تابع f1 هست پس تلاش می‌کنه بره به آدرسی که داخل x30 هست یعنی ‍8c و اون آدرس خودشه، پس دوباره ret کال می‌شه و می‌خواد بره به 8c و این ماجرا تا ابد ادامه پیدا می‌کنه.

حالا برگردیم به مسئله‌مون. فرض کنیم ما تابع ۱ و ۲ و ۳ رو کال می‌کنیم. انتظار داریم بعد از اتمام ۳، ادامه‌ی تابع ۲ ران شه و بعد از اتمام اون، ادامه‌ی تابع ۱ ران شه. یعنی انتظار ما یه فرآیند LIFOعه و این همون استکه. داخل آرم۶۴ یه پوینتر مخصوص وجود داره به اسم SP که داره همیشه به آخرین فریم استک اشاره می‌کنه و ما می‌تونیم ازش برای پوش و پاپ کردن دیتا استفاده کنیم. مشکل ما داخل کد بالا این بود که از با کال شدن تابع سوم و برگشتنش، ما LR رو داخل تابع دوم گم می‌کردیم و پس الان کافیه این رجیستر رو قبل از رفتن به تابع بعدی ذخیره کنیم و قبل از صدا زدن RET تابع خودمون اون رو برگردونیم. قبل از اینکه بریم و کد رو تغییر بدیم فقط باید به این نکته توجه کنیم که داخل آرم۶۴، استک رو به پایین رشد می‌کنه (موقع پوش باید آدرس رو کم کرد و موقع پاپ آدرس رو اضافه کرد).

 1.global _start  
 2  
 3.section .text  
 4_start:  
 5  bl f1  
 6  
 7  mov x8, #93  
 8  mov x0, #0  
 9  svc #0  
10  
11f1:  
12  stp x29, x30, [sp, #-16]!  
13  bl f2  
14  ldp x29, x30, [sp], #16  
15  ret  
16  
17f2:  
18  ret

داخل کد بالا ما x29 رو هم دخیره کردیم. این رجیستر فریم پورینتره (FP) و همیشه به آخرین فریم استک داره اشاره می‌کنه. واجب نیست بروز کردنش ولی اگه نیازه که از ابزارهای دیباگ استفاده کنیم، باید داشته باشیمش. و رانش:

 1meysam@ubuntu:~/www/test/arm/netmask$ gdb ./ctc
 2Reading symbols from ./ctc...
 3(gdb) break _start
 4Breakpoint 1 at 0x400078: file ctc.s, line 5.
 5(gdb) run
 6Starting program: /home/meysam/www/test/arm/netmask/ctc
 7Breakpoint 1, _start () at ctc.s:5
 85	  bl f1
 9(gdb) info registers x30
10x30            0x0                 0
11(gdb) s
12f1 () at ctc.s:12
1312	  stp x29, x30, [sp, #-16]!
14(gdb) s
1513	  bl f2
16(gdb) s
17f2 () at ctc.s:18
1818	  ret
19(gdb) info registers x30
20x30            0x400090            4194448
21(gdb) s
22f1 () at ctc.s:14
2314	  ldp x29, x30, [sp], #16
24(gdb) info registers x30
25x30            0x400090            4194448
26(gdb) s
2715	  ret
28(gdb) info registers x30
29x30            0x40007c            4194428
30(gdb) s
31_start () at ctc.s:7
327	  mov x8, #93
33(gdb) info registers x30
34x30            0x40007c            4194428
35(gdb) s
368	  mov x0, #0
37(gdb)
389	  svc #0
39(gdb)
40[Inferior 1 (process 79060) exited normally]

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

پردازش IP

برگردیم به مسئله‌ی خودمون. اول تلاش می‌کنیم یه رشته‌ی ۱۵ کاراکتری بخونیم و تبدیلش کنیم به یه عدد صحیح ۳۲ بیتی. یعنی ۸ بیت رو که خوندیم و به نقطه رسیدیم، ۸ بیت شیفت میدیم به چپ و میریم سراغ بعدی. از اینجا به بعد هم تلاش می‌کنیم که از اون جدول و نقش‌هایی که رجیسترها دارن استفاده کنیم.

 1.global _start  
 2  
 3.section .data  
 4  cidrIP: .space 15  
 5  ip: .word 0  
 6  
 7.section .text  
 8_start:  
 9  // read from input  
10  mov w8, #63  
11  mov w0, #0  
12  ldr x1, =cidrIP  
13  mov w2, #15  
14  svc #0  
15  
16  mov x0, x1  
17  bl process  
18  
19  // print  
20  mov w8, #64  
21  mov w0, #1  
22  ldr w1, =ip  
23  mov w2, #4  
24  svc #0  
25  
26  // exit  
27  mov w8, #93  
28  mov w0, #0  
29  svc #0  
30  
31process:  
32  stp x29, x30, [sp, #-16]!  
33  mov w10, #0 // temp octet  
34  loop:  
35  ldrb w11, [x0], #1  
36  cmp w11, #'.'  
37  beq store_and_loop  
38  cmp w11, #'\n'  
39  beq store_and_exit  
40  cmp w11, #'\0'  
41  beq store_and_exit  
42  bl char_to_num  
43  b loop  
44  
45store_and_exit:  
46  bl store  
47  ldp x29, x30, [sp], #16  
48  ret  
49  
50store_and_loop:  
51  bl store  
52  b loop  
53  
54store:  
55  ldr x14, =ip  
56  ldr w15, [x14]  
57  lsl w15, w15, #8  
58  orr w15, w15, w10  
59  mov w10, #0  
60  str w15, [x14]  
61  ret  
62  
63char_to_num:  
64  sub w12, w11, #'0'  
65  mov w13, #10  
66  mul w10, w10, w13  
67  add w10, w10, w12  
68  ret

اگه برنامه رو ران کنیم و 65.66.67.68 رو به عنوان آیپی (هر اکتت میشه اسکی حروف انگلیسی اول تا چهارم) بهش بدیم، نتیجه میشه این:

1meysam@ubuntu:~/www/test/arm/netmask$ strace ./main
2execve("./main", ["./main"], 0xffffe791dcf0 /* 26 vars */) = 0
3read(0, 65.66.67.68
4"65.66.67.68\n", 15)            = 12
5write(1, "DCBA", 4DCBA)                     = 4
6exit(0)                                 = ?
7+++ exited with 0 +++

که همونطور که مشخصه رشته‌ی DCBA رو چاپ می‌کنه که نتیجه می‌ده پردازش ما درست بوده.

چاب IP

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

 1.global _start  
 2  
 3.section .data  
 4  ip_str: .space 16  
 5  
 6.section .text  
 7_start:  
 8  movz w0, #0x0001           // 0.1
 9  movk w0, #0x7f00, lsl #16  // 127.0.0.1
10  bl print_as_ip  
11  mov w8, #93  
12  mov w0, #0  
13  svc #0  
14  
15print_as_ip:  
16  stp x29, x30, [sp, #-16]!  
17  ldr x1, =ip_str  
18  bl word_to_ip  
19  ldr x0, =ip_str  
20  bl str_len  
21  mov w2, w0  
22  mov w8, #64  
23  mov w0, #1  
24  ldr x1, =ip_str  
25  svc #0  
26  ldp x29, x30, [sp], #16  
27  ret  
28  
29word_to_ip:  
30  stp x29, x30, [sp, #-16]!  
31  mov x7, x1  
32  mov w2, #0xFF  
33  start_word_to_ip:  
34    and w3, w0, w2 // get the first octet  
35    lsr w0, w0, #8  
36    mov w4, #10  
37    loop_word_to_ip:  
38      udiv w5, w3, w4     // w5 <- w3 / 10 <- unsigned division  
39      mov w6, w5          // temp for future check  
40      msub w5, w5, w4, w3 // w5 <- w3 - ((w3 / 10) * 10) <- reminder  
41      mov w3, w6  
42      add w5, w5, #'0'  
43      strb w5, [x1], #1  
44      cbz w6, add_dot  
45      b loop_word_to_ip  
46  add_dot:  
47    cbz w0, done_word_to_ip // if there is no octet remained, return  
48    mov w3, #'.'  
49    strb w3, [x1], #1  
50    b start_word_to_ip  
51  done_word_to_ip:  
52    mov x0, x7  
53    mov x7, x1  
54    sub x1, x1, #1  
55    bl rev_string  
56    mov w5, #'\n'  
57    strb w5, [x7]  
58    ldp x29, x30, [sp], #16  
59    ret  
60  
61rev_string:  
62  start_rev_string:  
63    cmp x0, x1  
64    bge end_rev_string  
65    ldrb w2, [x0]  
66    ldrb w3, [x1]  
67    strb w2, [x1]  
68    strb w3, [x0]  
69    add x0, x0, #1  
70    sub x1, x1, #1  
71    b start_rev_string  
72  end_rev_string:  
73    ret  
74  
75str_len:  
76  stp x29, x30, [sp, #-16]!  
77  mov w1, 0  
78  start_str_len:  
79    ldrb w2, [x0]  
80    cmp w2, #0x00  
81    beq end_str_len  
82    cmp w2, #'\n'  
83    beq end_with_lf  
84    add w1, w1, #1  
85    add x0, x0, #1  
86    b start_str_len  
87  end_with_lf:  
88    add w1, w1, #1  
89  end_str_len:  
90    mov w0, w1  
91    ldp x29, x30, [sp], #16  
92    ret

و اجراش:

1meysam@ubuntu:~/www/test/arm/netmask$ strace ./word_to_ip
2execve("./word_to_ip", ["./word_to_ip"], 0xffffe4bf9ab0 /* 26 vars */) = 0
3write(1, "127.0.0.1\n", 10127.0.0.1
4)             = 10
5exit(0)                                 = ?
6+++ exited with 0 +++

البته قرار بود این کار رو تو بخش آخر انجام بدیم ولی خب الان نیاز بود برای پیش رفتن :)). و در نهایت گرفتن IP از ورودی و چاپش:

  1.global _start  
  2  
  3.section .data  
  4  cidrIP: .space 18  
  5  ip: .word 0  
  6  mask: .byte 0  
  7  
  8.section .text  
  9_start:  
 10  // read from input  
 11  mov w8, #63  
 12  mov w0, #0  
 13  ldr x1, =cidrIP  
 14  mov w2, #15  
 15  svc #0  
 16  
 17  mov x0, x1  
 18  bl process  
 19  
 20  // print  
 21  bl print  
 22  
 23  // exit  
 24  mov w8, #93  
 25  mov w0, #0  
 26  svc #0  
 27  
 28print:  
 29  stp x29, x30, [sp, #-16]!  
 30  ldr x0, =ip  
 31  bl print_as_ip  
 32  ldp x29, x30, [sp], #16  
 33  ret  
 34  
 35print_as_ip:  
 36  stp x29, x30, [sp, #-16]!  
 37  mov x9, x0  
 38  mov x1, x0  
 39  ldr x0, [x0]  
 40  bl word_to_ip  
 41  mov x0, x9  
 42  bl str_len  
 43  mov w2, w0  
 44  mov w8, #64  
 45  mov w0, #1  
 46  mov x1, x9  
 47  svc #0  
 48  ldp x29, x30, [sp], #16  
 49  ret  
 50  
 51process:  
 52  stp x29, x30, [sp, #-16]!  
 53  mov w10, #0 // temp octet  
 54  ldr x14, =ip  
 55  mov w16, #8  
 56  loop:  
 57    ldrb w11, [x0], #1  
 58    cmp w11, #'.'  
 59    beq store_and_loop  
 60    cmp w11, #'/'  
 61    beq update_to_store_mask  
 62    cmp w11, #'\n'  
 63    beq store_and_exit  
 64    cmp w11, #'\0'  
 65    beq store_and_exit  
 66    bl char_to_num  
 67    b loop  
 68  
 69store_and_exit:  
 70  bl store  
 71  ldp x29, x30, [sp], #16  
 72  ret  
 73  
 74store_and_loop:  
 75  bl store  
 76  b loop  
 77  
 78store:  
 79  ldr w15, [x14]  
 80  lsl w15, w15, #8  
 81  orr w15, w15, w10  
 82  mov w10, #0  
 83  str w15, [x14]  
 84  ret  
 85  
 86char_to_num:  
 87  sub w12, w11, #'0'  
 88  mov w13, #10  
 89  mul w10, w10, w13  
 90  add w10, w10, w12  
 91  ret  
 92  
 93update_to_store_mask:  
 94  bl store  
 95  ldr x14, =mask  
 96  mov w16, #4  
 97  mov w10, #0  
 98  b loop  
 99  
100word_to_ip:  
101  stp x29, x30, [sp, #-16]!  
102  mov x7, x1  
103  mov w2, #0xFF  
104  start_word_to_ip:  
105    and w3, w0, w2 // get the first octet  
106    lsr w0, w0, #8  
107    mov w4, #10  
108    loop_word_to_ip:  
109      udiv w5, w3, w4     // w5 <- w3 / 10 <- unsigned division  
110      mov w6, w5          // temp for future check  
111      msub w5, w5, w4, w3 // w5 <- w3 - ((w3 / 10) * 10) <- reminder  
112      mov w3, w6  
113      add w5, w5, #'0'  
114      strb w5, [x1], #1  
115      cbz w6, add_dot  
116      b loop_word_to_ip  
117  add_dot:  
118    cbz w0, done_word_to_ip // if there is no octet remained, return  
119    mov w3, #'.'  
120    strb w3, [x1], #1  
121    b start_word_to_ip  
122  done_word_to_ip:  
123    mov x0, x7  
124    mov x7, x1  
125    sub x1, x1, #1  
126    bl rev_string  
127    mov w5, #'\n'  
128    strb w5, [x7]  
129    ldp x29, x30, [sp], #16  
130    ret  
131  
132rev_string:  
133  start_rev_string:  
134    cmp x0, x1  
135    bge end_rev_string  
136    ldrb w2, [x0]  
137    ldrb w3, [x1]  
138    strb w2, [x1]  
139    strb w3, [x0]  
140    add x0, x0, #1  
141    sub x1, x1, #1  
142    b start_rev_string  
143  end_rev_string:  
144    ret  
145  
146str_len:  
147  stp x29, x30, [sp, #-16]!  
148  mov w1, 0  
149  start_str_len:  
150    ldrb w2, [x0]  
151    cmp w2, #0x00  
152    beq end_str_len  
153    cmp w2, #'\n'  
154    beq end_with_lf  
155    add w1, w1, #1  
156    add x0, x0, #1  
157    b start_str_len  
158  end_with_lf:  
159    add w1, w1, #1  
160  end_str_len:  
161    mov w0, w1  
162    ldp x29, x30, [sp], #16  
163    ret

و اجراش:

1meysam@ubuntu:~/www/test/arm/netmask$ strace ./main
2execve("./main", ["./main"], 0xffffe6bcc340 /* 26 vars */) = 0
3read(0, 65.66.67.68/18
4"65.66.67.68/18\n", 15)         = 15
5write(1, "65.66.67.68\n", 1265.66.67.68
6)           = 12
7exit(0)                                 = ?
8+++ exited with 0 +++

نت‌مسک رو بسازه (و بقیه‌ی IPها رو)

روزی که شروع کردم ۱۵ اسفند بود و قرار بود تو این بخش فقط نت‌مسک رو بسازیم و بعد در بخش بعد بقیه‌ی آیپی‌ها رو بسازیم، خیلی گنگ بود برام که قراره به کجا بریم و اینطور شکسته بودم مسئله رو و الان که ۲۱ اسفنده می‌بینم دیگه چیزی نمونده. پس تو همین بخش هر چهارتا آیپی رو می‌سازیم و چاپ می‌کنیم. از روی بخش بعدی در دسته‌بندی می‌پریم و می‌ریم برای خوشگل چاپ کردن آدرس‌ها در قسمت بعد.

چیزی برای توضیح نمونده. مسک رو که پردازش کردیم و داریم، یه ورد رو نقیض می‌کنیم که همه‌ی بیت‌هاش بشن یک، به اندازه‌ی ۳۲ منهای مسک شیفت می‌دیم به چپ میشه نت‌مسک‌مون. اند با آدرس می‌شه نتورک‌آدرس، نقیض نت‌مسک رو با آدرس یا می‌کنیم می‌شه برودکست آدرس و از دوتای آخری هم مثبت منفی یک میشه اولین و آخرین آدرس‌های قابل استفاده‌ی شبکه.

  1.global _start  
  2  
  3.section .data  
  4  cidrIP: .space 18  
  5  ip: .space 15  
  6  mask: .byte 0  
  7  netmask: .space 15  
  8  network: .space 15  
  9  broadcast: .space 15  
 10  first_usable: .space 15  
 11  last_usable: .space 15  
 12  
 13.section .text  
 14_start:  
 15  // read from input  
 16  mov w8, #63  
 17  mov w0, #0  
 18  ldr x1, =cidrIP  
 19  mov w2, #15  
 20  svc #0  
 21  
 22  mov x0, x1  
 23  bl process  
 24  bl calculate_netmask  
 25  bl calculate_addresses  
 26  
 27  // print  
 28  bl print  
 29  
 30  // exit  
 31  mov w8, #93  
 32  mov w0, #0  
 33  svc #0  
 34  
 35print:  
 36  stp x29, x30, [sp, #-16]!  
 37  ldr x0, =ip  
 38  bl print_as_ip  
 39  ldr x0, =network  
 40  bl print_as_ip  
 41  ldr x0, =netmask  
 42  bl print_as_ip  
 43  ldr x0, =broadcast  
 44  bl print_as_ip  
 45  ldr x0, =first_usable  
 46  bl print_as_ip  
 47  ldr x0, =last_usable  
 48  bl print_as_ip  
 49  ldp x29, x30, [sp], #16  
 50  ret  
 51  
 52print_as_ip:  
 53  stp x29, x30, [sp, #-16]!  
 54  mov x9, x0  
 55  mov x1, x0  
 56  ldr x0, [x0]  
 57  bl word_to_ip  
 58  mov x0, x9  
 59  bl str_len  
 60  mov w2, w0  
 61  mov w8, #64  
 62  mov w0, #1  
 63  mov x1, x9  
 64  svc #0  
 65  ldp x29, x30, [sp], #16  
 66  ret  
 67  
 68calculate_netmask:  
 69  ldr x0, =mask  
 70  ldrb w1, [x0]  
 71  mov w3, #32  
 72  sub w1, w3, w1  
 73  mov w3, #0  
 74  mvn w3, w3  
 75  lsl w3, w3, w1  
 76  ldr x2, =netmask  
 77  str w3, [x2]  
 78  ret  
 79  
 80calculate_addresses:  
 81  ldr x0, =netmask  
 82  ldr w0, [x0]  
 83  ldr x1, =ip  
 84  ldr w1, [x1]  
 85  mov w2, w1  
 86  and w2, w2, w0 // network = netmask & ip  
 87  ldr x3, =network  
 88  str w2, [x3]  
 89  add w2, w2, #1  
 90  ldr x3, =first_usable  
 91  str w2, [x3]  
 92  mov w2, w1  
 93  mvn w3, w0  
 94  orr w3, w3, w1  
 95  ldr x2, =broadcast  
 96  str w3, [x2]  
 97  sub w3, w3, #1  
 98  ldr x2, =last_usable  
 99  str w3, [x2]  
100  ret  
101  
102process:  
103  stp x29, x30, [sp, #-16]!  
104  mov w10, #0 // temp octet  
105  ldr x14, =ip  
106  mov w16, #8  
107  loop:  
108    ldrb w11, [x0], #1  
109    cmp w11, #'.'  
110    beq store_and_loop  
111    cmp w11, #'/'  
112    beq update_to_store_mask  
113    cmp w11, #'\n'  
114    beq store_and_exit  
115    cmp w11, #'\0'  
116    beq store_and_exit  
117    bl char_to_num  
118    b loop  
119  
120store_and_exit:  
121  bl store  
122  ldp x29, x30, [sp], #16  
123  ret  
124  
125store_and_loop:  
126  bl store  
127  b loop  
128  
129store:  
130  ldr w15, [x14]  
131  lsl w15, w15, #8  
132  orr w15, w15, w10  
133  mov w10, #0  
134  str w15, [x14]  
135  ret  
136  
137char_to_num:  
138  sub w12, w11, #'0'  
139  mov w13, #10  
140  mul w10, w10, w13  
141  add w10, w10, w12  
142  ret  
143  
144update_to_store_mask:  
145  bl store  
146  ldr x14, =mask  
147  mov w16, #4  
148  mov w10, #0  
149  b loop  
150  
151word_to_ip:  
152  stp x29, x30, [sp, #-16]!  
153  mov x7, x1  
154  mov w2, #0xFF  
155  start_word_to_ip:  
156    and w3, w0, w2 // get the first octet  
157    lsr w0, w0, #8  
158    mov w4, #10  
159    loop_word_to_ip:  
160      udiv w5, w3, w4     // w5 <- w3 / 10 <- unsigned division  
161      mov w6, w5          // temp for future check  
162      msub w5, w5, w4, w3 // w5 <- w3 - ((w3 / 10) * 10) <- reminder  
163      mov w3, w6  
164      add w5, w5, #'0'  
165      strb w5, [x1], #1  
166      cbz w6, add_dot  
167      b loop_word_to_ip  
168  add_dot:  
169    cbz w0, done_word_to_ip // if there is no octet remained, return  
170    mov w3, #'.'  
171    strb w3, [x1], #1  
172    b start_word_to_ip  
173  done_word_to_ip:  
174    mov x0, x7  
175    mov x7, x1  
176    sub x1, x1, #1  
177    bl rev_string  
178    mov w5, #'\n'  
179    strb w5, [x7]  
180    ldp x29, x30, [sp], #16  
181    ret  
182  
183rev_string:  
184  start_rev_string:  
185    cmp x0, x1  
186    bge end_rev_string  
187    ldrb w2, [x0]  
188    ldrb w3, [x1]  
189    strb w2, [x1]  
190    strb w3, [x0]  
191    add x0, x0, #1  
192    sub x1, x1, #1  
193    b start_rev_string  
194  end_rev_string:  
195    ret  
196  
197str_len:  
198  stp x29, x30, [sp, #-16]!  
199  mov w1, 0  
200  start_str_len:  
201    ldrb w2, [x0]  
202    cmp w2, #0x00  
203    beq end_str_len  
204    cmp w2, #'\n'  
205    beq end_with_lf  
206    add w1, w1, #1  
207    add x0, x0, #1  
208    b start_str_len  
209  end_with_lf:  
210    add w1, w1, #1  
211  end_str_len:  
212    mov w0, w1  
213    ldp x29, x30, [sp], #16  
214    ret

و اجراش:

1meysam@ubuntu:~/www/test/arm/netmask$ ./main
265.66.67.68/23
365.66.67.68
465.66.66.0
5255.255.254.0
665.66.67.255
765.66.66.1
865.66.67.254

کد نکته‌ی خاصی نداره واقعاً. لاجرم برای افزایش هیجان میریم خروجی رو قشنگ کنیم.

چاپ کنیم حاصل رو

حاصل که چاپ شد تو مرحله‌ی قبلی. اینجا بیایم خوشگلش کنیم. چیزی که دارم فکر می‌کنم اینه که قبل از چاپ هر کدوم یه رشته بیاد بگه که چیه (و طبیعتاً باید لیترال تعریف کنیم ولی می‌خوام طولش داینامیک حساب شه) و اینکه دارم فکر می‌کنم باحاله اگه رنگ رو هم وارد ماجرا کنیم. فعلاً بریم برای چاپ برچسب هر کدوم از آیپی‌ها.

چاپ برچسب برای آدرس‌ها

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

  1.global _start  
  2  
  3.section .rodata  
  4  cidrIP_label: .ascii "Please enter IP/mask: \0"  
  5  ip_label: .ascii "IP: \0"  
  6  netmask_label: .ascii "Netmask: \0"  
  7  network_label: .ascii "Subnet: \0"  
  8  broadcast_label: .ascii "Broadcast: \0"  
  9  first_usable_label: .ascii "First usable address: \0"  
 10  last_usable_label: .ascii "Last usable address: \0"  
 11  
 12.section .data  
 13  cidrIP: .space 18  
 14  ip: .space 15  
 15  mask: .byte 0  
 16  netmask: .space 15  
 17  network: .space 15  
 18  broadcast: .space 15  
 19  first_usable: .space 15  
 20  last_usable: .space 15  
 21  
 22.section .text  
 23_start:  
 24  ldr x0, =cidrIP_label  
 25  bl print_label  
 26  mov w8, #63  
 27  mov w0, #0  
 28  ldr x1, =cidrIP  
 29  mov w2, #15  
 30  svc #0  
 31  
 32  mov x0, x1  
 33  bl process  
 34  bl calculate_netmask  
 35  bl calculate_addresses  
 36  
 37  // print  
 38  bl print  
 39  
 40  // exit  
 41  mov w8, #93  
 42  mov w0, #0  
 43  svc #0  
 44  
 45print:  
 46  stp x29, x30, [sp, #-16]!  
 47  ldr x0, =ip_label  
 48  bl print_label  
 49  ldr x0, =ip  
 50  bl print_as_ip  
 51  ldr x0, =netmask_label  
 52  bl print_label  
 53  ldr x0, =netmask  
 54  bl print_as_ip  
 55  ldr x0, =network_label  
 56  bl print_label  
 57  ldr x0, =network  
 58  bl print_as_ip  
 59  ldr x0, =broadcast_label  
 60  bl print_label  
 61  ldr x0, =broadcast  
 62  bl print_as_ip  
 63  ldr x0, =first_usable_label  
 64  bl print_label  
 65  ldr x0, =first_usable  
 66  bl print_as_ip  
 67  ldr x0, =last_usable_label  
 68  bl print_label  
 69  ldr x0, =last_usable  
 70  bl print_as_ip  
 71  ldp x29, x30, [sp], #16  
 72  ret  
 73  
 74print_label:  
 75  stp x29, x30, [sp, #-16]!  
 76  mov w8, #64  
 77  mov x9, x0  
 78  bl str_len  
 79  mov w2, w0  
 80  mov x1, x9  
 81  mov w0, #1  
 82  svc #0  
 83  ldp x29, x30, [sp], #16  
 84  ret  
 85  
 86print_as_ip:  
 87  stp x29, x30, [sp, #-16]!  
 88  mov x9, x0  
 89  mov x1, x0  
 90  ldr x0, [x0]  
 91  bl word_to_ip  
 92  mov x0, x9  
 93  bl str_len  
 94  mov w2, w0  
 95  mov w8, #64  
 96  mov w0, #1  
 97  mov x1, x9  
 98  svc #0  
 99  ldp x29, x30, [sp], #16  
100  ret  
101  
102calculate_netmask:  
103  ldr x0, =mask  
104  ldrb w1, [x0]  
105  mov w3, #32  
106  sub w1, w3, w1  
107  mov w3, #0  
108  mvn w3, w3  
109  lsl w3, w3, w1  
110  ldr x2, =netmask  
111  str w3, [x2]  
112  ret  
113  
114calculate_addresses:  
115  ldr x0, =netmask  
116  ldr w0, [x0]  
117  ldr x1, =ip  
118  ldr w1, [x1]  
119  mov w2, w1  
120  and w2, w2, w0 // network = netmask & ip  
121  ldr x3, =network  
122  str w2, [x3]  
123  add w2, w2, #1  
124  ldr x3, =first_usable  
125  str w2, [x3]  
126  mov w2, w1  
127  mvn w3, w0  
128  orr w3, w3, w1  
129  ldr x2, =broadcast  
130  str w3, [x2]  
131  sub w3, w3, #1  
132  ldr x2, =last_usable  
133  str w3, [x2]  
134  ret  
135  
136process:  
137  stp x29, x30, [sp, #-16]!  
138  mov w10, #0 // temp octet  
139  ldr x14, =ip  
140  mov w16, #8  
141  loop:  
142    ldrb w11, [x0], #1  
143    cmp w11, #'.'  
144    beq store_and_loop  
145    cmp w11, #'/'  
146    beq update_to_store_mask  
147    cmp w11, #'\n'  
148    beq store_and_exit  
149    cmp w11, #'\0'  
150    beq store_and_exit  
151    bl char_to_num  
152    b loop  
153  
154store_and_exit:  
155  bl store  
156  ldp x29, x30, [sp], #16  
157  ret  
158  
159store_and_loop:  
160  bl store  
161  b loop  
162  
163store:  
164  ldr w15, [x14]  
165  lsl w15, w15, #8  
166  orr w15, w15, w10  
167  mov w10, #0  
168  str w15, [x14]  
169  ret  
170  
171char_to_num:  
172  sub w12, w11, #'0'  
173  mov w13, #10  
174  mul w10, w10, w13  
175  add w10, w10, w12  
176  ret  
177  
178update_to_store_mask:  
179  bl store  
180  ldr x14, =mask  
181  mov w16, #4  
182  mov w10, #0  
183  b loop  
184  
185word_to_ip:  
186  stp x29, x30, [sp, #-16]!  
187  mov x7, x1  
188  mov w2, #0xFF  
189  start_word_to_ip:  
190    and w3, w0, w2 // get the first octet  
191    lsr w0, w0, #8  
192    mov w4, #10  
193    loop_word_to_ip:  
194      udiv w5, w3, w4     // w5 <- w3 / 10 <- unsigned division  
195      mov w6, w5          // temp for future check  
196      msub w5, w5, w4, w3 // w5 <- w3 - ((w3 / 10) * 10) <- reminder  
197      mov w3, w6  
198      add w5, w5, #'0'  
199      strb w5, [x1], #1  
200      cbz w6, add_dot  
201      b loop_word_to_ip  
202  add_dot:  
203    cbz w0, done_word_to_ip // if there is no octet remained, return  
204    mov w3, #'.'  
205    strb w3, [x1], #1  
206    b start_word_to_ip  
207  done_word_to_ip:  
208    mov x0, x7  
209    mov x7, x1  
210    sub x1, x1, #1  
211    bl rev_string  
212    mov w5, #'\n'  
213    strb w5, [x7]  
214    ldp x29, x30, [sp], #16  
215    ret  
216  
217rev_string:  
218  start_rev_string:  
219    cmp x0, x1  
220    bge end_rev_string  
221    ldrb w2, [x0]  
222    ldrb w3, [x1]  
223    strb w2, [x1]  
224    strb w3, [x0]  
225    add x0, x0, #1  
226    sub x1, x1, #1  
227    b start_rev_string  
228  end_rev_string:  
229    ret  
230  
231str_len:  
232  stp x29, x30, [sp, #-16]!  
233  mov w1, 0  
234  start_str_len:  
235    ldrb w2, [x0]  
236    cmp w2, #0x00  
237    beq end_str_len  
238    cmp w2, #'\n'  
239    beq end_with_lf  
240    add w1, w1, #1  
241    add x0, x0, #1  
242    b start_str_len  
243  end_with_lf:  
244    add w1, w1, #1  
245  end_str_len:  
246    mov w0, w1  
247    ldp x29, x30, [sp], #16  
248    ret

و اجراش:

1meysam@ubuntu:~/www/test/arm/netmask$ ./main
2Please enter IP/mask: 23.24.25.26/14
3IP: 23.24.25.26
4Netmask: 255.252.0.0
5Subnet: 23.24.0.0
6Broadcast: 23.27.255.255
7First usable address: 23.24.0.1
8Last usable address: 23.27.255.254

الان نمی‌دونم چیو رنگی پنگی کنم، اگه به ذهنم رسید اینجا رو کامل می‌کنم.

کد چجوری داخل CPU ران میشه؟

به نظرم این رو انگشت‌نکرده نباید تموم کنیم ماجرا رو. فرض کنیم می‌خوایم بفهمیم این کد بعد از اینکه ما اجراش کردیم، چه اتفاقی براش میفته:

1.global _start  
2  
3_start:  
4  mov x0, #128  
5  add x2, x1, x0

بعد از اینکه کد رو با as -o ldr.o ldr.s (فرض کنیم اسم فایل‌مون ldr.s هست) کامپایل کردیم، یه فایل آبجکت داریم (که در مرحله‌ی بعد از اون استفاده می‌کنیم و لینک پینک‌ها رو با ld انجام می‌دیم و فایل اجرایی رو می‌سازیم) که با استفاده از دستور objdump میشه محتویاتش رو دید:

 1meysam@ubuntu:~/www/test/arm/netmask$ objdump -D ldr.o
 2
 3ldr.o:     file format elf64-littleaarch64
 4
 5
 6Disassembly of section .text:
 7
 80000000000000000 <_start>:
 9   0:	d2801000 	mov	x0, #0x80                  	// #128
10   4:	8b000022 	add	x2, x1, x0

تو قسمت آخر، هر خط اسمبلی‌ای که نوشته بودیم، نوشته شده: در ستون اول آفست اومده (بایت صفر خط اول، بایت ۴ خط دوم و الی آخر. یادمونه که هر دستور آرم۶۴ اندازه‌ش ۳۲بیته یا ۴ بایت)، بعدش یه عدد هگز اومده و بعدش دستوری که می‌خواستیم تا ران شه. در نهایت آخر خط هم اگه لیترالی داشتیم به صورت کامنت نشون داده شده. قسمت مهم ماجرا برای عنوان این زیربخش در واقع اون عددهای هگزن. و اگه تبدیلشون کنیم به باینری اینطور چیزی میشن:

1meysam@ubuntu:~/www/test/arm/netmask$ echo "obase=2; ibase=16; D2801000" | bc | sed 's/.\{4\}/& /g'
21101 0010 1000 0000 0001 0000 0000 0000
3meysam@ubuntu:~/www/test/arm/netmask$ echo "obase=2; ibase=16; 8B000022" | bc | sed 's/.\{4\}/& /g'
41000 1011 0000 0000 0000 0000 0010 0010

که اگه اون داکیومنت ISR رو ببینیم (صفحه ۵۲۸)، قالب دستور movz اینطور چیزیه:

1   3                   2                   1                   0
2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 
3+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4|s|1 0|1 0 0 1 0 1|h w|               imm16           |         |
5+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

که پترن مچ می‌شه با اون چیزی که برای نمایش باینری‌مون هست (از بیت ۳۰ تا ۲۳ که بیت‌های هاردکد شده هستن و طبعاً ثابتن):

1   3                   2                   1                   0
2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 
3+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4|s|1 0|1 0 0 1 0 1|h w|               imm16           |   RD    |
5+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
6|1|1 0|1 0 0 1 0 1|0 0|0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0|0 0 0 0 0|
7+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

همونطور که مشخصه imm16 که مقدارش ۱۲۸ هست (بیت ۷) و RD هم که شماره‌ی رجیستر رو مشخص می‌کنه صفر هست، بیت ۳۱ هم چون ۱عه یعنی رجیستر ما ۶۴بیتی‌ه.

برای ورد دومی هم ماجرا به همین منواله (چون می‌دونیم ADD هست دستور، تو داکیومنت برای پترنش دنبال این دستور می‌گردیم، صفحه‌ی ۲۰):

1   3                   2                   1                   0
2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 
3+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4|s|0|0|0 1 0 1 1|sft|0|    Rm   |    imm6   |    Rn   |    Rd   |
5+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
6|1|0|0|0 1 0 1 1|0 0|0|0 0 0 0 0|0 0 0 0 0 0|0 0 0 0 1|0 0 0 1 0|
7+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

در این مورد هم بیت ۳۱ مشخص می‌کنه که با ۶۴ بیت رجیستر کار داریم، رجیستر مقصدمون در رنج ۰ تا ۴ هست که میشه X2، عملوند اول در رنج ۵ تا ۹ هست که میشه X1 و در نهایت عملوند دوم هم در رنج ۱۶ تا ۲۰ هستن که مقدارش صفره و میشه X0.

پس با داشتن این ۳۲بیت‌ها میشه برنامه‌مون رو ران کرد. پس حالا برای اینکه بفهمیم بعد از ساختن فایل اجرایی با ld -o ldr ldr.o چه اتفاقی سر ELF (مخفف Executable Linux File) چی میاد، اون رو باز می‌کنیم که ببینیم چی توشه:

 1meysam@ubuntu:~/www/test/arm/netmask$ readelf -a ./ldr
 2ELF Header:
 3  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
 4  Class:                             ELF64
 5  Data:                              2s complement, little endian
 6  Version:                           1 (current)
 7  OS/ABI:                            UNIX - System V
 8  ABI Version:                       0
 9  Type:                              EXEC (Executable file)
10  Machine:                           AArch64
11  Version:                           0x1
12  Entry point address:               0x400078
13  Start of program headers:          64 (bytes into file)
14  Start of section headers:          520 (bytes into file)
15  Flags:                             0x0
16  Size of this header:               64 (bytes)
17  Size of program headers:           56 (bytes)
18  Number of program headers:         1
19  Size of section headers:           64 (bytes)
20  Number of section headers:         5
21  Section header string table index: 4
22
23Section Headers:
24  [Nr] Name              Type             Address           Offset
25       Size              EntSize          Flags  Link  Info  Align
26  [ 0]                   NULL             0000000000000000  00000000
27       0000000000000000  0000000000000000           0     0     0
28  [ 1] .text             PROGBITS         0000000000400078  00000078
29       0000000000000008  0000000000000000  AX       0     0     4
30  [ 2] .symtab           SYMTAB           0000000000000000  00000080
31       0000000000000120  0000000000000018           3     4     8
32  [ 3] .strtab           STRTAB           0000000000000000  000001a0
33       0000000000000044  0000000000000000           0     0     1
34  [ 4] .shstrtab         STRTAB           0000000000000000  000001e4
35       0000000000000021  0000000000000000           0     0     1
36Key to Flags:
37  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
38  L (link order), O (extra OS processing required), G (group), T (TLS),
39  C (compressed), x (unknown), o (OS specific), E (exclude),
40  D (mbind), p (processor specific)
41
42There are no section groups in this file.
43
44Program Headers:
45  Type           Offset             VirtAddr           PhysAddr
46                 FileSiz            MemSiz              Flags  Align
47  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
48                 0x0000000000000080 0x0000000000000080  R E    0x10000
49
50 Section to Segment mapping:
51  Segment Sections...
52   00     .text
53
54There is no dynamic section in this file.
55
56There are no relocations in this file.
57
58The decoding of unwind sections for machine type AArch64 is not currently supported.
59
60Symbol table '.symtab' contains 12 entries:
61   Num:    Value          Size Type    Bind   Vis      Ndx Name
62     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
63     1: 0000000000400078     0 SECTION LOCAL  DEFAULT    1 .text
64     2: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS ldr.o
65     3: 0000000000400078     0 NOTYPE  LOCAL  DEFAULT    1 $x
66     4: 0000000000410080     0 NOTYPE  GLOBAL DEFAULT    1 _bss_end__
67     5: 0000000000410080     0 NOTYPE  GLOBAL DEFAULT    1 __bss_start__
68     6: 0000000000410080     0 NOTYPE  GLOBAL DEFAULT    1 __bss_end__
69     7: 0000000000400078     0 NOTYPE  GLOBAL DEFAULT    1 _start
70     8: 0000000000410080     0 NOTYPE  GLOBAL DEFAULT    1 __bss_start
71     9: 0000000000410080     0 NOTYPE  GLOBAL DEFAULT    1 __end__
72    10: 0000000000410080     0 NOTYPE  GLOBAL DEFAULT    1 _edata
73    11: 0000000000410080     0 NOTYPE  GLOBAL DEFAULT    1 _end
74
75No version information found in this file.

اگه به قسمت Section Headers نگاه بندازیم می‌تونیم قسمت اجرایی کد رو که همون text. هست پیدا کنیم:

1  [ 1] .text             PROGBITS         0000000000400078  00000078
2       0000000000000008  0000000000000000  AX       0     0     4

تو این قسمت گفته که آفست این بخش 00000078 در فایل اجرایی هست و طولش هم 0000000000000008 بایت (اون هشت آخری با بقیه صفرا قاطی نشه :دی) هست و فلگ اجرایی X رو همداره. و اگه این قسمت از فایل اجرایی رو بخونیم محتویاتش اینه (با فلگ g گفتیم تو دسته‌های ۴ بایتی گروه‌بندی کنه موقع خوندن):

1meysam@ubuntu:~/www/test/arm/netmask$ xxd -g 4 -s 0x00000078 -l 8 ./ldr
200000078: 001080d2 2200008b                    ...."...

همینطور در اطلاعات اول فایل اومده که فایل به صورت 2s complement, little endian اینکود شده. لیتل اندین یعنی چینش راست به چپ بایت‌های پراهمیت. پس اگه ۴ بایت ۴ بایت این رشته رو بخونیم و به صورت آدم‌وار بنویسیم به این دوتا عدد هگز می‌رسیم:

10xD2_80_10_00
20x8B_00_00_22 

در بالا هشت بیت هشت بیت با اندراسکور ماجرا رو جدا کردم که بشه خوندش. و اگه به بالا جایی که فایل آبجکت رو باز کردیم برگردیم، می‌بینیم این همون دوتا ۴ بیت اجرایی ما هستن که در بالاتر ته‌شون رو در آوردیم. میمونه اونجا که لینوکس چطور می‌فهمه این رو ران کنه. باز اگه به اطلاعاتی که در مورد فایل اجرایی گرفتیم برگردیم، می‌بینیم که مقدار Entry point address مقدارش 0x400078 هست که همون مقدار Address در Section Headersعه.

خوب شد دیگه. برنامه رو که اجرا می‌کنیم، لینوکس براش یه جدول مجازی حافظه می‌سازه (یچی مثل mmap برا مپ کردن فایل روی مموری که MMU داخل CPU انجامش می‌ده) بین فضای اجرا و مموری فیزیکال (که آدرس‌دهی‌ها اونجا میشه شبیه چیزی که داریم می‌بینیم در الف فایل)، مقدار Entry point address رو تو رجیستر PC می‌ذاره و شروع می‌کنه ۴ بیت ۴ بیت خوندن و اجرا کردن. یچیزی که الان برام سواله که این فرآیند خوندنه و مچ کردن داخل CPU چطور اتفاق می‌افته که برا هدفی که الان دارم اهمیتی نداره و اگه یبار دیگه جنگ شد بهش بر می‌گردم.

نتیجه

تو این پست دیدیم که معماری آرم۶۴ چیه، چطوری اسمبلیش نوشته می‌شه و اجرا می‌شه و در نهایت هم تو این روزای ننگ تونستم چند ساعتی خودم رو مشغول کنم و (تا الان) زنده بمونم. خوب بود.