Just to be completely clear: the CALL command pushes the address of the instruction following it onto the stack and jumps to the target address. It means that
x: call start y:
morally equivalent (ignoring the fact that we are rubbish %rax ):
x: lea y(%rip), %rax push %rax jmp start y:
Conversely, RET pops the address from the stack and jumps to it.
Now in your code you make popq %rsi and then RET jump back to what you call. If you just changed popq to lea str(%rip), %rsi to load %rsi with str address, you still have the return value ( str address) on the stack! To fix your code, simply manually pull the return value from the stack ( add $8, %rsp ) or more safely move str after the function so you don't need an inconvenient call.
Updated with full standalone example:
# ps
Parsing the code with objdump -dp shows that the code is really position-independent, even when using .data .
p: file format elf64-x86-64 Disassembly of section .text: 000000000040010c <start>: 40010c: 48 c7 c0 01 00 00 00 mov $0x1,%rax 400113: 48 c7 c7 01 00 00 00 mov $0x1,%rdi 40011a: 48 8d 35 1b 00 20 00 lea 0x20001b(%rip),%rsi
source share