x86 asm casetable 实现 [英] x86 asm casetable implementation

查看:26
本文介绍了x86 asm casetable 实现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在理解 asm x86 中的 casetable 时遇到问题.我的教授已经使用这个例子在幻灯片中解释了它:

i'm having issues in understanding casetable in asm x86. My professor has already explained it in the slides using this example:

.data
CaseTable BYTE 'A'  ; lookup value
DWORD Process_A ; address of procedure
EntrySize = ($ - CaseTable)
BYTE 'B'
DWORD Process_B
BYTE 'C'
DWORD Process_C
BYTE 'D'
DWORD Process_D
NumberOfEntries = ($ - CaseTable) / EntrySize


mov ebx,OFFSET CaseTable    ; point EBX to the table
mov ecx,NumberOfEntries ; loop counter

L1: cmp al,[ebx]    ; match found?
jne L2  ; no: continue
call NEAR PTR [ebx + 1] ; yes: call the procedure
    ; +1: address after the byte        
jmp Default ; and exit the loop
L2: add ebx,EntrySize   ; point to next entry
loop L1 ; repeat until ECX = 0

Default:

但是,此代码不完整,我不知道如何使用它来构建我自己的 caseetable.如果有人能给我一个使用上述代码实现 casetable 的工作案例示例,并具体告诉我他何时在代码中准确调用过程,我将不胜感激?我会很感激的.我将使用示例来学习如何实现更多和其他案例.谢谢.

however, this code is incomplete and i don't know how to use it to build my own caseetable. I would appreciate it if someone can give me one working case example implementing casetable using the above code, and show me specifically when does he call the procedures exactly in the code? I would appreciate it a lot. I will use the example to learn how to implement more and other cases. Thank you.

推荐答案

你让这件事变得过于复杂.您不需要线性搜索或存储在表中的键;只需对您的值进行范围检查,然后将其用作表索引.

You are making this over-complicated. You don't need a linear search or keys stored in your table; just range-check your value and then use it as a table index.

我认为您使用的是 MASM 语法,因此我将尝试用 MASM 语法编写此代码,但我的语法可能有误.不过,实际的指令和逻辑应该是正确的.

I think you're using MASM syntax, so I'll try to write this in MASM syntax, but I may have the syntax wrong. The actual instructions and logic should be correct, though.

section .rdata    ; read-only data on Windows
  CaseTable:
    DWORD Process_A, Process_B   ; function pointers
    DWORD Process_C, Process_D
  NumberOfEntries = ($ - CaseTable) / 4
  ; optional: define constants for 'A' and 'D' and use those in the code below
  ; so the keys / values are still all in one place in the source.


.text   ; or .code or something.
        ; You were missing a section directive between your data and code.

; input: selector in EAX
dispatcher:        ; you were also missing a label for your function

    ; movzx  eax, al   ; if your selector really was just a byte
    sub   eax, 'A'         ; convert to idx. values below 'A' wrap to high unsigned
    cmp   eax, 'D' - 'A'   ; NumberOfEntries
    ja   @Default          ; unsigned compare rejects out-of-range high or low
    call  [CaseTable + eax*4]
    ; then fall through.  Use  jmp  as a tail-call if you don't want that.

 @Default:
    ret

编写漂亮(且高效)的 asm 的诀窍是看透问题,了解问题到底有多简单.您必须手动利用任何特殊情况,例如您的键都是连续的值.你是编译器.:)

The trick to writing nice (and efficient) asm is to see through the problem to how simple it really is. You have to manually take advantage of any special-case situation, like your keys all being contiguous values. You are the compiler. :)

函数指针应该指向其他函数,比如

The function pointers should point to other functions, like

Process_A:
    mov   eax, [esp+4]   ; return first arg unchanged
    ret

Process_B:
    mov   eax, [esp+4]
    add   eax, eax       ; return n * 2
    ret

Process_C:
    mov   eax, [esp+4]
    lea   eax, [eax + eax*2]   ; return n * 3
    ret

Process_D:
    mov   eax, [esp+4]
    shl   eax, 2       ; return n * 4
    ret

显然,您不会为此使用调度表,您只需使用 imul 乘以从 1 到 4 的未知数.但这只是一个示例.

Obviously you wouldn't use a dispatch table for this, you'd just use imul to multiply by an unknown number from 1 to 4. But this is just an example.

编译器知道很多用于优化 switch/case 语句的很酷的技巧.我最喜欢的一个是当很多 case 标签都做同样的事情,clang 和 gcc 将使用即时位图并行测试任何这些情况:

Compilers know a lot of cool tricks for optimizing switch/case statements. One of my favourites is when a lot of case labels do the same thing, clang and gcc will use an immediate bitmap to test for any of those cases in parallel:

void errhandler(enum errtype numError) {
    switch (numError) {  
      case ERROR_01 :  // intentional fall-through
      case ERROR_07 :  // intentional fall-through
      case ERROR_0A :  // intentional fall-through
      case ERROR_10 :  // intentional fall-through
      case ERROR_15 :  // intentional fall-through
      case ERROR_16 :  // intentional fall-through
      //case ERROR_20 :  // keep the range of cases smaller for simpler 32-bit code
         fire_special_event();
         break;

      default:
        // error codes that require no additional action
        break;       
    }
}

编译为这样的代码( <代码>clang4.0.1 -O3 -m32 在 Godbolt 编译器浏览器上)

Compiles to code like this (clang4.0.1 -O3 -m32 on the Godbolt compiler explorer)

errhandler(errtype):                 # @errhandler(errtype)
    mov     eax, dword ptr [esp + 4]    # load first function arg
    cmp     eax, 22
    ja      .LBB0_2
    mov     ecx, 6358146    # 0x610482 is a bitmap of those error codes
    bt      ecx, eax
    jae     .LBB0_2         # aka JNC: jump if CF=0, i.e. the bit wasn't set, i.e. ((1<<eax) & ecx) was false
    jmp     fire_special_event() # TAILCALL
.LBB0_2:
    ret

不幸的是,编译器不够聪明,无法使用 jcc 作为条件尾调用,因此它们有条件地跳过 jmp :/

Unfortunately compilers aren't smart enough to use jcc as a conditional tail-call, so instead they conditionally jump over the jmp :/

gcc 选择使用 mov eax, 1/shl 而不是 bt,即使在调整 CPU 时 bt 会更快:/

gcc chooses to use a mov eax, 1 / shl instead of using bt, even when tuning for CPUs where bt would be faster :/

这篇关于x86 asm casetable 实现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