如何在x86 DOS程序集中读取键盘输入而不“消耗”它?

我需要编写一种可以从C代码调用的键记录function。 这意味着ac程序将调用一个名为startlog的汇编函数,该函数将指示开始记录按下的键,直到调用一个名为endlog的函数。 日志应该像这样工作:写入任何键的ascii值而不干扰startlog和endlog之间的C代码,这意味着如果C代码也需要读取输入(让我们说通过scanf,它可以正常工作)。

我设法通过将中断向量第9个条目(键盘按下的中断)更改为我写的将值写入文件的函数来编写记录器,并且它工作正常。 但是C代码没有得到输入。 基本上我所做的是读取使用int 21h按下的键,但是在读取ascii值后它被“消耗”所以我需要一种方法来再次模拟按键或读取值而不“消耗”它以便下一次键读取它读取相同的键。 (我用英语描述了代码,因为它是冗长而笨拙的汇编代码)

以下是您可以这样做的方法:

 // Compile with Borland's Turbo C++ 1.01 #include  #include  #include  #include  const char ScanToChar[] = "??1234567890-=??" "QWERTYUIOP[]??AS" "DFGHJKL;\"`?\\ZXCV" "BNM,./??? "; void interrupt (*pOldInt9)(void); void interrupt (*pOldInt1C)(void); char far* pInDosFlag; #define SCAN_BUF_SIZE 1024 volatile unsigned char ScanBuf[SCAN_BUF_SIZE]; volatile unsigned ScanReadIdx = 0; volatile unsigned ScanWriteIdx = 0; volatile unsigned LogFileHandle; void DosWriteFile(unsigned handle, void* data, size_t size); volatile unsigned InDos0cnt = 0; void TryToSaveLog(void) { unsigned cnt; if (*pInDosFlag) return; cnt = (ScanWriteIdx - ScanReadIdx) & (SCAN_BUF_SIZE - 1); InDos0cnt++; while (cnt--) { static const char hex[] = "0123456789ABCDEF"; char s[80] = "0xXX \"?\"\r\n"; unsigned char scanCode = ScanBuf[ScanReadIdx]; s[2] = hex[scanCode >> 4]; s[3] = hex[scanCode & 0xF]; if ((scanCode & 0x7F) < strlen(ScanToChar)) { s[6] = ScanToChar[scanCode & 0x7F]; } DosWriteFile(LogFileHandle, s, strlen(s)); ScanReadIdx++; ScanReadIdx &= SCAN_BUF_SIZE - 1; } } void interrupt NewInt9(void) { unsigned char scanCode = inp(0x60); ScanBuf[ScanWriteIdx++] = scanCode; ScanWriteIdx &= SCAN_BUF_SIZE - 1; pOldInt9(); } volatile unsigned int1Ccnt = 0; void interrupt NewInt1C(void) { int1Ccnt++; pOldInt1C(); TryToSaveLog(); } unsigned DosCreateFile(const char* name) { union REGS regs; struct SREGS sregs; regs.h.ah = 0x3C; regs.x.cx = 0; sregs.ds = FP_SEG(name); regs.x.dx = FP_OFF(name); intdosx(&regs, &regs, &sregs); return regs.x.cflag ? 0 : regs.x.ax; } void DosWriteFile(unsigned handle, void* data, size_t size) { union REGS regs; struct SREGS sregs; if (!size) return; regs.h.ah = 0x40; regs.x.bx = handle; regs.x.cx = size; sregs.ds = FP_SEG(data); regs.x.dx = FP_OFF(data); intdosx(&regs, &regs, &sregs); } void DosCloseFile(unsigned handle) { union REGS regs; struct SREGS sregs; regs.h.ah = 0x3E; regs.x.bx = handle; intdosx(&regs, &regs, &sregs); } void StartLog(const char* FileName) { union REGS regs; struct SREGS sregs; LogFileHandle = DosCreateFile(FileName); regs.h.ah = 0x34; // get InDos flag address intdosx(&regs, &regs, &sregs); pInDosFlag = MK_FP(sregs.es, regs.x.bx); pOldInt1C = getvect(0x1C); setvect(0x1C, &NewInt1C); pOldInt9 = getvect(9); setvect(9, &NewInt9); } void EndLog(void) { setvect(9, pOldInt9); while (ScanWriteIdx != ScanReadIdx); setvect(0x1C, pOldInt1C); DosCloseFile(LogFileHandle); LogFileHandle = 0; } int main(void) { char str[256]; StartLog("keylog.txt"); printf("please enter some text:\n"); gets(str); printf("you have entered \"%s\"\n", str); EndLog(); printf("int 1Ch count: %u\n", int1Ccnt); printf("InDos=0 count: %u\n", InDos0cnt); return 0; } 

输出(在Windows XP上运行):

 please enter some text: qweasdzxc123 you have entered "qweasdzxc123" int 1Ch count: 175 InDos=0 count: 1 

KEYLOG.TXT:

 0x10 "Q" 0x90 "Q" 0x11 "W" 0x91 "W" 0x12 "E" 0x92 "E" 0x1E "A" 0x9E "A" 0x1F "S" 0x9F "S" 0x20 "D" 0xA0 "D" 0x2C "Z" 0xAC "Z" 0x2D "X" 0xAD "X" 0x2E "C" 0xAE "C" 0x02 "1" 0x82 "1" 0x03 "2" 0x83 "2" 0x04 "3" 0x84 "3" 0x1C "?" 

这里有一些问题。 繁忙时不能使用某些DOSfunction。 这就是我正在检查InDos标志的原因。 同时,InDos可以指示即使在等待键盘输入这样简单的事情时DOS也很忙(例如在gets() )。

这就是为什么有一个循环缓冲区用于扫描代码累积它们,而程序无法安全地调用DOS文件I / O例程。 EndLog()等待直到缓冲区耗尽。 您可能还需要提前排水。

我也试过挂钩int 28h作为int 1Ch的替代,但是我的ISR for int 28h从未被调用过,不知道为什么。

我避免使用C的fopen()fwrite() / fprintf()作为日志文件,以免干扰不知道后台发生的事情的主程序。 出于同样的原因,在ISR中仅使用最简单的标准C函数。

如果INT 9是键盘中断并且您正在更改此向量以指向您自己的代码来拦截字符,为什么您不能只存储旧向量并在钩子代码的末尾跳转到它?