i386 assembly example does not work

Took a sample program from https://www.freebsd.org/doc/en_US.ISO8859-1/books/developers-handbook/x86-first-program.html and put the code into these files.

This into hello.asm:
%include   'system.inc'
section   .data
hello   db   'Hello, World!', 0Ah
hbytes   equ   $-hello

section   .text
global   _start
push   dword hbytes
push   dword hello
push   dword stdout

push   dword 0
And this into system.inc
%define   stdin   0
%define   stdout   1
%define   stderr   2
%define   SYS_nosys   0
%define   SYS_exit   1
%define   SYS_fork   2
%define   SYS_read   3
%define   SYS_write   4
section   .text
align 4
   int   80h
%macro   system   1
   mov   eax, %1
   call   access.the.bsd.kernel
%macro   sys.exit   0
   system   SYS_exit

%macro   sys.fork   0
   system   SYS_fork

%macro   sys.read   0
   system   SYS_read

%macro   sys.write   0
   system   SYS_write
Compiled (changing format to elf64 as that would match the system's architecture):
% nasm -f elf64 hello.asm
% ld -s -o hello hello.o
That does not print Hello World. What am I doing wrong?
nitpick: "compile" is not the correct wording here ;) It's assembly language and a key property of assembly is that it isn't platform-independent like e.g. C. That's the nature of it, with assembly, you literally write down what the CPU should execute (well, with the help of mnemonics instead of the binary codes, but that's just syntactic sugar). I wrote my earlier reply on the phone, just wanted to make sure this wasn't overlooked. Thanks tobik for elaborating on it and giving a helpful answer!

On a side note, this thread should be in "userland scripting & programming".
To expand what is said above, the assembler never does any optimization on the code it's given. It literally takes a sequence of assembly language instructions and turns that sequence into executable code in 1:1 correspondence with the assembly language mnemonics and the machine instructions. It is really 1:1 mapping because you can use a disassembler on the executable object and you will get back the original assembly language program minus the macros that were used.
Well this only describes x86 -- amd64 is missing. But a very reliable "documentation" is always having a look in the source ;) See the KERNCALL macro here for how libc issues a syscall on FreeBSD amd64 -- as opposed to the i386 version using int 80h.

In fact, as amd64 provides a dedicated syscall instruction, it would be silly for an OS not to use it.
Are you sure? I thought lcall was for protected mode / segmented addressing only? Anyhow, you need an instruction that enters ring-0
The descriptors were set so that lcall 7 did all the right things. It's still visible in the i386 link you gave, wouldn't have remembered otherwise.

I love programming in assembler though it's often difficult to justify the additional time it needs over a compiled language and the lack of portability.

As a chatty introduction to assembly language I enjoyed Assembly Language Step-by-Step: Programming with Linux (ISBN 978-0-470-49702-9). It really is only an introduction to i386 assembly so don't expect to be selling yourself as a seasoned professional after you finish it. Ignore that it uses GNU/Linux and the i386 instruction set as a teaching base rather than FreeBSD and amd64 -- it doesn't matter. Once you understand the concepts you can start ploughing into more terse books and reference material on the ins and outs of the amd64 instruction set and FreeBSD system calls or indeed coding for microcontrollers like the PIC or AVR ranges.http://eu.wiley.com/WileyCDA/WileyTitle/productCd-0470497025.html
Thank you for all the comments, but I am not learning assembly. I am learning assembly for a 64 bit FreeBSD to be exact.
Developer guides for their respective x86-64 chips are available from AMD for AMD64 and Intel for Intel 64 (previously known as EM64T). Note the instruction sets are almost but not quite the same, though the differences likely won't matter to you. For your code that interacts with the operating system, the source code (syscalls.c) will show you FreeBSD system calls, which are described in section 2 of the man pages (for example write(2)). System calls use the System V calling convention. Alternatively you could call functions in the C standard library, which are described in section 3 of the man pages (for example fputs(3)), which will then make the relevant system call.
Be aware that register names are different in amd64, eg. eax becomes rax etc. You can still use the 32-bit names but only if the data will fit in 32 bits. Addresses in amd64 are 64 bit (strictly speaking only the lowest 48 bits are used, but you still need 64-bit registers to store them).