Looking at the difference between the generated assembly.
Foo::Foo() [base object constructor]: <
pushq %rbp <
movq %rsp, %rbp <
movq %rdi, -8(%rbp) <
movq -8(%rbp), %rax <
pxor %xmm0, %xmm0 <
movss %xmm0, (%rax) ; 1. <
nop <
popq %rbp <
ret <
my_func(): my_func():
pushq %rbp pushq %rbp ; Save previous stack frame addr
movq %rsp, %rbp ; 4. movq %rsp, %rbp ; Address of current stack frame as new base ptr
subq $16, %rsp ; save 16 bytes for local data | movl $0, %eax ; move value 0 to function return register
movq %rdi, -8(%rbp) ; 3. | popq %rbp ; unwind the stack to exit function
movq -8(%rbp), %rax <
movq %rax, %rdi ; 2. <
call Foo::Foo() [complete object constructor] <
movq -8(%rbp), %rax <
leave <
ret ret
movss %xmm0, (%rax)
Caused the SIGSEGV. Using gdb: p/x %rax points to 0x4004d6 which is the address of my_func(). Attempt to write to the stack is the problem. Memory pages are write or execute (read) only. So why is %rax pointing there?
-
movq %rax, %rdi
This line was when the %rax was last written to, in my_func. What did %rdi contain? -
movq %rdi, -8(%rbp)
Contained whatever was at the address of %rbp - 8. What's in %rbp? The stack's base pointer of course. This was 0x4004d6.
The mov instructions write things. In the assembly code of int my_func(), besides the standard function setup/teardown, there was only movl $0, %eax which isn't going to cause a problem.
Registers
%rbp : base pointer of current stack frame (called %ebp in 32 bit) %rsp : stack pointer (top element) %eax : return value of a function, 32 bit register %rax : 64 bit register, same use as %eax %rdi : 64 bit general purpose register
Instructions
movss : move scalar single precision floating point value (copies 32 lowest bits from a XMM 128 bit register) pxor : logical exclusive or movl : move long (32 bit) movq : move quad word (64 bit) pushq : push quad word onto the stack
Suffix b : 8b aka byte s : single 32b float w : 16b l : 32b int or 64b float q : 64b t : 80b, 10 bytes
Not quite. It's actually an unfortunate re-use of the
%rdi
register between theFoo()
constructor and theindirection
function.I've modified your code to simplify and focus on certain things. Notice that
main()
now invokes theFoo()
default constructor directly, no function call or RVO involved.Let's look at the
Foo()
constructor first:Most importantly, note that whatever the caller had in
%rdi
is assumed to be a writable pointer!This works fine for
main()
since it initializes%rdi
to point to the stack before callingFoo()
:Now let's look at how
indirection
handles its registers:So
indirection
is following the AMD64 ABI where the caller passes pointer sized arguments directly in registers: https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABISo... the caller puts the first argument to
indirection
inside%rdi
.indirection
doesn't do any cleanup of%rdi
but simply de-references it and jumps to it.You see where this is going: in
indirection
s case, the first argument is a function pointer, and so will point to the code segment.indirection
leaves%rdi
as the function pointer, and then theFoo()
constructor tries to write0
to the address in%rdi
, which blows up.Here's how
main()
invokesindirection
:That's basically what the paragraph above said, we load the an offset from the current instruction pointer into
%rdi
and then callindirection
. That means%rdi
is guaranteed to point to the non-writable code segment.So, this is NOT about stack smashing! This is simply
Foo()
writing to whatever pointer is in%rdi
, andindirection
using%rdi
as its first argument.To prove this, here's a slight tweak to the code that puts a writable pointer as the first argument of
indirection
:This allocates a float
z
inmain
and then passes it as the first argument ofindirection
. WhenFoo()
gets called, it grabs the first argument ofindirection
through the stack frame (&z
) and assigns0
to it.If you run this code, it should print: