Как прочитать ввод с клавиатуры, не «потребляя» его в сборке x86 DOS?

#c #assembly #x86 #dos #interrupt

#c #сборка #x86 #dos #прерывание

Вопрос:

Мне нужно написать что-то вроде функции регистрации ключей, которую можно вызвать из кода C. это означает, что программа на c будет вызывать функцию сборки с именем startlog, которая будет указывать на начало регистрации нажатых клавиш до тех пор, пока не будет вызвана функция с именем endlog. протоколирование должно работать следующим образом: запишите значение ascii любой нажатой клавиши, не нарушая код C между startlog и endlog, что означает, что если коду C также потребуется прочитать ввод (скажем, с помощью scanf, это будет работать нормально).

Мне удалось написать регистратор, изменив 9-ю запись вектора прерывания (прерывание при нажатии на клавиатуре) на функцию, которую я написал, которая записывает значения в файл, и она работает нормально. однако код C не получает ввод. В основном, что я сделал, это прочитал нажатую клавишу, используя int 21h, однако после считывания значения ascii оно «потребляется», поэтому мне нужен способ либо имитировать повторное нажатие клавиши, либо прочитать значение, не «потребляя» его, чтобы при следующем считывании клавиши считывался тот же ключ. (Я описал код на английском, потому что это длинный и неуклюжий ассемблерный код)

Комментарии:

1. Вы вызывали обработчик int 21 из int 9 , и он не взорвался?

2. если вы вызываете их правильно, ничего не взрывается…

Ответ №1:

Вот как вы можете это сделать:

 // 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) amp; (SCAN_BUF_SIZE - 1);

  InDos0cnt  ;

  while (cnt--)
  {
    static const char hex[] = "0123456789ABCDEF";
    char s[80] = "0xXX "?"rn";
    unsigned char scanCode = ScanBuf[ScanReadIdx];

    s[2] = hex[scanCode >> 4];
    s[3] = hex[scanCode amp; 0xF];

    if ((scanCode amp; 0x7F) < strlen(ScanToChar))
    {
      s[6] = ScanToChar[scanCode amp; 0x7F];
    }

    DosWriteFile(LogFileHandle, s, strlen(s));

    ScanReadIdx  ;
    ScanReadIdx amp;= SCAN_BUF_SIZE - 1;
  }
}

void interrupt NewInt9(void)
{
  unsigned char scanCode = inp(0x60);

  ScanBuf[ScanWriteIdx  ] = scanCode;
  ScanWriteIdx amp;= 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(amp;regs, amp;regs, amp;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(amp;regs, amp;regs, amp;sregs);
}

void DosCloseFile(unsigned handle)
{
  union REGS regs;
  struct SREGS sregs;

  regs.h.ah = 0x3E;
  regs.x.bx = handle;

  intdosx(amp;regs, amp;regs, amp;sregs);
}

void StartLog(const char* FileName)
{
  union REGS regs;
  struct SREGS sregs;

  LogFileHandle = DosCreateFile(FileName);

  regs.h.ah = 0x34; // get InDos flag address
  intdosx(amp;regs, amp;regs, amp;sregs);
  pInDosFlag = MK_FP(sregs.es, regs.x.bx);

  pOldInt1C = getvect(0x1C);
  setvect(0x1C, amp;NewInt1C);

  pOldInt9 = getvect(9);
  setvect(9, amp;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: %un", int1Ccnt);
  printf("InDos=0 count: %un", 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. EndLog() ожидает, пока буфер не будет опорожнен. Возможно, вам также потребуется принудительно выполнить слив раньше.

Я также пытался подключить int 28h в качестве альтернативы int 1Ch, но мой ISR для int 28h так и не был вызван, не уверен почему.

Я избегаю использования C fopen() и fwrite() / fprintf() для файла журнала, чтобы не мешать основной программе, которая не знает о том, что происходит в фоновом режиме. По той же причине в ISR используются только самые тривиальные стандартные функции C.

Комментарии:

1. Примечание: В "??" этом коде почти не используются тригонометрические числа . Возможно , что двойные '?' разделены, такие как : "??" "1234567890-=" "??" ...

Ответ №2:

Если INT 9 — это прерывание клавиатуры, и вы меняете этот вектор, чтобы указать на свой собственный код для перехвата символов, почему вы не можете просто сохранить старый вектор и перейти к нему в конце вашего кода подключения?

Комментарии:

1. Я делаю это, я перехожу к исходному INT 9, чтобы он обрабатывал ввод с клавиатуры, а затем вызываю int21h для его чтения.

2. Хорошо, похоже, что вам также нужно буферизировать символы и подключить int 21.