ɪɴ ᴍʏ ʜᴇᴀᴅ
دور زدن لایسنس و مهندسی معکوس برنامه 🔍 تحلیل EXE ، اسمبلی و منطق پشت کد
Media is too big
VIEW IN TELEGRAM
توی این ویدیو ی کد زدیم و یک لایسنس براش در نظر گرفتیم ، سعی کردیم بدون داشتن لایسنس به برنامه ورودی کنیم و در نهایت برنامه رو مهندسی معکوس کردیم تا منطقش رو بفهمیم.
برای درک بهتر با مثال های پایتونی هم سعی کردم کد های اسمبلی ای که از فایل exe گرفتیم رو توضیخ بدم تا دقیقاً متوجه بشید چه منطقی پشتش هست.
ویدیو ای هم که گفتم توی چنل امیرحسین هست اینه ( مشابهش تو همین چنل هم هست ) : https://t.me/comma_code_python1/581
ی جا هم گفتم 11 اوپ کدشه اشتباه گفتم ، منظورم operand بود ، اصن گیج شده بودم
برای درک بهتر با مثال های پایتونی هم سعی کردم کد های اسمبلی ای که از فایل exe گرفتیم رو توضیخ بدم تا دقیقاً متوجه بشید چه منطقی پشتش هست.
ویدیو ای هم که گفتم توی چنل امیرحسین هست اینه ( مشابهش تو همین چنل هم هست ) : https://t.me/comma_code_python1/581
ی جا هم گفتم 11 اوپ کدشه اشتباه گفتم ، منظورم operand بود ، اصن گیج شده بودم
🔥3👍1
ɪɴ ᴍʏ ʜᴇᴀᴅ
چطوری سنجش رو هک کردم؟
Media is too big
VIEW IN TELEGRAM
اینم از آسیب پذیری افتضاح سنجش که توی لایو درموردش صحبت کردیم ( امیدوارم به گونی هدایت نشم بعد از این ویدیو )
❤🔥1
ɪɴ ᴍʏ ʜᴇᴀᴅ
اعداد منفی چطوری توی مموری ذخیره میشن؟
اعداد منفی چطوری توی مموری سیو میشن؟
1 - روش علامت و مقدار
2 - روش متمم 1
3 - روش متمم 2
یکی یکی زیر همین پست توضیحشون میدم 👇🏼
1 - روش علامت و مقدار
2 - روش متمم 1
3 - روش متمم 2
یکی یکی زیر همین پست توضیحشون میدم 👇🏼
ɪɴ ᴍʏ ʜᴇᴀᴅ
اعداد منفی چطوری توی مموری سیو میشن؟ 1 - روش علامت و مقدار 2 - روش متمم 1 3 - روش متمم 2 یکی یکی زیر همین پست توضیحشون میدم 👇🏼
روش علامت و مقدار :
در این روش ، اعداد منفی مانند اعداد مثبت ذخیره میشوند ، با این تفاوت که در بیت علامت مقدار یک قرار میگیرد.
نمایش عدد -21 در ماشینی به طول کلمات ده بیت به روش علامت مقدار :
|1|0|0|0|0|1|0|1|0|1|
خب چیشد اینجا؟
مقدار باینری 21 که برابر 10101 هستش ، ولی چرا اولین بیت 1 شده؟
چونکه اون بیت علامته ، اگه 1 باشه نشانگر منفی بودن عدده و گه 0 باشه نشانگر مثبت بودنش...
این روش دوتا اشکال عمده داره :
1. برای صفر منفی و صفر مثبت دو نمایش جداگانه وجود دارد.
2. برای عمل تفریق باید مدار جداگانه ای طراحی شود
مثال :
نمایش صفر منفی و صفر مثبت در ماشینی به طول کلمات پنج به روش علامت و مقدار.
صفر مثبت :
|0|0|0|0|0|
صفر منفی :
|1|0|0|0|0|
در این روش ، اعداد منفی مانند اعداد مثبت ذخیره میشوند ، با این تفاوت که در بیت علامت مقدار یک قرار میگیرد.
نمایش عدد -21 در ماشینی به طول کلمات ده بیت به روش علامت مقدار :
|1|0|0|0|0|1|0|1|0|1|
خب چیشد اینجا؟
مقدار باینری 21 که برابر 10101 هستش ، ولی چرا اولین بیت 1 شده؟
چونکه اون بیت علامته ، اگه 1 باشه نشانگر منفی بودن عدده و گه 0 باشه نشانگر مثبت بودنش...
این روش دوتا اشکال عمده داره :
1. برای صفر منفی و صفر مثبت دو نمایش جداگانه وجود دارد.
2. برای عمل تفریق باید مدار جداگانه ای طراحی شود
مثال :
نمایش صفر منفی و صفر مثبت در ماشینی به طول کلمات پنج به روش علامت و مقدار.
صفر مثبت :
|0|0|0|0|0|
صفر منفی :
|1|0|0|0|0|
ɪɴ ᴍʏ ʜᴇᴀᴅ
اعداد منفی چطوری توی مموری سیو میشن؟ 1 - روش علامت و مقدار 2 - روش متمم 1 3 - روش متمم 2 یکی یکی زیر همین پست توضیحشون میدم 👇🏼
روش متمم 1 :
برای نمایش اعداد منفی به روش متمم 1 ، عدد را به مبنای دو تبدیل کرده ، نمایش مثبت عدد را مشخص می کنیم و در نمایش حاصل ، تمام صفر ها را به یک و تمام یک ها را به صفر تبدیل میکنیم.
مثال :
نمایش عدد مثبت و منفی 19 در ماشینی به طول کلمات ده بیت به روش متمم 1 :
19 مثبت :
|0|0|0|0|0|1|0|0|1|1
19 منفی :
|1|1|1|1|1|0|1|1|0|0
در این روش هم برای صفر منفی و صفر مثبت دو نمایش متفاوت وجود داره ولی برای انجام عمل تفریق نیازی به مدار جداگانه نیست ، یعنی روش متمم 1 اشکال دوم روش علامت و مقدار رو برطرف میکنه.
مثال نمایش صفر مثبت و منفی در روش متمم 1 ، در ماشینی به طول کلمات پنج بیت :
صفر مثبت :
|0|0|0|0|0
صفر منفی :
|1|1|1|1|1
برای نمایش اعداد منفی به روش متمم 1 ، عدد را به مبنای دو تبدیل کرده ، نمایش مثبت عدد را مشخص می کنیم و در نمایش حاصل ، تمام صفر ها را به یک و تمام یک ها را به صفر تبدیل میکنیم.
مثال :
نمایش عدد مثبت و منفی 19 در ماشینی به طول کلمات ده بیت به روش متمم 1 :
19 مثبت :
|0|0|0|0|0|1|0|0|1|1
19 منفی :
|1|1|1|1|1|0|1|1|0|0
در این روش هم برای صفر منفی و صفر مثبت دو نمایش متفاوت وجود داره ولی برای انجام عمل تفریق نیازی به مدار جداگانه نیست ، یعنی روش متمم 1 اشکال دوم روش علامت و مقدار رو برطرف میکنه.
مثال نمایش صفر مثبت و منفی در روش متمم 1 ، در ماشینی به طول کلمات پنج بیت :
صفر مثبت :
|0|0|0|0|0
صفر منفی :
|1|1|1|1|1
ɪɴ ᴍʏ ʜᴇᴀᴅ
اعداد منفی چطوری توی مموری سیو میشن؟ 1 - روش علامت و مقدار 2 - روش متمم 1 3 - روش متمم 2 یکی یکی زیر همین پست توضیحشون میدم 👇🏼
روش متمم 2 :
این روش ، هر دو اشکال روش علامت و مقدار رو حل میکنه. در این روش باید به صورت زیر عمل کرد :
1. نمایش مثبت عدد
2. پیدا کردن متمم 1 عدد
3. افزودن یک واحد به عمل حاصل
نمایش عدد -23 در ماشینی به طول کلمات ده بیت ، به روش متمم 2 :
-23 = (-10111)
نمایش عدد 23 مثبت :
|0|0|0|0|0|1|0|1|1|1|
متمم 1 عدد منفی 23 :
|1|1|1|1|1|0|1|0|0|0|
متمم 2 عدد منفی 23 :
|1|1|1|1|1|0|1|0|0|1|
اگه M تعداد بیت های کلمات ماشین باشه ، بازه اعداد قابل نمایش در این روش عبارت است از :
[-2^M-1, -(2^(M-1)-1)]
این روش ، هر دو اشکال روش علامت و مقدار رو حل میکنه. در این روش باید به صورت زیر عمل کرد :
1. نمایش مثبت عدد
2. پیدا کردن متمم 1 عدد
3. افزودن یک واحد به عمل حاصل
نمایش عدد -23 در ماشینی به طول کلمات ده بیت ، به روش متمم 2 :
-23 = (-10111)
نمایش عدد 23 مثبت :
|0|0|0|0|0|1|0|1|1|1|
متمم 1 عدد منفی 23 :
|1|1|1|1|1|0|1|0|0|0|
متمم 2 عدد منفی 23 :
|1|1|1|1|1|0|1|0|0|1|
اگه M تعداد بیت های کلمات ماشین باشه ، بازه اعداد قابل نمایش در این روش عبارت است از :
[-2^M-1, -(2^(M-1)-1)]
منبع این دوسه تا پست بالا کتاب «برنامه نویسی به زبان اسمبلی» تالیف جعفرنژاد قمی هستش
کتاب خوبیه ، واسه اینکه درک کاملی داشته باشید اول میاد این مباحث پایه ای مثل چگونگی سیو شدن اعداد مثبت و منفی و اعشاری و تبدیل مبناها و... رو توضیح میده و بعد میره سر آموزش اسمبلی
کتاب خوبیه ، واسه اینکه درک کاملی داشته باشید اول میاد این مباحث پایه ای مثل چگونگی سیو شدن اعداد مثبت و منفی و اعشاری و تبدیل مبناها و... رو توضیح میده و بعد میره سر آموزش اسمبلی
ɪɴ ᴍʏ ʜᴇᴀᴅ
پیچیدگی محاسباتی چیه و به چه دردی میخوره؟
پیچیدگی محاسباتی یا همون Computational Complexity یکی از مهم ترین مسائل توی علوم کامپیوتره
با استفاده از پیچیدگی محاسباتی میتونیم میزان منابع مورد نیاز برای حل مسائل رو تخمین بزنیم.
منظور از منابع زمان و فضا(حافظه) هستش...
ما برای نشون دادن پیچیدگی محاسباتی هر الگوریتم از نماد های Asymptotic Notation یا O/Ω/Θ استفاده میکنیم.
حالا اینا چین؟
نماد O(f(n)) کران بالای رشد الگوریتمو نشون میده ، یعنی حداکثر منابعی که ممکنه مصرف بشه. معمولاً ازش برای نشون دادن بدترین حالت ممکن استفاده میکنیم...
نماد Ω(f(n)) کران پایین رو نشون میده ، یعنی حداقل منابعی که الگوریتم در هر حال باید مصرف کنه. معمولاً برای بهترین حالت ممکن استفاده میشه.
نماد Θ(f(n)) کران دقیق الگوریتم رو نشون میده. زمانی به کار میاد که هم کران بالا و هم کران پایین یکی باشن (یا تقریبا اندازه همدیگه باشن)
خلاصه کران بالا (بدترین حالت ممکن) رو با او (O) ، کران پایین (بهترین حالت ممکن) رو با امگا (Ω) و حالت کران دقیق(متوسط) رو با تتا (Θ) نشون میدیم
خب حالا چطوری کار میکنن؟
برای بهتر متوجه شدنتون ی کد پایتون نوشتم که سعی میکنه توی ی لیست ، ی عددی که ما بهش میدیم رو پیدا کنه :
این فانکشن لیست و عددی که باید دنبالش بگرده رو از ما میگیره و یکی یکی اعداد رو از اول لیست پیمایش میکنه تا به عددی که بهش دادیم برسه.
حالا کد زیر رو در نظر بگیرید :
خب اینجا توی لیستی که بهش دادیم عدد 10 وجود نداره و مجبوره تا اخر لیست رو پیمایش کنیم ، پس کران بالای رشد این الگوریتمو با O(n) نشون میدیم ، یعنی در بدترین حالت ممکن این الگوریتم باید کل لیست رو طی کنه تا به عدد مد نظر برسه
حالا کد زیر رو در نظر بگیرید :
اینجا اولین عدد رو بررسی میکنه و میبینه همون عددیه که دنبالشه ، پس دیگه نیازی نیست تا آخر لیست رو پیمایش بکنه
کران پایین این الگوریتمو میتونیم با Ω(1) نشون بدیم ، یعنی این الگوریتم در بهترین حالت ممکن با یک بار اجرا شدن به عدد میرسه
و در آخر تتا...
واسه تتا میتونیم ی همچین مثالی بزنیم :
اینجا بطور میانگین الگوریتم ما نصف لیستو پیمایش کرده تا تونسته به 15 برسه ، اگه فرض کنیم که داده ها در موقعیت های مختلفی از لیست بطور یکنواخت قرار گرفتن اونوقت میانگین زمان اجرا تقریباً Θ(n) میشه...
پس توی مثالای بالا Ω(1) یعنی عدد اولین عنصر لیست بوده ، O(n) یعنی آخر لیست بوده یا اصلا وجود نداشته و Θ(n) یعنی عدد ما ی جاهایی از وسطای لیست بوده
حالا شما کران پایین و کران بالای الگوریتم زیر رو تو کامنتا بهم بگید و دلیلشو برام بنویسید :
با استفاده از پیچیدگی محاسباتی میتونیم میزان منابع مورد نیاز برای حل مسائل رو تخمین بزنیم.
منظور از منابع زمان و فضا(حافظه) هستش...
ما برای نشون دادن پیچیدگی محاسباتی هر الگوریتم از نماد های Asymptotic Notation یا O/Ω/Θ استفاده میکنیم.
حالا اینا چین؟
نماد O(f(n)) کران بالای رشد الگوریتمو نشون میده ، یعنی حداکثر منابعی که ممکنه مصرف بشه. معمولاً ازش برای نشون دادن بدترین حالت ممکن استفاده میکنیم...
نماد Ω(f(n)) کران پایین رو نشون میده ، یعنی حداقل منابعی که الگوریتم در هر حال باید مصرف کنه. معمولاً برای بهترین حالت ممکن استفاده میشه.
نماد Θ(f(n)) کران دقیق الگوریتم رو نشون میده. زمانی به کار میاد که هم کران بالا و هم کران پایین یکی باشن (یا تقریبا اندازه همدیگه باشن)
خلاصه کران بالا (بدترین حالت ممکن) رو با او (O) ، کران پایین (بهترین حالت ممکن) رو با امگا (Ω) و حالت کران دقیق(متوسط) رو با تتا (Θ) نشون میدیم
خب حالا چطوری کار میکنن؟
برای بهتر متوجه شدنتون ی کد پایتون نوشتم که سعی میکنه توی ی لیست ، ی عددی که ما بهش میدیم رو پیدا کنه :
def search(lst, num):
for i in range(len(lst)):
if lst[i] == num:
return i
return -1
این فانکشن لیست و عددی که باید دنبالش بگرده رو از ما میگیره و یکی یکی اعداد رو از اول لیست پیمایش میکنه تا به عددی که بهش دادیم برسه.
حالا کد زیر رو در نظر بگیرید :
search([1, 2, 3, 4, 5], 10)
خب اینجا توی لیستی که بهش دادیم عدد 10 وجود نداره و مجبوره تا اخر لیست رو پیمایش کنیم ، پس کران بالای رشد این الگوریتمو با O(n) نشون میدیم ، یعنی در بدترین حالت ممکن این الگوریتم باید کل لیست رو طی کنه تا به عدد مد نظر برسه
حالا کد زیر رو در نظر بگیرید :
search([1, 2, 3, 4, 5], 1)
اینجا اولین عدد رو بررسی میکنه و میبینه همون عددیه که دنبالشه ، پس دیگه نیازی نیست تا آخر لیست رو پیمایش بکنه
کران پایین این الگوریتمو میتونیم با Ω(1) نشون بدیم ، یعنی این الگوریتم در بهترین حالت ممکن با یک بار اجرا شدن به عدد میرسه
و در آخر تتا...
واسه تتا میتونیم ی همچین مثالی بزنیم :
search([3, 7, 12, 15, 18, 22], 15)
اینجا بطور میانگین الگوریتم ما نصف لیستو پیمایش کرده تا تونسته به 15 برسه ، اگه فرض کنیم که داده ها در موقعیت های مختلفی از لیست بطور یکنواخت قرار گرفتن اونوقت میانگین زمان اجرا تقریباً Θ(n) میشه...
پس توی مثالای بالا Ω(1) یعنی عدد اولین عنصر لیست بوده ، O(n) یعنی آخر لیست بوده یا اصلا وجود نداشته و Θ(n) یعنی عدد ما ی جاهایی از وسطای لیست بوده
حالا شما کران پایین و کران بالای الگوریتم زیر رو تو کامنتا بهم بگید و دلیلشو برام بنویسید :
def getFirstElement(lst: list):
if isinstance(lst, list) and len(lst):
return lst[0]
ɪɴ ᴍʏ ʜᴇᴀᴅ
آیا واقعا switch/case پیچیدگی زمانیش O(1) هستش؟ اصلا چطوری کار میکنه؟ توی این پست میریم تو دل کدای اسمبلی ای که کامپایلر C برای پیاده سازی switch/case زده و موشکافانه تجزیه و تحلیلش میکنیم تا ببینیم دقیقا این مکانیزم چطوری پیاده سازی شده ;)
🤔 واقعا پیچیدگی زمانی switch/case برابر با O(1) یا نه؟
این همیشه واسه من سوال بوده که چطوری switch/case میاد این کار رو میکنه ، مگه نباید مقایسه انجام بشه؟
بخاطر همین از روی کنجکاوی رفتم سراغ بررسی کردن کدای Assembly 🗿😂
اومدم همچین کدی نوشتم :
این همیشه واسه من سوال بوده که چطوری switch/case میاد این کار رو میکنه ، مگه نباید مقایسه انجام بشه؟
بخاطر همین از روی کنجکاوی رفتم سراغ بررسی کردن کدای Assembly 🗿😂
اومدم همچین کدی نوشتم :
#include <stdio.h>
int main() {
int x = 5;
switch (x) {
case 0: printf("its 0"); break;
case 1: printf("its 1"); break;
case 2: printf("its 2"); break;
case 3: printf("its 3"); break;
case 4: printf("its 4"); break;
case 5: printf("its 5"); break;
case 6: printf("its 6"); break;
case 7: printf("its 7"); break;
case 8: printf("its 8"); break;
case 9: printf("its 9"); break;
default: printf("default case"); break;
}
return 0;
}
کدی که با C زده بودم رو کامپایل کردم و با GDB فانکشن main رو disassemble کردم و خروجی زیر رو گرفتم :
توی پستای بعدی تیکه تیکه کد اسمبلی بالا رو کوت میکنم و توضیحش میدم واستون تا براتون جا بیوفته دقیقا چطوری این اتفاق داره میوفته.
الگوریتمش که واسه من خیلی جالب بود...
0x0000000140001450 <+0>: push %rbp
0x0000000140001451 <+1>: mov %rsp,%rbp
0x0000000140001454 <+4>: sub $0x30,%rsp
0x0000000140001458 <+8>: call 0x140001610 <__main>
0x000000014000145d <+13>: movl $0x5,-0x4(%rbp)
0x0000000140001464 <+20>: cmpl $0x9,-0x4(%rbp)
0x0000000140001468 <+24>: ja 0x140001544 <main+244>
0x000000014000146e <+30>: mov -0x4(%rbp),%eax
0x0000000140001471 <+33>: lea 0x0(,%rax,4),%rdx
0x0000000140001479 <+41>: lea 0x2bcc(%rip),%rax # 0x14000404c
0x0000000140001480 <+48>: mov (%rdx,%rax,1),%eax
0x0000000140001483 <+51>: cltq
0x0000000140001485 <+53>: lea 0x2bc0(%rip),%rdx # 0x14000404c
0x000000014000148c <+60>: add %rdx,%rax
0x000000014000148f <+63>: jmp *%rax
0x0000000140001491 <+65>: lea 0x2b68(%rip),%rax # 0x140004000
0x0000000140001498 <+72>: mov %rax,%rcx
0x000000014000149b <+75>: call 0x140002640 <printf>
0x00000001400014a0 <+80>: jmp 0x140001554 <main+260>
0x00000001400014a5 <+85>: lea 0x2b5a(%rip),%rax # 0x140004006
0x00000001400014ac <+92>: mov %rax,%rcx
0x00000001400014af <+95>: call 0x140002640 <printf>
0x00000001400014b4 <+100>: jmp 0x140001554 <main+260>
0x00000001400014b9 <+105>: lea 0x2b4c(%rip),%rax # 0x14000400c
0x00000001400014c0 <+112>: mov %rax,%rcx
0x00000001400014c3 <+115>: call 0x140002640 <printf>
0x00000001400014c8 <+120>: jmp 0x140001554 <main+260>
0x00000001400014cd <+125>: lea 0x2b3e(%rip),%rax # 0x140004012
0x00000001400014d4 <+132>: mov %rax,%rcx
0x00000001400014d7 <+135>: call 0x140002640 <printf>
0x00000001400014dc <+140>: jmp 0x140001554 <main+260>
0x00000001400014de <+142>: lea 0x2b33(%rip),%rax # 0x140004018
0x00000001400014e5 <+149>: mov %rax,%rcx
0x00000001400014e8 <+152>: call 0x140002640 <printf>
0x00000001400014ed <+157>: jmp 0x140001554 <main+260>
0x00000001400014ef <+159>: lea 0x2b28(%rip),%rax # 0x14000401e
0x00000001400014f6 <+166>: mov %rax,%rcx
0x00000001400014f9 <+169>: call 0x140002640 <printf>
0x00000001400014fe <+174>: jmp 0x140001554 <main+260>
0x0000000140001500 <+176>: lea 0x2b1d(%rip),%rax # 0x140004024
0x0000000140001507 <+183>: mov %rax,%rcx
0x000000014000150a <+186>: call 0x140002640 <printf>
0x000000014000150f <+191>: jmp 0x140001554 <main+260>
0x0000000140001511 <+193>: lea 0x2b12(%rip),%rax # 0x14000402a
0x0000000140001518 <+200>: mov %rax,%rcx
0x000000014000151b <+203>: call 0x140002640 <printf>
0x0000000140001520 <+208>: jmp 0x140001554 <main+260>
0x0000000140001522 <+210>: lea 0x2b07(%rip),%rax # 0x140004030
0x0000000140001529 <+217>: mov %rax,%rcx
0x000000014000152c <+220>: call 0x140002640 <printf>
0x0000000140001531 <+225>: jmp 0x140001554 <main+260>
0x0000000140001533 <+227>: lea 0x2afc(%rip),%rax # 0x140004036
0x000000014000153a <+234>: mov %rax,%rcx
0x000000014000153d <+237>: call 0x140002640 <printf>
0x0000000140001542 <+242>: jmp 0x140001554 <main+260>
0x0000000140001544 <+244>: lea 0x2af1(%rip),%rax # 0x14000403c
0x000000014000154b <+251>: mov %rax,%rcx
0x000000014000154e <+254>: call 0x140002640 <printf>
0x0000000140001553 <+259>: nop
0x0000000140001554 <+260>: mov $0x0,%eax
0x0000000140001559 <+265>: add $0x30,%rsp
0x000000014000155d <+269>: pop %rbp
0x000000014000155e <+270>: ret
توی پستای بعدی تیکه تیکه کد اسمبلی بالا رو کوت میکنم و توضیحش میدم واستون تا براتون جا بیوفته دقیقا چطوری این اتفاق داره میوفته.
الگوریتمش که واسه من خیلی جالب بود...
ɪɴ ᴍʏ ʜᴇᴀᴅ
0x0000000140001450 <+0>: push %rbp
0x0000000140001451 <+1>: mov %rsp,%rbp
0x0000000140001454 <+4>: sub $0x30,%rsp
0x0000000140001458 <+8>: call 0x140001610 <__main>
0x0000000140001451 <+1>: mov %rsp,%rbp
0x0000000140001454 <+4>: sub $0x30,%rsp
0x0000000140001458 <+8>: call 0x140001610 <__main>
این قسمت رو نادیده بگیرید واسه تنظیمات استک و... اس که به ما ربطی ندارن😁
ɪɴ ᴍʏ ʜᴇᴀᴅ
0x000000014000145d <+13>: movl $0x5,-0x4(%rbp)
این قسمت داره همون متغیرمون رو سیو میکنه
توی کد سی گفته بودیم که :
اینجا داره همون x رو تعریف میکنه...
دستور movl توی اسمبلی واسه انتقال ی مقدار استفاده میشه که سینتکسش به این شکل هستش :
movl x, y
اینجا مثلا مقدار x رو داریم به y منتقل میکنیم ، اون l آخر این دستور به معنی long هستش و به این معنیه که این دستور 4 بایت دیتا رو منتقل میکنه...
حالا اینجا کدی که ما داریم گفته
movl $0x5, -0x4(%rbp)
اینجا داره 0x5 یا همون 5 دهدهی رو به آدرس -0x4(%rbp) منتقل میکنه.
توی کد سی گفته بودیم که :
int x = 5;
اینجا داره همون x رو تعریف میکنه...
دستور movl توی اسمبلی واسه انتقال ی مقدار استفاده میشه که سینتکسش به این شکل هستش :
movl x, y
اینجا مثلا مقدار x رو داریم به y منتقل میکنیم ، اون l آخر این دستور به معنی long هستش و به این معنیه که این دستور 4 بایت دیتا رو منتقل میکنه...
حالا اینجا کدی که ما داریم گفته
movl $0x5, -0x4(%rbp)
اینجا داره 0x5 یا همون 5 دهدهی رو به آدرس -0x4(%rbp) منتقل میکنه.
ɪɴ ᴍʏ ʜᴇᴀᴅ
0x0000000140001464 <+20>: cmpl $0x9,-0x4(%rbp)
0x0000000140001468 <+24>: ja 0x140001544 <main+244>
0x0000000140001468 <+24>: ja 0x140001544 <main+244>
اینجا توی خط اول داره مقدار عددی که توی متغیر x هست رو با 0x9یا همون 9 دهدهی مقایسه میکنه
خط بعدی اومده گفته که اگه 9 < x بود به انتهای تابع بپر...
دستور ja یعنی jump if above ، موقعی پرش انجام میشه که مقایسه ای که انجام شده نشون بده ی مقدار بزرگتر از ی مقدار دیگس ، پس اگه x از 9 بزرگتر باشه کد یهو میپره به آخر تابع و بقیه کدای دیگه رو درنظر نمیگیره...
خط بعدی اومده گفته که اگه 9 < x بود به انتهای تابع بپر...
دستور ja یعنی jump if above ، موقعی پرش انجام میشه که مقایسه ای که انجام شده نشون بده ی مقدار بزرگتر از ی مقدار دیگس ، پس اگه x از 9 بزرگتر باشه کد یهو میپره به آخر تابع و بقیه کدای دیگه رو درنظر نمیگیره...
ɪɴ ᴍʏ ʜᴇᴀᴅ
0x000000014000146e <+30>: mov -0x4(%rbp),%eax
0x0000000140001471 <+33>: lea 0x0(,%rax,4),%rdx
0x0000000140001479 <+41>: lea 0x2bcc(%rip),%rax # 0x14000404c
0x0000000140001480 <+48>: mov (%rdx,%rax,1),%eax
0x0000000140001483 <+51>: cltq
0x0000000140001485 <+53>: lea 0x2bc0(%rip),%rdx # 0x14000404c
0x000000014000148c <+60>: add %rdx,%rax
0x000000014000148f <+63>: jmp *%rax
0x0000000140001471 <+33>: lea 0x0(,%rax,4),%rdx
0x0000000140001479 <+41>: lea 0x2bcc(%rip),%rax # 0x14000404c
0x0000000140001480 <+48>: mov (%rdx,%rax,1),%eax
0x0000000140001483 <+51>: cltq
0x0000000140001485 <+53>: lea 0x2bc0(%rip),%rdx # 0x14000404c
0x000000014000148c <+60>: add %rdx,%rax
0x000000014000148f <+63>: jmp *%rax
اینجا داره switch/case ای که نوشتیم رو پردازش میکنه ، این قسمت رو توی پستای بعدی بیشتر شرح میدم ، اول میخوام تقسیم بندی هارو انجام بدم واسه هر بخش تا بعد برگردم روی این قسمت و جزئی تر بهش بپردازیم ;)
ɪɴ ᴍʏ ʜᴇᴀᴅ
0x0000000140001491 <+65>: lea 0x2b68(%rip),%rax # 0x140004000
0x0000000140001498 <+72>: mov %rax,%rcx
0x000000014000149b <+75>: call 0x140002640 <printf>
0x00000001400014a0 <+80>: jmp 0x140001554 <main+260>
0x0000000140001498 <+72>: mov %rax,%rcx
0x000000014000149b <+75>: call 0x140002640 <printf>
0x00000001400014a0 <+80>: jmp 0x140001554 <main+260>
اینجا داره آدرس ی رشته(string) خاص رو توی %rax میزاره و اونو به %rcx میده تا تابع printf اونو پرینت کنه و بعدش پرش میزنه به آخر تابع
دقیق تر بخوام بگم دستور lea مخفف Load Effective Address هستش ، اینجا مقدار
%rip + 0x2b68
داره محاسبه میشه و داخل رجیستر rax ریخته میشه ، پس توی رجیستر rax الآن ما ی رشته رو داریم ، کدوم رشته؟ ما توی کد C که زده بودیم توی هر case اومدیم ی مقدار رو پرینت کردیم ، مثلا واسه case 1 اومدیم گفتیم مقدار its 1 رو پرینت کن ، حالا ما استرینگ "its 1" رو توی رجیستر rax داریم...
توی خط بعدی اومده مقداری که توی رجیستر rax هستش رو با دستور mov به رجیستر rcx منتقل کرده
اینجا چونکه من توی ویندوز کد C رو کامپایل کردم داره این مقدارو توی رجیستر rcx میریزه چونکه توی ABI استاندارد ویندوز اولین آرگومان تابع توی رجیستر rcx قرار میگیره... ولی رو لینوکس توی رجیستر rdi قرار میگیره
بعدش داره با دستور call تابع printf رو فراخوانی میکنه ، حالا فهمیدین چرا مقدار رشته رو از rax ریخته توی rcx؟ :)
چون الآن اولین مقدار آرگومانی که داره به printf داده میشه همون رجیستر rcx هستش ، مثل اینه که گفته باشیم printf(rcx)
خلاصه اینجا داریم ی رشته رو پرینت میکنیم
توی لاین بعدی هم که پریدیم به آخر تابع...
دقیق تر بخوام بگم دستور lea مخفف Load Effective Address هستش ، اینجا مقدار
%rip + 0x2b68
داره محاسبه میشه و داخل رجیستر rax ریخته میشه ، پس توی رجیستر rax الآن ما ی رشته رو داریم ، کدوم رشته؟ ما توی کد C که زده بودیم توی هر case اومدیم ی مقدار رو پرینت کردیم ، مثلا واسه case 1 اومدیم گفتیم مقدار its 1 رو پرینت کن ، حالا ما استرینگ "its 1" رو توی رجیستر rax داریم...
توی خط بعدی اومده مقداری که توی رجیستر rax هستش رو با دستور mov به رجیستر rcx منتقل کرده
اینجا چونکه من توی ویندوز کد C رو کامپایل کردم داره این مقدارو توی رجیستر rcx میریزه چونکه توی ABI استاندارد ویندوز اولین آرگومان تابع توی رجیستر rcx قرار میگیره... ولی رو لینوکس توی رجیستر rdi قرار میگیره
بعدش داره با دستور call تابع printf رو فراخوانی میکنه ، حالا فهمیدین چرا مقدار رشته رو از rax ریخته توی rcx؟ :)
چون الآن اولین مقدار آرگومانی که داره به printf داده میشه همون رجیستر rcx هستش ، مثل اینه که گفته باشیم printf(rcx)
خلاصه اینجا داریم ی رشته رو پرینت میکنیم
توی لاین بعدی هم که پریدیم به آخر تابع...
ɪɴ ᴍʏ ʜᴇᴀᴅ
0x00000001400014a5 <+85>: lea 0x2b5a(%rip),%rax # 0x140004006
0x00000001400014ac <+92>: mov %rax,%rcx
0x00000001400014af <+95>: call 0x140002640 <printf>
0x00000001400014b4 <+100>: jmp 0x140001554 <main+260>
0x00000001400014b9 <+105>: lea 0x2b4c(%rip),%rax # 0x14000400c
0x00000001400014c0 <+112>: mov %rax,%rcx
0x00000001400014c3 <+115>: call 0x140002640 <printf>
0x00000001400014c8 <+120>: jmp 0x140001554 <main+260>
0x00000001400014cd <+125>: lea 0x2b3e(%rip),%rax # 0x140004012
0x00000001400014d4 <+132>: mov %rax,%rcx
0x00000001400014d7 <+135>: call 0x140002640 <printf>
0x00000001400014dc <+140>: jmp 0x140001554 <main+260>
0x00000001400014de <+142>: lea 0x2b33(%rip),%rax # 0x140004018
0x00000001400014e5 <+149>: mov %rax,%rcx
0x00000001400014e8 <+152>: call 0x140002640 <printf>
0x00000001400014ed <+157>: jmp 0x140001554 <main+260>
0x00000001400014ef <+159>
0x00000001400014ac <+92>: mov %rax,%rcx
0x00000001400014af <+95>: call 0x140002640 <printf>
0x00000001400014b4 <+100>: jmp 0x140001554 <main+260>
0x00000001400014b9 <+105>: lea 0x2b4c(%rip),%rax # 0x14000400c
0x00000001400014c0 <+112>: mov %rax,%rcx
0x00000001400014c3 <+115>: call 0x140002640 <printf>
0x00000001400014c8 <+120>: jmp 0x140001554 <main+260>
0x00000001400014cd <+125>: lea 0x2b3e(%rip),%rax # 0x140004012
0x00000001400014d4 <+132>: mov %rax,%rcx
0x00000001400014d7 <+135>: call 0x140002640 <printf>
0x00000001400014dc <+140>: jmp 0x140001554 <main+260>
0x00000001400014de <+142>: lea 0x2b33(%rip),%rax # 0x140004018
0x00000001400014e5 <+149>: mov %rax,%rcx
0x00000001400014e8 <+152>: call 0x140002640 <printf>
0x00000001400014ed <+157>: jmp 0x140001554 <main+260>
0x00000001400014ef <+159>
و بعدش هم که دوباره و دوباره و دوباره دقیقا همون کار رو انجام داده (ی رشته رو لود کرده داده به printf تا برامون پرینتش کنه)
اینا همون کدای بدنه case هاییه که نوشتیم
ولی فقط یکی از اینا قراره اجرا بشه...
اینا همون کدای بدنه case هاییه که نوشتیم
ولی فقط یکی از اینا قراره اجرا بشه...
ɪɴ ᴍʏ ʜᴇᴀᴅ
0x0000000140001553 <+259>: nop
0x0000000140001554 <+260>: mov $0x0,%eax
0x0000000140001559 <+265>: add $0x30,%rsp
0x000000014000155d <+269>: pop %rbp
0x000000014000155e <+270>: ret
0x0000000140001554 <+260>: mov $0x0,%eax
0x0000000140001559 <+265>: add $0x30,%rsp
0x000000014000155d <+269>: pop %rbp
0x000000014000155e <+270>: ret
به تعریف بلوک case ها ادامه میده تا میرسه به این قسمت...
این قسمت واسه خروج از تابع هستش که داره فضای stack رو آزاد میکنه و... که اینم به ما مربوط نمیشه و زیاد توضیحش نمیدم
این قسمت واسه خروج از تابع هستش که داره فضای stack رو آزاد میکنه و... که اینم به ما مربوط نمیشه و زیاد توضیحش نمیدم