间接调用指令是否总是指向函数序言?

Do indirect `call` instructions always point to a function prologue?

假设我们有一些 C 代码通过函数指针调用函数,无论是通过函数指针 table 还是作为参数或其他传递的函数指针,如下所示:

/* ... some other code .. */
void (*f)(void) = something; // f function pointer to some function
(*f)();

这应该被编译为(或等同的东西)

mov  %rcx, [something] ; here f=ecx
callq *%rcx

问题: %ecx 是否总是指向函数序言或者它是否可以指向函数末尾的一小段代码?

示例:

void big_func(){
    /* lots of code here */
    printf("bar");
    printf("foo");
}
void small_func(){
    printf("foo");

big_func C编译成

; some more code up here
1: 48 8d 3d c4 0e 00 00    lea    %rdi,[0xec4+%rip]  ;ptr to "bar"
2: b8 00 00 00 00          mov    %eax,[=13=]x0
3: e8 e6 fe ff ff          callq  1030 <printf@plt>
4: 48 8d 3d b7 0e 00 00    lea    %rdi,[0xeb7+%rip]  ;ptr to "foo"
5: b8 00 00 00 00          mov    %eax,[=13=]x0
6: e8 d5 fe ff ff          callq  1030 <printf@plt>
7: b8 00 00 00 00          mov    %eax,[=13=]x0
8: 5d                      pop    %rbp
9: c3                      ret

small_func 的调用是否可以指向 4: 作为入口点?这种情况是否曾经发生过(使用像 gcc 这样的通用编译器)或仅当某些人在幕后修改汇编代码时发生过?

问题限制:

附加小问题:如果故意修改函数指针以跳过函数序言中的某些字节,会发生什么情况?这被认为是未定义的行为吗?
示例:

void (*f)(void) = something; // f function pointer to some function
f=(void (*func_ptr)(void)) ((*char)f+2)
(*f)(); //skips `push ebp`

编辑: 我应该更清楚为什么 这个问题被问到。它是在研究大师的背景下,寻求一种新的方法来在非常低的软件或硬件级别上减轻基于 ROP 的攻击。如果间接调用有可能指向函数序言之外的其他地方,它可能会破坏我们基于标记的实现之一(缺少标记并在错误检测到攻击后终止程序)

函数指针将始终引用函数的开头。 C 标准不允许从其他指针类型进行转换。它调用未定义的行为。

但是特定的实现可能会生成正确的代码,尤其是在函数未设置堆栈帧的情况下。但大多数情况下会失败

但这根本没有任何意义 - 您应该简单地将 @big@ 函数拆分为两个较小的函数,并在需要时调用它们,而不要使用伪技巧。

在 C 中,与大多数语言一样,函数具有非常具体的含义、目的和实现。在调用中,函数指针必须像函数一样工作。我们不能调用不是函数的代码,无论是直接调用还是函数指针。该函数通常不知道它是被直接调用还是被指针调用——它必须接受传递的参数,执行它的函数并 return 给调用者(通常),不管它是如何被调用的(直接或通过函数调用)指针)。在 C 语言中,函数只有一个入口点。

C 语言中没有非函数代码片段的概念,可以通过函数指针传输(或调用)。 (C 有标签和 goto,但没有标签指针,或者标签变量或标签变量 goto,例如。)

并非所有函数都需要序言,它们只需要能够接受它们的参数(做某事)和 return 给调用者——这不一定需要序言或结语,但它们仍然是函数(只有一个入口点)。