C# va assembler

Bo’sh vaqtlarimda boshqotirma sifatida quyi darajali dasturlash bilan qiziq turganim uchun doim boshqa tillarga assemblerda yozilgan kodlarni tiqishtirib yuraman. Qilmoqchi bo’lgan ishimni to’iq assemblerda yozib qo’ya qolay desam unda boshqotirma emas boshog’riqqa aylanib ketib qolishidan ko’ra, shu usul ma’qulroq.
Aslida .NET platformada CLI uchun o’zini “rodnoy” assembleri bor albatta. Buni IL (Oraliq til) deyiladi. .NET platformadagi ko’p tillar ushbu tilga kompilyasiya qilinadi va JIT kompilyator tomonidan bajariladi. Bunda yozilgan kod .NET dagi yuqori darajali tillardan biroz tezroq ishlaydi. Sababi kompilyasiya jarayonida optimizasiyani dastur odamchalik eplolmasligida albatta. Hullas hozir bizni IL qiziqtirmaydi, maqsad faqat “toza” assembler.

Demak, Windows API tarkibida CallWindowProc degan funksiya bor. Ushbu funksiya oyna “obrabotchigini”* chaqirish uchun ishlatiladi. Ya’ni, odatda oynalarni subclassing qilishda. Sodda qilib aytganda, masalan, biz qandaydir oynani “obrabotchigi”ni o’rniga o’zimizni “obrabotchik”ni qo’yib, kelayotgan kerakli habarlarni “eshitib”, unga mos amallarni bajaramiz va bizga keraksiz habarlar shunchaki yo’q bo’lib ketmasligi uchun eski obrabotchikni chaqirib qo’yamiz, “senga quyidagi habar bor” mazmunida. Bunda CallWindowProc ga oyaning eski “obrabotchigi” joylashgan manzilni beramiz (buni oldin aniqlab olgan bo’lamiz albatta). Bunday olib qarasak shu manzilni o’rniga ihtiyoriy kod joylashgan manzilni ham berishimiz mumkin. Ushbu usulda Visual Basic da “toza” kodlarni chaqirishda ishlatardim. Buning uchun oldin kodni biror assemblerda yozib, natijaviy mashina kodlarini biror massivga yozib, CallWindowProc ga ushbu massiv ko’rsatkichini (pointer) uzatilardi.
C# da yoziladigan kodlar CLR tomonidan doimiy nazorat ostidaligini bilamiz (managed code). Pointerlar, xotira bilan to’g’ridan to’g’ri ishlab bo’lmaslik va boshqa cheklovlar ataylab kiritilgan. Dastur xavfsizligini oshirish maqsadida albatta. Biroq Microsoftdagi okalar buyerda emin-erkin (unmanaged) yozish uchun ham har qalay biroz imkon qoldirishgan. C# da unsafe kalit so’zi ostida boshqaruvsiz kodlar qo’yish, fixed blogi ostida pointerlar bilan ishlash mumkin. Biz ham shundan foydalanamiz.
Avvalo C# dan chaqiradigan mashina kodidagi dasturni biror assemblerda yozib olamiz. Man NASM dan foydalanaman. Boshlanishiga faqat 2 ta sonni qo’shadigan kodda tekshirib ko’ramiz.
1.asm fayl yaratamiz (masalan notepad++ da):

%Define param1 [ebp+4]	; param1 
%Define param2 [ebp+8]	; param2 
%Define param3 [ebp+12]	; param3 
%Define param4 [ebp+16]	; param4 

[BITS 32]
mov ebp, esp			
mov edi, param1
mov eax, [edi]
mov edi, param2
add eax, [edi]
mov edi, param3
mov [edi], eax
mov esp, ebp			
ret

Yuqoridagini nasm da kompilyasiya qilamiz:
nasmw -f bin 1.asm -o 1.bin 
-f kaliti natijaviy dasturni koddan tashqari ma’lumotlarsiz hosil qiladi. DOS dagi .com kabi, faqatgina kodni o’zi. Natijada 20 baytli 1.bin degan fayl hosil bo’ladi.

Visual Studio ga o’tib C# da konsolli proyekt yaratamiz.

using System;
using System.Runtime.InteropServices;
    
namespace ConsoleApplication1
{
    unsafe class Program
    {

        [DllImport("User32.dll")]
        public static extern IntPtr CallWindowProc(byte* ptr, 
        int* param1 , int* param2 , 
        int* param3 , int* param4 );

        static void Main(string[] args)
        {
            
            // mashina kodi
            byte[] code ={0x89, 0xe5, 0x8b, 0x7d, 0x04, 0x8b, 0x07, 0x8b, 0x7d, 0x08, 0x03, 
			              0x07, 0x8b, 0x7d, 0x0c, 0x89, 0x07, 0x89, 0xec, 0xc3, 0x00};

            // code uchun pointerni olish va o'zgaruvchilar uchun 
            // "qo'zg'almas" hududga kirish
            fixed (byte* pointer = &code[0])
            {

                int p1, p2, p3, p4;
                p1 = 15; p2 = 75; p3 = p4 = 0;
                Console.WriteLine("Avval: p1={0} p2={1} p3={2} p4={3}", p1, p2, p3, p4);
                
                // kodni chaqirish
                CallWindowProc(pointer, &p1, &p2, &p3, &p4);

                Console.WriteLine("Keyin: p1={0} p2={1} p3={2} p4={3}", p1, p2, p3, p4);


                Console.ReadKey();

            }

        }


    }

}



