Strange syntax

S

seenutn

Hi All,
I found a piece of code in linux/kernel/signal.c:

int
send_sig(int sig, struct task_struct *p, int priv)
{
return send_sig_info(sig, (void*)(long)(priv != 0), p);
}


What is the meaning of "(void*)(long)(priv != 0)" ?

TIA

Regards,
Seenu.

Note: Even though this code is not existing in the current kernel, it
was avlbl in 2.4.xx.
 
R

Richard Heathfield

(e-mail address removed) said:
Hi All,
I found a piece of code in linux/kernel/signal.c:

int
send_sig(int sig, struct task_struct *p, int priv)
{
return send_sig_info(sig, (void*)(long)(priv != 0), p);
}


What is the meaning of "(void*)(long)(priv != 0)" ?

It means "warning! dodgy code! fix me!"

(priv != 0) yields a result that is either 0 or 1.
Casting this to (long) yields a value that is either 0L or 1L.
Casting to to (void *) yields a value that is very unlikely to be a valid
pointer to any object or function.

Stupid stupid stupid.
Note: Even though this code is not existing in the current kernel, it
was avlbl in 2.4.xx.

So what you're saying, then, is that someone came to their senses and
ripped it out. Good!
 
G

Gene

Hi All,
I found a piece of code in linux/kernel/signal.c:

int
send_sig(int sig, struct task_struct *p, int priv)
{
return send_sig_info(sig, (void*)(long)(priv != 0), p);

}

What is the meaning of "(void*)(long)(priv != 0)" ?

TIA

The second parameter has the type struct siginfo*. A NULL (0) pointer
here means a user kill sig. The special reserved pointer (struct
siginfo*)1 is for kernel sigs. (priv != 0) has value 0 if priv is 0
and 1 otherwise. The casts coerce this int value to a compatible
pointer. The effect is that privilege level 0 causes a user sig and
all others a kernel sig. It's not clear to me why (void*)(long) is
used instead of (struct siginfo*), but the effect ought to be the
same.
 
C

Chris Dollin

Richard said:
(e-mail address removed) said:


It means "warning! dodgy code! fix me!"

(priv != 0) yields a result that is either 0 or 1.
Casting this to (long) yields a value that is either 0L or 1L.
Casting to to (void *) yields a value that is very unlikely to be a valid
pointer to any object or function.

Stupid stupid stupid.

To be fair, code found in linux/kernel/anything is unlikely to be written
in portable standard C and may be permitted to exploit implementation-
specific details. (The casting looks bizarre, but for all we know, it
could be avoiding an implementation-specific bug.)

As you know, Bob, the standard doesn't stop you from relying on
implementation details; it just doesn't define what happens if you
do. Sometimes this is a Good Thing.
 
R

Richard Heathfield

Chris Dollin said:

To be fair, code found in linux/kernel/anything is unlikely to be written
in portable standard C and may be permitted to exploit implementation-
specific details.

Understood - but disguising a flag as a pointer is Just Plain Daft. I've
seen the same trick used a few times in application code. It was daft
then, and it's daft now. Obfuscation, sheer obfuscation.
 
M

Mark Bluemel

Richard said:
Chris Dollin said:



Understood - but disguising a flag as a pointer is Just Plain Daft.

Hmmm... It's just a special value, and I'm not convinced it's
significantly different to the special value returned by getc() to
indicate end of file, or (more to the point) the SIG_* macros in
<signal.h>.
 
K

Kenny McCormack

Chris Dollin said:



Understood - but disguising a flag as a pointer is Just Plain Daft. I've
seen the same trick used a few times in application code. It was daft
then, and it's daft now. Obfuscation, sheer obfuscation.

Which means that you should be right at home with it.
 
R

Richard Tobin

Richard Heathfield said:
Understood - but disguising a flag as a pointer is Just Plain Daft. I've
seen the same trick used a few times in application code. It was daft
then, and it's daft now. Obfuscation, sheer obfuscation.

It might be wise to use a #define for it, but if we allow NULL pointers,
why not several varieties of not-a-pointer value?

-- Richard
 
R

Richard Heathfield

Richard Tobin said:
It might be wise to use a #define for it, but if we allow NULL pointers,
why not several varieties of not-a-pointer value?

Why not a method that is guaranteed by ISO to work?

