[bits 32]
global _start
section .data
str_hello db \"HelloWorld\", 0xa
str_hello_length db $-str_hello
section .text
_start
You're asking the assembler to target 32-bit mode (with bits 32), but you're putting that 32-bit machine code into a 64-bit object file and then looking at what happens when you disassemble it as x86-64 machine code.
So you're seeing the differences between instruction encoding in x86-32 and x86-64. i.e. This is what happens when you decode 32-bit machine code as 64-bit.
mov 0x6000e5(%rip),%edx # 0xa001a5
The key one in this case being that 32-bit x86 has two redundant ways to encode a 32-bit absolute address (with no registers): with or without a SIB byte. 32-bit mode doesn't have RIP-relative (or EIP-relative) addressing.
x86-64 repurposed the shorter (ModR/M + disp32) form as the RIP-relative addressing mode, while 32-bit absolute addressing is still available with the longer ModR/M + SIB + disp32 encoding. (With a SIB byte that encodes no base register and no index register, of course).
Note that the offset from RIP is actually the absolute static address where your data is placed (in 64-bit code), 0x6000e5.
The comment is the disassembler showing you the effective absolute address; RIP-relative addressing counts from the byte after the instruction, i.e. the start of the next instruction.
movabs 0x4b8c289006000e5,%eax
When the destination register is EAX, your assembler (in 32-bit mode) chooses the shorter mov encoding that loads eax from a 32-bit absolute address with no ModR/M byte, just A1 disp32. Intel's manual calls this a moffs (memory offset) instead of an effective address.
In x86-64 mode, that opcode takes a 64-bit absolute address. (And is unique in being able to load/store from a 64-bit absolute (not RIP-relative) address without getting the address into a register first). Thus, decoding consumes part of the next instruction as part of the 64-bit address, and that's where some of those high bytes in the address come from. The 0x6000e5 in the low 32 bits is correct, and is how it would decode as 32-bit machine code.
Changed
[bits 32]to[bit 64]
See What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?.
Better to build a 32-bit executable if you aren't going to use native 64-bit system calls. Use nasm -felf32, and link with gcc -m32 -nostdlib -static.