Brainfuck منخفضة المستوى

الجزء الأول
الجزء الثاني
الجزء الثالث

في هذه المقالة ، سنكتب مترجم Brainfuck على TurboAssembler
هنا يمكنك تصحيح برامج bf في وضع خطوة بخطوة.

أولاً ، سنكتب المترجم ببعض اللغات عالية المستوى ، على سبيل المثال ، بلغة باسكال.

سوف تمثل مصفوفة data_arr ذاكرة البيانات ، وستحتوي سلسلة str_arr على الأوامر.

سنكتب برنامجًا يعرض حرفًا يتوافق رمز ascii الخاص به مع الرقم + (لذلك نحتاج فقط إلى الأوامر + و . )

var data_arr:array[1..10] of integer; //   str_arr: string; //  i, j: integer; //     begin j:=1; //       readln(str_arr); //  for i:=1 to length(str_arr) do begin //     if (str_arr[i]='+') then data_arr[j]:= data_arr[j]+1; if (str_arr[i]='.') then write(chr(data_arr[j])); end; end. 

bf code +++++++++++++++++++++++++++++++++++++. سيعطي ! (رمز أسكي للحرف ! 33).

يمكن التحقق من البرنامج على الإنترنت ideone.com

بعد ذلك ، استبدل الحلقة for بـ goto وأضف الأوامر <>
في النهاية ، سنخرج صفيف data_arr

 LABEL prev,next; var data_arr:array[1..10] of integer; //   str_arr: string; //  i,j,k: integer; //     begin i:=1; j:=1; readln(str_arr); //  prev: if i>length(str_arr) then goto next; if (str_arr[i]='+') then data_arr[j]:= data_arr[j]+1; if (str_arr[i]='-') then data_arr[j]:= data_arr[j]-1; if (str_arr[i]='>') then j:=j+1; if (str_arr[i]='<') then j:=j-1; if (str_arr[i]='.') then write(chr(data_arr[j])); i:=i+1; goto prev; next: for k:=1 to 10 do begin write(data_arr[k]); write(' '); end; end. 

كود +>++>+++سيعطي 1 2 3 0 0 0 0 0 0 0
كود +>++>+++سيعطي 1 2 2 0 0 0 0 0 0 0
ideone.com

بعد ذلك ، أضف [ و ]
أضف متغير i_stor آخر.
إذا اجتاز العنصر الحالي الاختبار في [ ، فإننا نتحقق من العنصر الحالي في صفيف data_arr إلى صفر ، وإذا كان العنصر أكبر من الصفر ، فقم بتحميل القيمة من المتغير i إلى i_stor .

عند معالجة قوس الإغلاق ] ، إذا لم يكن data_arr صفرًا ، فإننا نقوم بتحميل عنوان قوس الفتح في المتغير i من المتغير i_stor [

بعد ذلك ، انتقل إلى الأمر i: = i + 1؛
إذا تم تحميل قيمة من i_stor قبل ذلك إلى i (أثناء التحقق ] ) ، فعندئذٍ بعد التفريغ سنكون في [ (وإلا سنكون في ] )
 LABEL prev,next; var data_arr:array[1..10] of integer; //   str_arr: string; //  i,j,k: integer; //     i_stor: integer; begin j:=1; i:=1; readln(str_arr); //  prev: if i>length(str_arr) then goto next; if (str_arr[i]='+') then data_arr[j]:= data_arr[j]+1; if (str_arr[i]='-') then data_arr[j]:= data_arr[j]-1; if (str_arr[i]='>') then j:=j+1; if (str_arr[i]='<') then j:=j-1; if (str_arr[i]='.') then write(chr(data_arr[j])); if (str_arr[i]='[') then begin if data_arr[j]>0 then i_stor:=i; end; if (str_arr[i]=']') then begin if data_arr[j]>0 then begin i:=i_stor; end; end; i:=i+1; goto prev; next: for k:=1 to 10 do begin write(data_arr[k]); write(' '); end; end. 

كود +++++[>+<]ينقل الرقم 5 إلى الخلية التالية 0 5 0 0 0 0 0 0 0 0
ideone.com
يبدو رمز HelloWorld مثل ideone.com

دعنا ننتقل إلى المجمع


لتنظيم حلقة (حلقة) ، تحتاج إلى وضع عدد دورات الدورة في CX ووضع تسمية يتم الانتقال إليها في نهاية الدورة (بواسطة الأمر حلقة).

 mov CX, 28h ; -   prev: ;   ;  ;  ;   loop prev ;    prev 

