Skip to content

Instantly share code, notes, and snippets.

@gurchik
Last active January 8, 2020 16:45
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gurchik/79dca38ccc2a5e263d0635f1b7737ec9 to your computer and use it in GitHub Desktop.
Save gurchik/79dca38ccc2a5e263d0635f1b7737ec9 to your computer and use it in GitHub Desktop.
A heavily-documented Hello World in x86 assembly
; Allow the linker to find the _start symbol. The linker will begin program execution
; there.
global _start
; Start the .data section of the executable, which stores constants (read-only data)
; It doesn't matter which order your sections are in, I just like putting .data first
section .rodata
; Declare some bytes at a symbol called hello_world. NASM's db pseudo-instruction
; allows either a single byte value, a constant string, or a combination of the two
; as seen here. 0xA = new line, and 0x0 = string-terminating null
hello_world: db "Hello world!", 0xA, 0x0
; Start the .text section, which stores program code
section .text
_start:
enter 0, 0
; save the caller-saved registers (I choose to not save any)
push hello_world ; push the argument to _print_msg
call _print_msg
mov eax, 0x01 ; 0x01 = exit()
mov ebx, 0 ; 0 = no errors
int 0x80
_print_msg:
enter 0, 0
; My function begins here
mov eax, 0x04 ; 0x04 = the write() syscall
mov ebx, 0x1 ; 0x1 = standard output
mov ecx, [ebp+8] ; the string we want to print is the first argument of this function
; at this point we wish to set edx to the length of the string. time to call _strlen
push eax ; save the caller-saved registers (I choose to not save edx)
push ecx
push dword [ebp+8] ; push _strlen's argument, the string argument to _print_msg. NASM
; complains if you do not put a size directive here, and I'm not sure
; why. Anyway, a pointer is a dword (4 bytes)
call _strlen ; eax is now equal to the length of the string
mov edx, eax ; move the length into edx where we wanted it
add esp, 4 ; remove 4 bytes from the stack (one 4-byte char* argument)
pop ecx ; restore caller-saved registers
pop eax
; we're done calling _strlen and setting up the syscall
int 0x80
leave
ret
_strlen:
enter 0, 0 ; save the previous frame's base pointer and adjust ebp
; Here I'd save the callee-saved registers, but I won't be modifying any
; My function begins here
mov eax, 0 ; length = 0
mov ecx, [ebp+8] ; copy the function's first argument (pointer to the first character
; of the string) into ecx (which is caller-saved, so no need to save it)
_strlen_loop_start:
cmp byte [ecx], 0 ; dereference that pointer and compare it to null. Here we have to
; explicitly mention it's a byte since the size of the pointer is
; ambiguous (is it a 4 byte integer? 2? 1?). This is called called a
; Size Directive
je _strlen_loop_end ; jump out of the loop if it is equal to null
add eax, 1 ; add 1 to our return value
add ecx, 1 ; increment to the next character in the string
jmp _strlen_loop_start ; jump back to the start
_strlen_loop_end:
; My function ends here. eax is equal to my function's return value
; Here I'd restore the callee-saved registers, but I didn't save any
leave ; deallocate and restore the previous frame's base pointer
ret
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment