2017-03-12 23 views
2

我现在遇到了一些问题,我不确定这是否是我的程序存在的问题,但这是我不是100%的一件事,所以我打算使用它作为学习的机会。如何正确推送字节'AL'到C函数?

我有这条指令: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¯¯被按下,0×12是Ë,等等...

问题:

  • 我正在做什么将正确的值传递给函数呢?
  • 如果我只推动AX而不是EAX,结果会不一样吗?
  • 我只需要字节AL,但很明显我不能push AL,所以我一直把它扩展到EAX。所以,让我们假设如果Q被按下,并且我将它比较为:if(scancode == 0x10),那么无论如何EAXAX被推送?或者,我只需要将AL的值存入扫描码中?如果没有,我该怎么去得到AL的功能?
+2

你不想'推ax'(如果这就是即使你的汇编允许的),因为你需要按4个字节一般,在这种情况下C函数需要一个4字节的无符号整型反正。你做的是对的。 – Jester

+0

如果您使用的是现代操作系统或使用USB键盘,则无法工作。 – Olaf

+0

您将AL扩展到EAX为零,所以它的值是RAX = EAX = AX = AL = scancode。 –

回答

2

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

一些注意事项:

  1. 最好是在字节使用的C数据类型与确切的大小等uint32_tint其尺寸是不固定的。这些数据类型在stdint.h
  2. 定义。如果你想使用AX代替EAX,你必须定义功能
    void cFunction(uint16_t scancode).
  3. 由于ALAX(和EAX)的一部分,最好是刚擦除AX(或EAX在读之前关键扫描码比用MOVZX之后延长。在装配它的典型方式:

    XOR与自己注册

    XOR EAX, EAX 
    

    注册

    MOV EAX, 0 
    

    移动零也是正确的,但XOR通常是快一点(和使用它的寄存器现在擦除是某种传统)

0

所以有几件事情在这里谈论。让我们一次一个覆盖他们。

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

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

第三,端口60h是一个奇怪的野兽,从我为它写驱动程序已经很长时间了。您读取的值不是您认为您将在很多时间读取的值,并且有E0h扩展代码,并且存在Pause键的行为。编写一个无bug的键盘驱动程序比看起来要困难得多。但是让我们忽略这个问题的一切。因此,我们假设你没有操作系统来阻挡,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位大小的,所以您只能推eax,ebx,esi, edi等等:您无法直接推al。 (即使你可以 - 而且在技术上,你可以使用直接堆栈写入 - 它会错位,因为在32位模式下,每个项目必须对齐到一个4字节的边界。)所以唯一的方法来推动值首先将其从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位。

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