یادگیری اسمبلی با فن مهندسی معکوس

یادگیری اسمبلی با فن مهندسی معکوس

زبان اَسمبلی (به انگلیسی: Assembly language) یا زبان هم‌گذاری، به اختصار asm، به هر زبان برنامه‌نویسی سطح پایین برای برنامه‌ریزی دستگاه‌های برنامه‌پذیر گفته می‌شود. دستورات زبان اسمبلی ساختار و ارتباط بسیار نزدیکی با معماری پردازش ماشین دارند. هر دستگاه بر مبنای نوعی معماری طراحی شده است؛ اسمبلی حتی علاوه بر نوع معماری، گاهی به سیستم‌عامل وابستگی دارد، پس با طیف گوناگونی از زبان‌های اسمبلی روبه‌رو خواهیم بود.با مطالعۀ این نوشتار، در کنار تبدیل یک برنامه ساده از سطح بالا به سطح پایین، با برخی قواعد نحو گونه‌های اسمبلی نظیر دستورات جمع، تفریق، انتقال، پاپ، پوش، مقایسه‌ای، پرش، زیربرنامه، ثبات و پرچم آشنا خواهیم شد.برنامۀ ساده ما قادر به تغییر مبنای عدد از شکل ده‌دهی به دودویی و شانزده‌شانزدهی (و بالعکس) خواهد بود. به همین منظور قطعه کد زیر را به زبان سطح بالای سی‌شارپ نوشتیم: https://gist.github.com/alireza-rezaee/1370c9286bc0fff9e120d467a6117799 این برنامه با کمک توابع کتابخانه‌ای پیش‌نوشته، شش تابع برای تغییر مبنا تعریف و سپس نمونه‌ای از کارکرد هر یک از توابع را چاپ می‌کند.در این مثال از ابزارهای زیر استفاده می‌کنیم:توجه: قطعه کد بالا از قابلیت «top-level statements» استفاده می‌کند و این قاعده در سی‌شارپ ۹.۰ معرفی شد. پس پیش از اجرای کد، از نسخه زبان اطمینان حاصل کنید. در غیر این صورت، می‌توانید برای پیشگیری از خطای نحو (Syntax error) بلاک‌ها را بازگردانید.توجه: محیط توسعه نرم‌افزاری ویژوال استدیو اگرچه چندسکویی است امّا تنها برای «ویندوز» و «مک‌اُواِس» منتشر می‌شود. برای سایر سیستم‌عامل‌ها از نرم‌افزارهای جایگزین استفاده کنید.قصد داریم از ابزار عیب‌یابی ویژوال استدیو برای دیس‌اسمبلی استفاده کنیم. برای نمایش این حالت نیاز به نقطه توقف (Breakpoint) داریم. مانند نمونۀ زیر عمل می‌کنیم:تصویر نحوۀ قراردادن breakpoint در محیط ویژوال استدیوبا کلیک روی سربرگ Debug، از زیر منوی Windows، گزینه Disassembly را انتخاب می‌کنیم (میانبر: Ctrl+Alt+D)؛ پنجره دیس‌اسمبلی نمایش داده می‌شود.نحوه فعال‌سازی پنجرۀ Disassemblyمحتویات پنجره باز شده «Disassembly» مانند زیر است:نمای پنجرۀ Disassembly ویژوال استدیومحتوایی که در تصویر فوق مشاهده می‌کنید کدهای زبان اسمبلی است. دیس‌اسمبلر کد برنامه‌ای که پیشتر نوشته بودیم را از زبان ماشین به زبان اسمبلی بازگردانده است. در ادامه به بررسی این دستورات می‌پردازیم.دستور MOV، ساده‌ترین دستور اسمبلی و نقش آن انتقال مقدار است. عملگر MOV، مقدار عملوند دوم را داخل عملوند اوّل می‌ریزد (برخی زبان‌های اسمبلی وارونه عمل می‌کنند؛ یعنی عملوند دوم را در عملوند اول قرار می‌دهند). مثال زیر یک مدل مفهومی از کاربرد این دستور است. برای نمونه، عبارت نخست مقدار یک ثبّاب (Register) را به ثبات دیگری انتقال می‌دهد. https://gist.github.com/alireza-rezaee/e527685e1d19a03b25dfc6aa07331b1c توجه: اندازه هر دو عملوند باید یکسان باشند، در غیر این صورت با خطا نحو زبان مواجه خواهیم شد.دستور ADD، دو عملوند را با هم جمع و نتیجه را در عملوند نخست جایی می‌دهد. به عبارتی دیگر مقدار عملوند دوم را به مقدار عملوند اوّل می‌افزاید. https://gist.github.com/alireza-rezaee/008766784fea25d98271867ba833f781 دستور SUB، به مقدار عملوند دوم، عملوند اوّل را کاهش می‌دهد. یعنی این عملگر مقدار عملوند دوم را از عملوند اوّل کسر می‌کند، سپس نتیجه را درون عملوند اوّل قرار می‌دهد. https://gist.github.com/alireza-rezaee/be692aa6d453144eb451d0a8d83c39fd https://gist.github.com/alireza-rezaee/61ca2181f4c7455128e2c15d8bfc5f17 پشته (به انگلیسی: Stack) یکی از ساختمان‌های داده است. داده‌ها در این نوع ساختمان داده در سطوح لایه‌لایه، طبقه طبقه روی هم قرار می‌گیرند و ما تنها به بالاترین طبقه فوقانی دسترسی داریم؛ خواه برای گذاشتن یا خواه برای برداشتن. به عبارتی هر لایه که زودتر وارد شود، دیرتر خارج می‌شود و بالعکس (LIFO).در مواجهه با پشته دو دستور داریم. PUSH برای گذاشتن و POP برای برداشتن. PUSH مؤلفه‌ای دریافت می‌کند تا به پشته افزاید و معمولاً مقدار بازگشتی ندارد. در سوی مقابل POP از مؤلفۀ ورودی بی‌نیاز است، امّا آخرین لایه را از بالای پشته برمی‌دارد و در قالب مقدار بازگشتی باز می‌گرداند. https://gist.github.com/alireza-rezaee/a2bea056a0bbbb6e2f2f0f81fe598a5c زیربرنامه‌ها کمک شایانی به کاهش حجم دستورات می‌کنند، سرعت، خوانش و دقت برنامه‌نویس را بسیار زیاد بهبود می‌دهند. البته زیربرنامه‌ها در اسمبلی نسبت به متدهای زبان‌های سطح بالا بسیار متفاوتند. زیربرنامه‌ها با واژۀ RET خاتمه می‌یابند. لیبل نقطه‌ای از کد است که نشانه‌گذاری می‌شود تا بعداً از سایر نقاط قابل دسترسی باشد. در برخی زبان‌ها، زیربرنامه‌ها و لیبل‌ها تعریف یکسانی دارند. https://gist.github.com/alireza-rezaee/aad63aabb136b62f44353486a9e292d8 حافظه‌های بسیار سریعی درون پردازنده هستند. در ابعاد سخت‌افزاری، ثبات‌ها از کنارهم قرارگیری مجموعه‌ای از فلیپ‌فلاپ‌ها تشکیل می‌شوند. در دسته بندی کلی ثبات‌ها به سه گروه تقسیم می‌شوند:ثبات‌های عمومی (General Registers)ثبات‌های کنترلی (Control Registers)ثبات‌های سگمنت (Segment Registers)شرکت‌های سازنده پردازشگر در قواعد نامگذاری متفاوت عمل می‌کنند. برای نمونه شرکت اینتل ممکن است از این نام‌ها استفاده کند:RAX, RBX, RCX, RDX, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15, RIP, RSP, RBP, EFL, etc.ثبات پرچم‌ها (به انگلیسی: Flag Register)، بر اساس نتایج عملیات، وضعیت پردازشگر را ثبت می‌کنند. برای نمونه عبارتند از:پرچم Overflow (OV): در صورت رخداد سرریز علامت‌دار (منفی ۱۲۸ تا ۱۲۷) برابر ۱ می‌شود. برای مثال حاصل ۱۰۰ + ۵۰ در این بازۀ قرار ندارد.پرچم Direction (UP): توسط برخی دستورالعمل‌ها برای تعیین جهت پردازش داده مورد استفاده می‌شود. زمانی که ۰ باشد رو به جلو و زمانی که ۱ باشد رو به عقب انجام شده است.پرچم Interrupt (EI): زمانی که برابر ۱ باشد، پردازنده به وقفه دستگاه‌های خارجی واکنش نشان می‌دهد.پرچم Sign (PL): زمانی که نتیجه منفی باشد، در حالت ۰ قرار می‌گیرد و زمانی که نتیجه مثبت باشد، یک (مقدار پرارزش‌ترین بیت را می‌گیرد.)پرچم Zero (ZR): هنگامی که نتیجه ۰ شده باشد، ۱ قرار می‌گیرد و در صورتی که نتیجه ۱ شده باشد، ۰ قرار می‌گیرد.پرچم Auxiliary carry (AC): در صورت سرریز برای بیت کم‌ارزش (۴ بیتی) برابر یک می‌شود.پرچم Parity (PE): بر اساس تعداد «۱»های موجود در نتیجه تعیین می‌شود. اگر تعداد «۱»ها زوج بودند، ۱ و اگر فرد بودند، ۲ قرار می‌گیرد.پرچم Carry (CY): اگر برابر ۱ باشد، سرریز بی‌علامت رخ داده است. برای نمونه ۲۵۵ + ۱ در بازۀ ۰ تا ۲۵۵ قرار نمی‌گیرد. و در غیر این صورت برابر ۰ قرار می‌گیرد.دستور CMP، دو عملوند را مقایسه می‌کند. مانند دستور SUB عمل می‌کند امّا نتیجه عملیات مقایسه را صرفاً در Flagها منعکس می‌کند.اگر عملوند اول و دوم برابر بودند، آنگاه: ZR = 1 و CY = 0اگر عملوند اول بزرگتر از دومی بود، آنگاه: ZR = 0 و CY = 0اگر عملوند اول کوچکر از دومی بود، آنگاه ZR = 0 و CY = 1 https://gist.github.com/alireza-rezaee/da313aeb6235aa6822c1f4a293aee352 دستور JMP، بر اساس Flagها برای پرش (پردیدن یا نپریدن) تصمیم می‌گیرد. در قالب یک مثال با انواع گونه‌های پرش آشنا می‌شویم. https://gist.github.com/alireza-rezaee/4aadb4aa7db0e7fb6d8e18a7d50832c0 اینک می‌توان با ابزار Disassembly ویژوال استدیو هر برنامه‌ای -مانند برنامۀ فوق- را به اسمبلی بازگردانی کنیم و کدهای آن را بررسی کنیم. با این آموزش مقدماتی قادر به درک بخشی از کدهای اسمبلی خواهیم بود.کد بالا بر بستر چارچوب نرم‌افزاری دات‌نت ۵.۰ نوشته شده بود، که چندسکویی است و به سبک JIT کامپایل می‌شود که به خودی خود دستورات اسمبلی پیچیده‌ای از آن متصاعد می‌شود. با انتخاب برنامه‌های ساده، کدهای ساده‌تری بدست می‌آید.این مقاله پیرو درس دانشگاهی «ریزپردازنده و زبان اسمبلی» به فرمایش استاد جناب آقای مهندس یعقوبی‌تبار به رشته تحریر درآمد.

Author: admin

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *