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, %rdiThis 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
%rdiregister between theFoo()constructor and theindirectionfunction.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
%rdiis assumed to be a writable pointer!This works fine for
main()since it initializes%rdito point to the stack before callingFoo():Now let's look at how
indirectionhandles its registers:So
indirectionis 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
indirectioninside%rdi.indirectiondoesn't do any cleanup of%rdibut simply de-references it and jumps to it.You see where this is going: in
indirections case, the first argument is a function pointer, and so will point to the code segment.indirectionleaves%rdias the function pointer, and then theFoo()constructor tries to write0to 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
%rdiand then callindirection. That means%rdiis 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, andindirectionusing%rdias 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
zinmainand then passes it as the first argument ofindirection. WhenFoo()gets called, it grabs the first argument ofindirectionthrough the stack frame (&z) and assigns0to it.If you run this code, it should print: