65.9K
CodeProject 正在变化。 阅读更多。
Home

如何将宏汇编器变成一种高级语言

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.44/5 (6投票s)

2021 年 9 月 9 日

CPOL

5分钟阅读

viewsIcon

6691

有助于汇编程序员改进源代码的宏

引言

使用 MacroAssembler 的程序员,经常希望获得高级语言在结构化块中所提供的灵活性,例如“if .. then .. else .. endif”、“while .. wend”、“repeat .. until”或“for..next”。为什么这类语法没有包含在 MacroAssembler 中?可能是因为 MacroAssembler 语言的强大之处在于其宏本身,因此我们可以模仿上述块并编写更清晰的代码。

我将使用 VB 命名约定,因为它更接近人类可读的语法。(当您作为“C”程序员在代码中看到“}”时,您必须回溯源代码才能知道它关闭的是“if”、“else”、“while”还是“do”块。)

如前所述,所有这些块都是基于条件执行(或不执行)的。单个条件可以被读取为 Operand1 Operator Operand2 的结果,其中 Operand1Operand2 是代码中的变量或常量值,而 operator 是算术运算符,例如 =><>=<=,等等。

因此,高级语言可以处理如下语法

    IF age > 50 THEN    'Age is Operand1, > is the operator and 50 is Operand2
        'Actions
    ENDIF

- 或者换句话说 -

    IF Condition THEN
        'Actions
    ENDIF

在这种情况下,如果我们考虑 VB 程序在代码中找到这个块时会做什么,那就是

  1. 评估条件。
  2. 如果结果为 true,则处理从“THEN”到“ENDIF”关键字之间的所有操作。

一个稍微复杂一点的块可以是

    IF Condition THEN
        'Actions_when_true
    ELSE
        'Actions_when_false
    ENDIF

在这种情况下,过程是

  1. 评估条件
  2. 如果结果为 true,则处理从“THEN”到“ELSE”标签之间的所有操作,否则处理从“ELSE”到“ENDIF”标签之间的所有操作。

在深入研究结构化宏的创建之前,我们必须考虑到 MacroAssembler 没有算术运算符的语法。取而代之的是助记符。例如:E (等于), L (小于), LE (小于等于), B (低于), BE (低于等于), G (大于), GE (大于等于), A (高于), AE (高于等于)

或它们的相反

NE (不等于), NL (不小于), NLE (不小于等于), NB (不低于), NBE (不低于等于), NG (不大于), NGE (不大于等于), NA (不高于), NAE (不高于等于)。

什么是宏?

对于使用 C 语言变体的人来说,宏相当于基于参数的 #define 结果。当 MacroAssembler 在您的代码中找到一个先前已定义的宏时,它会替换参数并展开宏。

到了这一点,我们可以使用以下语法编写(并研究)我们的第一个非常基本的宏:([] 之间的文本表示从此处开始可选)

;===================================================
;$iif    op1, oper, op2, label1 [,label2] 
;===================================================
$iif    macro   op1, oper, op2, label1, label2
         cmp     &op1, &op2
         j&oper  &label1
         ifnb    <&label2>  ;if label2 is no blank, 
                 (the 'label2' parameter exists) then MacroAssembler assembles then next line
                 jmp &label2
         endif
         endm 

如果我们在代码中放入以下文本...

     $iif    ax,e,5,IsFive

...MacroAssemble 将宏展开为...

         cmp     ax, 5 
         je      IsFive

如果我们将在代码中放入以下文本...

     $iif    ax,e,5,IsFive,NoIsFive

...MacroAssemble 将宏展开为...

         cmp     ax, 5 
         je      IsFive 
         jmp     NoIsFive        

因此,通过使用这个小宏,我们可以将比较中的所有源代码从两三行减少到一行,从而使我们的代码更具可读性。

接下来我们可以研究的是高级语言的迭代块。

  1.     Repeat  
            'Actions 
        Until Condition (is true)

    在这种情况下,编译器在找到“Repeat”标签时所做的是保存第一个操作的地址。当找到“Until”标签时,程序需要评估条件。如果条件求值为 false,那么程序需要跳转到之前保存的块的第一个操作的地址(否则工作已完成)。

  2. While Condition (is true) 
            'Actions 
        Wend

    在这种情况下,当编译器找到“While”标签时,它会保存操作的起始位置。运行时会评估“condition”。如果为 true,执行将继续执行第一个操作;如果条件求值为 false,则执行将继续到“Wend”标签之后。

  3. For variable,Initial_Value,Final_Value,Step 
            'Actions 
        Next

    这有点复杂。其功能是使用“variable”值从“Initial_Value”到“Final_Value”执行“Actions”,每次迭代将“variable”增加或减少“Step”。当“variable”超过“final_value”时,迭代完成。

