Solved Assembly - simple Hello World

Hi,

I'm beginning assembly on FreeBSD and tried a simple HelloWorld. I'm using 2 separate files, one for some macro and defines, one for the actual code (looks a long post but hang on, it's just hello world =D )

Here is the macro file :

Code:
;
; system.inc : aliases definitions (defines & macros)
;

; File descriptors
%define stdin  0
%define stdout 1
%define stderr 2

; symbolic names for syscalls
%define SYS_nosys 0
%define SYS_exit 1
%define SYS_fork 2
%define SYS_read 3
%define SYS_write 4

; Add a short, non-global procedure with a long name, so we do not accidentally reuse the name in our code
section .text
align 4
access.the.bsd.kernel:
int 80h
ret

; syscall number macro
%macro system 1
mov eax, %1
call access.the.bsd.kernel
%endmacro

; syscall macros
%macro sys.exit 0
system SYS_exit
%endmacro

%macro sys.fork 0
system SYS_fork
%endmacro

%macro sys.read 0
system SYS_read
%endmacro

%macro sys.write 0
system SYS_write
%endmacro

And here is the program itself :

Code:
;
; helloWorld.asm
;

%include '/usr/home/fgauthier/Documents/Dev/system.inc'

section .data
hello db 'hello, World!', 0Ah
helloLength equ hello

section .text
global _start
_start :
push dword helloLength
push dword hello
push dword stdout
sys.write

push dword 0
sys.exit


I assembled it with NASM using the following commands :
# nasm -f elf64 /usr/home/fgauthier/Documents/Dev/helloWorld.asm
# ld -s -o /usr/home/fgauthier/Documents/Dev/helloWorld /usr/home/fgauthier/Documents/Dev/helloWorld.o


Here are my questions :
1/ As you can see I'm using ELF64. If I try with ELF instead I get an error from the linker (on the second command) that says:
Code:
ld: i386 architecture of input file `/usr/home/fgauthier/Documents/Dev/helloWorld.o' is incompatible with i386:x86-64 output
So what if I want 32 bits assembly?

2/ As you can see in my program, the files to assemble are not in the same directory as NASM. I'd like to put that on some environment variable (...a bit like on *cough* Windows *cough*...) so I don't have to specify the whole path every time.

3/ When I launch the program... nothing happens... no Hello World displayed =(
Any hint?
 
haaaa, nifty little command truss(1) !!
Didn't knew about it, I'm fairly new to FreeBSD I'm using it as a desktop for a few month but didn't really dug into it until now.
So thanks, I have my answer :

~% truss /usr/home/fgauthier/Documents/Dev/helloWorld
Code:
write(-6392,0x0,0)                ERR#9 'Bad file descriptor'
process exit, rval = 8

(Still have to learn how to use gdb(1), I'm used to graphical debugger from a well known software giant. Probably I can find a front-end GUI though)

Any guess for the other questions?
Why do I have to use elf64?
 
1/ As you can see I'm using ELF64. If I try with ELF instead I get an error from the linker (on the second command) that says:
*snip*
So what if I want 32 bits assembly?
You appear to be building binaries for a 64 bit architecture. The FreeBSD Developers' Handbook was written with a 32 bit architecture in mind. The calling conventions are different. Read Native 64-bit “Hello World” With NASM on FreeBSD.

If I really wanted to produce 32-bit binaries I would hunt down a 32-bit machine. That is probably not very helpful.

2/ As you can see in my program, the files to assemble are not in the same directory as NASM. I'd like to put that on some environment variable (...a bit like on *cough* Windows *cough*...) so I don't have to specify the whole path every time.
Is nasm(1) in your path?
Code:
$ echo $PATH
/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/home/sgeos/bin
$ which nasm
/usr/local/bin/nasm
$ pwd
/home/sgeos/asm/hello
$
The following works for me. Note the above path and directory related information. Source code follows.
Code:
$ nasm -f elf64 main64.s
$ ld -s -o hello main64.o
$ ./hello
Hello, World!
$
system.inc
Code:
%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

%macro system 1
  mov rax, %1
  syscall
%endmacro

%macro sys.exit 0
  system SYS_exit
%endmacro

%macro sys.fork 0
  system SYS_fork
%endmacro

%macro sys.read 0
  system SYS_read
%endmacro

%macro sys.write 0
  system SYS_write
%endmacro
main64.s
Code:
%include 'system.inc'

section .data
message db  'Hello, World!', 0x0A, 0x00
length  equ $ - message

section .text
align 4

global _start
_start:
  mov rdi, stdout
  mov rsi, message
  mov rdx, length
  sys.write

  xor rdi, rdi
  sys.exit
 
I just adapted the code from the article I linked to. The Linux compatibility layer is not installed. I built without branding the executable and it works.

64-bit syscall conventions are completely different from 32-bit calling conventions. There are no stack based parameters. Parameters are passed on registers rdi, rsi, rdx, r10, r8 and r9. System call number is passed on rax. Call is done with the syscall instruction and rax contains the result of the syscall. Kernel destroys rcx and r11. See section A.2.1 of the System V AMD64 ABI Reference for complete details.
 
Ho I see!
Yes I tried your code, and it works great but of course the primary goal is to play a bit with things to see how it works not just display "hello world" =D
Didn't knew about the 64bit convention being different, thanks again.
 
If you are serious about doing anything beyond hello world in x64, I recommend reading the entire System V AMD64 ABI Reference. It contains code examples for common programming constructs. The FreeBSD Developers' Handbook is also a good read, even if the x86 ASM examples can not be run as is on x64. You may also consider writing programs in C and using objdump(1) to see the ASM.

I never learned x86 ASM, so I am having trouble writing a program that echoes command line parameters in x64. If you want to try my echo exercise, I have two versions written in ASM-like C. I will not continue the exercise in x64. I am more interested in ARM.
 
When I said I would give up on the exercise, I lied. Getting this to work was non-trivial. I had to figure out basic x86-64 assembler, followed by how FreeBSD actually passes argc and argv. Aside from working on my machine, this is probably a bad example because I am really not an expert on x86-64 assembler. =P
Code:
%include 'system.inc'

%define BUFFER_SIZE 2048

section .bss
buffer  resb BUFFER_SIZE

section .text
align 4

global _start
_start:
  mov rsp, rdi ; rdi contains the stack pointer to argc, argv[n]...
  pop rbx ; argc

  jmp is_last_arg
  proc_arg:
    pop rsi  ; argv[n]
    mov rdx, buffer
  copy_char:
    mov byte cl, [rsi] ; c = *argv[n]
    cmp cl, 0 ; if 0 == c
    je output
    mov byte [rdx], cl ; *buffer = c
    inc rsi ; argv[n]++
    inc rdx ; buffer++
    jmp copy_char

  output:
    mov byte [rdx], 0x0A ; append \n
    inc rdx ; buffer++
    ; write stdout, buffer, length
    mov rdi, stdout
    mov rsi, buffer
    sub rdx, buffer ; length
    sys.write
    dec rbx ; argc--
  is_last_arg:
    cmp rbx, 0 ; if 0 != argc
    jne proc_arg

  ; exit(0)
  xor rdi, rdi
  sys.exit
References (may have closed a few):
 
Interesting,

I have no clear plan with Assembly yet, most probably will use it in C programs (or maybe as asm DLL).
 
Back
Top