Mentor for C self study wanted
Heiko Wundram (Beenic)
wundram at beenic.net
Tue Oct 23 15:58:33 PDT 2007
Am Dienstag, 23. Oktober 2007 23:24:09 schrieb Harald Schmalzbauer:
> #include <stdio.h>
> void main()
> short nnote;
> // Numerischen Notenwert einlesen
> printf("Bitte numerischen Schulnotenwert eingeben: ");
man 3 scanf (most important thing to look at with any such problem is the
C-library documentation, which is excellent on FreeBSD) says that for "%d"
the passed pointer has to be a pointer to "integer", which &nnote is not.
&nnote is a pointer to short, which points to 2 bytes, whereas a pointer to
integer is a pointer to 4 bytes of storage.
Generally, nnote is reserved by the compiler on the stack (as it's a local
variable) with two bytes (but this depends on your platform), and &nnote
points to the beginning of this area.
As you are probably running on a little-endian architecture, the layout that
scanf presumes is (from low to high):
---> increasing addresses
lsbyte 2 3 msbyte
|-- &nnote points here
of which only the first two are interpreted as nnote by the rest of the
program; the upper two are different stack content (probably a return address
to the C initialization code calling main(), or a pushed stack pointer, or
such, as your procedure defines no other locals, see below).
Now, when scanf assigns the four bytes, it'll properly enter the lower two
bytes of the integer into "lsbyte 2" (which is nnote, in the same byte
order), but overwrite two bytes that are above it.
When main() finishes, the (now broken) saved address (of which "3 msbyte" is
the lower half) is popped, which leads to the SIGSEGV you're seeing.
In case you were on big-endian, the result would be different (i.e., the order
would be reversed, so that nnote would always be zero or minus one in case
you entered small integral values in terms of absolute value), but
effectively, the return address would be overwritten as well, breaking it.
This is effectively what can be called a buffer-overflow.
Just to finish this: the proper format would be "%hd", for which the flag "h"
signifies that the pointer is a pointer to a "short int", also documented in
man 3 scanf.
Why aren't you seeing this behaviour with printf (i.e., why can you pass a
short but still specify "%d")? Because C defines that functions that take a
variable number of arguments (of which printf is one such) get each argument
as type "long" (the type that's at least as big as a pointer on the current
platform), so when passing a short as argument to a var-args function, the
C-compiler inserts code which makes sure that the value is promoted to a long
in the argument stack for printf. scanf is also a varargs function, but
you're not passing the value of nnote, but rather a pointer to it, which
(should) already be as wide as a long.
Finally, looking at (parts of) the assembly that gcc generates (on a
little-endian i386 machine):
.type main, @function
leal 4(%esp), %ecx
andl $-16, %esp
; Set up the pointer to the local frame (EBP on i386). All locals are
; relative to EBP in a function.
movl %esp, %ebp
; ECX is the first (hidden) local.
subl $20, %esp
subl $12, %esp
addl $16, %esp
subl $8, %esp
; Load the effective address of EBP-6, i.e., nnote, into EAX, which
; is pushed for scanf. scanf will thus write its output on EBP-6 up to
; EBP-3, where EBP-4 and EBP-3 are part of the value that's been
; pushed in the "pushl %ecx" above.
leal -6(%ebp), %eax
; Restore the value at EBP-4 (i.e., the ECX that was pushed above) into
; ECX at function exit. This value has been corrupted by the integer
; assignment due to scanf.
movl -4(%ebp), %ecx
; Restore the stack pointer from the (invalidated) %ecx, i.e. produce a
; bogus stack pointer.
leal -4(%ecx), %esp
This produces a segfault, after the return to the C initialization code,
simply because the stack pointer is totally bogus.
> I found that declaring nnote as int soleves my problem, but I couldnÄt
> understand why.
Everything clear now? ;-)
Product & Application Development
Office Germany - EXPO PARK HANNOVER
Beenic Networks GmbH
Mailänder Straße 2
Fon +49 511 / 590 935 - 15
Fax +49 511 / 590 935 - 29
Mobil +49 172 / 437 3 734
Mail wundram at beenic.net
Beenic Networks GmbH
Sitz der Gesellschaft: Hannover
Geschäftsführer: Jorge Delgado
Registernummer: HRB 61869
Registergericht: Amtsgericht Hannover
More information about the freebsd-questions