Author Topic: Changing entry of Interrupt Vector Table  (Read 10712 times)

Offline patdes20

  • Jr. Member
  • *
  • Posts: 3
Changing entry of Interrupt Vector Table
« on: June 10, 2022, 11:41:35 AM »
Hi everyone, I am trying to change the entry at 0x000:0x0020 (Timer Interrupt) of the Interrupt Vector Table and replace it with an own interrupt handler that jumps to the old interrupt handler after it finishes. My method needs to be executed every 55ms. The program is in real mode and works without OS. Could anybody help me and give me some code examples? Thank you.  :)
With best regards, Patrick

Offline fredericopissarra

  • Full Member
  • **
  • Posts: 373
  • Country: br
Re: Changing entry of Interrupt Vector Table
« Reply #1 on: June 10, 2022, 05:38:41 PM »
Code: [Select]
; boot.asm - Legacy boot sector.
  bits  16
  cpu   386
 
  org   0x7c00

_start:
  ; Since CS is always zero at this point, just copy
  ; CS to DS and ES.
  mov   ax,cs
  mov   ds,ax
  mov   es,ax

  ; Disable interrupts.
  cli

  ; Change int 0x1c to new handler.
  ; if there is an old handler, store it.
  ;
  ; int 0x1c is called by irq8, which is adjusted to
  ; occur 18.2 times/sec (timer 0 is 18.2 Hz rate generator
  ; by default - roughly 55 ms).
  mov   eax,es:[0x1c*4]
  test  eax,eax
  jz    .no_old_handler
  mov   dword es:[oldint],eax
.no_old_handler:
  mov   word es:[0x1c*4],newint
  mov   word es:[0x1c*4+2],cs

  ; Re-enable interrupts
  sti

.loop:
  hlt
  jmp   .loop

; --- Our interrupt handler.
  align 2
newint:
  pushf
  push  ds
  push  ax
  push  bx
  push  si

  mov ax,cs
  mov ds,ax

  ; We'll print 'tick' on string each second (roughly).
  inc   byte [cnt]
  cmp   byte [cnt],18
  jne   .not_time_yet

  cld
  mov   byte [cnt],0
  mov   ax,cs
  mov   ds,ax
  lea   si,[msg]
  call  puts
.not_time_yet:
  pop   si
  pop   bx
  pop   ax
  pop   ds

  ; if there is an old handler, jump to it.
  ; Otherwise iret.
  cmp   dword cs:[oldint],0
  je    .retfromint
  popf
  jmp   far [cs:oldint]
.retfromint:
  popf
  iret

  align 2
puts:
  lodsb
  test  al,al
  je    .exit
  mov   bx,7
  mov   ah,0x0e
  int   0x10
  jmp   puts
.exit:
  ret

  align 4
oldint:
  dd  0
cnt:
  db  0
msg:
  db  `tick\r\n`,0

  times 510-($-$$) db 0
  dw    0xaa55
Code: [Select]
$ nasm -f bin boot.asm -o boot.bin
$ qemu-system-i386 -drive file=boot.bin,index=0,media=disk,format=raw
« Last Edit: June 10, 2022, 06:16:12 PM by fredericopissarra »

Offline debs3759

  • Global Moderator
  • Full Member
  • *****
  • Posts: 224
  • Country: gb
    • GPUZoo
Re: Changing entry of Interrupt Vector Table
« Reply #2 on: June 10, 2022, 09:40:10 PM »
My boot sector was written with the assumption that CS can be either 0000 or 07C0. I believe both are possible, so I use org 0, copy the boot sector and do a far jump to the copy, setting CS up how I want it. I'm pretty sure I did that based on research that showed you can't guarantee the values of CS:IP when the boot sector is first loaded.
My graphics card database: www.gpuzoo.com

Offline debs3759

  • Global Moderator
  • Full Member
  • *****
  • Posts: 224
  • Country: gb
    • GPUZoo
Re: Changing entry of Interrupt Vector Table
« Reply #3 on: June 10, 2022, 09:48:26 PM »
It's also important to set up the stack at the start of a boot sector, to be safe on all systems.

The start of my boot sector has the following code:

Code: [Select]
org 0
BootSector:
jmp short start ; There should always be 3 bytes of code
nop ; followed by the start of the data.

<insert BPD data here>
start:
push WORD stackseg ; set up the stack
pop SS
mov SP,stacksize ;

push WORD 07C0h
pop DS

push WORD moveseg ; where I move my boot sector to
pop ES

xor SI,SI ; Starting from bottom of each segment
xor DI,DI

mov CX,100h ; 256 words to be transferred
cld
rep movsw ; Move the code

;-----------------------------------------------------------------------+
;  Now we jump to the next instruction in the copy of the code +
;-----------------------------------------------------------------------+

jmp moveseg:next ; CS is now set to moveseg
next:

That way I make no assumptions about the initial values of any segment register, and I don't do anything (other than a short jump over the BPD) before executing any other code.
« Last Edit: June 10, 2022, 09:54:00 PM by debs3759 »
My graphics card database: www.gpuzoo.com

Offline debs3759

  • Global Moderator
  • Full Member
  • *****
  • Posts: 224
  • Country: gb
    • GPUZoo
Re: Changing entry of Interrupt Vector Table
« Reply #4 on: June 10, 2022, 10:00:46 PM »
See https://wiki.osdev.org/Boot_Sequence for confirmation that the boot sector could be loaded at either 0:07C00 or 07C0:0000
My graphics card database: www.gpuzoo.com

