موضوع مقاله: کد خودتغییردهنده | مخاطب: عمومی | لحن: دانشنامه‌ای | ساختار: عنوان، مقدمه، 3 بخش، جمع‌بندی | مقاله: | # کد خودتغییردهنده | مقدمه: در علوم کامپیوتر، کد خود تغییر دهنده کدی است که دستورالعمل‌های خود را تغییر می‌دهد در حالی که دارد اجرا می‌شود - معمولاً برای کاهش طول مسیر دستور و بهبود عملکرد یا به سادگی برای کاهش کد مشابه تکرارشده تا نگهداری آسان‌تر بشود. | ## بخش 1: کد خودتغییردهنده | خود-تغییر دهندگی یک جایگزین برای روش «تنظیم پرچم» و برنامه مشروط شاخه‌ای است که در درجه اول برای کاهش تعداد دفعاتی که یک شرایط باید مورد آزمایش قرار گیرد، استفاده می‌شود. این اصطلاح معمولاً فقط برای کدی استفاده می‌شود که در آن خود تغییر دهندگی عمدی و با قصد است، نه در مواردی که کد به صورت تصادفی به علت خطایی مانند سرریز بافر(buffer overflow) تغییر می‌کند. این روش غالباً برای استفاده مشروط از کد تست / اشکال زدایی کد بدون نیاز به هزینه اضافی محاسباتی برای هر چرخه ورودی / خروجی استفاده می‌شود. تغییرات ممکن است انجام شود: فقط در طی راه اندازی اولیه - براساس پارامترهای ورودی است (زمانی که فرایند بیشتر به عنوان پیکربندی نرم‌افزار توصیف می شود و در اصطلاحات سخت‌افزار، به تنظیم کردن jumperها برای صفحات مدار چاپی تقریباً مشابه است). تغییر اشاره گرهای ورودی برنامه یک روش غیرمستقیم معادل خود تغییر دهندگی است، اما نیاز به وجود یک یا بیشتر مسیرهای دستورالعملی جایگزین دارد که افزایش حجم برنامه را شاهد خواهیم بود. در حالت دیگر، تغییرات ممکن است به‌طور مستقیم خودشان به دستورالعمل‌های کد ماشین اعمال بشوند، با پوشاندن یا جایگزینی دستورالعمل‌های جدید بر روی دستورالعمل‌های در حال حاضر (به عنوان مثال: تغییر مقایسه و branch به branch بدون شرط یا " NOP ") انجام می‌شود. این اثر خود تغییر دهندگی را نسان می‌دهد، اگر چه دستورالعمل واقعی در حافظه تغییر نمی‌کند. کاربرد در زبان‌های سطح پایین و سطح بالا خود-تغییر دهندگی را می‌توان به روش‌های مختلفی بسته به زبان برنامه‌نویسی و پشتیبانی آن از اشاره گرها و / یا دسترسی به کامپایلر پویا یا موتورهای مترجم انجام داد: پوشاندن دستورالعمل‌های موجود (یا بخشی از دستورالعمل‌هایی مانند opcode, register, flags یا آدرس) یا ایجاد مستقیم دستورالعمل‌های کامل یا توالی دستورالعمل‌ها در حافظه ایجاد یا تغییر عبارات کد منبع که با یک مینی کامپایل یا یک تفسیر یا ترجمه پویا صورت می‌گیرد (نگاه کنید به eval) ایجاد یک برنامه کامل به صورت پویا و سپس اجرای آن زبان اسمبلی کد خود تغییر دهندگی برای پیاده‌سازی در هنگام استفاده از زبان اسمبلی بسیار ساده است. دستورالعمل‌ها می‌توانند به‌طور پویا در حافظه ایجاد شوند (یا در موارد دیگر بر روی کدهای موجود در ذخیره‌سازی برنامه‌های محافظت نشده)، در توالی معادل با آن‌هایی که کامپایلر استاندارد ممکن است به عنوان کد هدف تولید کند. با پردازنده‌های مدرن، ممکن است عوارض جانبی ناخواسته بر روی حافظه نهان CPU وجود داشته باشد که باید در نظر گرفته شود. این روش اغلب برای آزمایش شرایط «اولین بار» مورد استفاده قرار می‌گیرد، همان‌طور که در این نمونه زبان اسمبلی مناسب IBM / 360 توضیح داده شده‌است. این روش با استفاده از پوشش دستور برای کاهش طول مسیر دستورالعمل به N × 1) -1) که در آن N که تعداد رکوردها در فایل است. دستور پرش بدون قید و شرط کمی سریعتر از دستور مقایسه ای است، و همچنین باعث کاهش طول مسیر کلی می‌شود. در سیستم عامل‌های بعدی برای برنامه‌هایی که در حافظه محافظت شده قرار دارند، این تکنیک نمی‌تواند مورد استفاده قرار گیرد و به جای آن از تغییر اشاره گر به زیر برنامه استفاده خواهد شد. اشاره گر در حافظه پویا قرار دارد و می‌تواند پس از گذراندن اولین مرحله برای دور زدن OPEN تغییر یابد (ابتدا باید یک اشاره گر به جای یک پرش مستقیم بارگذاری شود و به زیر برنامه ارتباط پیدا کند که N دستورالعمل را به طول مسیر اضافه می‌کند - اما یک کاهش متناظر با N برای پرش بدون قید و شرط وجود دارد که دیگر لازم نیست). به عنوان مثال، در مجموعه دستورها اینتل ۸۰۸۰ نمی‌توان یک بایت را از یک پورت ورودی که توسط یک ثبات مشخص شده‌است را وارد کرد. پورت ورودی به صورت استاتیکی در خود دستور، به عنوان بایت دوم دستور دو بایتی رمزگذاری می‌شود. با استفاده از کد خود تغییر دهنده، می‌توان محتویات ثبات را در بایت دوم دستور ذخیره کرد، سپس دستور اصلاح شده را برای رسیدن به اثر مورد نظر، اجرا می‌کند. زبانهای سطح بالا برخی از زبان‌های کامپایل شده به صراحت اجازه می‌دهند کد خود را تغییر دهید. به عنوان مثال، فعل ALTER در کوبول ممکن است به عنوان دستور پرشی اجرا شود که در حین اجرای برنامه اصلاح می‌شود. کامپایلر Algol در سیستم‌های B6700 یک رابط را برای سیستم عامل ارائه داد که در آن اجرای کد می‌تواند یک رشته متن یا یک فایل دیسک نام‌گذاری شده را به کامپایلر Algol منتقل کند و سپس قادر به فراخوانی نسخه جدیدی از یک روش بود. با زبان‌های تفسیر شده، "کد ماشین" متن منبع است و ممکن است حساس به ویرایش بر روی on-the-fly باشد: در اسنوبول، جملات مرجع که اجرا می‌شوند، عناصر یک آرایه متنی هستند. زبان‌های دیگر مانند Perl و Python به برنامه‌ها برای ایجاد کد جدید در زمان اجرا و اجرای آن با استفاده از یک تابع eval، اجازه می‌دهند، اما اجازه تغییر در کد فعلی قابل را نمی‌دهند. زبان برنامه‌نویسی Push یک سیستم برنامه‌نویسی ژنتیکی است که به‌طور ویژه برای ایجاد برنامه‌های خود تغییر دهنده طراحی شده‌است. تغییر مرکب قبل از ظهور تعدادی از ویندوزها، سیستم‌های command-line ممکن است یک سیستم منو که شامل اصلاح یک اسکریپت فرمان اجرایی است را ارائه دهند. اگر چه این حالت شروع هیچ استفاده ای برای برچسب ندارد، یا مقدار معادل آن متن لازم است، زیرا مترجم فرمان DOS مکان فرمان بعدی را هنگامی که فرمان بعدی را اجرا می‌کند به یاد می‌آورد، بنابراین فایل دوباره نوشته شده باید هماهنگی برای نقطه شروع فرمان بعدی را حفظ کند تا در واقع شروع فرمان بعدی باشد. | ## بخش 2: کد خودتغییردهنده | بجز راحتی یک سیستم منو (و ویژگی‌های کمکی احتمالی)، این روش به این معنی است که سیستم ShowMenu.exe زمانی که فرمان انتخاب شده فعال نیست، مزیت قابل توجهی در هنگام محدود بودن حافظه دارد. جداول کنترل مترجم‌های جدول کنترل می‌توانند در یک معنا "خود تغییر داده شده" با مقادیر داده استخراج شده از ورودی‌های جدول (به جای اینکه به‌طور خاص دسته‌بندی شده در جمله‌های شرطی به فرم "IF inputx = 'yyy'") در نظر گرفته شود. برنامه‌های کانال بعضی از روش‌های دسترسی IBM به‌طور سنتی برنامه‌های کانال خود تغییر دهنده را استفاده می‌کنند، جایی که یک مقدار مانند آدرس دیسک، در یک ناحیه اشاره شده توسط برنامه کانال، که از طریق یک فرمان کانال بعدی برای دسترسی به دیسک استفاده می‌شود، خوانده می‌شود. تاریخ IBM SSEC در ماه ژانویه سال ۱۹۴۸ نشان داد که توانایی تغییر دستورهایش را دارد یا در غیر این صورت با آن‌ها دقیقاً مانند داده رفتار کند. در اولین روزهای رایانه‌ها، کد خود تغییر دهنده اغلب برای کاهش استفاده از حافظه محدود یا بهبود عملکرد یا به هر دو دلیل استفاده می‌شد. همچنین گاهی اوقات برای پیاده‌سازی توابع زیر برنامه ای استفاده می‌شد و هنگامی برمی‌گشت که مجموعه دستورها تنها پرشی ساده یا رد شدن از دستورها برای تغییر دادن کنترل جریان، باشند. کاربرد کد خود تغییر دهنده را می‌توان برای اهداف مختلف استفاده کرد: بهینه‌سازی نیمه اتوماتیک یک حلقه وابستگی حالت. تولید کد زمان اجرا یا تخصص یک الگوریتم در زمان اجرا یا زمان بارگذاری (که محبوب است، به عنوان مثال در حوزه گرافیک در زمان واقعی) مانند یک ابزار مرتب‌سازی کلی - آماده‌سازی کد برای انجام مقایسه کلیدی که در یک فراخوانی خاص شرح داده شده‌است. پچ کردن آدرس زیر برنامه (اشاره گر) صدا زده شده، معمولاً به عنوان زمان بارگذاری یا مقداردهی اولیه کتابخانه‌های پویا انجام می‌شود، یا در هر فراخوانی، پچ کردن مرجع‌های داخلی زیر برنامه به پارامترهای آن، برای استفاده از آدرس‌های واقعی آنها. پر کردن ۱۰۰٪ حافظه (در بعضی از معماری‌ها) با یک الگوی نورد از تکرار opcodes، برای پاک کردن تمام برنامه‌ها و داده‌ها، یا سوزاندن سخت‌افزار. فشرده سازی کد را باید در زمان اجرا فشرده و اجرا کرد، مثلاً زمانی که حافظه یا فضای دیسک محدود است. برخی از مجموعه‌های دستور بسیار محدود هیچ گزینه ای ندارند، اما از کد خود تغییر دهنده برای انجام وظایف خاص استفاده می‌کنند. میکرو رایانه‌های اولیه اغلب از کد خود تعییر دهنده در بوت لودرها استفاده می‌کردند. از آنجا که بوت لودر از طریق قطعه جلو در هر دکمه پاور کلید شده بود، مهم نبود که بوت لودر خود را تغییر دهد. تخصص فرض کنید مجموعه ای از آمار مانند میانگین، بیشینه، موقعیت بیشینه، انحراف استاندارد و … برای برخی از مجموعه داده‌های بزرگ محاسبه می‌شود. در یک وضعیت کلی، ممکن است یک حالت مربوط به وزن با داده وجود داشته باشد، بنابراین هر x i با aw i همراه است و به جای آزمودن حضور وزن در هر مقدار شاخص، ممکن است دو نسخه از محاسبه، یکی برای استفاده با وزن و یکی برای استفاده بدون وزن، با یک آزموندن در آغاز کار، موجود باشد. حال یک گزینه دیگر را در نظر بگیرید که هر مقدار ممکن است با یک عدد بولین همراه باشد تا نشان دهد که آیا این مقدار باید حذف شود یا خیر. در عوض، وزن و آرایه‌های جست و جوی می‌توانند به یک آرایه موقت (با وزن صفر برای مقادیری که باید پر شوند)، با هزینه پردازش، ترکیب شوند و هنوز هم باد کردن وجود دارد. با این حال، با تغییر کد، به الگوی محاسبه آمار می‌تواند کد مورد نظر که برای پرش از مقادیر ناخواسته و برای اعمال وزن است، اضافه شود. تست‌های تکراری از گزینه‌ها وجود نخواهد داشت و به آرایه داده‌ها یک بار دسترسی انجام می‌شود، همچنین وزن و آرایه‌های جستجو می‌تواند به همین شکل باشد، اگر شامل کد باشند باشد. استفاده به عنوان پوشش کد خود تغییر دهنده برای مخفی کردن کپی حفاظت دستورها در برنامه‌های مبتنی بر دیسک ۱۹۸۰ برای سیستم عامل‌هایی مانند IBM PC و Apple II استفاده شده‌است. به عنوان مثال، در IBM PC (یا سازگار)، دستور دستیابی درایو فلاپی دیسک که int 0x13 می‌باشد در تصویر برنامه اجرایی نمایش داده نخواهد شد، اما بعد از اجرای برنامه، در تصویر حافظه اجرایی نوشته می‌شود. کد خود تغییر دهنده گاهی اوقات توسط برنامه‌هایی استفاده می‌شود که نمی‌خواهند حضورشان را نشان دهند، مانند ویروس‌های کامپیوتری و برخی ازshellcodeها. ویروس‌ها و کد شل‌ها که از کد خود تغییر دهنده استفاده می‌کنند، عمدتاً این کار را با کد پلی مورفیک انجام می‌دهند. سیستم یادگیری ماشین خود ارجاعی سیستم‌های یادگیری ماشین سنتی دارای الگوریتم یادگیری ثابت و پیش برنامه‌ریزی شده برای تنظیم پارامترهایشان می باشند. این سیستم‌ها از خطر خودکفائی فاجعه آمیز جلوگیری می‌کنند، از این طریق اطمینان حاصل می‌کنند که خود تغییرات تنها در صورتی قابل قبول خواهند بود که با توجه به آمادگی کاربر، خطا یا عملکرد پاداش مفید باشد. | ## بخش 3: کد خودتغییردهنده | نگرانی معمولاً این نیست که برنامه‌ها عمداً خودشان را تغییر دهند، اما می‌توانند از طریق سوء استفاده از آنها سوءاستفاده کنند. به عنوان نتیجهٔ مشکلاتی که می‌تواند توسط این سوء استفاده‌ها ایجاد شود، یک ویژگی OS به نام W ^ X (برای "نوشتن xor execute") توسعه داده شده‌است که به برنامه اجازه نمی‌دهد که هیچ صفحهٔ حافظه را هم قابل خواندن و هم قابل اجرا کند. بعضی از سیستم‌ها از یک صفحه که قابل نوشتن است جلوگیری می‌کنند تا به حالت قابل اجرا درآید، حتی اگر اجازه نوشتن حذف شود. سیستم‌های دیگر " درهای عقب " را ارائه می‌دهند که اجازه می‌دهد چندین صفحه بندی از یک صفحهٔ حافظه دارای مجوزهای متفاوت باشد. یک راه نسبتاً قابل حمل برای دور زدن W ^ X این است که یک پرونده با تمام مجوزها ایجاد کنید، سپس فایل را به حافظه دو بار نگاشت کنید. در لینوکس، می‌توان از یک پرچم حافظه مشترک SysV ناموجود استفاده کرد تا به حافظه مشترک قابل اجرا بدون نیاز به ایجاد یک فایل رسید. صرفنظر از موارد فوق، در سطح فراگیر، برنامه‌ها همچنان می‌توانند رفتار خود را با تغییر داده‌های ذخیره شده در جاهای دیگر تغییر دهند یا از طریق استفاده از پلی مورفیسم. تعامل حافظهٔ نهان و کد خود تغییر دهنده در معماری‌های بدون داده‌های مرتبط و حافظه پنهان دستورالعمل (برخی از هسته‌های ARM و MIPS)، هماهنگ سازی حافظه پنهان باید به وضوح توسط کد تغییر دهنده انجام شود (حافظه پنهان دادهٔ تخلیه و حافظهٔ پنهان دستورالعمل بی‌اعتبار برای محدوده حافظه تغییر داده شده). در بعضی موارد، بخش‌های کوتاهی از کد خود تغییر دهنده به آرامی روی پردازنده‌های مدرن اجرا می‌شود. این به این دلیل است که یک پردازنده مدرن معمولاً سعی می‌کند بلوک‌های کد را در حافظه پنهان خود نگه دارد. هر بار که برنامه بخشی از خود را بازنویسی می‌کند، قسمت بازنویسی شده باید دوباره در حافظه پنهان بارگذاری شود، که باعث یک تأخیر جزئی می‌شود، اگر کدهای تغییر داده همان خط حافظهٔ پنهان را با کد تغییری به اشتراک گذارند، همانند مواردی که حافظه تغییر داده شده را آدرس درون چند بایت در یکی از کدهای تغییر داده شده‌است. مسئله عدم اعتبار حافظهٔ پنهان در پردازنده‌های مدرن معمولاً بدین معنی است که کد خود تغییر دهنده هنوز هم سریع تر خواهد بود تنها زمانی که تغییر به ندرت رخ می‌دهد، مانند تعویض حالت درون حلقه داخلی. بیشتر پردازنده‌های مدرن قبل از اجرای آن، کد ماشین را بارگذاری می‌کنند، بدین معنی که اگر دستورالعملی که خیلی نزدیک اشارهگر دستورالعمل است، تغییر داده شده باشد، پردازنده متوجه نمی‌شود، بلکه کد را قبل از اینکه آن را تغییر دهد اجرا می‌کند. پردازنده‌های PC باید به کد خود تغییر دهنده را به درستی برای دلایل سازگاری عقبی انجام دهند، اما در انجام این کار بسیار دور از حالت بهینه‌اند. با این حال، ساختار تکنیک‌ها نشان می‌دهد که آن‌ها می‌توانند با یک زبان سطح بالاتری پیاده‌سازی شوند، اگرچه پیچیده‌تر از زبان‌های سطح متوسط موجود هستند. چنین زبان و کامپایلر می‌تواند اجازهٔ توسعه سریعتر سیستم عامل‌ها و برنامه‌های کاربردی را دهد. پل هابرلی و بروس کارش با «حاشیه سازی» کد خود تغییر دهنده و بهینه‌سازی به‌طور کلی به نفع کاهش هزینه‌های توسعه مخالفت کردند. مزایا می‌توان راه‌های سریع برای اجرای برنامه ایجاد کرد و برخی از شاخه‌های شرطی تکراری دیگر را کاهش داد. معایب کد خودتغییر داده شده برای خواندن و نگهداری سخت‌تر است، زیرا دستورالعمل‌ها در فهرست برنامهٔ منبع، لزوماً دستورالعمل‌هایی نیستند که اجرا می‌شوند. خود تغییری که شامل تعویض اشاره گرهای عملکرد است ممکن است به صورت معکوس نباشد، اگر روشن باشد که نام توابع نامیده می‌شود، متغیرهایی برای توابع شناسایی شده‌است. کد خود تغییر دهنده می‌تواند به عنوان کد بازنویسی شود که پرچم و شاخه‌ها را به دنباله‌های جایگزین بر اساس نتیجه آزمایش تست می‌کند، اما کد خود تعییر دهنده معمولاً سریعتر اجرا می‌شود. در پردازنده‌های مدرن با یک خط لوله دستورالعمل، کد ای که خود را به صورت متناوب تغییر می‌دهد، ممکن است به آرامی اجرا شود، در صورتی که دستورالعمل‌هایی را که پردازشگر از حافظه به خط لوله خوانده‌است، تغییر دهد. در بعضی از این پردازنده‌ها، تنها راه برای اطمینان از این که دستورالعمل‌های تغییر داده شده به درستی اجرا می‌شوند، این است که خط لوله تخلیه شود و دوباره دستورالعمل‌ها را بخواند. در برخی از محیط‌ها، کد خود تغییر دهنده نمی‌تواند مورد استفاده قرار گیرد، مانند موارد زیر: نرم‌افزاری کاربردی که تحت یک سیستم عامل اجرا می‌شود با امنیت سخت‌افزار W ^ X نمی‌تواند دستورالعمل‌هایی را در صفحات مجاز به نوشتن. تنها سیستم عامل مجاز به هر دو دستورالعمل نوشتن به حافظه و بعد از آن دستورالعمل‌ها را اجرا کند. بسیاری از میکروکنترلرهای معماری هاروارد نمی‌توانند دستورالعملهایی را در حافظه خواندن و نوشتن اجرا کنند، اما فقط دستورالعملهایی را در حافظه ای که نمی‌توان آن را نوشت، ROM یا حافظه فلش‌های غیر خودپرداز. | ## جمع‌بندی | یک برنامه چند رشته‌ای ممکن است چندین رشته را اجرا کند که قسمت مشابهی از کد خود تغییر دهنده را اجرا کرده باشد، احتمالاً به خطاهای محاسباتی و خرابی‌های برنامه منجر می‌شود.