What I use on Linux:
- Vim
- GNU Make
- NASM (nasm.us)
NASM uses Intel assembly syntax. If you want to learn and use AT&T syntax you can use GNU Assembler (as
) provided by the bintutils
package instead.
How I use it:
Create a project
mkdir hello_world
cd hello_world
touch Makefile hello_world.asm
Write a Makefile
Note the indents below are supposed to be TAB characters, not spaces.
Makefile
all: hello_world
hello_world.o: hello_world.asm
nasm -o $@ -f elf32 -g $<
hello_world: hello_world.o
ld -m elf_i386 -g -o $@ $<
.PHONY: clean
clean:
rm -f hello_world *.o
Write a program
hello_world.asm
; Assemble as a 32-bit program
bits 32
; Constants
SYS_EXIT equ 1 ; Kernel system call: exit()
SYS_WRITE equ 4 ; Kernel system call: write()
FD_STDOUT equ 1 ; System file descriptor to write to
EXIT_SUCCESS equ 0
; Variable storage
section .data
msg: db "hello world from ", 0
msg_len: equ $-msg
linefeed: db 0xa ; '\n'
linefeed_len: equ $-linefeed
; Program storage
section .text
global _start
_start:
; Set up stack frame
push ebp
mov ebp, esp
; Set base pointer to argv[0]
add ebp, 8
; Write "hello world from " message to stdout
mov eax, SYS_WRITE
mov ebx, FD_STDOUT
mov ecx, msg
mov edx, msg_len
int 80h
; Get length of argv[0]
push dword [ebp]
call strlen
mov edx, eax
; Write the program execution path to stdout
mov eax, SYS_WRITE
mov ebx, FD_STDOUT
mov ecx, [ebp]
; edx length already set
int 80h
; Write new line character
mov eax, SYS_WRITE
mov ebx, FD_STDOUT
mov ecx, linefeed
mov edx, linefeed_len
int 80h
; End of stack frame
pop ebp
; End program
mov eax, SYS_EXIT
mov ebx, EXIT_SUCCESS
int 80h
strlen:
; Set up stack frame
push ebp
mov ebp, esp
; Set base pointer to the first argument on the stack
; strlen(buffer);
; ^
add ebp, 8
; Save registers we plan to write to
push ecx
push esi
; Clear string direction flag
; (i.e. lodsb will *increment* esi)
cld
; Zero counter
xor ecx, ecx
; Load address of buffer into the "source index" register
mov esi, [ebp]
.loop:
; Read byte from esi
; Store byte in eax
lodsb
; Loop until string NUL terminator
cmp eax, 0
je .return
; else: Increment counter and continue
inc ecx
jmp .loop
.return:
; Return string length in eax
mov eax, ecx
; Restore written registers
pop esi
pop ecx
; End stack frame
pop ebp
; Pop stack argument
; 32-bit word is 4 bytes. We had one argument.
ret 4 * 1
Compile and run
$ make
nasm -o hello_world.o -f elf32 -g hello_world.asm
ld -m elf_i386 -g -o hello_world hello_world.o
$ ./hello_world
hello world from ./hello_world
Adding a debug target to the makefile
Want to fire up your debugger immediately and break on the main entrypoint? No problem.
Makefile
gdb: hello_world
gdb -tui -ex 'b _start' -ex 'run' --args $<
Now you can clean the project, rebuild, and start a debugging session with one command...
$ make clean gdb
rm -f hello_world *.o
nasm -o hello_world.o -f elf32 -g hello_world.asm
ld -m elf_i386 -g -o hello_world hello_world.o
gdb -tui -ex 'b _start' -ex 'run' --args hello_world
# You're debugging the program in GDB now. Poof.