For example, have a couple of file scope objects, defined in Some
Appropriate Place and declared in a header:

void * const G_flag_off = &G_flag_off;
void * const G_flag_on = &G_flag_on;

Then you can do, say, something like this:

return send_sig_info(sig, priv != 0 ? G_flag_on : G_flag_off, p);

Of course, you could wrap it up a bit prettier than that, and equally
of-coursely, the callee would need to know about the changed convention.
But this is hardly difficult, surely?
 
R

Richard Tobin

Richard Heathfield said:
Why not a method that is guaranteed by ISO to work?

For example, have a couple of file scope objects, defined in Some
Appropriate Place and declared in a header:

void * const G_flag_off = &G_flag_off;
void * const G_flag_on = &G_flag_on;

This has two minor disadvantages: it requires space for the objects,
which might be large, and the values are not constants.

And in the case cited - an operating system kernel - the code has no
need to be guaranteed-by-ISO-to-work.

There are also cases where your solution won't work because the
interface is across an operating system boundary. For example
the unix function mmap() returns (void *)-1 on error, and it
couldn't use the address of an object instead unless the kernel
and user programs shared some address space.

But in typical cases, your solution is preferable.

-- Richard
 
R

Richard

Chris Dollin said:
To be fair, code found in linux/kernel/anything is unlikely to be written
in portable standard C and may be permitted to exploit implementation-
specific details. (The casting looks bizarre, but for all we know, it
could be avoiding an implementation-specific bug.)

As you know, Bob, the standard doesn't stop you from relying on
implementation details; it just doesn't define what happens if you
do. Sometimes this is a Good Thing.

Is there anything wrong with comparing that void * with (void *)(1L) in
the calling hierarchy elsewhere?

Its almost certainly an optimization to pass back control flags or
pointers depending on usage.
 
S

santosh

Richard said:
Is there anything wrong with comparing that void * with (void *)(1L)
in the calling hierarchy elsewhere?

Its almost certainly an optimization to pass back control flags or
pointers depending on usage.

In x86 under flat memory model a general purpose register can contain a
pointer and they are compatible with ordinary integer values, which is
the reason why the Linux kernel source can do these sort of tricks.
Presumably code such as these would be rewritten for another
incompatible architecture, for a port.
 
R

Richard Heathfield

Richard Tobin said:
This has two minor disadvantages: it requires space for the objects,
which might be large, and the values are not constants.

Yes, I agree, although it is at least *unlikely* that they will be large.
Typically they'll be 16, 32 or possibly even 64 bits each, but probably
not larger than that (for now, anyway). As for the values not being
constants, well, that's also true, but the constness should at least warn
people off, if nothing else.
And in the case cited - an operating system kernel - the code has no
need to be guaranteed-by-ISO-to-work.

Again, true - but I'm very much in the "make it portable unless you have a
darn good reason not to" camp, and this case doesn't strike me as having a
darn good reason not to.
There are also cases where your solution won't work because the
interface is across an operating system boundary. For example
the unix function mmap() returns (void *)-1 on error, and it
couldn't use the address of an object instead unless the kernel
and user programs shared some address space.

Okay, and I realise that nobody is about to rewrite the entire kernel to my
spec, but whoever designed mmap needs a good kicking. It should return
NULL on error.
But in typical cases, your solution is preferable.

Aye.
 
C

Chris Torek

Richard Tobin said:
Again, true - but I'm very much in the "make it portable unless
you have a darn good reason not to" camp, and this case doesn't
strike me as having a darn good reason not to.

Indeed, and apparently this was cleaned up at some point anyway,
so the linux kernel development folks appear to agree with you
(Richard Heathfield I mean). :)
Okay, and I realise that nobody is about to rewrite the entire
kernel to my spec, but whoever designed mmap needs a good kicking.
It should return NULL on error.

Indeed -- although in this case, the "broken" interface is now
enshrined in a standard (not Standard C, but POSIX). It might help
to illustrate a bit how the situation came about, and how it could
have been avoided, though.

In Ye Aulde Daze, er, Days, "Unix" was "whatever Ken wrote for the
PDP-11". If it ran on the 11, that was good enough -- and if you
had more than 32K or so of RAM on your machine, you had a really
big machine, so conserving memory space, including code space, was
quite important.