قم بإنشاء مجموعة من أوامر str_arr ، ضعها هناك +++
إنشاء مصفوفة بيانات data_arr ، (للتوضيح) ضع هناك 1،1،1،1،1،1،1،1،1،1،1

في حلقة ، قارن الحرف الحالي بالحرف +وإذا كانت الأحرف متساوية ، قم بزيادة القيمة في الخلية الحالية بمقدار 1.

 text segment ; bf1.asm assume cs:text, ds:data, ss:stk begin: ;   mov AX,data ;    mov DS,AX mov DL, str_arr ;   DL 1  mov CX, 0Ah ; 10  prev: cmp DL, 2Bh ;   + jne next ; ,    next mov BL, 00h ;   BL  inc data_arr[BX] ; ,      1 next: inc i ;       mov BL, i mov DL, str_arr [BX] loop prev mov AX, 4c00h ;   int 21h text ends data segment str_arr DB 2Bh,2Bh,2Bh,'$' ;  +++ data_arr DB 1,1,1,1,1,1,1,1,1,1,'$' ;  i DB 0 ;    data ends stk segment stack db 100h dup (0) ;  256  stk ends end begin 

يتم تنفيذ التجميع (الترجمة) بواسطة الأمر tasm.exe bf1.asm
يتم الربط بواسطة tlink.exe bf1.obj

بعد تشغيل البرنامج في مصحح أخطاء TurboDebagger ، يمكنك رؤية أنه بدءًا من العنوان 0130 ، توجد الأوامر +++
التالي هو مجموعة البيانات التي قمنا فيها بتغيير العنصر الأول ، ثم المتغير i ، الذي يصبح بعد تنفيذ الحلقة مساوياً لـ 0Ah.



أضف أوامر <>.
لإخراج حرف واحد باستخدام وظيفة المقاطعة 02h int 21h ، من الضروري (قبل استدعاء المقاطعة) وضع رمز الحرف في سجل DL .

  mov AH,2 mov DL,   int 21h 

سنكتب البرنامج بأكمله

 text segment ; bf2.asm assume cs:text,ds:data, ss:stk begin: ;   mov AX,data ;    mov DS,AX mov DL, str_arr ;   DL 1  mov CX, 0Ah ; 10  prev: cmp DL, 2Bh ;   + jne next ; ,    next mov BL, j ;   BL   inc data_arr[BX] ; ,      1 next: cmp DL, 2Dh ;   - jne next1 ; ,    next1 mov BL, j dec data_arr[BX] next1: cmp DL, 3Eh ;   > jne next2 ; ,    next2 inc j ; ,      data_arr next2: cmp DL, 3Ch ;   < jne next3 ; ,    next3 dec j ; ,      data_arr next3: cmp DL, 2Eh ;   . jne next4 ; ,    next4 mov AH,2 ; ,    mov BL, j mov DL, data_arr[BX] int 21h next4: inc i ;       mov BL, i mov DL, str_arr [BX] loop prev mov AX, 4c00h ;   int 21h text ends data segment str_arr DB 2Bh,3Eh,2Bh,2Bh,'$' ;  +>++ data_arr DB 0,0,0,0,0,0,0,0,0,0,'$' ;  i DB 0, '$' ;    j DB 0, '$' ;    data ends stk segment stack db 100h dup (0) ;  256  stk ends end begin 



تعمل الحلقة على النحو التالي:
إذا لم يكن العنصر الحالي من str_arr +ثم انتقل إلى الملصق التالي: (وإلا ، نفذ +)
إذا لم يكن العنصر الحالي من str_arr ثم القفز إلى تسمية next1 التالية:
إذا لم يكن العنصر الحالي من str_arr >ثم القفز إلى تسمية next2:
إذا لم يكن العنصر الحالي من str_arr <ثم القفز إلى التسمية next3:
إذا لم يكن العنصر الحالي من str_arr .ثم انتقل إلى التسمية التالية 4 :
بعد الملصق next4: قم بزيادة فهرس السلسلة str_arr وانتقل إلى بداية الحلقة - إلى الملصق prev:

بعد ذلك ، أضف [ و ]
أضف متغير i_stor .

