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

Hex*_*dus 3 x86 assembly intel nasm 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: ...
...
Run Code Online (Sandbox Code Playgroud)

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

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

Pet*_*des 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
        ...
Run Code Online (Sandbox Code Playgroud)

使用表而不是条件跳转链是非常广泛适用的.如果这是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, ...
Run Code Online (Sandbox Code Playgroud)

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


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

  • 我有一个观察.如果您要使用JMP表(就像您在第二个代码片段中一样),并且您在进入保护模式之前在引导加载程序中执行此操作(可能是因为他尝试启用A20行,因此可能使用此OP)应该确保_CS_设置为您期望的.一些人在此发布了[SO Q&A](http://stackoverflow.com/questions/34548325/near-call-jump-tables-dont-always-work-in-a-bootloader),因为这对某些人来说是一个问题我知道IRL.大多数人没有FAR JMP来显式设置_CS_,因此最终在某些环境中JMP/CALL表可能会失败. (3认同)