Without IBT, you'd have this equivalence between C and assembly:
main() {
void (*f)();
f = foo;
f();
}
void foo() { }
---
main:
movl $foo, %edx
call *%edx
ret
foo:
ret
If IBT is enabled, the above code triggers an exception because foo doesn't begin with an "end branch" instruction. When IBT is enabled by the compiler, the above code gets assembled as: main:
endbr64
movl $foo, %edx
call *%edx
ret
foo:
endbr64
ret
Now the compiler inserts endbr64 at the start of each function prologue. The reason for this feature, is to use as a defense in depth against JOP, and COP attacks, as it means that the only "widgets" available to you are entire functions, which can be far harder to exploit and chain.[1]: https://www.intel.com/content/dam/develop/external/us/en/doc...