装配跳/分支/查找表而不是很多cmp / je?

时间:2016-10-05 13:34:05

标签: assembly x86 nasm intel x86-16

我刚刚开始学习汇编,并且正在制作一个简单的启动加载器作为我的OS类的一部分。我正在努力使我的代码更有效率,即我不认为我到目前为止所做的是实现我想要的特别好的方法。也就是说,我一直在努力寻找任何在线资源,记录跳转/分支/查找表,我认为这是最有效的方法。

为了解释我想要实现的目标,我正在调用一个函数,该函数在dx寄存器中返回一个值,从0到4.目前我正在使用cmp个指令另一个比较值,如果值相同则进行条件je跳转。如果我用更高级别的语言编写这个,我基本上会一个接一个地执行多个if语句,而不是使用更有效的switch语句。

所以这就是我现在正在做的事情:

cmp dx, 1          
je .F_1
cmp dx, 2
je .F_2
cmp dx, 3
je .F_3
cmp dx, 4
je .F_4
cmp dx, 0
je .F_5
jmp RangeError_Handler

.F1:
  mov   si, msg1
  jmp   F_Exit
.F2:
  mov   si, msg2
  jmp   F_Exit
...  ; .F3 and .F4 follow the pattern

.F5:             ; special case
  mov   si, msg_error
  call  PrintLn
  hlt

F_Exit:
  call  PrintLn
  ...            ; and do something else


msg1: db 'Message 1', 0
msg2: ...
...

必须有更好的方法来做到这一点。我的导师暗示跳转表是理想的但是没有时间给我任何关于如何在汇编中起作用的进一步解释所以如果有人可以在上下文中提供某种示例我会非常感激我的情况。

理论上,我有一个函数可以检查dx的值然后跳转到一个特定的函数,而不是分别检查5次,我只是看不出我将如何在汇编中实现它。为字符串使用查找表会更有效吗?即返回值为1表示表中的字符串1?

1 个答案:

答案 0 :(得分:5)

大多数情况下使用不同数据的指令相同,因此您甚至不需要跳转表。只需使用一个字符串表,只针对需要运行不同指令的条件跳转,而不是使用不同数据的相同指令。

    mov  si, dx                   ; SI can be used in addressing modes, DX can't
    shl  si                       ; 16-bit doesn't allow scaled indices, so we can't just do [table + si*2].  And shl sets flags
    cmp  dx, 4
    ja   RangeError_Handler

    mov  si, [F_messages + si]
      ; call PrintLn   could be here, if it preserves DX or SI for us to test after

    test dx,dx             ; detect the one special case.
    jnz  .F_Exit

    ;; fall through only in the dx==0 case
    call  PrintLn
RangeError_Handler:
    hlt                             ; Are interrupts disabled?  if not, execution will continue after hlt

.F_exit
    call  PrintLn
    ...   ; and do whatever else your code needs to do


F_messages:                # char* F_messages[]
    dw  msg1,
        msg2
        ...

使用表而不是条件跳转链是非常广泛适用的。如果这是64位x86代码,甚至是ARM或MIPS程序集,那么逻辑将完全相同。甚至是C.(一个优秀的C编译器可能会将您的switch转换为数据的表查找而不是跳转表。)

您可以将call PrintLn从分支的两边分解出来,但前提是它保留了DX或SI。如果您必须按下输入值以使其能够再次测试,那么它就不值得了。由于特殊情况是DX == 0,(不是DX == 5,就像这个答案的先前版本一样),我们不能用一个CMP的FLAGS做两个JCC。

如果您确实想制作跳转表:

jmp  [jump_table + si]


jump_table:
   dw   .F_1,  .F_2, ...

然后使用DW在内存中创建代码地址表,而不是字符串地址。如果每种情况都是相同的大小(在机器代码字节中),你可以避免使用指针表,只计算相对于第一个地址的跳转距离。

确保在使用绝对地址之前知道CS的设置。正常跳转是相对的,但间接跳转/调用使用绝对地址。正如@ MichaelPetch的评论指出的那样,代码中某个点的FAR JMP将为您设置CS。