64位NASM+GCC编译报错R_X86_64_32S:共享对象无法使用该重定位
Let's work through your problems one by one, starting with the linking error you hit first.
The Initial R_X86_64_32S Linking Error
That error happens because modern GCC defaults to building Position-Independent Executables (PIEs) for security reasons. Your original code uses push hi, which tries to use a 32-bit absolute address reference to your string in the .data section. PIEs can't use this kind of direct 32-bit addressing—they need code that can run at any memory address, so absolute 32-bit offsets aren't allowed (they limit where the program can load, and pose security risks).
There are two easy fixes for this:
- Disable PIE during linking: Add
-no-pieto your GCC command to tell the linker to build a traditional non-position-independent executable:
This lets your originalnasm -f elf64 main.asm gcc main.o -o main -no-pie rm main.opush hi/call putscode work (though it's not following x86-64 calling conventions, which we'll cover next). - Use x86-64's standard calling convention: The System V AMD64 calling convention (used on Linux) passes the first argument in the
rdiregister, not on the stack. This is the cleaner, modern approach that works with PIEs by default.
Why Your Modified Code Caused a Segmentation Fault
Your updated code tried to manually access the GOT (Global Offset Table) with call [puts wrt ..got], but that's unnecessary—and wrong. When you call a dynamic library function like puts, the linker automatically creates a PLT (Procedure Linkage Table) stub that handles lazy loading of the function through the GOT. Directly dereferencing the GOT entry before the dynamic linker has populated it will cause a segfault, since that memory isn't valid yet.
Instead, just use call puts directly—the linker takes care of the PLT/GOT magic for you.
Understanding rbp and rax in x86-64 Code
Let's break down what those registers are doing:
rbp(Base Pointer): This is used as a frame pointer to mark the start of your function's stack frame. The sequencepush rbp; mov rbp, rspsets up the frame pointer:push rbpsaves the previous frame pointer on the stack so the caller's stack frame isn't lost.mov rbp, rspsets the current stack pointer as the base for accessing local variables and function arguments.
This is optional for simple functions (you can omit it to save instructions), but it makes debugging much easier—debuggers use frame pointers to unwind the call stack. Your modified code pushedrbpbut didn't setmov rbp, rsp, which is an incomplete frame setup (though it wasn't the cause of your segfault).
rax(Accumulator Register): In x86-64 System V:- It holds the return value of a function (for integer/pointer types). For
main, returning0tells the OS the program exited successfully—hencemov rax, 0beforeret. - For variadic functions (like
printf), you setraxto the number of floating-point arguments passed in XMM registers. Sinceputsisn't variadic, you don't strictly need to setraxbefore callingputs, but setting it to0formain's return is required.
- It holds the return value of a function (for integer/pointer types). For
The Correct Working Code
Here's a properly written version that follows x86-64 conventions, works with PIEs, and avoids errors:
global main extern puts section .data hi db 'hello', 0 section .text main: ; Optional: Set up stack frame (good for debugging) push rbp mov rbp, rsp ; Pass the first argument to puts in rdi (x86-64 System V convention) mov rdi, hi call puts ; Linker handles PLT/GOT automatically ; Optional: Tear down stack frame pop rbp ; Return 0 (success) from main mov rax, 0 ret
Compile this with your original commands—no -no-pie needed, since it uses position-independent-friendly register addressing.
内容的提问来源于stack exchange,提问作者Ethan McTague

