Handling A Keyboard Interrupt with Turbo C ++ 3.0 - c

I have a project. That is a simple game , "Falling Blocks" . The game area is considered as a grid, which has 20x20 size. There will be falling blocks from top of the screen and a hero at the bottom, who will shoot the blocks. The aim of game is shooting blocks before they reach the bottom line. He always stays at the bottom line . Whenever the user press space button of the keyboard I will generate a bullet, and the hero moves on the bottom line with the right and left arrow keys. I do not have an idea about handling these keyboard interrupt with Turbo C ++ 3.0. It is forbidden that using "dos.h" and "int 21H", also. Could you give me hints about these project?
Edit: I have found this information but I could not understand how to implement it :
When a key is pressed on keyboard, an interrupt along with a scan code named “make code” is produced and when the key released a “break code” is produced by the keyboard controller. On a PC, keyboard is controlled by a chip and assigned to port numbers 60h and 61h. When a key is pressed on keyboard, scan value is put in register at 60h. You can get this scan code with the following command:
in al,60h
After getting the scan code, you have to reset the keyboard programming the command register of the chip at 61h with following commands:
in al,61h
or al,82h
out 61h,al
and al,7fh
out 61h,al
At the end of every interrupt service routine, you clear PIC service bit, sending End Of Interrupt (EOI) command, 20h to PIC port at address 20h.
mov al,20h
out 20h,al