Class dagi unsafe kalit so’zi nega kerakligini aytdim, unmanaged kod uchun kerak. code nomli massivga natijaviy 1.bin fayli tarkibi yozilgan. Buni qo’lda yozib chiqmaslik uchun boshqa oddiy bir dasturcha qilib oldim, yoki shu dasturni ichida fayldan o’qib olish ham mumkin. pointer nomli o’zgaruvchiga code massivini ko’rsatkichini olamiz va “qo’zg’almas” sohaga kiramiz. Qolgani tushunarli bo’lsa kerak. CallWindowProc funksiyaga kod yozilgan manzilni (pointer) va 4 ta parametrlarni beramiz.

Omadli chipta haqidagi masalani ham shunday ishlatib natijasini solishtirib ko’rdim. Ushbu misolni c# ni o’zidagi yechimidan ~5 marta tezroq hisoblandi. Albatta bu bilan demak assemblerdagi kod C# dagidan faqat 4 marta tez ishlar ekan deb hulosa chiqarish noto’g’ri. Haqiqiy tezliklarini solishtirish uchun hamma kodni assemblerda yozish kerak va boshqa maxsus testlardan foydalanish lozim bo’ladi. Shunday bo’lsada yetarlicha tezroq deyishimiz mumkin. Va ushbu narsa VB da menga ko’p asqotar edi.

Omadli chipta kodi:

byte[] code={0x55, 0x89, 0xe5, 0x81, 0xec, 0x0c, 0x00, 0x00, 0x00, 0x8b, 0x7d, 
			0x08, 0x8b, 0x07, 0x89, 0x45, 0xf8, 0x8b, 0x7d, 0x10, 0x8b, 0x07, 
			0x89, 0x45, 0xf4, 0x31, 0xc9, 0x51, 0x8b, 0x7d, 0x0c, 0xc7, 0x07, 
			0x01, 0x00, 0x00, 0x00, 0xb9, 0x01, 0x00, 0x00, 0x00, 0x51, 0x89, 
			0xc8, 0xe8, 0x35, 0x00, 0x00, 0x00, 0x89, 0x5d, 0xfc, 0x89, 0xd9, 
			0x51, 0x89, 0xc8, 0xe8, 0x28, 0x00, 0x00, 0x00, 0x3b, 0x5d, 0xfc, 
			0x75, 0x05, 0x8b, 0x7d, 0x0c, 0xff, 0x07, 0x59, 0x81, 0xc1, 0x09, 
			0x00, 0x00, 0x00, 0x3b, 0x4d, 0xf8, 0x72, 0xe2, 0x59, 0x41, 0x3b, 
			0x4d, 0xf8, 0x72, 0xce, 0x59, 0x41, 0x3b, 0x4d, 0xf4, 0x72, 0xb8, 
			0x89, 0xec, 0x5d, 0xc3, 0x31, 0xdb, 0x31, 0xd2, 0xb9, 0x0a, 0x00, 
			0x00, 0x00, 0xf7, 0xf1, 0x01, 0xd3, 0x85, 0xc0, 0x75, 0xf1, 0xc3, 
			0x00};


Dastur kodi: cs_asm1.zip

9 комментариев

geniuz
unsafedan foydalanish uchun direktiva ochsa xam bulardi adashmasam, keyin project propertylaridayam kursatiladigan joyi bulardi…
xozir windows va vs yuqligi sabab aniqlab qaray olmadim.
0
U2B3K
axa, tushunadigan odam o'zi tushunib olaveradi. Свойства -> Построение -> Разрешить небезопасный код
0
geniuz
assemblerga keladigan bulsak, windowsda bunaqa experimentlar qilish yaxshi va shu yunalishdagi maqolalarni kutib qolaman, ammo assemblerda kod yozib temirga (atmel,pic) jon kiritish undanda zavqli.
0
U2B3K
bir vaqtlar bu bilan ham 2 xaftacha shug'ullanganman, majburiyat yuzasidan. APP moduli uchun PIC16F28 ga dastur yozib sochlarim oqarib ketgan :) Kompyuterdagidek bemalol keng hudud (RAM,ROM) yo'q, steklar cheklangan va xokazo. Lekin haqiqatdan ham juda qiziq jarayon )
0
geniuz
pic da togri rosayam odam uzi sigmaydigan xonaga kirib qolganday buladi :)
atmel tarafga bir qarab kursangiz bularkan
1
U2B3K
xa datasheetlarini qarab ko'rdim bir ikki MK larni, mini kompyuterchalar ekan hisob :)
0
shranet
Demak, CallWindowProc ga o'xshagan yana qaysidir manzilni chaqiradigan WinAPI funksiyasi bo'lsa ASM ishlatsa bo'larkanda unda?
0
U2B3K
xa, lekin bunaqa funksiya kam. Yana bitta variant: C++ da yozib kutubxona sifatida foydalanish. C++ inline assembleri borligi asqotadi.
0
jamic
MKlarni bir uz urnida yaxshi lekin mk larning chastota xarakteristikasi chegarlanga 50 mhz dan yoqorisi uje Arm dek yani kirish chiqish portlarini,pereferik qurulmalarni ustroysva deb tipa driver yozishdek yozishga to`gri keladi. Pic xususda kelsak picda yuqori chastotada ishlash imkoniyati bor lekin uning arxitekturasida kupchilik ASM komandalarini bajarish uchun 2-3 takt kere buladi. yana qaytib AVR ga kep qolamiz ))
0