Powered By Blogger

<< Patricians VS Arriviste >> Not the very obvious in Computer science.

Thursday, April 23, 2009

"Code Templating" C installers for Interupt Service Routines (ISR's)

Any OS would like to install ISR handlers for atleast as few interupts. Skipping all the i386 jargon lets get down to coding it. Rest is arranged as
1. required functionality,
2. the problem
3. the solution and notes.
4. Also attached are links to the actual C files implementing the functionality in my OS.

Needed Functionality
typdef void(*ISR_FNPTR)(int ISRNR,void *isrCtx);
KRET_STATUS install_isr(int ISRNR,ISR_FNPTR fnPtr,void *isrCtx);

So on interrupt number ISRNR I want to invoke, the C function pointed by fnPtr. To this function I want the context isrCtx and the interupt number to be passed.

The Problem.
Just initializing the IDT entry with the fnPtr will not do, because the i386 won't push the ISR number and the context isrCtx on the stack before calling your C handler. The quick dirty and often resorted fix by (you know whom) is to have assembly wrappers for _every_ ISR, and then trampolines into your 'C' code after setting up the stack correctly, not sweet not sweet.

something like
.extern isr1
ISR1.S
push 1
push isrCtx1 ;;you c the point here that this is not Cish.
call isr1
iRet

ISR1.C
void isr1(int isrNr,void *isrCtx) {
}

The problem statement is to avoid the need for writing assembly wrappers for every ISR, that is what ISR code templating does.

Solution.
1. Solution is to have an ISR template code as listed in ISR_TEMPLATE.S
2. Next to install ISR, one actually mem copies the template code block into a kernel allocated data buffer.
3. You patch the copied binary assembly code, to generate assembly instruction equivalent to
push yourIsrNr
push yourCntx
call yourC_FN
4. Below is the rough algo and C and assembly psuedocode. It almost would work :D
5. Refer to attached files to the actual implementation.

hypothetical ISR_TEMPLATE.S for the isr template
.export start
.export end
.export iretoffset
.export start_isr_template

start_isr_template:
push 0xCAFECAFE
isrNRPatchOffset:
push 0xC001D00D
callPatchOffset:
call fn
iretoffset:
IRET
template_end:

end


.getIdt
SIDT;

hypothetical isr_handler_install.c
#define DBG_REL_ASSERT(cond) do { \
ASSERT((cond)); \
if( !(cond) ) \
return FAILURE; \
}while(0)

/* Get the offsets into the template code to be patched */
extern char *start_isr_template,*isrNRPatchOffset,*callPatchOffset,*iretoffset,*template_end ;

/* Installs the ISR in the IDT after, alloc and binary patch */
KRET_STATUS install_isr(int ISRNR,ISR_FNPTR fnPtr,void *isrCtx) {
IDT_ENTRY *idt;
char *isrTrampoline;


DBG_REL_ASSERT(isrNR);

//- Step1 : alloc memory for the code block -//
isrTrampoline = alloc(end-start);
if( NULL != isrTrampoline )
return NO_MEM;

//- Step2: Copy in the code template into the allocated memory -//
memcpy(isrTrampoline,start,end-start)

//- Step3: patch the address of isrCtx -//
ASSERT(*((uint32 *)(isrTrampoline + sizeof_push_Instr))==0xCAFECAFE)
*((uint32 *)(isrTrampoline + sizeof_push_Instr)) = isrCtx;

//- Step4: patch the isrNR -//
ASSERT(*((uint32 *)(isrTrampoline + (isrNRPatchOffset - start_isr_template) + sizeof_push_Instr))==0xC001D00D)
*((uint32 *)(isrTrampoline + (isrNRPatchOffset - start_isr_template) + sizeof_push_Instr)) = isrNR;

//- Step5: patch the address of the call as per an i386 near call -//
*((uint32 *)(isrTrampoline+(callPatchOffset-start_isr_template)+sizeof_call_instr)) =
((isrTrampoline + (iretoffset - start_isr_template)) > (char *) fnptr) ?
((isrTrampoline + (iretoffset - start_isr_template)) - (char *) fnptr):
((char *) fnptr - (isrTrampoline + (iretoffset - start_isr_template)));


//- Step6: Install the newly patched code block in the ISR -//
idt = getIDT();
idt[ISRNR].fnAddr = isrTrampoline;
return SUCCESS;
}



Here is what it install_isr(..) does.
Step1.
Allocates code block for the ISR trampoline (from here the code calls into the indented C fnPtr). Its an assembly code chunk templated from the assembly file ISR_TEMPLATE.S

Step2.
Memcpy copies the templated trampoline code into the allocated trampoline code block. The address of the template code and offsets with the template are made available to the C code via the externs.

Step3 & Step 4.
This template code then is pathced at 2 places,
Given we have offsets into the templated code via externs, we essentailly do fill in the following blanks.
push .... <- address of the context to be passed to the C ISR handler push .... <- isrNR to be passed to the C ISR handler. Step5.
This took me while to get it correct, (3 days to be precise). You want to patch the call instruction such that the patched ISR trampoline now calls your C function.
It can be done in many ways and
CALL [yourhexfnaddresshere] is not one of them.

Technically it can be done, though not in the case of 'as' and 'gcc' as is. The assembler generated binary for the call instruction generates a short call instruction. A short call instruction is special in the sense that the address that is takes is not absolute. A short call instruction as the name suggests is a call to an intra segment address. The way this address is encoded is the difference between the 'calls return address' and 'call's target function address'. You get this correct rest is cake walk C code, as explicated in step 5.

Step6.
With the trampoline (the allocate-pathced code block) ready you set its address into the IDT. From here is calls into your patched C code.


Notes on patching Intra segment near call.
Its exactly what it says it is i.e. a short call instruction with the same code segment. So your memory for the code block has to come from KERNEL_CS. The address for this instruction is coded as the diff of the target call address and the return address. This makes the return from the called function just a addition/subtraction from the eip. Recheck what opcode your assembler generates for the call instruction and the intel manual. Objdump and gcc -g are your friend.

Let 'your' head be with you ( and not somebody else's Open Source, including mine )

www.geocities.com/faraz_irulz/i386lib.zip
don't site stay around my webpage for long. You never know what happens !

2 comments:

Amit Mitkar said...

don't u need to mark your "allocated" stuff executable ... and won't that result in all sorts of vulnerabilites :) ...

The pre cooked assembly usually sits in "text" regions an is hence much safer ... no ?

Faraz Ahmed Shaikh said...

Amit, your concerns are correct they have to be taken care off. Fortunately it was my toy OS, which has a primitive kmalloc - tweaked to get the right type of memory.

Think of it as exposing a driver framework, you don't want driver writers writing assembly wrappers rt ?

Followers