如何推送字节' AL'到C功能正常吗?

时间:2017-03-12 19:47:11

标签: c assembly x86 masm x86-16

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

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

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

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

目标是将这样的值输入C函数:0x10,这意味着按下了 Q ,按下了 W 的0x11,0x12是< kbd> E ,等等......

问题:

  • 我正在做的是将正确的值传递给函数吗?
  • 如果我只推送AX而不是EAX,结果是否会有所不同?
  • 我只需要字节 AL,但显然我不能push AL,所以我一直将其扩展为EAX。所以,让我们说如果 Q 被按下并且我将其比较为:if(scancode == 0x10),无论EAX vs AX是什么,这都会正确解释推?或者我只需要将AL的值放入扫描码中?如果没有,我该如何获得AL函数?

2 个答案:

答案 0 :(得分:2)

答案取决于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自身注册

    XOR EAX, EAX
    

    在寄存器中移动零

    MOV EAX, 0
    

    也是正确的,但XOR通常更快一些(现在使用它进行寄存器擦除是某种传统)

答案 1 :(得分:0)

所以这里有几点要讨论。我们一次覆盖一个。

首先,我假设您运行的是没有操作系统(即制作您自己的操作系统)或在MS-DOS等操作系统中运行而不会妨碍I / O.正如奥拉夫正确指出的那样,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字节边界对齐。)所以推动它的唯一方法是值首先将它从8位提升到32位,movzx很好地做到了这一点。

除了它的价值之外,还有其他方法可以做到。您可以在eax

之前清除in
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位。

所以你有它。根据您的CPU和环境,各种与C ABI交互的方式将您的端口数据输入您的功能。