Page 1 of 1
calls missing in action
Posted: Thu Jul 26, 2007 1:49 am
by enrico_granata
I am trying to write a syscall() routine for my kernel and map it onto INT 0x80
This is the main handler:
Code: Select all
void syscall(struct regs *r)
{
int opcode = r->eax;
int operand1 = r->ebx;
int operand2 = r->ecx;
switch(opcode)
{
case SYSCALL_PUTCHAR: // 1
putch(operand1);
break;
case SYSCALL_REBOOT:
raise_signal(SIGNAL_DOREBOOT);
break;
case SYSCALL_MORE_MEMORY:
{
ptr_t *ret_addr = (ptr_t*)((void*)operand2);
*ret_addr = (ptr_t)sbrk(operand1);
}
break;
default:
break;
}
}
and I am calling it through this other standard library routine
Code: Select all
void make_syscall(int syscall_code, int param1, int param2)
{
__asm__ __volatile__ ("movl %0,%%eax" : : "m" (syscall_code));
__asm__ __volatile__ ("movl %0,%%ebx" : : "m" (param1));
__asm__ __volatile__ ("movl %0,%%ecx" : : "m" (param2));
__asm__ __volatile__ ("int $0x80");
}
In printf() I use the following routine to output single characters to the screen
Code: Select all
void putchar(char c)
{
make_syscall(1,c,0);
}
The issue, indeed a mysterious one, is that when I use printf's format codes (%i,%s,...) and the resulting string is longer than 1 character, only the first character really goes on the screen (all the rest is silently ignored). However, if I change putchar() to
Code: Select all
void putchar(char c)
{
putch(c);
make_syscall(1,c,0);
}
where putch() is the routine used by the system call that directly writes on video memory, all characters are written *twice*
Does anyone have any idea about this problem?
Thanks a lot,
Enrico[/code]
Posted: Thu Jul 26, 2007 2:01 am
by JamesM
Your putch() routine could be/is of interest - could you post it?
Posted: Thu Jul 26, 2007 2:05 am
by enrico_granata
I kept Bran's routine as I didn't see anything wrong with it (maybe not yet)
Code: Select all
void scroll(void)
{
unsigned blank, temp;
/* A blank is defined as a space... we need to give it
* backcolor too */
blank = 0x20 | (attrib << 8);
/* Row 25 is the end, this means we need to scroll up */
if(csr_y >= 25)
{
/* Move the current text chunk that makes up the screen
* back in the buffer by a line */
temp = csr_y - 25 + 1;
memcpy (textmemptr, textmemptr + temp * 80, (25 - temp) * 80 * 2);
/* Finally, we set the chunk of memory that occupies
* the last line of text to our 'blank' character */
memsetw (textmemptr + (25 - temp) * 80, blank, 80);
csr_y = 25 - 1;
}
}
/* Updates the hardware cursor: the little blinking line
* on the screen under the last character pressed! */
void move_csr(void)
{
unsigned temp;
/* The equation for finding the index in a linear
* chunk of memory can be represented by:
* Index = [(y * width) + x] */
temp = csr_y * 80 + csr_x;
/* This sends a command to indicies 14 and 15 in the
* CRT Control Register of the VGA controller. These
* are the high and low bytes of the index that show
* where the hardware cursor is to be 'blinking'. To
* learn more, you should look up some VGA specific
* programming documents. A great start to graphics:
* http://www.brackeen.com/home/vga */
outportb(0x3D4, 14);
outportb(0x3D5, temp >> 8);
outportb(0x3D4, 15);
outportb(0x3D5, temp);
}
/* Puts a single character on the screen */
void putch(strtype c)
{
unsigned short *where;
unsigned att = attrib << 8;
/* Handle a backspace, by moving the cursor back one space */
if(c == 0x08)
{
if(csr_x != 0) csr_x--;
}
/* Handles a tab by incrementing the cursor's x, but only
* to a point that will make it divisible by 8 */
else if(c == 0x09)
{
csr_x = (csr_x + 8) & ~(8 - 1);
}
/* Handles a 'Carriage Return', which simply brings the
* cursor back to the margin */
else if(c == '\r')
{
csr_x = 0;
}
/* We handle our newlines the way DOS and the BIOS do: we
* treat it as if a 'CR' was also there, so we bring the
* cursor to the margin and we increment the 'y' value */
else if(c == '\n')
{
csr_x = 0;
csr_y++;
}
/* Any character greater than and including a space, is a
* printable character. The equation for finding the index
* in a linear chunk of memory can be represented by:
* Index = [(y * width) + x] */
else if(c >= ' ')
{
where = textmemptr + (csr_y * 80 + csr_x);
*where = c | att; /* Character AND attributes: color */
csr_x++;
}
/* If the cursor has reached the edge of the screen's width, we
* insert a new line in there */
if(csr_x >= 80)
{
csr_x = 0;
csr_y++;
}
/* Scroll the screen if needed, and finally move the cursor */
scroll();
move_csr();
}
Posted: Thu Jul 26, 2007 2:11 am
by os64dev
and how about your printf code? as that is the one causing the problems
Posted: Thu Jul 26, 2007 2:17 am
by enrico_granata
This is used in a loop to output the characters inside a buffer:
Code: Select all
void printf_help( const char ch, char **string )
{
UNUSED( string );
// just output the character
putchar(ch);
} // end printf_help
The reference to a char** is due to the fact that both printf() and sprintf() share a function pointer to (void)(char, char**)
Code: Select all
for ( i = 0; i < length; i++ )
{
// we printed another character
charsout++;
// print the string
func( string[i], &out_string );
} // end for
However, what really
IS strange is that adding a putch() call inside putchar() instead of solely calling make_syscall() outputs *everything* properly
twice
issue solved
Posted: Thu Jul 26, 2007 6:16 am
by enrico_granata
the problem was in make_syscall():
Code: Select all
void make_syscall(int syscall_code, int param1, int param2)
{
__asm__ ("movl %0,%%eax" : : "m" (syscall_code));
__asm__ ("movl %0,%%ebx" : : "m" (param1));
__asm__ ("movl %0,%%ecx" : : "m" (param2));
__asm__ ("int $0x80");
}
as GCC was using EBX as a counter inside a loop. Moving something else into it destroyed this counter and so the loop was never finished... Probably, calling putch() before making the syscall persuaded the compiler not to put the counter inside EBX...
This new version seems to work:
Code: Select all
void make_syscall(int syscall_code, int param1, int param2)
{
int old_eax,old_ebx,old_ecx;
/*
* GCC may use the same registers that we use for its own motives, so save those
* (failing to do so will cause all kind of mysterious bugs)
*/
__asm__ ("movl %%eax,%0" : "=m" (old_eax));
__asm__ ("movl %%ebx,%0" : "=m" (old_ebx));
__asm__ ("movl %%ecx,%0" : "=m" (old_ecx));
/*
* put parameters into registers and ask the kernel to do its deed
* (we use INT 0x80 as Linux does)
*/
__asm__ ("movl %0,%%eax" : : "m" (syscall_code));
__asm__ ("movl %0,%%ebx" : : "m" (param1));
__asm__ ("movl %0,%%ecx" : : "m" (param2));
__asm__ ("int $0x80");
/*
* restore previous registers values so GCC doesn't even imagine what we did
* with 'em ;)
*/
__asm__ ("movl %0,%%eax" : : "m" (old_eax));
__asm__ ("movl %0,%%ebx" : : "m" (old_ebx));
__asm__ ("movl %0,%%ecx" : : "m" (old_ecx));
}
However, is there a way to tell GCC
not to put local variables in registers?
Thanks,
Enrico
Posted: Thu Jul 26, 2007 6:30 am
by JamesM
No, you have no choice. You can do:
Code: Select all
asm volatile ("push %%eax"
"push %%ebx"
"push %%ecx"
"push %%edx"
"movl %0,%%eax"
"movl %1,%%ebx"
"movl %2,%%ecx"
"int $0x80"
"pop %%edx"
"pop %%ecx"
"pop %%ebx"
"pop %%eax" :: "m" (syscall_code),"m"(param1),"m"(param2) );
However, the more generally accepted method is:
Code: Select all
asm volatile("int $0x80" : "=r"(return_val" :
"a" (syscall_code),
"b"(param1),
"c"(param2) );
which will accomplish the same thing.
JamesM
Posted: Thu Jul 26, 2007 6:38 am
by enrico_granata
how do I return a value from the system call back to the user, however? I am using Bran's tutorial ISR handler:
Code: Select all
syscalls_stub:
pusha
push ds
push es
push fs
push gs
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov eax, esp
push eax
mov eax, _syscall
call eax
pop eax
pop gs
pop fs
pop es
pop ds
popa
add esp, 8
iret
so it just seems to me that every change _syscall (the true C handler for system calls) does, is wiped out
Posted: Thu Jul 26, 2007 6:56 am
by JamesM
You have to modify that handler. What I do is when _syscall returns, it places a value in ecx - either 0 or 1. 1 means the handler should return the value in eax (clobbering whatever the user had there originally). This is used in syscalls.
0 means don't return anything, be invisible. This is used in irq handlers etc.
so, you'd do something like:
Code: Select all
u32int _syscall(struct regs *r)
{
// do syscall
if (shouldreturn) asm volatile("mov $1, %ecx"); else asm volatile("mov $0, %ecx");
return valtoreturn;
}
syscalls_stub:
push ds
push es
push fs
push gs
push edx
push ecx
push ebx
push eax
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov eax, esp
push eax
call _syscall
cmp ecx, $0x1 ; should we clobber?
jne .noclobber ; no?
pop ebx ; pop the value that WAS eax, into ebx.
; we don't care about ebx. It will be
; clobbered in a few lines time.
jmp .after
.noclobber:
pop eax ; pop the original eax out
.after:
pop ebx
pop ecx
pop edx
pop gs
pop fs
pop es
pop ds
popa
add esp, 8
iret
You have to take out that 'pusha/popa' because then you can't change the value of eax. You'd also have to change the layout of your 'struct regs' so eax et al. come FIRST instead of LAST.
Is that understandable? I'll recomment it nicer otherwise
JamesM
Posted: Thu Jul 26, 2007 7:19 am
by urxae
JamesM wrote:You have to modify that handler. What I do is when _syscall returns, it places a value in ecx - either 0 or 1. 1 means the handler should return the value in eax (clobbering whatever the user had there originally). This is used in syscalls.
0 means don't return anything, be invisible. This is used in irq handlers etc.
so, you'd do something like:
Code: Select all
u32int _syscall(struct regs *r)
{
// do syscall
if (shouldreturn) asm volatile("mov $1, %ecx"); else asm volatile("mov $0, %ecx");
return valtoreturn;
}
syscalls_stub:
[snip]
call _syscall
cmp ecx, $0x1 ; should we clobber?
jne .noclobber ; no?
pop ebx ; pop the value that WAS eax, into ebx.
; we don't care about ebx. It will be
; clobbered in a few lines time.
jmp .after
.noclobber:
pop eax ; pop the original eax out
.after:
pop ebx
[snip]
iret
You have to take out that 'pusha/popa' because then you can't change the value of eax. You'd also have to change the layout of your 'struct regs' so eax et al. come FIRST instead of LAST.
Or you could just do this:
Code: Select all
void _syscall(struct regs *r)
{
// do syscall
r->eax = valtoreturn;
}
which changes the register value saved on the stack and then lets the normal exit code pop the modified version. This requires no special handling or checking in your syscalls_stub, it can just restore the registers from the stack. No need to change your struct layout either...
Posted: Thu Jul 26, 2007 7:22 am
by JamesM
urxae:
Ha! ah yes, you could! Lol, what a retarded way I was doing it - I'm going to have to change my codebase tonight!
D'OH!
JamesM
Posted: Thu Jul 26, 2007 9:53 am
by enrico_granata
thanks a lot
I'm gonna try right now