Hello all,
Sometime ago while porting SAMBA to QNX4 I came across a bug which
eventually led to the requirement that SAMBA could only be built using
stack calling convention. Unfortunately this bug is still there in the
latest release of SMABA (2.2.0). So I decided to raise the issue again,
this time in the comp.protocols.smb newsgroup. The latest poster
suggests that this is a bug in Watcom, but I’m not quite convinced. I
would like to here more opinions on this issue. I’ve included the
thread below. Any input to help me form an opinion is greatly
appreciated. (However, I’m not looking for a workaround. I’m trying to
determine if this is a bug in the code or a bug in the compiler.)
If this is indeed a bug in Watcom, then my vendor to whom I should
complain would be QSSL, right?
Anyway, here’s the thread…
regards,
rick
=======================================================================
Message-ID: <3AE18E39.5FFF5EA5@SPAM.REDIRECTED.TO.DEV.NULL>
Date: Sat, 21 Apr 2001 15:42:17 +0200
From: Rick Lake <rwlake@SPAM.REDIRECTED.TO.DEV.NULL>
Organization: A.N.P., Dutch National Press Agency
Newsgroups: comp.protocols.smb
Subject: Building samba with register calling convention
Hello all,
For years now I’ve been bumping into a particular problem with samba.
I’ve reported it before, but I guess it was too complex to remedy at the
time. I was hoping that by 2.2.0 this would be addressed, but
unfortunately the problem is still there.
OK so what’s the problem then? Consider the following peace of code that
represents code used in samba:
-----------------------------8<---------------------------
#include <stdio.h>
typedef void (*functype)(char *, …);
void func(char *text, int n)
{
printf("%s %d\n", text, n);
}
main()
{
functype f = (functype)func;
(*f)(“testing…”, 123);
}
-----------------------------8<----------------------------
Compiling this with a compiler that uses stack calling convention and
then running it gives the expected results, since all functions are
expected to use the stack to pass arguments.
However, using a compiler that passes arguments in registers gives
unexpected results: the arguments are passed incorrectly. The problem is
that when a function is prototyped with unspecified arguments (i.e.
“functype” which uses ellipsis ("…")), all arguments are passed via
the stack. On the other hand, functions that are fully prototyped (i.e.
“func”) use registers to pass arguments. So when the pointer to function
“f” is declared with “functype” and initialized to point to “func”, the
compiler is forced to generate code to pass arguments to “func” via the
stack, but func is expecting them in registers. Hence the unexpected
results.
I don’t consider my self a C language guru but I believe this means that
the code has compiler dependencies in it. I don’t know how to remedy
this, other than clobbering up lots of code. Here are some options:
-
prototype only WITHOUT unspecified args (i.e. without “…”) where
this conflict exists. -
(conversely) prototype only WITH unspecified args (i.e. with “…”)
where this conflict exists. -
explicitly declare functions with the “__cdecl” modifier where this
conflict exists, thereby forcing them to use only stack args. -
compile everything using a special compiler switch to use only stack
calling convention. (assuming the compiler has such an option.)
I’ve been using the last option for quite some versions now.
Unfortunately this has some practical problems, which I won’t go into
right now. In any event, it’s a shame to exclude compilers using
register calling convention, since these clearly have some performs
advantages. Furthermore, I find the samba code to be very portable in
general. It’s a shame to let this problem get in the way of this
portability.
If you’re looking for examples in the samba code where the problem
exists, scan for modules using “response records”, which are structs
which have (among others) a field called “fail_fn” which are declared as
pointers to functions with prototypes involving ellipsis ("…")
arguments.
Perhaps people who know the samba code well enough have suggestions for
the best way on how to remedy this without braking other things?
TIA,
regards,
rick
=======================================================
From: Nicholas Brealey <nospam@orpwood.demon.co.uk>
Newsgroups: comp.protocols.smb
Subject: Re: Building samba with register calling convention
Date: Sun, 22 Apr 2001 09:02:39 +0100
Message-ID: <3AE2901F.C5847997@orpwood.demon.co.uk>
References: <3AE18E39.5FFF5EA5@SPAM.REDIRECTED.TO.DEV.NULL>
Rick Lake wrote:
-----------------------------8<---------------------------
#include <stdio.htypedef void (*functype)(char *, …);
void func(char *text, int n)
{
printf("%s %d\n", text, n);
}main()
{
functype f = (functype)func; /* THIS LINE IS REALLY BAD */(*f)(“testing…”, 123);
}
-----------------------------8<----------------------------
I think this is really bad code.
The problem is the cast in the line where f is initialised.
The line should be:
functype f = func;
With this change the compiler should give a warning or error message.
gcc gives the warning message:
bug.c: In function `main’:
bug.c:13: warning: initialization from incompatible pointer type
Sun’s cc gives the more informative warning message:
“bug.c”, line 13: warning: assignment type mismatch:
pointer to function(pointer to char, …) returning void “=”
pointer to function(pointer to char, int) returning void
If you use Microsoft VC++ 6.0 with stdcall or fastcall it will give the
error message:
bug.c(13) : error C2152: ‘initializing’ : pointers to functions with
different attributes
If Samba has the the equivalent of the cast in line 13 it is really bad.
(It is like sweeping dust under the carpet!).
prototype only WITHOUT unspecified args (i.e. without “…”) where
this conflict exists.(conversely) prototype only WITH unspecified args (i.e. with “…”)
where this conflict exists.
I.e.
typedef void (*functype)(char *, int);
void func(char *text, int n)
{
…
Or
#include <stdarg.h>
typedef void (*functype)(char *, …);
void func(char *text, …)
{
va_list ap;
va_start(ap,text);
printf("%s %d\n", text, va_arg(ap,int));
va_end(ap);
}
Both alternatives are good and will compile with cdecl, stdcall
and fastcall, although you get warnings from VC++ 6.0 if you use
variable argument lists with fastcall (I don’t know if this means that
the program could fail in horrible ways).
- explicitly declare functions with the “__cdecl” modifier where this
conflict exists, thereby forcing them to use only stack args.
Explicitly using __cdecl is very ugly. Avoid doing this if at all
possible.
Littering the code with macros which expand to __cdecl on your
particular
platform will probably not make you friends.
- compile everything using a special compiler switch to use only stack
calling convention. (assuming the compiler has such an option.)
This looks like the best short term option if one of the first two
options
can’t be done.
I would be intersted to know what platform you are working on. Win32 is
the
only platform I know which has __cdecl and I don’t know why anyone would
want Samba on Win32.
Regards
Nick
============================================================
Message-ID: <3AE2D44A.61A9B3E@SPAM.REDIRECTED.TO.DEV.NULL>
Date: Sun, 22 Apr 2001 14:53:30 +0200
From: Rick Lake <rwlake@SPAM.REDIRECTED.TO.DEV.NULL>
Organization: A.N.P., Dutch National Press Agency
Newsgroups: comp.protocols.smb
Subject: Re: Building samba with register calling convention
References: <3AE18E39.5FFF5EA5@SPAM.REDIRECTED.TO.DEV.NULL>
<3AE2901F.C5847997@orpwood.demon.co.uk>
Nicholas Brealey wrote:
Rick Lake wrote:
-----------------------------8<---------------------------
#include <stdio.htypedef void (*functype)(char *, …);
void func(char *text, int n)
{
printf("%s %d\n", text, n);
}main()
{
functype f = (functype)func; /* THIS LINE IS REALLY BAD */(*f)(“testing…”, 123);
}
-----------------------------8<----------------------------I think this is really bad code.
The problem is the cast in the line where f is initialised.
The line should be:functype f = func;
With this change the compiler should give a warning or error message.
gcc gives the warning message:
bug.c: In function `main’:
bug.c:13: warning: initialization from incompatible pointer typeSun’s cc gives the more informative warning message:
“bug.c”, line 13: warning: assignment type mismatch:
pointer to function(pointer to char, …) returning void “=”
pointer to function(pointer to char, int) returning voidIf you use Microsoft VC++ 6.0 with stdcall or fastcall it will give the
error message:bug.c(13) : error C2152: ‘initializing’ : pointers to functions with
different attributes
yes without the cast my compiler also flags this as an error (“E1010:
Type mismatch”), but I included it here because the cast is also there
in the samba code.
If Samba has the the equivalent of the cast in line 13 it is really bad.
(It is like sweeping dust under the carpet!).
well here’s an actual example from the samba code:
assuming you’re in ${srcdir}
-
in include/nameserv.h (line 293-295)
/* typedef to define the function called when the request that caused
this
response record to be created is unsuccessful. */
typedef void (*fail_function)(struct subnet_record *, struct
response_record *, …); -
a little further down in include/nameserv.h (line 306)
typedef void (*register_name_fail_function)( struct subnet_record *,
struct response_record *,
struct nmb_name *); -
then in nmbd/nmbd_packets.c the following function is defined (line
465):
/****************************************************************************
Queue a register name packet to the broadcast address of a subnet.
****************************************************************************/
struct response_record *queue_register_name( struct subnet_record
*subrec,
response_function resp_fn,
timeout_response_function timeout_fn,
register_name_success_function success_fn,
register_name_fail_function fail_fn,
struct userdata_struct *userdata,
struct nmb_name *nmbname,
uint16 nb_flags)
{
struct packet_struct *p;
struct response_record rrec;
…
…
if((rrec = make_response_record(subrec, / subnet record. /
p, / packet we sent. /
resp_fn, / function to call on response. /
timeout_fn, / function to call on timeout. /
(success_function)success_fn, / function to call on
operation success. /
=> (fail_function)fail_fn, / function to call on
operation fail. */
userdata)) == NULL)
{
…
…
}
The above line marked with “=>” is where the typecast from type
“register_name_fail_function” to type “fial_function” takes place. The
latter has variable args and the former doesn’t. There are more of those
constructs throughout the code. I think this code managed to survive
lots of versions undetected, because the majority of systems uses gcc
which uses stack calling convention.
prototype only WITHOUT unspecified args (i.e. without “…”) where
this conflict exists.(conversely) prototype only WITH unspecified args (i.e. with “…”)
where this conflict exists.
I.e.typedef void (*functype)(char *, int);
void func(char *text, int n)
{
…Or
#include <stdarg.h
typedef void (*functype)(char *, …);void func(char *text, …)
{
va_list ap;
va_start(ap,text);printf("%s %d\n", text, va_arg(ap,int));
va_end(ap);
}Both alternatives are good and will compile with cdecl, stdcall
and fastcall, although you get warnings from VC++ 6.0 if you use
variable argument lists with fastcall (I don’t know if this means that
the program could fail in horrible ways).
- explicitly declare functions with the “__cdecl” modifier where this
conflict exists, thereby forcing them to use only stack args.Explicitly using __cdecl is very ugly. Avoid doing this if at all
possible.
Littering the code with macros which expand to __cdecl on your
particular
platform will probably not make you friends.
\
- compile everything using a special compiler switch to use only stack
calling convention. (assuming the compiler has such an option.)This looks like the best short term option if one of the first two
options
can’t be done.I would be intersted to know what platform you are working on. Win32 is
the
only platform I know which has __cdecl and I don’t know why anyone would
want Samba on Win32.
> )
Well I’m on QNX4. QNX is a POSIX compliant realtime OS, popular in the
embedded systems world. (See also http://www.qnx.com) It uses the Watcom
compiler. This third party compiler (which is also available for win32
platform) uses register calling convention by default.
Regards
Nick
===============================================================
From: malmberg@encompasserve.org (John E. Malmberg)
Newsgroups: comp.protocols.smb
Subject: Re: Building samba with register calling convention
Message-ID: <YcDGkZlSLxCZ@eisner.encompasserve.org>
References: <3AE18E39.5FFF5EA5@SPAM.REDIRECTED.TO.DEV.NULL>
Organization: Encompasserve
Date: 23 Apr 2001 12:39:09 -0500
In article <3AE18E39.5FFF5EA5@SPAM.REDIRECTED.TO.DEV.NULL>,
Rick Lake <rwlake@SPAM.REDIRECTED.TO.DEV.NULL> writes:
Hello all,
For years now I’ve been bumping into a particular problem with samba.
I’ve reported it before, but I guess it was too complex to remedy at the
time. I was hoping that by 2.2.0 this would be addressed, but
unfortunately the problem is still there.
From the description below, it is not a SAMBA problem.
OK so what’s the problem then? Consider the following peace of code that
represents code used in samba:-----------------------------8<---------------------------
#include <stdio.htypedef void (*functype)(char *, …);
void func(char *text, int n)
{
printf("%s %d\n", text, n);
}main()
{
functype f = (functype)func;(*f)(“testing…”, 123);
}
-----------------------------8<----------------------------Compiling this with a compiler that uses stack calling convention and
then running it gives the expected results, since all functions are
expected to use the stack to pass arguments.However, using a compiler that passes arguments in registers gives
unexpected results: the arguments are passed incorrectly. The problem is
that when a function is prototyped with unspecified arguments (i.e.
“functype” which uses ellipsis ("…")), all arguments are passed via
the stack. On the other hand, functions that are fully prototyped (i.e.
“func”) use registers to pass arguments. So when the pointer to function
“f” is declared with “functype” and initialized to point to “func”, the
compiler is forced to generate code to pass arguments to “func” via the
stack, but func is expecting them in registers. Hence the unexpected
results.I don’t consider my self a C language guru but I believe this means that
the code has compiler dependencies in it. I don’t know how to remedy
this, other than clobbering up lots of code. Here are some options:
There is nothing in the code fragment that indicates a dependancy on
how the compiler generate arguments to function calls.
If your compiler is not generating the corect code, I would recommend
talking to the compiler vendor.
The Compaq ALPHA chip architecture passes arguments by register if it
can for speed, and the Compaq C compilers do not have any problems
dealing with the constructs in your example.
prototype only WITHOUT unspecified args (i.e. without “…”) where
this conflict exists.(conversely) prototype only WITH unspecified args (i.e. with “…”)
where this conflict exists.
The prototypes are used for the error checking phase of the compilation,
and also potentially for optimization.
The presense or lack of a prototype should not change the parameter
passing mechanism.
What you have found is a reproducer for a bug in your compiler, or your
compiler by design can not handle legal C code.
explicitly declare functions with the “__cdecl” modifier where this
conflict exists, thereby forcing them to use only stack args.compile everything using a special compiler switch to use only stack
calling convention. (assuming the compiler has such an option.)I’ve been using the last option for quite some versions now.
Unfortunately this has some practical problems, which I won’t go into
right now. In any event, it’s a shame to exclude compilers using
register calling convention, since these clearly have some performs
advantages. Furthermore, I find the samba code to be very portable in
general. It’s a shame to let this problem get in the way of this
portability.
There was a documented calling convention for C in the past using the
stack, it was one of the few languages with one. With the advent of
RISC platforms, that calling convention was modified. I am not sure
what it currently says.
There are still rules, and they do not depend on the presence or
absence of prototypes. See the documentation for your platform or your
compiler.
If you’re looking for examples in the samba code where the problem
exists, scan for modules using “response records”, which are structs
which have (among others) a field called “fail_fn” which are declared as
pointers to functions with prototypes involving ellipsis ("…")
arguments.Perhaps people who know the samba code well enough have suggestions for
the best way on how to remedy this without braking other things?
Get your compiler vendor to fix their compiler, or move to a compiler
that is not broken.
-John
wb8tyw@qsl.network
Personal Opinion Only.