File kbdc.c:
#include <stdio.h>
extern void SetNewIrq9Isr(void);
extern void RestoreOldIrq9Isr(void);
#define SCAN_BUF_SIZE 1024
extern volatile unsigned char ScanBuf[SCAN_BUF_SIZE];
extern volatile unsigned ScanReadIdx;
extern volatile unsigned ScanWriteIdx;
const char ScanToChar[] =
"??1234567890-=??"
"QWERTYUIOP[]??AS"
"DFGHJKL;\"`?\\ZXCV"
"BNM,./??? ";
int IsScanCodeAvailable(void)
{
return ((ScanWriteIdx - ScanReadIdx) & (SCAN_BUF_SIZE - 1)) != 0;
}
unsigned char GetScanCode(void)
{
unsigned char code;
while (!IsScanCodeAvailable());
code = ScanBuf[ScanReadIdx];
ScanReadIdx++;
ScanReadIdx &= SCAN_BUF_SIZE - 1;
return code;
}
int main(void)
{
SetNewIrq9Isr();
printf("Press keys to see scan codes.\nPress ESC to exit.\n");
for (;;)
{
unsigned code, symbol;
code = GetScanCode();
symbol = code & 0x7F;
symbol = (symbol < sizeof(ScanToChar)) ? ScanToChar[symbol] : '?';
printf("scan code: 0x%02X, symbol: \"%c\"\n", code, (char)symbol);
if (code == 1)
{
break;
}
}
RestoreOldIrq9Isr();
return 0;
}
File kbda.asm:
GLOBAL _SetNewIrq9Isr, _RestoreOldIrq9Isr
GLOBAL _ScanBuf, _ScanReadIdx, _ScanWriteIdx
SEGMENT _TEXT PUBLIC CLASS=CODE USE16
; void SetNewIrq9Isr(void);
_SetNewIrq9Isr:
push bx
push es
mov bx, 9 * 4
mov ax, 0
mov es, ax
cli
mov ax, [es:bx]
mov [_pOldIrq9Isr], ax
mov word [es:bx], _NewIrq9Isr
mov ax, [es:bx + 2]
mov [_pOldIrq9Isr + 2], ax
mov [es:bx + 2], cs
sti
pop es
pop bx
ret
; void RestoreOldIrq9Isr(void);
_RestoreOldIrq9Isr:
push bx
push es
mov bx, 9 * 4
mov ax, 0
mov es, ax
cli
mov ax, [_pOldIrq9Isr]
mov [es:bx], ax
mov ax, [_pOldIrq9Isr + 2]
mov [es:bx + 2], ax
sti
pop es
pop bx
ret
_NewIrq9Isr:
pusha
push ds
mov ax, _DATA
mov ds, ax
in al, 60h
push ax
in al, 061h
mov ah, al
or al, 080h
out 061h, al
mov al, ah
out 061h, al
pop ax
; ScanBuf[ScanWriteIdx] = scan code;
; ScanWriteIdx = (ScanWriteIdx + 1) & (SCAN_BUF_SIZE - 1);
mov bx, [_ScanWriteIdx]
mov [_ScanBuf + bx], al
inc bx
and bx, 1023
mov [_ScanWriteIdx], bx
mov al, 20h
out 20h, al
pop ds
popa
iret
SEGMENT _DATA PUBLIC CLASS=DATA
_pOldIrq9Isr resd 1
; #define SCAN_BUF_SIZE 1024
; volatile unsigned char ScanBuf[SCAN_BUF_SIZE];
; volatile unsigned ScanReadIdx = 0;
; volatile unsigned ScanWriteIdx = 0;
_ScanBuf resb 1024
_ScanReadIdx dw 0
_ScanWriteIdx dw 0
Output:
Press keys to see scan codes.
Press ESC to exit.
scan code: 0x10, symbol: "Q"
scan code: 0x90, symbol: "Q"
scan code: 0x11, symbol: "W"
scan code: 0x91, symbol: "W"
scan code: 0x12, symbol: "E"
scan code: 0x92, symbol: "E"
scan code: 0x02, symbol: "1"
scan code: 0x82, symbol: "1"
scan code: 0x03, symbol: "2"
scan code: 0x83, symbol: "2"
scan code: 0x04, symbol: "3"
scan code: 0x84, symbol: "3"
scan code: 0x01, symbol: "?"
Now, some words on how to compile this.
Compile the assembly file with NASM using nasm.exe -f obj kbda.asm. It'll produce kbda.obj. Create a project in Borland/Turbo C/C++ IDE, include in it kbdc.c and kbda.obj. Make sure the code is going to be compiled in the small or tiny memory model (basically, we need to make sure SetNewIrq9Isr() and RestoreOldIrq9Isr() are going to be called as near functions). Compile it.
There are a few caveats.
First, none of the getc(), gets(), scanf(), etc functions will work if called between SetNewIrq9Isr() and RestoreOldIrq9Isr(). They will hang the program.
Second, the code doesn't keep track of the shift, control and alt keys. What it means to you is that, if you run this program from within the IDE by pressing ctrl+F9, when the program finishes, the IDE will most likely think ctrl is still being held down. To "unlock" the keyboard you'll have to press and release ctrl. The same may apply to other similar keys if they're held down when this program starts. You can include extra code to wait until all of shift, control and alt are released. I believe you can find their current state in the BIOS data area.
You can, of course, convert the assembly file from NASM syntax to TASM syntax and compile it with TASM. I'm simply using free tools, Turbo C++ 1.01 and NASM.
UPDATE: Here's the asm file for TASM:
PUBLIC _SetNewIrq9Isr, _RestoreOldIrq9Isr
PUBLIC _ScanBuf, _ScanReadIdx, _ScanWriteIdx
.386
_TEXT SEGMENT PUBLIC 'CODE' USE16
ASSUME CS:_TEXT, DS:_DATA
; void SetNewIrq9Isr(void);
_SetNewIrq9Isr PROC NEAR
push bx
push es
mov bx, 9 * 4
mov ax, 0
mov es, ax
cli
mov ax, es:[bx]
mov _pOldIrq9IsrOfs, ax
mov word ptr es:[bx], offset _NewIrq9Isr
mov ax, es:[bx + 2]
mov _pOldIrq9IsrSeg, ax
mov es:[bx + 2], cs
sti
pop es
pop bx
ret
_SetNewIrq9Isr ENDP
; void RestoreOldIrq9Isr(void);
_RestoreOldIrq9Isr PROC NEAR
push bx
push es
mov bx, 9 * 4
mov ax, 0
mov es, ax
cli
mov ax, _pOldIrq9IsrOfs
mov es:[bx], ax
mov ax, _pOldIrq9IsrSeg
mov es:[bx + 2], ax
sti
pop es
pop bx
ret
_RestoreOldIrq9Isr ENDP
_NewIrq9Isr PROC NEAR
pusha
push ds
mov ax, _DATA
mov ds, ax
in al, 60h
push ax
in al, 061h
mov ah, al
or al, 080h
out 061h, al
mov al, ah
out 061h, al
pop ax
; ScanBuf[ScanWriteIdx] = scan code;
; ScanWriteIdx = (ScanWriteIdx + 1) & (SCAN_BUF_SIZE - 1);
mov bx, _ScanWriteIdx
mov _ScanBuf[bx], al
inc bx
and bx, 1023
mov _ScanWriteIdx, bx
mov al, 20h
out 20h, al
pop ds
popa
iret
_NewIrq9Isr ENDP
_TEXT ENDS
_DATA SEGMENT PUBLIC 'DATA' USE16
_pOldIrq9IsrOfs dw ?
_pOldIrq9IsrSeg dw ?
; #define SCAN_BUF_SIZE 1024
; volatile unsigned char ScanBuf[SCAN_BUF_SIZE];
; volatile unsigned ScanReadIdx = 0;
; volatile unsigned ScanWriteIdx = 0;
_ScanBuf db 1024 dup (?)
_ScanReadIdx dw 0
_ScanWriteIdx dw 0
_DATA ENDS
END
You compile it using tasm.exe /ml kbda.asm. The rest is the same.

I also took a similar course back in the days. Basically what you need to do, is to catch the keyboard interrupt before it handled by the system keyboard interrupt handler.
You need to create your own interrupt handler, and the bind it to the keyboard interrupt.
once you're done with your work, call the original system keyboard interrupt handler.

