【问题标题】:How to push byte 'AL' to C function correctly?如何正确将字节“AL”推送到 C 函数?
【发布时间】:2017-03-12 19:47:11
【问题描述】:

目前我遇到了一些问题,我不确定这是否是我的程序的问题,但这是我不是 100% 关心的一件事,所以我将使用它作为一个学习机会。

我有这个指令:in al, 0x60 从键盘读取扫描码。 我正在尝试将此扫描码发送到用 C 编写的函数。C 函数声明如下所示:void cFunction(unsigned int scancode)

所以基本上,这就是我正在做的事情:

in al, 0x60
movzx EAX, AL
push EAX
call Cfunction

目标是在 C 函数中获取这样的值:0x10,这意味着 Q 被按下,0x11 W 被按下,0x12 是 E,等等……

问题:

  • 我所做的是否将正确的值传递给函数?
  • 如果我只推送AX 而不是EAX,结果会不会有所不同?
  • 我只需要byteAL,但显然我不能push AL,所以我一直将它归零到EAX。那么,假设如果按下 Q 并且我将其比较为:if(scancode == 0x10),那么无论EAXAX 被按下,这是否会正确解释?还是我只需要将AL 的值放入扫描码?如果没有,我该如何让AL 加入该函数?

【问题讨论】:

  • 你不想push ax(如果你的汇编程序甚至允许的话)因为你通常需要推送 4 个字节,在这种情况下,C 函数无论如何都需要一个 4 字节的 unsigned int .你做的是正确的。
  • 如果您有现代操作系统或使用 USB 键盘,这将不起作用。
  • 您将 AL 扩展到 EAX 是零,因此值得 RAX = EAX = AX = AL = 扫描码。
  • @Olaf 我正在创建一个操作系统
  • @Jester 谢谢你,我会在别处寻找问题。我很感激你们所有的时间。

标签: c assembly x86 masm x86-16


【解决方案1】:

答案取决于您的 C 编译器使用什么 calling convention。如果它是标准的 cdecl 比是,通常你做对了。

一些注意事项:

  1. 最好使用精确大小的 C 数据类型,例如 uint32_t,而不是大小不固定的 int。这些数据类型在 stdint.h 中定义
  2. 如果您想使用AX 而不是EAX,您必须将您的函数定义为
    void cFunction(uint16_t scancode).
  3. 由于ALAX(和EAX)的一部分,最好只擦除AX(或EAX在读取之前键扫描码而不是扩展它MOVZX 阅读后。它在装配中的典型方式:

    与自身进行异或寄存器

    XOR EAX, EAX
    

    在寄存器中移动零

    MOV EAX, 0
    

    也是正确的,但是XOR 通常会快一点(现在用它来擦除寄存器是一种传统)

【讨论】:

    【解决方案2】:

    所以这里有几件事要谈。让我们一次一个地介绍它们。

    首先,我假设您要么在没有操作系统的情况下运行(即制作自己的操作系统),要么在不妨碍 I/O 的 MS-DOS 等操作系统中运行。正如 Olaf 正确指出的那样,in al, 0x60 无法在现代的保护模式操作系统上运行。它也不能在 USB 键盘上工作(除非您在假装提供经典 PS/2 键盘的模拟器或虚拟机中运行)。

    其次,我假设您在 32 位 CPU 上编程。 C ABI(应用程序二进制接口)对于 16 位 CPU、32 位 CPU 和 64 位 CPU 是不同的,因此代码的读取方式会根据您使用的 CPU 有所不同。

    第三,60h端口是个怪怪的,好久没给它写驱动了。你读入的值并不是你认为你会在很多时候读到的值,还有 E0h 扩展代码,还有 Pause 键的行为。编写一个没有错误的键盘驱动程序比看起来要难得多。但是,让我们忽略这个问题的所有内容。

    所以让我们假设您没有操作系统可以阻止,并且有一个 32 位 CPU,并且只有最基本的击键。如何将数据从键盘传递到 C 函数?和你做的差不多:

    in al, 0x60
    movzx eax, al
    push eax
    call cFunction
    

    为什么这是正确的?

    嗯,第一行用键盘加载了一个 8 位寄存器;它必须是al,因为这是in 可以写入的唯一8 位寄存器。

    32 位 C ABI 期望函数的参数以相反的调用顺序被压入堆栈。所以为了调用C函数,前面必须有push指令。但是,在32位模式下,所有push指令都是32位大小的,所以只能推送eaxebxesiedi等:不能推送直接al。 (即使你可以——从技术上讲,你可以,使用直接堆栈写入——它也会错位,因为在 32 位模式下,推送的每个项目都必须与 4 字节边界对齐。)因此,推送value 首先将其从 8 位提升到 32 位,movzx 做得很好。

    不管怎样,还有其他方法可以做到这一点。您可以在in 之前清除eax

    xor eax, eax
    in al, 0x60
    push eax
    call cFunction
    

    这个解决方案在性能上比原来的解决方案差一点;它的代价是部分寄存器停顿:处理器内部实际上并没有将al 作为eax 的一部分,而是作为一个单独的寄存器;任何将寄存器的不同大小的子片段混合在一起的尝试都会导致处理器在能够这样做之前停止:当您在这里push eax 时,处理器意识到al 被前一条指令突变,并停止一个时钟周期将al 的位快速混入eax,所以看起来al 实际上是eax 的一部分。

    值得指出的是,如果您处于经典的 16 位模式(8086 或 '286 保护模式),调用顺序会略有不同:

    in al, 0x60
    movzx ax, al
    push ax
    call cFunction
    

    在这种情况下,int 是 16 位大小的,因此将所有内容都作为 16 位进行处理是正确的。或者,在 64 位模式下,您需要改用 rax

    in al, 0x60
    movzx rax, al
    push rax
    call cFunction
    

    尽管 cFunction 可能已经编译为 int 只有 32 位,但 64 位模式下的堆栈对齐要求要求将推送 64 位值。 C 函数会正确地将 64 位值读出为 32 位值,但您只能将其推送为 64 位。

    所以你有它。与 C ABI 交互以将端口数据导入函数的各种方式,具体取决于您的 CPU 和环境。

    【讨论】:

      猜你喜欢
      • 2019-12-14
      • 1970-01-01
      • 2020-02-28
      • 1970-01-01
      • 2022-01-11
      • 2021-12-29
      • 1970-01-01
      • 2014-12-30
      • 1970-01-01
      相关资源
      最近更新 更多