首先,运行时将“Initial_Value”赋给“variable”。其次,评估“variable”是否超过“Final_Value”。如果未超过 Final_Value,则执行所有操作,变量增加或减少,然后过程在第 2 点继续。如果超过 final_value,则迭代完成。

将所有内容整合到 MacroAssembler

MacroAssembler 像所有高级语言一样,在您的代码中自上而下进行汇编。这意味着当汇编器尝试汇编指令(或宏)时,它知道该指令在代码中的位置(其地址)。首先,我们有一个代码中符号位置的计数器。Macroassemble 的每个位置名称将是 $sim_ 加上计数器值。

由于迭代块需要知道它们的开始和/或结束位置以允许跳转到这些位置,因此提供了一些内部宏。

  • $pushaddr:找到时,它会增加符号名称的计数器,并生成一个 $sim_counter 名称来标识块的开始,该块等于宏所在的位置。
  • $popaddr:找到时,它将 $sim_counter 值恢复为 $jmp 变量,并减少符号名称的计数器。
  • $makenops:为“jmp 'xxxx'”分配一个 3 字节的代码,该代码稍后将由 $filljmp 设置。
  • $filljmp:用“jmp 'xxxx'”填充预分配的 3 字节空间。

注意:这些宏是为 MacroAssembler 5.1 创建的。在汇编扁平内存代码时,您可能应该在 $makenops 中添加更多的 nops,以允许代码的偏移量大于有符号字。要做到这一点,您需要编写一个小程序,例如

 jmp    veryfar 
 db    100000 dup(0) 
veryfar:

... 汇编后,您可以查看“jmp veryfar”使用了多少字节来存储指令。这些字节数就是您需要在 $makenops 中保留的 nops。请记住,如果需要,请在提供的代码中修改关键字“near”。

每个宏的解释在包含的文本中有详细说明。这些宏可以嵌套,这意味着从现在开始,您可以像这样编写 MacroAssembler 代码:

    $IF value,le,100 
    $THEN 
        ;actions 
    $ELSE 
        ;actions 
    $ENDIF

    $FOR    x,0,1366-1,1 
        $FOR y,0,768-1,1 
            ;GetPixel(x,y) 
            ;some actions more 
        $NEXT 
    $NEXT

    $REPEAT 
        ;Get Keyboard key 
    $UNTIL key,e,Escape

    $WHILE value,l,100 
        ;some actions more 
        ;value = calculated_value 
    $WEND

Using the Code

只需将提供的文本包含在代码的开头或使用 #include

代码

        .XLIST
        .XCREF

; STRUCTURED PROGRAMMING MACROS ======================================================
;-------------------------------------------------------------------------------------
; internals
;-------------------------------------------------------------------------------------
        $simcount = 0   ;Counter used to generate symbolic names along the program as $sim_1, &sim_2 ...     

$pushaddr  macro
        $simcount = $simcount + 1    ; increase symbol count
        $newsim    %$simcount        ; creates new sim name
        endm

$newsim macro $n
        $sim_&$n = this near ;generates symbolic address. I.E. $sim_100 = place into code where $newsim was expanded, I.E.= 506
        endm

$popaddr  macro
        $getsim %$simcount
        $simcount = $simcount - 1   ; decrease symbol count
        endm

$getsim macro $n
        ifndef $sim_&$n
               %out     fail in structure !!!
        endif
        $jmp = $sim_&$n            
        endm

$makenops macro
        nop    ;allocate space to place a jmp when other macros will be processed
        nop    ;a jmp instructions use 1 byte for the coding of the instruction itself plus 2 bytes more
        nop    ;that are treated as a signed word (offset potitive or negative from position of the instruction)
        endm

$makejump macro towhere
        here = this near ;we save or position in the source code
        $popaddr         ;recover address of last 3 nops
        org $jmp         ;we go there ...
        jmp &towhere    ;... and replace the three nops with "jmp towhere"
        org here         ;we come back to our position in the source code
        endm

