Friday, September 22, 2006

Obscure GCC Functionality

I spent some time tonight playing around with GCC (v4.0.3-1ubuntu5) macros and came across some 'oddness'. Ever use the 'noreturn' attribute? Yah me either. It looks a litle something like this:

void test_func() __attribute__((noreturn));

We are basically telling GCC that the function 'test_func()' will not return. By further research we see this means that no 'leave' or 'ret' instruction will be setup after 'test_func()' is called and the return address will not be set for the instruction that sits immediately after 'call test_func()'. Heres some sample C code.

/* no return attribute */
void test_func() __attribute__((noreturn));

int count = 0;

int main(int argc, char *argv[])
{

test_func();

return 0;
}

void test_func()
{

/*
A variable to count how many times
we have been inside this function
*/
count++;

printf("count=[%d]\n", count);

int *ptr1, *ptr2, *ptr3, *ptr4;

ptr1 = __builtin_return_address(0);
/* test_func() will return to this address */
ptr2 = __builtin_return_address(1);
/* main() will return to this address */
ptr3 = __builtin_frame_address(0);
/* current frame address of test_func() */
ptr4 = __builtin_frame_address(1);
/* current frame address of main() */

printf("\ntest_func() returns to %x\n"
"main() returns to %x\n"
"current frame addy of test_func() %x\n"
"current frame addy of main() %x\n\n",
ptr1, ptr2, ptr3, ptr4);
}

The disassembly listing below confirms that there is no leave/ret instruction. Compare the address called against the output of the program above.

08048360 main:
8048360: 55 push %ebp
8048361: 89 e5 mov %esp,%ebp
8048363: 83 ec 08 sub $0x8,%esp
8048366: 83 e4 f0 and $0xfffffff0,%esp
8048369: b8 00 00 00 00 mov $0x0,%eax
804836e: 83 c0 0f add $0xf,%eax
8048371: 83 c0 0f add $0xf,%eax
8048374: c1 e8 04 shr $0x4,%eax
8048377: c1 e0 04 shl $0x4,%eax
804837a: 29 c4 sub %eax,%esp
804837c: e8 00 00 00 00 call 8048381
Notice the missing leave/ret instructions


08048381 :
8048381: 55 push %ebp
8048382: 89 e5 mov %esp,%ebp
8048384: 83 ec 28 sub $0x28,%esp

... rest of test_func() goes here

So when test_func() ends the instructions at the address EIP holds are executed. In this case that puts us right back in test_func(). The second time through test_func() the return address is set to '1'. Which of course results in a segmentation fault.

./1
count=[1]

test_func() returns to 8048381
main() returns to b7dfbea2
current frame addy of test_func() bf84a888
current frame addy of main() bf84a8a8

count=[2]

test_func() returns to 1
main() returns to b7dfbea2
current frame addy of test_func() bf84a88c
current frame addy of main() bf84a8a8

Segmentation fault (core dumped)

$gdb -core=core -q
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
Failed to read a valid object file image from memory.
Core was generated by `./1'.
Program terminated with signal 11, Segmentation fault.
#0 0x00000001 in ?? ()
(gdb) info reg
eax 0x87 135
ecx 0x0 0
edx 0x87 135
ebx 0xb7f11adc -1208935716
esp 0xbf84a894 0xbf84a894
ebp 0xbf84a8a8 0xbf84a8a8
esi 0xbf84a934 -1081824972
edi 0xbf84a8c0 -1081825088
eip 0x1 0x1
eflags 0x210292 2163346
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51



At this point we could use the ptrace() API for setting the return address correctly, but whats the point. Now this is of course the correct behavior for GCC. But sometimes its fun exploring the more obscure side of the most widely used open source compiler.

- chris

No comments: