1

I am attempting to pass a function pointer defined within the parent block scope to another function. I get both working and segfaults in different environments. (I am not a C expert)

The code:

#include <stdio.h>
#include <stdbool.h>

void test_function(bool (*function_pointer) (int x)) {
        printf("addr passed function_pointer %p\n", function_pointer);
        if (function_pointer(100)) {
                printf("  run: true\n");
        } else {
                printf("  run: false\n");
        }
}

bool function_outside_main(int x) {
        return x < 0;
}

int main(void) {
        // run with function defined globally
        printf("addr function_outside_main %p\n", function_outside_main);
        test_function(function_outside_main);

        // run with function defined in this stack block
        bool function_inside_main(int x) {
                return x > 0;
        }
        printf("addr function_inside_main %p\n", function_inside_main);
        test_function(function_inside_main); // shouldn't the address be valid?
}

On Ubuntu 16.04.4 with GCC version 5.4.0 (on an Amazon EC2) it works with output:

addr function_outside_main 0x400620
addr passed function_pointer 0x400620
  run: false
addr function_inside_main 0x7ffc018d5690
addr passed function_pointer 0x7ffc018d5690
  run: true

On Ubuntu 20.04.1 with GCC version 9.3.0 (under Windows WSL) it fails with a segfault:

addr function_outside_main 0x7ff19c8631dd
addr passed function_pointer 0x7ff19c8631dd
  run: false
addr function_inside_main 0x7ffffc033b20
addr passed function_pointer 0x7ffffc033b20
zsh: segmentation fault (core dumped)  ./a.out
Mat
  • 202,337
  • 40
  • 393
  • 406
md5madman
  • 306
  • 1
  • 5
  • This is not function pointer defined "in stack" but entire function defined inside other function which is invalid. – i486 Sep 07 '20 at 17:03
  • 2
    @i486 it is valid in gcc. It is gcc extension. – 0___________ Sep 07 '20 at 17:06
  • The issue might be that you should not use %p to print function pointers; it is for object pointers only. See https://stackoverflow.com/questions/2741683/how-to-format-a-function-pointer – dave Sep 07 '20 at 17:12
  • @dave thanks for the info, i tried even removing all printf statements produces sames results – md5madman Sep 07 '20 at 17:18
  • 2
    Ok. I expect the issue is that on WSL the stack is not executable, which it needs to be for nested functions to work -- take a look at the GCC documentation for this extension https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html – dave Sep 07 '20 at 17:25
  • Of course in this case you don't actually need the function to be nested, and the trampoline that GCC creates is pointless. But if you referenced local variables inside the function it would be necessary. The normal way to do this in C would be to add an extra void* argument to the function, and pass around a pointer to the local data with the function pointer. Hopefully that makes sense. – dave Sep 07 '20 at 17:28
  • @dave I tried to distill the error I was receiving in a larger program to this example. In the larger program I am also using locally scoped variables in the nested function. I think you may be correct about it being an issue in WSL. I ran a third test on gcc 7.5.0 with Ubuntu 18 and it ran correctly. – md5madman Sep 07 '20 at 17:35
  • @md5madman FWIW I've tried the code on non-WSL Ubuntu 20.04.1 with GCC 9.3.0 and it's fine. There is this github issue about executable stacks not working on WSL https://github.com/microsoft/WSL/issues/286; it's been closed but it's not clear what the resolution was. – dave Sep 07 '20 at 17:51
  • related: https://stackoverflow.com/questions/49965980/segmentation-fault-when-passing-internal-function-as-argument – Chris Dodd Sep 07 '20 at 19:37

3 Answers3

2

Nested functions like this is a gcc extension, not part of the C standard.

The implementation used by gcc for this generally1 involves creating an on-stack thunk for the nested function, so calling it requires executable stack support. More recent versions of Linux (and Windows) default to a non-executable stack, so will crash.

To make this work, you can use the -z execstack option to gcc, or you can use the execstack tool to modify the binary to specify an executable stack after creating it.


1In some versions of gcc with -O it can determine when nested functions don't actually need to be nested (they never reference the containing scope), and not use the thunk for those cases

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • Interesting, this is new territory for me. I installed execstack then I ran `gcc -z execstack test.c` and it still segfaulted. I also tried running `execstack a.out` which output "X a.out" and then running but it still segfaulted. Again, its not my are of expertise, but you and @dave have stated the similar things around this. I am not too concerned if its a Windows WSL limitation only. – md5madman Sep 07 '20 at 18:08
  • A google search for `WSL execstack` reveals that this is a common problem on WSL, and it doesn't support setting execstack (it ignores the setting and always makes the stack non-executable). – Chris Dodd Sep 07 '20 at 18:12
0

I don't have access to WSL (or 9.3 gcc for that matter), but I did note a caveat that an internal function may not be called after the function it is defined in has returned. Usually this would be troublesome for functions that are deferred, but I a suspicious that your compiler has collapsed the final statement ( test_function(function_inside_main); // shouldn't the address be valid?) with the function epilogue; so can you add a line: printf("Hello, world\n"); after that statement and see if it magically fixes it?

Such are the dangers of extensions aka defining your own C like language.

mevets
  • 10,070
  • 1
  • 21
  • 33
  • I tested that idea and it produced the same segmentation fault for WSL. – md5madman Sep 07 '20 at 17:32
  • Then it is debugger/ examine assembly time. If you break at that line, then set an abs breakpoint on the address of function_inside_main (as printed before), that will be the trampoline. step through the trampoline, you will end up in your nested function, then single step your way out. – mevets Sep 07 '20 at 17:39
  • I ran a debugger under WSL with gdb and when it executed `if (function_pointer(100)) {` it failed with "Program received signal SIGSEGV, Segmentation fault." – md5madman Sep 07 '20 at 17:52
  • You have to step in assembly to get through the trampoline. From that statement, repeatedly stepi It should take about 5 stepi's to etner function_inside_main. – mevets Sep 07 '20 at 17:55
-1

Using this gcc extension you can't call it outside the scope of the function it was defined in. It is an Undefined Behaviour. Very similar to derefencing local automatic variables referenced by the pointer outside the function scope

0___________
  • 60,014
  • 4
  • 34
  • 74
  • 2
    The GCC documentation (https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html) disagrees with you: "It is possible to call the nested function from outside the scope of its name by storing its address or passing the address to another function" – dave Sep 07 '20 at 17:10
  • Isn't it about lifetime rather than scope? If I have `void foo(void) { int x = 7; other_func(&x); }`, then it is perfectly legal for `other_func` to dereference the passed pointer, just so long as it doesn't save a copy that gets dereferenced after `foo` returns. The situation for nested functions is the same. – Nate Eldredge Sep 07 '20 at 17:43