At that time, which was also before C had <stdio.h> and "FILE"s --
maybe even before the syntax for "struct" used "{", and back when
"i += 2" was spelled "i =+ 2" -- there was one kind of "system
call", and it was not the system() function: it consisted of putting
a syscall number into one of the eight registers and trapping to
the Unix kernel, which would then pick out which "system operation"
you wanted, read your (user) space to obtain any necessary arguments,
and do whatever system-level privileged operation was requested,
or fail the request. To "fail" a request, the kernel would set
the carry flag in the PSW -- remember, the only machine that *exists*
(as far as we are concerned) is the PDP-11, and its carry flag is
in the PSW -- and return; to indicate success, the kernel would
clear the carry flag and return. The return value went into register
r0; on failure, this value was the error number to store in "errno".

So, now we take a short trip back in time, to the mid-1970s...

In C code, there is no way to test "the carry flag" directly. As
a result, the C-to-kernel interface is written in assembly. Because
code space is at a premium, and there is no need for portability
(your code only runs on the PDP-11), one short assembly stub could
handle pretty much every kernel call. It starts with a system-call
trap instruction, then a test of the carry flag. If the carry flag
is set, it stores r0 in the global variable "_errno" ("the" C
compiler -- there is only one of those, remember, as it the mid
1970s right now -- prepends an underscore) and sets r0 to -1. Then
in any case it returns to the caller.

(I glossed over a few details here. The actual syscall stubs were
four instructions long: constant into r0; sys; branch on carry;
return. The branch-on-carry went to the shared "store errno and
set r0 to -1" code. The principal still applies, though.)

Now, this is all well and good for the system calls that return an
"int", because successful return values are normally 0 or small
positive numbers. No successful int-valued system call returns
-1. The one call that does not quite fit is the "brk" ("set break")
system call, which returns a pointer -- but, luckily for us, we
are only using the PDP-11, and "-1" is the address of the very last
byte of memory, which is never a valid address for the "break".
So we need not come up with a special system-call handler for brk();
we can just use (char *)-1. (We are still in the 1970s and "void"
does not exist, much less "void *".)

Now we begin to return back to the present. Time accelerates: the
PDP-11 Unix kernel acquires new versions, being ported to other
models of PDP-11 (the various 40s and the split-I&D 11/70), then
to newer machines like the VAX. C, too, begins to be ported, to
machines like the Interdata 8/32, Honeywell mainframes, IBM
mainframes, and even "weird" 9-bit-byte, 36-bit-word machines.
Many things change, with C compilers growing new features like a
"long" datatype, multiple flavors of "unsigned" integers, and
standardized file I/O via <stdio.h> and company. But the "brk"
system call remains unchanged throughout. This is not a big deal,
as "normal user code" uses malloc() anyway, which hides the funny
return value from "normal users". (The malloc() implementations
on "non-Unixy" machines use various other system-specific mechanisms
to obtain memory, so portable code *never* calls brk(), which often
does not even exist.)

As we zip through the 1980s, UC Berkeley (and others) work with
DARPA on a grant for an "improved" Unix-like system that will
function well on the "ARPAnet". They decide that there should be
a file-to-memory-mapping interface similar to what was in Multics.
This is called "mmap", and an initial interface is hammered out.
For some reason, no one on the steering committee notices that
mmap() probably should return NULL on failure; instead, they let
the old code-space-constrained model, i.e., the one that had "brk"
return (char *)-1, persist. And when 4.2BSD is released, even
though mmap() is not actually supported, the documentation for it
claims that it returns (char *)-1 on failure.

Meanwhile, ANSI committee X3J11 works on a standard for C. They
add "void *" and change malloc()'s return type. The Unix brk()
call is not part of the proposed standard, nor is mmap(), which is
just as well since no one actually has an mmap() that works yet.
Sun Microsystems releases SunOS, and at some point, SunOS implements
mmap(), sticking with the "wrong" failure-return-value. 4.3-net-n-BSD
adopts (and adapts) the CMU Mach VM system, and also implements
mmap(), in a way that differs from SunOS, but also still has the
"wrong" failure-return-value.

As we fly through the 1990s, POSIX picks up the effort, and
standardizes mmap(), with some attempt to resolve the various
differences between implementations. Since they all return
(char *)-1 on failure, however, POSIX -- which picks up ANSI C as
a base item -- has mmap() return (void *)-1 on failure.

If, at any point along this path, someone had said: "hang on a
moment, returning -1 is silly, mmap() should return NULL on failure",
we would have had to do one extra thing: modify the mmap() system
call stub, to stop sharing its error-path with that for "int"-valued
system-call stubs. Since code space was no longer at such a great
premium -- systems with 32K of RAM were no longer the norm; indeed,
many machines came with as much as 16 entire *mega*bytes of RAM by
1990 or so -- throwing a dozen bytes at this "special" syscall
error path would not have been a big deal. (We could even have
fixed the brk() return value at the same time, although that would
have entailed changing existing code that tests the return value.
Since mmap() had not yet been implemented, fixing it then would
have been cheaper. Fixing it in POSIX would have been a bit more
expensive, but we already had the problem of resolving incompatible
mmap() implementations anyway.)

But it never happened, probably because it never rose above the
noise level. Instead, a trick for saving space on machines with
32K of RAM is now firmly embedded in the POSIX standard, used on
machines that routinely come with a minimum of over 100 thousand
times as much memory.
 
R

Richard Tobin

Richard Heathfield said:
Okay, and I realise that nobody is about to rewrite the entire kernel to my
spec, but whoever designed mmap needs a good kicking. It should return
NULL on error.

I assume it's possible (or was considered to be, when the interface
was specified) for it to return a region mapped at address zero, which
has the same representation as a NULL pointer on the systems in
question.

