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

时间:2011-11-12 09:23:57

标签: c assembly x86 dos interrupt

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

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

2 个答案:

答案 0 :(得分:2)

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

// Compile with Borland's Turbo C++ 1.01

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dos.h>

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 "?"

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

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

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

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

答案 1 :(得分:1)

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