Last active
January 8, 2020 16:45
-
-
Save gurchik/79dca38ccc2a5e263d0635f1b7737ec9 to your computer and use it in GitHub Desktop.
A heavily-documented Hello World in x86 assembly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; 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