;-------------------------------------------------------------------------------------
; $iif    op1, oper, op2, label1 [,label2]
;-------------------------------------------------------------------------------------
$iif    macro op1, oper, op2, label1, label2
        cmp     &op1, &op2
        j&cond  &label1
        ifnb <&label2>        ;if label2 was passed as argument, then is expanded
                jmp &label2
        endif
        endm

;=====================================================================================
; Sintax:
; $IF op1, oper ,op2
; [$THEN] 
;    block1
; [$ELSE]
;    block2
; $ENDIF
;=====================================================================================
$IF     macro   op1, oper, op2
        local   block1
        $iif    <op1>, oper, <op2>, block1
        $pushaddr
        $makenops   ;nops will be filled by $ELSE or by $ENDIF with a jmp tosomewhere when condition evaluates false
block1:             ;this is the start of block of instructions executed when condition evaluates to true
        endm

;-------------------------------------------------------------------------------------
$THEN   macro
        endm        ;macro formal. $THEN is optional and used to produce only a better readability of the source code

;-------------------------------------------------------------------------------------
$ELSE   macro
        local   block2
        $makejump block2 ;as we use $ELSE macro, the $IF nops has to be replaced by a jmp to the start of the $ELSE block
        $pushaddr   ;also, we have to reserve space for the jmp out the $IF
        $makenops
block2:             ;this is the start of block of instructions executed when the $IF condition evaluates to false
        endm

;-------------------------------------------------------------------------------------
$ENDIF  macro
        local   exitif
    $makejump exitif ;if $ELSE is not used then the $IF nops are replaced with "jmp exitif"
exitif:              ;if $ELSE has been used then the $ELSE nops are replaced with "jmp exitif"
        endm

;=====================================================================================
; Sintax:
; $WHILE op1, oper, op2
;        ...         ;Actions executed if condition evaluates true
; $WEND
;=====================================================================================
$WHILE  macro  op1, oper, op2
    local istrue
        $pushaddr           ;address of start of loop
        $iif   <op1>, oper, <op2>, istrue
        $pushaddr           ;this is the address reached when condition is false
        $makenops           ;reserve nops to be filled with 'jmp outofloop'
istrue:
        endm

$WEND   macro
    local quitloop
        $makejump quitloop
        $popaddr        ;recover address of the start of loop
        jmp     $jmp        ;goto there
quitloop:
        endm
  
;=====================================================================================
; Sintax:
; $REPEAT
;     ...            ;Actions executed until condition evaluates true
; $UNTIL op1, oper, op2
;=====================================================================================
$REPEAT macro
        $pushaddr           ;address of start of loop
        endm

$UNTIL  macro   op1, oper, op2
    local    quitloop
        $iif   <op1>, oper, <op2>, quitloop
        $popaddr        ;recover address of the start of loop
        jmp     $jmp        ;goto there
quitloop:
        endm

;=====================================================================================
; Syntax:
; $FOR index,initial_value,final_value,step_value,[register]
;     ...
; $NEXT | $LOOP
; register has to be used if both index and initial_value are variables
;=====================================================================================
$FOR    macro   index,initial_value,final_value,step,register
    local compare, inrange
        ;set the initial value to index
        ifnb    <&register>
                mov     &register, &initial_value    
                mov     &index, &register
        else
                mov     &index, &initial_value
        endif
        jmp short compare

        $pushaddr           ;address of start of loop
        if step eq 1
              inc     index
        else
              if step eq -1
                   dec index
              else
                   add index, step
              endif
        endif
compare:
        ifnb <&register>
                mov     &register, &final_value
                cmp     &index, &register
        else
                cmp     &index, &final_value
        endif
        if      step gt 0
                jle  inrange    ;goto block if less or equal
        else
                jge  inrange    ;goto block if greater or equal
        endif
        $pushaddr            ;here = address of out of range
        $makenops        ;we will fill nops with "jmp quitloop"
inrange:
        endm

$NEXT   macro
        local    quitloop
        $makejump quitloop
        $popaddr        ;recover address of the start of loop
        jmp     $jmp        ;goto there
quitloop:
        endm

$LOOP   equ <$NEXT>
;----------------------------------------------------------------------

        .CREF
        .LIST

关注点

在反编译了“C”代码片段后,我能够确定编译器是如何工作的,因此这些宏模仿了相同的过程。

历史

这些宏从未被修改过。

© . All rights reserved.