إذا اجتاز العنصر الحالي الاختبار في [ ، فإننا نتحقق من العنصر الحالي في مصفوفة data_arr إلى الصفر ، وإذا كان العنصر صفرًا ، فإننا نقفز أكثر (إلى الملصق التالي) ، وإلا فإننا نحمل القيمة من المتغير i إلى i_stor .

 next4: cmp DL, 5Bh ;   [ jne next5 ; ,    next5 mov BL, j mov DL, data_arr[BX] cmp DL, 00 ; ,    data_arr   jz next5 ;  ,   mov DL, i ;   mov i_stor, Dl ;  i_stor   i next5: 

عند معالجة قوس الإغلاق ] ، إذا لم يكن data_arr صفرًا ، فقم بتحميل عنوان قوس الفتح في المتغير i من المتغير i_stor [

 next5: cmp DL, 5Dh ;   ] jne next6 ; ,    next6 mov BL, j mov DL, data_arr[BX] cmp DL, 00 ; ,    data_arr   jz next6 ;  ,   mov DL, i_stor ;   mov i, Dl ;  i_stor   i next6: 

تحقق من الرمز +++++[>+<]

 text segment ; bf4.asm assume cs:text, ds:data, ss:stk begin: ;   mov AX,data ;    mov DS,AX mov DL, str_arr ;   DL 1  mov CX, 50h ; 80  prev: cmp DL, 2Bh ;   + jne next ; ,    next mov BL, j ;   BL   inc data_arr[BX] ; ,      1 next: cmp DL, 2Dh ;   - jne next1 ; ,    next1 mov BL, j dec data_arr[BX] ;BX,   Bl next1: cmp DL, 3Eh ;   > jne next2 ; ,    next2 inc j ; ,      data_arr next2: cmp DL, 3Ch ;   < jne next3 ; ,    next3 dec j ; ,      data_arr next3: cmp DL, 2Eh ;   . jne next4 ; ,    next4 mov AH,2 ; ,    mov BL, j mov DL, data_arr[BX] int 21h next4: cmp DL, 5Bh ;   [ jne next5 ; ,    next5 mov BL, j mov DL, data_arr[BX] cmp DL, 00 ; ,    data_arr   jz next5 ;  ,   mov DL, i ;   mov i_stor, Dl ;  i_stor   i next5: cmp DL, 5Dh ;   ] jne next6 ; ,    next6 mov BL, j mov DL, data_arr[BX] cmp DL, 00 ; ,    data_arr   jz next6 ;  ,   mov DL, i_stor ;   mov i, Dl ;  i_stor   i next6: inc i ;     mov BL, i mov DL, str_arr[BX] loop prev ;    prev: mov AX, 4c00h ;   int 21h text ends data segment str_arr DB 2Bh,2Bh,2Bh,2Bh,5Bh, 3Eh,2Bh,3Ch,2Dh ,5Dh, '$' ;  ++++[>+<-] data_arr DB 0,0,0,0,0,0,0,0,0,0,'$' ;  i DB 0,'$' ;    j DB 0,'$' ;    i_stor DB 0,'$' data ends stk segment stack db 100h dup (0) ;  256  stk ends end begin 



إضافة وظيفة إلى خط الإدخال 3fh المقاطعة 21 ساعة
  mov ah, 3fh ;   mov cx, 100h ; 256  mov dx,OFFSET str_arr int 21h 


سنخرج من الحلقة بعد الوصول إلى نهاية السلسلة "$" .
للقيام بذلك ، سنقوم بمقارنة الحرف الحالي بالحرف "$"
 cmp DL, 24h ;  '$' je exit_loop 

استبدل حلقة الحلقة بالأمر jmp.
 text segment assume cs:text,ds:data, ss: stk begin: ;   mov AX,data ;    mov DS,AX ;   mov ah, 3fh mov cx, 100h ; 256  mov dx,OFFSET str_arr int 21h ; mov DL, str_arr ;   DL 1  ;mov CX, 100h ; 256  prev: cmp DL, 24h ;    '$' je exit_loop cmp DL, 2Bh ;   + jne next ; ,    next mov BL, j ;   BL   inc data_arr[BX] ; ,      1 next: cmp DL, 2Dh ;   - jne next1 ; ,    next1 mov BL, j dec data_arr[BX] ;BX,   Bl next1: cmp DL, 3Eh ;   > jne next2 ; ,    next2 inc j ; ,      data_arr next2: cmp DL, 3Ch ;   < jne next3 ; ,    next3 dec j ; ,      data_arr next3: cmp DL, 2Eh ;   . jne next4 ; ,    next4 mov AH,2 ; ,    mov BL, j mov DL, data_arr[BX] int 21h next4: cmp DL, 5Bh ;   [ jne next5 ; ,    next5 mov BL, j mov DL, data_arr[BX] cmp DL, 00 ; ,    data_arr   jz next5 ;  ,   mov DL, i ;   mov i_stor, Dl ;  i_stor   i next5: cmp DL, 5Dh ;   ] jne next6 ; ,    next6 mov BL, j mov DL, data_arr[BX] cmp DL, 00 ; ,    data_arr   jz next6 ;  ,   mov DL, i_stor ;   mov i, Dl ;  i_stor   i ;       prev: next6: inc i ;     mov BL, i mov DL, str_arr[BX] ; loop prev ;    prev: jmp prev exit_loop: MOV AH,2 ;     MOV DL,0Ah INT 21h mov AX, 4c00h ;   int 21h text ends data segment str_arr DB 256h DUP('$') ;   256  data_arr DB 0,0,0,0,0,0,0,0,0,0,'$' ;  i DB 0,'$' ;    j DB 0,'$' ;    i_stor DB 0,'$' data ends stk segment para stack db 100h dup (0) ;  256  stk ends end begin 

أثناء التجميع نحصل على خطأ
القفز النسبي خارج النطاق بمقدار 0001 ساعة بايت

والحقيقة هي أن أوامر je / jne يمكن أن تقفز فقط بعد بضعة أسطر من البرنامج (يأخذ كل سطر من 1 إلى 5 بايت في الذاكرة).


لا يمكن إجراء قفزات طويلة حتى نهاية برنامج je / jne .
لذلك ، نستبدل التعبير
  cmp DL, 24h ;  '$' je exit_loop ... exit_loop: 

التعبير
 cmp DL, 24h ;  '$' jne exit_ jmp exit_loop exit_ ... exit_loop: 


لذا ، إذا كان الحرف الحالي يتطابق مع $ ، فانتقل إلى exit_loop: label بالأمر jmp ، وإلا فإننا نقفز فوق الأمر jmp .
يمكن للأمر jmp إجراء انتقال قصير نسبي داخل المقطع (انتقال أقل من 128 بايت ، أي IP: = IP + i8) أو انتقال طويل نسبي داخل المقطع (انتقال أقل من 327 بايت ، أي IP: = IP + i16).
بشكل افتراضي ، يقوم الأمر jmp بعمل قفزة طويلة نسبيًا ، وهو ما نحتاجه (بشكل عام ، بدلاً من ذلك ، يمكنك ببساطة إضافة التوجيهات السريعة إلى بداية البرنامج).
 ;jumps text segment assume cs:text,ds:data, ss: stk begin: ;   mov AX,data ;    mov DS,AX ;;; mov ah, 3fh ;   mov cx, 100h ; 256  mov dx,OFFSET str_arr int 21h ;;; mov DL, str_arr ;   DL 1  ;mov CX, 100h ; 256  prev: cmp DL, 24h ;  '$' ;je exit_loop jne l1 jmp SHORT exit_loop l1: cmp DL, 2Bh ;   + jne next ; ,    next mov BL, j ;   BL   inc data_arr[BX] ; ,      1 next: cmp DL, 2Dh ;   - jne next1 ; ,    next1 mov BL, j dec data_arr[BX] ;BX,   Bl next1: cmp DL, 3Eh ;   > jne next2 ; ,    next2 inc j ; ,      data_arr next2: cmp DL, 3Ch ;   < jne next3 ; ,    next3 dec j ; ,      data_arr next3: cmp DL, 2Eh ;   . jne next4 ; ,    next4 mov AH,2 ; ,    mov BL, j mov DL, data_arr[BX] int 21h next4: cmp DL, 5Bh ;   [ jne next5 ; ,    next5 mov BL, j mov DL, data_arr[BX] cmp DL, 00 ; ,    data_arr   jz next5 ;  ,   mov DL, i ;   mov i_stor, Dl ;  i_stor   i next5: cmp DL, 5Dh ;   ] jne next6 ; ,    next6 mov BL, j mov DL, data_arr[BX] cmp DL, 00 ; ,    data_arr   jz next6 ;  ,   mov DL, i_stor ;   mov i, Dl ;  i_stor   i ;       prev: next6: inc i ;     mov BL, i mov DL, str_arr[BX] ; loop prev ;    prev: jmp prev exit_loop: MOV AH,2 ;     MOV DL,0Ah INT 21h mov AX, 4c00h ;   int 21h text ends data segment str_arr DB 256h DUP('$') ;   256  data_arr DB 0,0,0,0,0,0,0,0,0,0,'$' ;  i DB 0,'$' ;    j DB 0,'$' ;    i_stor DB 0,'$' data ends stk segment para stack db 100h dup (0) ;  256  stk ends end begin 




رابط إلى github مع قوائم البرامج.

ملاحظة هنا مقال حول إنشاء حاسبة x86 عكس التدوين البولندي (RPN).

Source: https://habr.com/ru/post/ar423121/


All Articles