Related

External C code not executing from two stage x86 bootloader?

I want to make a toy os but i am stuck on the bootloader stage.
I just wanted to write a very simple bootloader that loads my main C code, i don't like x86 assembly all that much, what happens is once in the second stage bootloader, after switching to protected/32bit mode, i call my kmain() c function using the extern keyword it seems to go into some kind of infinite rebooot loop OR qemu crashes with this error "Trying to execute code outside RAM or ROM at 0x000a0000 "
I also have some x86 assembler based functions to print text to the screen, those work well if used, but calling the extern c function causes the above mentioned arrors
the "flow" is like this
bootloader.asm(first stage bootloader)->Stage2.asm(2nd stage bootloader)->kmain.cpp
GDT, stack, A20 are setup in the Stage2.asm to start protected mode/32 bit mode, and then the extern kmain function is called.The kmain() c++ file only tries to write one character with a color to the VGA buffer
I am on an UBuntu 18.04 host, using nasm for assembly, gcc for linking and c compiling and qemu for testing this so-called OS
These are the commands i use to build and run
nasm -f elf bootload.asm -o bootload.o
nasm -f elf Stage2.asm -o stage2.o
gcc -m32 stage2.o bootload.o kmain.cpp -o kernel.bin -g -nostdlib -ffreestanding -std=c++11 -mno-red-zone -fno-exceptions -nostdlib -fno-rtti -Wall -Wextra -Werror -T linker.ld
qemu-system-i386 -fda kernel.bin
This is my bootloader.asm
[bits 16]
section .boot
global boot
boot:
hello: db "Hello world!",0
mov si,hello
mov ah,0x0e
.loop:
lodsb
or al,al
jz diskboot
int 0x10
jmp .loop
diskboot:
mov ax,0x800
mov es,ax
xor bx,bx
mov ah,0x2
mov al,0x1
mov ch,0x0
mov cl,0x2
mov dh,0x0
int 0x13
jmp 0x800:0
Stage2.asm
section .kernel
bits 16
jmp main ; go to start
;*******************************************************
; Preprocessor directives
;*******************************************************
%include "stdio.inc" ; basic i/o routines
%include "Gdt.inc" ; Gdt routines
%include "A20.inc" ; A20 enabling
;*******************************************************
; Data Section
;*******************************************************
LoadingMsg db 0x0D, 0x0A, "Searching for Operating System...", 0x00
main:
cli ; clear interrupts
xor ax, ax ; null segments
mov ds, ax
mov es, ax
mov ax, 0x9000 ; stack begins at 0x9000-0xffff
mov ss, ax
mov sp, 0xFFFF
sti ; enable interrupts
call InstallGDT ; install our GDT
call EnableA20_KKbrd_Out
mov si, LoadingMsg
call Puts16
EnterStage3:
cli ; clear interrupts
mov eax, cr0 ; set bit 0 in cr0--enter pmode
or eax, 1
mov cr0, eax
jmp CODE_DESC:Stage3 ; far jump to fix CS
bits 32
Stage3:
mov ax, DATA_DESC
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000
mov esp, ebp
extern kmain
call kmain
cli
hlt
msg db 0x0A, "<[ OS Development Series Tutorial 10 ]>", 0x0A, 0
A20.inc
;********************************************
; Enable A20 address line
;
; OS Development Series
;********************************************
%ifndef __A20_INC_67343546FDCC56AAB872_INCLUDED__
%define __A20_INC_67343546FDCC56AAB872_INCLUDED__
bits 16
;----------------------------------------------
; Enables a20 line through keyboard controller
;----------------------------------------------
EnableA20_KKbrd:
cli
push ax
mov al, 0xdd ; send enable a20 address line command to controller
out 0x64, al
pop ax
ret
;--------------------------------------------
; Enables a20 line through output port
;--------------------------------------------
EnableA20_KKbrd_Out:
cli
pusha
call wait_input
mov al,0xAD
out 0x64,al ; disable keyboard
call wait_input
mov al,0xD0
out 0x64,al ; tell controller to read output port
call wait_output
in al,0x60
push eax ; get output port data and store it
call wait_input
mov al,0xD1
out 0x64,al ; tell controller to write output port
call wait_input
pop eax
or al,2 ; set bit 1 (enable a20)
out 0x60,al ; write out data back to the output port
call wait_input
mov al,0xAE ; enable keyboard
out 0x64,al
call wait_input
popa
sti
ret
; wait for input buffer to be clear
wait_input:
in al,0x64
test al,2
jnz wait_input
ret
; wait for output buffer to be clear
wait_output:
in al,0x64
test al,1
jz wait_output
ret
;--------------------------------------
; Enables a20 line through bios
;--------------------------------------
EnableA20_Bios:
pusha
mov ax, 0x2401
int 0x15
popa
ret
;-------------------------------------------------
; Enables a20 line through system control port A
;-------------------------------------------------
EnableA20_SysControlA:
push ax
mov al, 2
out 0x92, al
pop ax
ret
%endif
Gdt.inc
;*************************************************
; Gdt.inc
; -GDT Routines
;
; OS Development Series
;*************************************************
%ifndef __GDT_INC_67343546FDCC56AAB872_INCLUDED__
%define __GDT_INC_67343546FDCC56AAB872_INCLUDED__
bits 16
;*******************************************
; InstallGDT()
; - Install our GDT
;*******************************************
InstallGDT:
cli ; clear interrupts
pusha ; save registers
lgdt [toc] ; load GDT into GDTR
sti ; enable interrupts
popa ; restore registers
ret ; All done!
;*******************************************
; Global Descriptor Table (GDT)
;*******************************************
gdt_data:
dd 0 ; null descriptor
dd 0
; gdt code: ; code descriptor
dw 0FFFFh ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 11001111b ; granularity
db 0 ; base high
; gdt data: ; data descriptor
dw 0FFFFh ; limit low (Same as code)10:56 AM 7/8/2007
dw 0 ; base low
db 0 ; base middle
db 10010010b ; access
db 11001111b ; granularity
db 0 ; base high
end_of_gdt:
toc:
dw end_of_gdt - gdt_data - 1 ; limit (Size of GDT)
dd gdt_data ; base of GDT
; give the descriptor offsets names
%define NULL_DESC 0
%define CODE_DESC 0x8
%define DATA_DESC 0x10
%endif ;__GDT_INC_67343546FDCC56AAB872_INCLUDED__
linker.ld
ENTRY(boot)
OUTPUT_FORMAT("binary")
SECTIONS {
. = 0x7c00;
.boot :
{
*(.boot)
}
. = 0x7dfe;
.sig : {
SHORT(0xaa55);
}
. = 0x8000;
.kernel : AT(0x7e00) /* place immediately after the boot sector */
{
*(.kernel)
*(.text)
*(.rodata)
*(.data)
*(.bss)
*(COMMON)
}
kernel_sectors = (SIZEOF(.kernel) + 511) / 512;
/DISCARD/ : {
*(.eh_frame)
}
}
kmain.cpp
extern "C" void kmain()
{
unsigned char* vga = (unsigned char*) 0xb8000;
vga[0] = 'S';
vga[1] = 0x09;
for(;;);
}
EDIT : added stdio.inc
%ifndef __STDIO_INC_67343546FDCC56AAB872_INCLUDED__
%define __STDIO_INC_67343546FDCC56AAB872_INCLUDED__
;==========================================================
;
; 16 Bit Real Mode Routines
;==========================================================
;************************************************;
; Puts16 ()
; -Prints a null terminated string
; DS=>SI: 0 terminated string
;************************************************;
bits 16
Puts16:
pusha ; save registers
.Loop1:
lodsb ; load next byte from string from SI to AL
or al, al ; Does AL=0?
jz Puts16Done ; Yep, null terminator found-bail out
mov ah, 0eh ; Nope-Print the character
int 10h ; invoke BIOS
jmp .Loop1 ; Repeat until null terminator found
Puts16Done:
popa ; restore registers
ret ; we are done, so return
;==========================================================
;
; 32 Bit Protected Mode Routines
;==========================================================
bits 32
%define VIDMEM 0xB8000 ; video memory
%define COLS 80 ; width and height of screen
%define LINES 25
%define CHAR_ATTRIB 63 ; character attribute (White text on light blue background)
_CurX db 0 ; current x/y location
_CurY db 0
;**************************************************;
; Putch32 ()
; - Prints a character to screen
; BL => Character to print
;**************************************************;
Putch32:
pusha ; save registers
mov edi, VIDMEM ; get pointer to video memory
;-------------------------------;
; Get current position ;
;-------------------------------;
xor eax, eax ; clear eax
;--------------------------------
; Remember: currentPos = x + y * COLS! x and y are in _CurX and _CurY.
; Because there are two bytes per character, COLS=number of characters in a line.
; We have to multiply this by 2 to get number of bytes per line. This is the screen width,
; so multiply screen with * _CurY to get current line
;--------------------------------
mov ecx, COLS*2 ; Mode 7 has 2 bytes per char, so its COLS*2 bytes per line
mov al, byte [_CurY] ; get y pos
mul ecx ; multiply y*COLS
push eax ; save eax--the multiplication
;--------------------------------
; Now y * screen width is in eax. Now, just add _CurX. But, again remember that _CurX is relative
; to the current character count, not byte count. Because there are two bytes per character, we
; have to multiply _CurX by 2 first, then add it to our screen width * y.
;--------------------------------
mov al, byte [_CurX] ; multiply _CurX by 2 because it is 2 bytes per char
mov cl, 2
mul cl
pop ecx ; pop y*COLS result
add eax, ecx
;-------------------------------
; Now eax contains the offset address to draw the character at, so just add it to the base address
; of video memory (Stored in edi)
;-------------------------------
xor ecx, ecx
add edi, eax ; add it to the base address
;-------------------------------;
; Watch for new line ;
;-------------------------------;
cmp bl, 0x0A ; is it a newline character?
je .Row ; yep--go to next row
;-------------------------------;
; Print a character ;
;-------------------------------;
mov dl, bl ; Get character
mov dh, CHAR_ATTRIB ; the character attribute
mov word [edi], dx ; write to video display
;-------------------------------;
; Update next position ;
;-------------------------------;
inc byte [_CurX] ; go to next character
; cmp byte [_CurX], COLS ; are we at the end of the line?
; je .Row ; yep-go to next row
jmp .done ; nope, bail out
;-------------------------------;
; Go to next row ;
;-------------------------------;
.Row:
mov byte [_CurX], 0 ; go back to col 0
inc byte [_CurY] ; go to next row
;-------------------------------;
; Restore registers & return ;
;-------------------------------;
.done:
popa ; restore registers and return
ret
;**************************************************;
; Puts32 ()
; - Prints a null terminated string
; parm\ EBX = address of string to print
;**************************************************;
Puts32:
;-------------------------------;
; Store registers ;
;-------------------------------;
pusha ; save registers
push ebx ; copy the string address
pop edi
.loop:
;-------------------------------;
; Get character ;
;-------------------------------;
mov bl, byte [edi] ; get next character
cmp bl, 0 ; is it 0 (Null terminator)?
je .done ; yep-bail out
;-------------------------------;
; Print the character ;
;-------------------------------;
call Putch32 ; Nope-print it out
;-------------------------------;
; Go to next character ;
;-------------------------------;
inc edi ; go to next character
jmp .loop
.done:
;-------------------------------;
; Update hardware cursor ;
;-------------------------------;
; Its more efficiant to update the cursor after displaying
; the complete string because direct VGA is slow
mov bh, byte [_CurY] ; get current position
mov bl, byte [_CurX]
call MovCur ; update cursor
popa ; restore registers, and return
ret
;**************************************************;
; MoveCur ()
; - Update hardware cursor
; parm/ bh = Y pos
; parm/ bl = x pos
;**************************************************;
bits 32
MovCur:
pusha ; save registers (aren't you getting tired of this comment?)
;-------------------------------;
; Get current position ;
;-------------------------------;
; Here, _CurX and _CurY are relitave to the current position on screen, not in memory.
; That is, we don't need to worry about the byte alignment we do when displaying characters,
; so just follow the forumla: location = _CurX + _CurY * COLS
xor eax, eax
mov ecx, COLS
mov al, bh ; get y pos
mul ecx ; multiply y*COLS
add al, bl ; Now add x
mov ebx, eax
;--------------------------------------;
; Set low byte index to VGA register ;
;--------------------------------------;
mov al, 0x0f
mov dx, 0x03D4
out dx, al
mov al, bl
mov dx, 0x03D5
out dx, al ; low byte
;---------------------------------------;
; Set high byte index to VGA register ;
;---------------------------------------;
xor eax, eax
mov al, 0x0e
mov dx, 0x03D4
out dx, al
mov al, bh
mov dx, 0x03D5
out dx, al ; high byte
popa
ret
;**************************************************;
; ClrScr32 ()
; - Clears screen
;**************************************************;
bits 32
ClrScr32:
pusha
cld
mov edi, VIDMEM
mov cx, 2000
mov ah, CHAR_ATTRIB
mov al, ' '
rep stosw
mov byte [_CurX], 0
mov byte [_CurY], 0
popa
ret
;**************************************************;
; GotoXY ()
; - Set current X/Y location
; parm\ AL=X position
; parm\ AH=Y position
;**************************************************;
bits 32
GotoXY:
pusha
mov [_CurX], al ; just set the current position
mov [_CurY], ah
popa
ret
%endif ;__STDIO_INC_67343546FDCC56AAB872_INCLUDED__
stdio.inc had functions that were already writing to the VGA framebuffer using x86 assembler in 32 bit protected mode, these functions were conflicting with my C code that was also trying to write to the framebuffer.
removing stdio.inc and all references to its functions has solved the issue
I can now write to the VGA Buffer and have colored text display on the screen using C code.

Why do keyboard interrupts work in QEMU, but not on real hardware?

I have my own OS that I am working on, and I have asked a few questions on before. Currently, the OS works perfectly fine on QEMU, but on real hardware (Tested on an old AMD athlon64 system with IDE drives, and on an intel core i5 system), it fails to handle any keyboard interrupts, and instead, it receives a [#GP0] exception.
I have tried printing the error code received by the interrupt handler, and it is set to 0, which means it is not a segment error. I have tested it on two different machines, both with the same results.
Here is some relevant code:
; Common ISR code
isr_common_stub:
; 1. Save CPU state
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
mov ax, ds ; Lower 16-bits of eax = ds.
push eax ; save the data segment descriptor
mov ax, 0x10 ; kernel data segment descriptor
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp
; 2. Call C handler
call isr_handler
pop eax
; 3. Restore state
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popa
add esp, 8 ; Cleans up the pushed error code and pushed ISR number
sti
iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
; Common IRQ code. Identical to ISR code except for the 'call'
; and the 'pop ebx'
irq_common_stub:
pusha
mov ax, ds
push eax
mov ax, 0x10 ;0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp ; At this point ESP is a pointer to where DS (and the rest
; of the interrupt handler state resides)
; Push ESP as 1st parameter as it's a
; pointer to a registers_t
call irq_handler
pop ebx ; Remove the saved ESP on the stack. Efficient to just pop it
; into any register. You could have done: add esp, 4 as well
pop ebx
mov ds, bx
mov es, bx
mov fs, bx
mov gs, bx
popa
add esp, 8
sti
iret
static void keyboard_callback(registers_t regs) {
/* The PIC leaves us the scancode in port 0x60 */
uint8_t scancode = port_byte_in(0x60);
//kprint_int(scancode);
bool iskeyup = false;
if (scancode >= KEYUPOFFSET) {
iskeyup = true;
scancode -= KEYUPOFFSET;
}
key_handler(scancode, iskeyup);
UNUSED(regs);
}
void isr_handler(registers_t *r) {
kprint("received interrupt: ");
char s[3];
int_to_ascii(r->int_no, s);
kprint(s);
kprint("\n");
kprint("error code: ");
char e[3];
int_to_ascii(r->err_code, s);
kprint(s);
kprint("\n");
kprint(exception_messages[r->int_no]);
kprint("\n");
}
void irq_handler(registers_t *r) {
/* After every interrupt we need to send an EOI to the PICs
* or they will not send another interrupt again */
if (r->int_no >= 40) port_byte_out(0xA0, 0x20); /* slave */
port_byte_out(0x20, 0x20); /* master */
/* Handle the interrupt in a more modular way */
if (interrupt_handlers[r->int_no] != 0) {
isr_t handler = interrupt_handlers[r->int_no];
handler(r);
}
else {
if (loaded == 1) {
kprint("Error! Unhandled interrupt!");
}
}
}
And the repo for building:
https://github.com/Menotdan/DripOS/tree/dev
I expect that keyboard input should work on the real hardware, not really sure why it doesn't. The emulator is running as x86_64, as is the hardware. But instead of working the OS just receives [GP#0] as fast as it can handle interrupts.
EDIT: Sorry about the link spacing, for some reason StackOverflow thought it was code so I had to add lines between them so it wouldn't complain :/
There were a few problems with the code that I have fixed, and now the OS boots just fine. First problem was that the keyboard driver wasn't taking a pointer as a parameter. The next problem was the use of pusha and popa in interrupt.asm, instead of pushad and popad.
After changing those two things, the OS boots on real hardware.

Initialize char[] fails, esi contains wrong value

I want to initialize a char array, but during I do this my programm crashes. Here's my code:
void kernelEnteredMsg() {
char str[] = "Kernel successfully entered!";
}
Here's the disassembly:
push ebp
mov ebp,esp
push edi
push esi
push ebx
sub esp,byte +0x30
lea edx,[ebp-0x2d]
mov ebx,0x402000 ; load an address outside my data segment
mov eax,0x1d
mov edi,edx
mov esi,ebx ; move this address to edi
mov ecx,eax
rep movsb ; here the programm crashes
add esp,byte +0x30
pop ebx
pop esi
pop edi
pop ebp
ret
I don't understand why it loads esi with 0x402000. But this seems to cause the error. Can somebody explain what happens here and how to fix it?
PS: "Kernel successful entered!" is at 0x1000 in binary file.
C code:
void kernelEnteredMsg();
void entryPoint() {
kernelEnteredMsg();
}
void kernelEnteredMsg() {
char str[] = "Kernel successfully entered!";
int size = 28;
}
Calling assembly code:
extern _entryPoint
global _main
section .text
_main: ; start of kernel
nop
; setup ds, es, ss and gs
mov ax, 16
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x4000
mov ax, 24
mov gs, ax
mov [gs:0], dword 0x07690748 ; test graphics
call _entryPoint ; enter kernel C code
jmp $
This code does copy the string from the .text section to the local stack, because the char array is not 'const'. This may provide a simple solution if you do not need the string to be modified - just make it const char.
I don't understand why it loads esi with 0x402000.
ESI is the source of the string copy instruction 'rep movsb', EDI is the destination.
The address is constructed by IMAGE_BASE+SECTION (IIRC) in the PE file(assuming it is PE.)
Remember in the file there is a FILE_ALIGN and a SECTION_VIRTUAL_ADDRESS, so a section may be
at position 0x1000 in the file(FILE_ALIGN) and at 0x2000 in memory(VIRTUAL_ADDRESS) resulting in IMAGE_BASE+VIRTUAL_ADDRESS=0x402000.
You can use a PE explorer like CFF Explorer(http://www.ntcore.com/exsuite.php)
to display this(if it's a .bin file it may be unapplicable but it has to have some kind of format)
Another possibility may be a wrong state of the DF-Flag leading to wrong behaviour of the string copy instruction (should not happen, because the compiler should take care of this).
Try inserting
__asm__ ("cld");
before the char str[] or in the __main procedure to set string increment to 'UP'.

When replacing keyboard interrupt (Interrupt 9) scanf doesn't appear to accept input [duplicate]

This question already has an answer here:
How to prevent duplicate chars when I press keys on the keyboard
(1 answer)
Closed 4 years ago.
I'm writing the main program in Turbo-C and the functions are in assembly. My code is as follows:
lastc.c:
#include <stdio.h>
#include <dos.h>
#include <string.h>
extern void eliminate_multiple_press(); // save old function adress in 32bit pointer
// setvect , add new function to inturupt 9
extern void uneliminate_multiple_press(); // restore old function to inturupt 9
int main()
{
char *str;
eliminate_multiple_press();
printf("Enter a string:\n");
scanf("%s",str);
printf("the string you entered:\n");
printf("%s\n",str);
uneliminate_multiple_press();
return 0;
}
lasta.asm:
.MODEL LARGE
PUBLIC _eliminate_multiple_press
PUBLIC _uneliminate_multiple_press
.STACK 100H
.DATA
INT9SAVE DD ?
hexa_code db 0
scan_code db 0
.CODE
KEY_HANDLER PROC FAR
PUSH AX
MOV AH,0
int 16h
mov scan_code,ah
mov hexa_code,al
POP AX
iRET
KEY_HANDLER ENDP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_eliminate_multiple_press PROC FAR
PUSH AX
PUSH ES
MOV AX,0
MOV ES,AX
;INT9SAVE = GETVECT(9);
MOV AX,ES:[9*4] ; **ax=c1 hex =193 dec**
MOV WORD PTR INT9SAVE,AX
MOV AX,ES:[9*4+2] **ax=15c7 hex = 5575 dec**
MOV WORD PTR INT9SAVE+2,AX
;SETVECT(9,KEY_HANDLER);
CLI
MOV WORD PTR ES:[9*4],OFFSET KEY_HANDLER ; **ES stays 0**
MOV WORD PTR ES:[9*4+2],SEG KEY_HANDLER ; **ES stays 0**
STI
POP ES
POP AX
RET
_eliminate_multiple_press ENDP
;SETVECT(9,INT9SAVE);
_uneliminate_multiple_press PROC FAR
PUSH ES
PUSH AX
MOV AX,0
MOV ES,AX
CLI
MOV AX,WORD PTR INT9SAVE
MOV ES:[9*4],AX
MOV AX,WORD PTR INT9SAVE+2
MOV ES:[9*4+2],AX
STI
POP AX
POP ES
RET
_uneliminate_multiple_press ENDP
END
I can compile the files without error using this command:
tcc -ml -r- lastc.c lasta.asm
The goal of this code is to eliminate multiple (duplicate) key presses on the keyboard. If I type this sequence of characters:
334ffffghjjjj of my keyboard
The output on the screen should be
34fghj
The Problem
When I run the program lastc.exe it gets stuck at printf("enter a string:\n");. I guess I'm having a problem changing Interrupt 9h interrupt service routine to my new function key_handler
Function key_handler is not complete but at least it should END the function and continue to the end of the code
Why does my program appear to be doing nothing when the scanf is called?
Why are eliminate_multiple_press and uneliminate_multiple_press declared as interrupt handlers? They should be normal functions with a return of RET. They are being called as functions and are not intended as responses to an interrupt.
In your C program then:
extern void eliminate_multiple_press(); // save old function adress in 32bit pointer
// setvect , add new function to inturupt 9
extern void uneliminate_multiple_press(); // restore old function to inturupt 9
And your assembly, RET instead of IRET. And you don't need to save so many registers:
_eliminate_multiple_press PROC FAR
push ES
push AX
MOV AX,0
MOV ES,AX
;INT9SAVE = GETVECT(9);
MOV AX,ES:[9*4] ; **ax=c1 hex =193 dec**
MOV WORD PTR INT9SAVE,AX
MOV AX,ES:[9*4+2] **ax=15c7 hex = 5575 dec**
MOV WORD PTR INT9SAVE+2,AX
;SETVECT(9,KEY_HANDLER);
CLI
MOV WORD PTR ES:[9*4],OFFSET KEY_HANDLER ; **ES stays 0**
MOV WORD PTR ES:[9*4+2],SEG KEY_HANDLER ; **ES stays 0**
STI
POP AX
POP ES
RET
_eliminate_multiple_press ENDP
;SETVECT(9,INT9SAVE);
_uneliminate_multiple_press PROC FAR
PUSH ES
PUSH AX
MOV AX,0
MOV ES,AX
CLI
MOV AX,WORD PTR INT9SAVE
MOV ES:[9*4],AX
MOV AX,WORD PTR INT9SAVE+2
MOV ES:[9*4+2],AX ; Was "[27*4+2]" which is incorrect.
STI
POP AX
POP ES
RET
_uneliminate_multiple_press ENDP
An End Of Interrupt (EOI) is a signal sent to a Programmable Interrupt Controller (PIC) to
indicate the completion of interrupt processing for a given interrupt.
mov al,020h ; =EOI
out 020h,al

Embedded assembly in C (DOS) - illegal instruction

I'm trying to re-program instruction vector table. Here is the code I use:
#include <stdio.h>
int a=1;
void func();
void keyboard()
{
printf("\n\nkeyboard!!!\n");
a=0;
asm{iret}
}
int main ()
{
printf("starting...");
func();
return 0;
}
int vectorcs = 0;
int vectorip = 0;
void func()
{
printf("\n*****\n");
asm{
cli
mov ax,0
mov es,ax
mov bx,36
mov ax,word ptr es:[bx]
mov vectorip,ax
push ax
mov ax,word ptr es:[bx+2]
mov vectorcs,ax
push ax
mov ax,cs
mov word ptr es:[bx],offset keyboard
mov es:[bx+2],ax
sti
}
printf("\n%d %d\n",vectorip,vectorcs);
while (a) {
}
asm {
cli
mov es,bx
mov bx,36
pop ax
mov word ptr es:[bx+2],ax
}
asm{
pop ax
mov word ptr es:[bx],ax
sti
}
}
I'm using Turbo C++ 3.0
When I try to run this program, "16 Bit MS-DOS Subsystem: The NTVDM CPU has encountered an illegal instruction." appears. Then it shows contents of CS, OP, and IP registers. I can't continue the program. Any suggestions?
What you're doing is not right for multiple reasons:
Regular C functions can't be safely used as interrupt service routines because they don't correctly save, load and restore the CPU registers. They must be declared with the interrupt keyword. And they'll have iret for you at the end.
Variables that can change in the program asynchronously from interrupt routines must be declared as volatile, otherwise you're risking to have accesses to them incorrectly optimized out by the compiler.
Your inline assembly code probably corrupts the contents of CPU registers. One thing that's wrong with this code is that your asm blocks mess with the stack pointer. The first block exits with several extra words on the stack. This may be completely unexpected for the compiler and can break your program. There may be other issues, but I'm not going to check with the compiler documentation which registers must be preserved by inline assembly blocks. I'd avoid doing this altogether and opt for the setvect() function instead.
Calling most of standard library functions from inside of interrupt service routines is asking for trouble because these functions generally aren't reentrant/thread-safe. They can modify some global variables or states in completely unexpected ways for the rest of the program. The same is true for calling DOS service functions from the interrupt service routines (which your printf() relies on, btw). You can only call those when DOS says it's OK. It does so via the InDos flag variable and still, not all are safe to call when InDos=0.
See how to change interrupt vectors, define interrupt service routines and call DOS functions from them, all with Turbo C, in the answer to this question.
You may also find this question and its answers useful.
EDIT:
This is how you do it without dos.h's functionality with inline asm:
#include <stdio.h>
volatile int a = 1;
void interrupt (*pOldInt9)(void);
void func(void);
void interrupt keyboard(void)
{
printf("\n\nkeyboard!!!\n");
asm {
in al, 0x60
in al, 0x61
mov ah, al
or al, 0x80
out 0x61, al
mov al, ah
out 0x61, al
}
a = 0;
asm {
mov al, 0x20
out 0x20, al
}
}
int main(void)
{
printf("starting...");
func();
return 0;
}
void func(void)
{
printf("\n*****\n");
asm {
push bx
push es
mov bx, 9 * 4
mov ax, 0
mov es, ax
cli
mov ax, es:[bx]
mov word ptr pOldInt9, ax
mov word ptr es:[bx], offset keyboard
mov ax, es:[bx + 2]
mov word ptr pOldInt9[2], ax
mov es:[bx + 2], cs
sti
pop es
pop bx
}
while (a) {}
asm {
push bx
push es
mov bx, 9 * 4
mov ax, 0
mov es, ax
cli
mov ax, word ptr pOldInt9
mov es:[bx], ax
mov ax, word ptr pOldInt9[2]
mov es:[bx + 2], ax
sti
pop es
pop bx
}
}
asm {
cli
mov es,bx
mov bx,36
pop ax
mov word ptr es:[bx+2],ax
}
What does bx contain before that code?
void keyboard()
{
printf("\n\nkeyboard!!!\n");
a=0;
asm{iret}
}
This function has set up a stack frame that you haven't correctly destroyed.

Resources