15213 Intro to computer systems Spring 2006 Recitation #5 Section F TA: Jernej Barbic Quiz 3 will cover: ================== - calling convention, stack - data representation, structures, unions - machine independent optimizations Buffer overflow lab =================== #include #include int f(int arg1, int arg2) { // local varibales go onto stack or into a register: int value; char s[8]; printf("Enter value: "); // a call to a dangerous function // 'gets' doesn't check how many bytes were actually read gets(s); // convert string to integer and compute result: value = strtol(s,NULL,10); return (arg1 * value + arg2); } int main() { int answer = f(5,8); printf("5 * value + 8 = %d\n",answer); return answer; } When compiled with -m32 and -O1 flags: 080483e4 : 80483e4: 55 push %ebp 80483e5: 89 e5 mov %esp,%ebp 80483e7: 83 ec 18 sub $0x18,%esp 80483ea: c7 04 24 4c 85 04 08 movl $0x804854c,(%esp) 80483f1: e8 2e ff ff ff call 8048324 80483f6: 8d 45 f8 lea 0xfffffff8(%ebp),%eax 80483f9: 89 04 24 mov %eax,(%esp) 80483fc: e8 f3 fe ff ff call 80482f4 8048401: c7 44 24 0c 00 00 00 movl $0x0,0xc(%esp) 8048408: 00 8048409: c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp) 8048410: 00 8048411: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) 8048418: 00 8048419: 8d 45 f8 lea 0xfffffff8(%ebp),%eax 804841c: 89 04 24 mov %eax,(%esp) 804841f: e8 e0 fe ff ff call 8048304 <__strtol_internal@plt> 8048424: 0f af 45 08 imul 0x8(%ebp),%eax 8048428: 03 45 0c add 0xc(%ebp),%eax 804842b: c9 leave 804842c: c3 ret Stack inside f, just before calling __strtol_internal: Note: this is 32-bit architecture stack ---------------------------------------- | ... | | more stack | | data of | | caller | ------------------ | | | | | arg2=8 | | | ------------------ | | | | | arg1=5 | | | ------------------ | | | return | | address of f | | | ------------------ | old ebp | |(that of caller;| | caller is main | ebp->| in this case) | ------------------ | s[7] | | s[6] | | s[5] | | s[4] | ------------------ | s[3] | | s[2] | | s[1] | | s[0] | ------------------ | | | arg4=0 | |(see note on | |internal strtol)| ------------------ | | | | | | stack | arg3=10 | | grows downward, | | | towards lower addresses ------------------ V | | | arg2=NULL | |(NULL is alias | | for 0 ) | ------------------ | | | | | arg1=&s[0] | esp->| | ------------------ Notes: ------ %esp always points to the lowest byte of the stack, i.e. to the lowest byte of the most recently allocated stack entry. %ebp always points to the lowest byte of the 4-byte space where the old frame address is stored At lower optimization settings, compiler might reserve more space on the stack than suggested by the C program. Always look into assembly code to see how much space was actually reserved for local variables. Variable 'int value' didn't get allocated on stack. Instead, compiler put it into a register. Again, this can only be determined by looking at the assembly code. Buffer overflow attack exploits situations when 'gets' receives a string longer than 8 characters. In this case, string overwrites the old ebp pointer, or even return address of f, or even data yet higher on the stack (depends on the length of string). Function __strtol_internal is an alias for strtol. It's just like strtol, except it takes 4 arguments. Last argument must always be zero, and the first three are identical to those of strtol. Assembly instructions 'call' and 'ret' have the side effect of changing the stack (pushing return address on stack, and popping it from stack, respectively). A review of C declarations ========================== Function pointers ----------------- If function is declared as: int f(double x) , then the corresponding function pointer is: int (*f)(double); How to remember this? Write function declaration in a standard way. Omit input variable name x. Then put parenthesis around the function name, and insert * before the function name. How to use a function pointer? int round(double x) { return (int)(x+0.5); } // point f to 'round' int (*f)(double) = &round; // ampersand is optional int main() { ... int result = f(3.17); // result = round(3.17) ... } General rule for C declarations ------------------------------- Can arbitrarily mix *, [], function pointer definitions. Must read inside-out according to operator priority. Priority: [] has higher priority to * Example: -------- struct myStructure * a[32][64][128]; is a 3D 32x64x128 array of pointers to myStructure How do we know this? It's parsed as: struct myStructure (* (((a[32])[64])[128])); Read inside-out: a is an array of 32 elements, each of which is an array of 64 elements, each of which is an array of 128 elements, each of which is a pointer to struct myStructure Another example: ---------------- struct myStructure (* a)[32][64][128]; is parsed as: struct myStructure ((((* a)[32])[64])[128]); Hence: a is a pointer to an array of 32 elements, each of which is an array of 64 elements, each of which is an array of 128 elements of struct myStructure. So, 'a' is a pointer to a 3D 32x64x128 array of structures 'myStructure'. Array of 32 function pointers: ------------------------------ int(*f[32])(double); It is parsed as: int(*(f[32]))(double); Therefore, f is an array of 32 elements, each of which is a pointer to a function mapping (double) to int. Pointer to a function pointer: ------------------------------ int(**f)(double); Machine-independent optimizations ================================= Common subexpression,strength reduction,constant folding -------------------------------------------------------- Suppose we wish to write a function that, given input x, computes 1/8 + (tan^2 (x))/4 . Attempt #1: double function_1(double x) { return 1.0/8 + tan(x)*tan(x) /4.0; } Attempt #2: Only compute tan(x) once (common subexpression): double function_2(double x) { double tanx = tan(x); return 1.0/8 + (tanx*tanx)/4.0; } Attempt #3: Convert 1.0/8 to 0.125 (constant folding) Note: this optimization will be performed automatically by the compiler, because all the arguments are known at compile time. double function_3(double x) { double tanx = tan(x); return 0.125 + (tanx*tanx)/4.0; } Attempt #4: Convert division to multiplication Strength reduction: an operation (division by 4) is replaced by a faster operation (multiplication by 0.25). Note: this optimization will be performed automatically by the compiler (for -O1 and higher level of optimizations). Also note that gcc will only perform this optimization when the divisor is a power of 2 (such as 2^2=4.0). In all other cases, it will perform a division. The reason is that multiplication by 1/x and division by x aren't equivalent in floating point arithmetics (due to finite precision). double function_4(double x) { double tanx = tan(x); return 0.125 + tanx*tanx*0.25; } Optimization blocker: Memory aliasing ------------------------------------- // swaps *xp and *yp (as long as xp != yp) void swap(int * xp, int * yp) { *xp = *xp + *yp; /* x+y */ *yp = *xp - *yp; /* x+y-y = x */ *xp = *xp - *yp; /* x+y-x = y */ } What happens if one calls: swap(xp,xp) ? Answer: *xp is set to zero