【问题标题】:Assembly Jump/Branch/Lookup Tables instead of lots of cmp/je?装配跳转/分支/查找表而不是大量 cmp/je?
【发布时间】:2017-02-13 23:40:13
【问题描述】:

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

为了解释我想要实现的目标,我正在调用一个函数,该函数在 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?

【问题讨论】:

  • 您的编辑在问题和我的答案中都引入了错误,并且使逻辑如此简单,以至于其他有一种特殊情况的人可能会认为此问答不适用于他们的情况。我想我又把它变成了一个理智的问题。我考虑只是回滚它,因为它并不难看出你的代码在做什么。 (并且知道它正在检查 A20 的东西,这使得 MichaelPetch 能够对确保 CS 已设置进行有用的观察。)只要易于遵循,代码就不需要完全通用。

标签: assembly x86 nasm intel x86-16


【解决方案1】:

您的大多数案例都有相同的指令,但数据不同,因此您甚至不需要跳转表。只需使用字符串表,只针对需要运行不同指令的条件跳转,而不是不同数据的相同指令。

    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。如果您必须 PUSH/POP 输入值以便能够再次测试它,那将是不值得的。由于特殊情况是 DX==0,(不像此答案的先前版本那样 DX==5),我们不能使用来自一个 CMP 的 FLAGS 来执行两个 JCC。


如果您确实想制作跳台:

jmp  [jump_table + si]


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

然后代替字符串地址,使用 DW 在内存中制作代码地址表。如果每种情况的大小相同(以机器代码字节为单位),您可以避免使用指针表,而只需计算相对于第一个地址的跳转距离。


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

【讨论】:

  • 我有一个观察结果。如果您要使用 JMP 表(就像您在第二个代码 sn-p 中一样),并且您在进入保护模式之前在引导加载程序中执行此操作(可能使用此 OP,因为他正在尝试启用 A20 线)那么你应该确保 CS 设置为你所期望的。我在此发布了SO Q&A,因为这对我认识的一些 IRL 来说是个问题。大多数人没有 FAR JMP 来显式设置 CS,因此最终在某些环境中 JMP/CALL 表可能会失败。
  • 感谢您,非常有帮助。我认为shl 指令有一个错字,因为您没有指定移动SI 的距离。也就是说,我不完全理解为什么 SI 必须首先移位?
  • @Jamie4840: 8086 仅支持单位移位,或按 CL 位移。它等同于shl si, 1,但我不确定纯 8086 汇编器是否完全支持立即常量操作数。您标记了这个 8086,而不是 386 兼容 CPU 上的 16 位。 (请参阅x86 tag wiki 获取一些指令集链接,以及许多其他好东西)。也等效:add si, si(但设置标志不同)。它需要移动,因为您正在索引 2 字节元素的表。
  • @PeterCordes 感谢您的链接,8086 标签确实是一个错误!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多