-- Richard
 
S

santosh

Chris Torek wrote:

<snip history of mmap()' interface in relation to the evolution of UNIX>

That was a very interesting post. Thanks.
 
D

Dik T. Winter

> Richard Tobin said: ....
>
> Okay, and I realise that nobody is about to rewrite the entire kernel to my
> spec, but whoever designed mmap needs a good kicking. It should return
> NULL on error.

But also NULL is not necessarily the same across OS boundaries. I have used
at least one system where a kernel NULL was different from a user NULL (the
pointer contains ring information that is inherently not equal for kernel
and user space).
 
R

Richard Tobin

Richard Tobin said:
I assume it's possible (or was considered to be, when the interface
was specified) for it to return a region mapped at address zero, which
has the same representation as a NULL pointer on the systems in
question.

Though Chris has explained that it's just a historical consequence of
the way unix system calls returned errors, it is in fact possible on
the system I'm using to mmap() a file at address zero, and have mmap()
succeed and return a [pointer identical to the] null pointer.

The program below, running on an Apple Powerbook under MacOS X 10.4
with /etc/motd as standard input, prints

mmap()ed at 0x0
Welcome to Darwin!

Given a much larger file, it gets an illegal instruction, presumably because
it has mapped the file over the executable code.

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>

int main(void)
{
int i;
void *memory;
struct stat sb;

if(fstat(0, &sb) == -1)
{
perror("fstat");
return 1;
}

if((memory = mmap(0, sb.st_size, PROT_READ, MAP_FIXED|MAP_PRIVATE, 0, 0)) == (void *)-1)
{
perror("mmap");
return 0;
}

printf("mmap()ed at %p\n", memory);

for(i=0; i<sb.st_size; i++)
putchar(*(char *)i);

return 0;
}

-- Richard
 
C

Chris Dollin

Chris said:
Indeed -- although in this case, the "broken" interface is now
enshrined in a standard (not Standard C, but POSIX). It might help
to illustrate a bit how the situation came about, and how it could
have been avoided, though.

Thanks for, and for taking the trouble to write, that historical view.
 
T

Tor Rustad

Richard said:
(e-mail address removed) said:


It means "warning! dodgy code! fix me!"

(priv != 0) yields a result that is either 0 or 1.
Casting this to (long) yields a value that is either 0L or 1L.
Casting to to (void *) yields a value that is very unlikely to be a valid
pointer to any object or function.

In <sched.h>, one can now find

#define SEND_SIG_NOINFO ((struct siginfo *) 0)
#define SEND_SIG_PRIV ((struct siginfo *) 1)

which I guess give a clue to what the programmer intended.
Stupid stupid stupid.

Agreed, very obfuscated code.

So what you're saying, then, is that someone came to their senses and
ripped it out. Good!

Yes, the current send_sig() function, doesn't look anything like OP's
version.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,050
Latest member
AngelS122

Latest Threads

Top