Offline fredericopissarra

  • Full Member
  • **
  • Posts: 373
  • Country: br
Re: Changing entry of Interrupt Vector Table
« Reply #5 on: June 11, 2022, 11:50:43 AM »
It's also important to set up the stack at the start of a boot sector, to be safe on all systems.

Assuming CS=0 and IP=0x7c00 at INT 0x19 is pretty safe -- the same for DS, since int 0x19 is prepared to access BIOS data area. The actual specification for IBM-PC Boot (I forgot the name of the document) says so... Only non conforming BIOS use a different setup. But I agree we have to be careful.

The stack is already set by BIOS, and before 0x0:0x7C00. But, again, nothing bad happens if we are careful!

I would code this setup like this:

Code: [Select]
  bits 16
  cpu 386

  org 0

  ; ok... not "standard" (for FAT)...
  jmp  0x7c0:_start
  ; ...
_start:
  mov ax,cs
  mov ds,ax
  ; mov es,ax     ; if we want to mimic small model.
                  ; otherwise leave es for other uses...

  ; I like to setup the stack at the end of lower memory, just before
  ; VGA video framebuffer
  mov ax,0x9000
  xor bx,bx       ; NOTE: push will decrease SP first...
  mov ss,ax       ; CPU don't accept any interruptions when these
  mov sp,bx       ; two instructions are executing... (Intel SDM)..
  ...

Notice I'm not using the stack until the stack is ready. :)

BTW... I like to do something different... to relocate the code to the begining of usable RAM (address 0x60:0 - after Extended BIOS Data Area). This way all the space between physical address 0x800 to 0x8ffff is usable to us (574 KiB):

Code: [Select]
[code]
  bits 16
  cpu 386

  org 0

  jmp  _start
  ; ...
_start:
  ; relocate this code to begining of RAM.
  cld
  push 0x7c0
  pop ds
  push byte 0x60
  pop es
  xor si,si
  mov di,si
  mov cx,256
  rep movsw

  jmp 0x60:here
here:
  mov ax,es
  mov ds,ax

  ; I like to setup the stack at the end of lower memory, just before
  ; VGA video framebuffer
  mov ax,0x9000
  xor bx,bx       ; NOTE: push will decrease SP first (386+)...
  mov ss,ax       ; CPU don't accept any interruptions when this
  mov sp,bx       ; to instructions are executing... (Intel SDM)..
  ...

  times 510 - ($ - $$) db 0
  dw 0xaa55
heap:

Since all near jumps are IP relative and all memory references are relative to a segment selector...

PS: This is the actual specification. See 6.5.1:

Quote
"When the boot handler is called, the BIOS passes a pointer to the PnP Installation
Check Structure in ES:DI. This is so that once the boot handler has successfully
loaded the device’s boot sector into memory at address 0000:7C00h, execution control
can be transferred with the following register contents:..."
« Last Edit: June 11, 2022, 12:44:33 PM by fredericopissarra »

Offline patdes20

  • Jr. Member
  • *
  • Posts: 3
Re: Changing entry of Interrupt Vector Table
« Reply #6 on: June 12, 2022, 01:12:05 PM »
Thanks a lot for all the great examples and explanations.  :)

Offline debs3759

  • Global Moderator
  • Full Member
  • *****
  • Posts: 224
  • Country: gb
    • GPUZoo
Re: Changing entry of Interrupt Vector Table
« Reply #7 on: June 12, 2022, 02:48:55 PM »
PS: This is the actual specification. See 6.5.1:

That is valid for all BIOS after the spec was written, but not before, so if code is to potentially run on older hardware, it may not be 100% valid.

Thanks for the link. It will be handy when I get back into writing OS code, especially for pnp systems and devices.
My graphics card database: www.gpuzoo.com

Offline patdes20

  • Jr. Member
  • *
  • Posts: 3
Re: Changing entry of Interrupt Vector Table
« Reply #8 on: June 19, 2022, 03:51:08 PM »
Hi everyone, I just have one further question: How do I return to a different address with the interrupt return. I want to change the executed function every 55ms. Can I just pop the stack and push a different address or does iret put more information on the stack?

Offline debs3759

  • Global Moderator
  • Full Member
  • *****
  • Posts: 224
  • Country: gb
    • GPUZoo
Re: Changing entry of Interrupt Vector Table
« Reply #9 on: June 19, 2022, 07:32:15 PM »
From nasmdocs 0.98:

Code: [Select]
B.4.127 IRET , IRETW , IRETD : Return from Interrupt
        IRET ; CF [8086]
        IRETW ; o16 CF [8086]
        IRETD ; o32 CF [386]
IRET returns from an interrupt (hardware or software) by means of popping IP (or EIP ), CS and
the flags off the stack and then continuing execution from the new CS:IP .
IRETW pops IP , CS and the flags as 2 bytes each, taking 6 bytes off the stack in total. IRETD pops
EIP as 4 bytes, pops a further 4 bytes of which the top two are discarded and the bottom two go
into CS , and pops the flags as 4 bytes as well, taking 12 bytes off the stack.
IRET is a shorthand for either IRETW or IRETD , depending on the default BITS setting at the time.

So to return to a different address, you need to change the address stored on the stack.
My graphics card database: www.gpuzoo.com