Just a quick question. Are the variable arguement functions, found usually
in stdarg.h, OS specific? The reason I am asking is that I have code that
works perfectly under DOS, but under my kernel, it doesnt work
(data/arguements go missing). I am using Watcom C. I have written my own
very basic printf() type function that uses a variable arg list. My kernel
wont build unless I include stdarg.h from the compiler (naturally). Would
there be anything unsafe in these functions/macro's?
Anyone else run into similar issues?
Alex
--
Maxim Shatskih, Windows DDK MVP
StorageCraft Corporation
ma...@storagecraft.com
http://www.storagecraft.com
"alex" <n...@spam.pls.com> wrote in message
news:41f3b6dd$0$11097$afc3...@news.optusnet.com.au...
I've noticed the problem even with this simple scenario:
eg:
char finalstring[200];
void write_string(char *actualstring)
{
/*Code here to write the string to the screen*/
}
void printf(char *stringdata, ...)
{
va_list args;
va_start(args,stringdata);
//Code in here does basically the following.
//process each char, look for a valid %x %d %s %lu combination,
//use value=va_arg(args, unsigned long), if %lu were found, pass
//"value" on to ultoa() function etc, appending the result to "finalstring".
//The size of the arguement pulled from the list depends on the "%" type
//discovered, then the appropriate routine to covert the number to ascii
//is called.
//if the char is not a "%", simply copy the char to the "finalstring"
va_end(args);
write_string(finalstring);
}
The basic approach of this seems ok. Works flawlessly under DOS. But when
run under my kernel, when the function looks at "stringdata" it appears to
be empty. What I did as a test was to comment out all the code in the
printf() function so the end result looked as follows
void printf(char *stringdata, ...)
{
va_list args;
va_start(args,stringdata);
/* All code in here commented out */
va_end(args);
write_string(stringdata);
}
An empty string is displayed.. When I Changed the function to:
void printf(char *stringdata)
{
write_string(stringdata);
}
The characters in "stringdata" were displayed fine. This is why I am
under the impression that variable arguements are broken here for some
odd reason..
Can anyone think of a reason why?
Hope this helps.
Sincerely,
Francesco Frigo
--
To contact me remove the "NOSPAM" part from my e-mail address.
The compiler generates 32 Bit code designed for Pmode. I am using
Watcom C (wcc386.exe)
Under Watcom, the size of "int" is 4 bytes. This caught me out in the early
stages. To get the "int" that appears to be 16 bit under borlandc, I have to
use "short" or unsigned short.
Alex.
Get my FAT code. It comes with small standard C library, with all source
code. It compiles with Borland/Turbo C/C++ and Open Watcom C/C++.
There's inded one problem with Watcom C/C++, which I encountered when trying
to make printf()-kind of functions with <stdarg.h>:
Open Watcom C/C++ 1.2 va_list definition (as per <stdarg.h>):
typedef char *__va_list[1];
typedef __va_list va_list;
Notice: va_list is an _array_ of 1 pointer to char
Borland/Turbo C/C++ and GCC va_list definition (as per <stdarg.h>):
typedef void *va_list;
Notice: va_list is just a pointer to void
Now, if we define:
va_list ap;
What would be the type and value of &ap (e.g. we want to pass a pointer
to ap)?
Right, it will depend on the definition of the va_list type.
And it won't work for Watcom, so here's a workaround...
#ifdef __WATCOMC__
#define valistptr(ap) ((va_list*)&ap[0])
#else
#define valistptr(ap) (&ap)
#endif
...
Then I use valistptr(ap) in the code, where ap is obviously of type va_list.
Enjoy!
Alex
http://alexfru.narod.ru
I'll have a look at it :-) Thanks.
PS: Any ideas why the code works fine under DOS pmode (with pmode/w or
dos4g),
but not during the operation of my kernel, which is running in 32 bit PM?
Could it be because I am
not linking against the standard libraries and the compiler is making some
silly assumptions?
Wrong GDT may be the cause (in DOS, DPMI sets it for you, but in your
kernel, you are *the* resposible). Same holds for the segment registers and
stack -- you must set them up correctly.
If it was linking problem, you'd have a link-time error issued by wlink.
Alex
If it were to be a GDT issue, what sort of GDT problem do you think might
cause this? I have a 32 Bit code descriptor
with base 0, limit 4g and a descriptor for data, base 0, limit 4G. All with
PL0. I have the kernel
linked at 1M (paging is not enabled), my second stage loader loads all the
headers to their respective addresses (ELF image).
Then transfers control to flat_cs_sel:elf_code_entrypoint, once my crt0 code
starts executing ds,es,fs,gs,ss are all loaded with the
selector for my flat data descriptor, esp is set @ 2M, BSS is zeroe'd, then
control is given to kmain()
All my C ISR's seem to work correctly, and the code has no trouble accessing
data stored as global variables or locally within a function.
I know this to be true as I have a global variable: char *vidmemptr= (char
*) 0xb8000;
Any writes to vidmemptr affect output on the screen.
I am compiling using the -5r -s -mf flags.
So 586 register calling conventions, flat memory model and the switch to not
include stack overflow checking (eliminates the
__CHK undefined linking error)
Any ideas on what I may have missed? Did you have issues with the va_
functions under DOS when testing?
Alex
I had an idea of uninitialized stack... You know, the objects on stack are
accessed either through [ebp+offset] (ss used) or through [some other
reg+offset] (ds used). If ds and ss point to different segments then taking
an address of a local (on-stack) variable (calculated using ebp) and passing
it to another function in a reg (other than ebp), could cause a problem
because the compiler normally assumes ss=ds.
> All my C ISR's seem to work correctly, and the code has no trouble
accessing
> data stored as global variables or locally within a function.
> I know this to be true as I have a global variable: char *vidmemptr= (char
> *) 0xb8000;
> Any writes to vidmemptr affect output on the screen.
>
> I am compiling using the -5r -s -mf flags.
> So 586 register calling conventions, flat memory model and the switch to
not
> include stack overflow checking (eliminates the
> __CHK undefined linking error)
>
> Any ideas on what I may have missed? Did you have issues with the va_
> functions under DOS when testing?
Other than that I've pointed out about the odd type of va_list, no.
Btw, I also use -zl and -zls.
Why don't you disassembly your printf() (say it simply passes
va_arg(ap,char*) to write_string()) and study the asm code of printf()? You
could post it here.
Alex
Might be the way to go. Create a minimal function that uses variable arg and
pull it apart. Is there a working front end debugger for Bochs 2.1.1 yet?
Boch's built in debugger is a bit of a pain to use. Usable, but a pain.
regards,
Alex
Perhaps you could make a normal 32-bit DPMI app and see the disasm in WD.
Alex
I could try this.. But i'd be debugging code that works under dos..
but..........
I could make a comparison. I actually wrote a little util that grabs the
code
section from an elf image, dumps it to a binary file. I can then use
ndisasm -u -o 0x100100
and see exactly what would be running on the real machine, and compare it to
what
the code of the DPMI is doing.
For some reason when I tell wlink to link @ 1M, it seems the code entry is
at 0x100100 rather
than 0x100000 for some reason..
C:\KERNBLD>elfinfo.exe kernel32.sys
ELF executable information utility v1.0, by alex r
--------------------------------------------------
Executable platform: i386
Entry Point Address: 0x00100100
Number Of Headers : 3
Section type: Entry Address: Size (bytes):
------------- -------------- -------------
PT_PHDR 0x00100034 00000096
PT_LOAD 0x00100100 00002040
PT_LOAD 0x00101000 00000277
There should be no difference in code generation. Well, it's up to you how
to proceed.
Alex
Btw,
you might ask at news : openwatcom.<contributors|users.c_cpp>. No pass
needed to read and post if you use the news.openwatcom.org server.
Alex
I actually set a breakpoint at where kmain() starts and did a CPU
dump. I actually set the stack at 3M, esp & ebp both get set at 3M
although the code generated by wcc386 never uses the ebp register,
maybe because I am using -5r when compiling. As far as you can
see below, does the machine state seem ok to you? It looks ok to me.
(0) Breakpoint 1, 0x100135 in ?? ()
Next at t=4437205
(0) [0x00100135] 0020:00100135 (unk. ctxt): push edx
<bochs:3> dump_cpu
eax:0x0
ebx:0x37
ecx:0x0
edx:0x0
ebp:0x300000
esi:0x6022ad
edi:0x104480
esp:0x2ffffc
eflags:0x42
eip:0x100135
cs:s=0x20, dl=0xffff, dh=0xdf9a00, valid=1
ss:s=0x18, dl=0xffff, dh=0xdf9300, valid=7
ds:s=0x18, dl=0xffff, dh=0xdf9300, valid=1
es:s=0x18, dl=0xffff, dh=0xdf9300, valid=5
fs:s=0x18, dl=0xffff, dh=0xdf9300, valid=1
gs:s=0x18, dl=0xffff, dh=0xdf9300, valid=1
ldtr:s=0x0, dl=0x0, dh=0x0, valid=0
tr:s=0x0, dl=0x0, dh=0x0, valid=0
gdtr:base=0x60208, limit=0x800
idtr:base=0x200000, limit=0x800
dr0:0x0
dr1:0x0
dr2:0x0
dr3:0x0
dr6:0xffff0ff0
dr7:0x400
tr3:0x0
tr4:0x0
tr5:0x0
tr6:0x0
tr7:0x0
cr0:0x60000011
cr1:0x0
cr2:0x0
cr3:0x0
cr4:0x0
inhibit_mask:0
done
<bochs:4>
I am about to go through the kernel at asm level and see whats going on.
Wish me luck!
This might be nothing, but I am looking at ALL possibilities here..
I did notice on a couple of machines, my a20 enable code seems to fail.
My code does check for this, if it was unsucessful, it is set to display a
message
and halt the machine dead in its tracks. What would be the effect if my code
thought the a20 had been sucessfully enabled (when it actually wasnt),
and proceeded to execute code from 1M and beyond? Would it crash
straight away?
Alex
"Alexei A. Frounze" <ale...@chat.ru> wrote in message
news:35qhs7F...@individual.net...
I only put -3, no r no nothing.
As for ebp, it may be used when there're too many arguments and they start
getting passed on stack.
I'm not going to check all these, I think you'll check them.
> I am about to go through the kernel at asm level and see whats going on.
> Wish me luck!
I wish.
> This might be nothing, but I am looking at ALL possibilities here..
> I did notice on a couple of machines, my a20 enable code seems to fail.
> My code does check for this, if it was unsucessful, it is set to display a
> message
> and halt the machine dead in its tracks. What would be the effect if my
code
> thought the a20 had been sucessfully enabled (when it actually wasnt),
> and proceeded to execute code from 1M and beyond? Would it crash
> straight away?
It will simply use all even megabytes of physical memory in place of all odd
megabytes, effectively losing access to half the memory. Just that. But if
you use 0th, 2nd, 4th, 6th, 8th, etc megs only, you may never touch a20 :)
Anyway, it really is easy to check wether a20 is enabled or disabled.
This is code for real mode (Borland/Turbo C/C++ and Open Watcom C/C++):
#include <dos.h>
typedef unsigned char uchar;
int A20_IsEnabled()
{
volatile uchar far *at0 = (uchar far *) MK_FP(0,0);
volatile uchar far *at1M = (uchar far *) MK_FP(0xFFFF,0x10);
uchar xat0, xat1M, IF, res;
// disable ints here if enabled:
IF = GetIF();
SetIF (0);
xat0 = (*at0);
xat1M = (*at1M);
(*at1M) = ~xat0;
if ((*at0) == xat0)
{
(*at1M) = xat1M;
res = 1;
}
else
{
(*at0) = xat0;
res = 0;
}
// restore ints here if we disabled them:
SetIF (IF);
return res;
}
And I use both methods to enable a20: old keyboard port method and new ps/2
port method. So far, had no problems.
Alex
I had a look at the disassembly.. It seems you're right.. It is indeed
a stack
related issue... I have pasted the relevant pieces of code (sorry about
the length).
Perhaps you may be able to spot/suggest something that I am
overlooking.
Here's what I have got:
;kmain() disassembly
00100135 52 push edx
00100136 E8C7010000 call 0x100302 ;Call put_cursor() - works
0010013B BAD4101000 mov edx,0x1010d4
00100140 B8E0201000 mov eax,0x1020e0
00100145 E898020000 call 0x1003e2 ;call strcpy();
0010014A BA07000000 mov edx,0x7
0010014F B8E0201000 mov eax,0x1020e0
00100154 E857020000 call 0x1003b0 ;call write_string()
00100159 8B150C101000 mov edx,[0x10100c]
0010015F B8E4101000 mov eax,0x1010e4
00100164 E85E030000 call 0x1004c7 ;call myprintf() - bad
00100169 8B1508101000 mov edx,[0x101008]
0010016F 85D2 test edx,edx
00100171 74FC jz 0x10016f ;sit in an infinate loop.
00100173 5A pop edx
00100174 C3 ret
;disasm of myprintf()
001004C7 52 push edx
001004C8 8D44240C lea eax,[esp+0xc]
001004CC 8B00 mov eax,[eax]
001004CE A34B101000 mov [0x10104b],eax ;dword=va_arg()
001004D3 BA07000000 mov edx,0x7
001004D8 8B442408 mov eax,[esp+0x8]
001004DC E8CFFEFFFF call 0x1003b0 ;call write_string
001004E1 5A pop edx
001004E2 C3 ret
;write_string() - seems to have no issues
001003B0 53 push ebx
001003B1 51 push ecx
001003B2 89C1 mov ecx,eax
001003B4 88D3 mov bl,dl
001003B6 C705181010000000 mov dword [0x101018],0x0
-0000
001003C0 A118101000 mov eax,[0x101018]
001003C5 01C8 add eax,ecx
001003C7 803800 cmp byte [eax],0x0
001003CA 7413 jz 0x1003df
001003CC 0FB610 movzx edx,byte [eax]
001003CF 0FB6C3 movzx eax,bl
001003D2 E8F0FDFFFF call 0x1001c7 ;call pm_putchar
001003D7 FF0518101000 inc dword [0x101018]
001003DD EBE1 jmp short 0x1003c0
001003DF 59 pop ecx
001003E0 5B pop ebx
001003E1 C3 ret
Now the C code itself.
char mystring[200];
dword someothervalue=0;
dword conversion_number;
void kmain()
{
//pm_irq_subsystem_init();
//keyboard_init();
setup_cursor();
strcpy(mystring,"This is a test\n");
write_vga_string(mystring,0x07); //works
myprintf("You silly man!\n",testval);
while(someothervalue==0)
{
someothervalue=0;
}
}
void myprintf(const char *userstring, ...)
{
va_list args;
va_start(args,userstring);
/*
<snipped commented out code>
*/
conversion_number=va_arg(args,unsigned long);
va_end(args);
write_vga_string(userstring,0x07); //set as userstring so I could see
} //where things are going wrong.
void write_vga_string(char *userstring, byte stringcolour)
{
stringpointer=0;
while(userstring[stringpointer])
{
write_vga_char(stringcolour,userstring[stringpointer]);
++stringpointer;
}
}
What I found is a problem in the asm code of myprintf() -
;disasm of myprintf()
001004C7 52 push edx
001004C8 8D44240C lea eax,[esp+0xc]
001004CC 8B00 mov eax,[eax]
001004CE A34B101000 mov [0x10104b],eax ;dword=va_arg()
001004D3 BA07000000 mov edx,0x7
001004D8 8B442408 mov eax,[esp+0x8] ;this is broken!!
001004DC E8CFFEFFFF call 0x1003b0 ;call write_string
001004E1 5A pop edx
001004E2 C3 ret
The value placed in EAX before the call to write_string() is wrong.
EAX is loaded with 0. The value of EAX when entering the function
is never pushed on the stack, and is destroyed by "lea eax,[esp+0xc]".
Other functions that dont go near VA functions dont have any issues
with values passed to functions.. So I am baffled why the compiler
is choosing to generate code like this..
The mov eax,[esp+0x8] should place the value 0x1010e4 into EAX. If I
actually
set a breakpoint at 0x1004E2 and manually change the contents of the
EAX
register to that value, then resume execution, the myprintf() function
worked as expected.
So I am stumped, I can see "where" the problem is.. But "why" ? :-(
Alex
This *is* perfectly correct.
> The value placed in EAX before the call to write_string() is wrong.
> EAX is loaded with 0. The value of EAX when entering the function
> is never pushed on the stack, and is destroyed by "lea eax,[esp+0xc]".
>
> Other functions that dont go near VA functions dont have any issues
> with values passed to functions.. So I am baffled why the compiler
> is choosing to generate code like this..
>
> The mov eax,[esp+0x8] should place the value 0x1010e4 into EAX. If I
> actually
> set a breakpoint at 0x1004E2 and manually change the contents of the
> EAX
> register to that value, then resume execution, the myprintf() function
> worked as expected.
>
> So I am stumped, I can see "where" the problem is.. But "why" ? :-(
Why? :)
It's easy, you called myprintf() w/o a prototype. See...
I made this file out of what you posted and compiled it under windows as
wcl386 -5r -zld -zls -s -fm=z.map z.c:
-------8<-------
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
typedef unsigned long dword;
typedef unsigned char byte;
char mystring[200];
dword someothervalue=0;
dword conversion_number;
dword stringpointer;
dword testval = 32767;
void write_vga_char (byte colour, int c)
{
(void) colour;
printf ("%c", c);
}
void write_vga_string (const char *userstring, byte stringcolour)
{
stringpointer=0;
while(userstring[stringpointer])
{
write_vga_char (stringcolour, userstring[stringpointer]);
++stringpointer;
}
}
void setup_cursor()
{
}
void myprintf(const char *userstring, ...);
void main()
{
//pm_irq_subsystem_init();
//keyboard_init();
setup_cursor();
strcpy(mystring,"This is a test\n");
write_vga_string(mystring,0x07); //works
myprintf ("You silly man!\n", testval);
while (someothervalue==0)
{
someothervalue=1;//0;
}
}
#if 0
void myprintf(const char *userstring, ...)
{
va_list args;
va_start(args,userstring);
/*
<snipped commented out code>
*/
conversion_number=va_arg(args,unsigned long);
va_end(args);
write_vga_string(userstring,0x07); //set as userstring so I could see
} //where things are going wrong.
#endif
-------8<-------
If the prototype "void myprintf(const char *userstring, ...);" is defined, I
get in disassembly of main() this (z.obj viewed with HIEW):
000001B6: 52 push edx
000001B7: E800000000 call 000001BC
000001BC: BA03000000 mov edx,000000003
000001C1: B800000000 mov eax,000000000
000001C6: E800000000 call 000001CB
000001CB: BA07000000 mov edx,000000007
000001D0: B800000000 mov eax,000000000
000001D5: E800000000 call 000001DA
000001DA: 8B1500000000 mov edx,[000000000]
000001E0: 52 push edx
000001E1: 6813000000 push 000000013
000001E6: E800000000 call 000001EB ; myprintf()
000001EB: 83C408 add esp,008
000001EE: 833D0000000000 cmp d,[000000000],000
000001F5: 750A jne 00000201
000001F7: C7050000000001000000 mov d,[000000000],000000001
00000201: 5A pop edx
00000202: C3 retn
As you can see the params to myprintf() are passed on stack (2 dwords pushed
before the call).
Now, if I comment out the prototype, I'll get exactly the same problem as
yours:
000001B6: 52 push edx
000001B7: E800000000 call 000001BC
000001BC: BA03000000 mov edx,000000003
000001C1: B800000000 mov eax,000000000
000001C6: E800000000 call 000001CB
000001CB: BA07000000 mov edx,000000007
000001D0: B800000000 mov eax,000000000
000001D5: E800000000 call 000001DA
000001DA: 8B1500000000 mov edx,[000000000]
000001E0: B813000000 mov eax,000000013 ; myprintf()
000001E5: E800000000 call 000001EA
000001EA: 833D0000000000 cmp d,[000000000],000
000001F1: 750A jne 000001FD
000001F3: C7050000000001000000 mov d,[000000000],000000001
000001FD: 5A pop edx
000001FE: C3 retn
But here everything's passed in the registers, as if you had a function with
fixed number of arguments, or effectively a wrong prototype (or no at all).
Learn C! :)
Alex
/me Braindamaged from too much asm :/
Thanks for taking the time to help.
Appreciate it a lot!
Regards
Alex
:)
> Thanks for taking the time to help.
>
> Appreciate it a lot!
>
> Regards
> Alex
Keep a good C reference handy, if you're not yet OK with it.
Btw, I highly recommend that everyone turns all warnings on.
This one could be caught at compile time. For Watcom use these
switches: -w=3 -we. They indeed catch this proto prob. And I'm sure you'll
get a dozen of other warnings/errors elswhere if you use these switches :)
Alex