According to this question, I have made an agreement with Peter Cordes to create such a question here, at the Code Review website.
I think that I should prepare the tutorial using the modern technologies as the Continuous Integration (hereinafter referred to as “CI”).
Here is the public repo (branch tests
).
And here is the successful CI-build with the tests.
The tests were written in C#/.NET Core, using the MSTest
framework, on Ubuntu (CI also uses Ubuntu docker image).
The source code may be found here.
It was divided into 4 files:
- functions.asm, the main source, which is working with the 64-bit syscalls
- sys_write.asm, the program, which using the
SYS_WRITE
syscall - sys_write.loop.asm, the program, which using the
SYS_WRITE
syscall repeatedly (10 times) - sys_read.asm, the program, which is using the
SYS_READ
syscall
The corresponding tests (in C#) are located here: https://gitlab.com/amegas/nasm.experiments/blob/tests/tests.cs
Let’s look at the main source file (functions.asm):
SECTION .data inputTemplate db "User input: ", 0h maxBytesToRead equ 255 sprint: push rdx push rdi push rax push rsi mov rax, rsi pop rsi jmp next_char ret sprint_linefeed: call sprint push rsi mov rsi, 0Ah push rsi mov rsi, rsp call sprint pop rsi pop rsi ret read_input: push rsi call kernel_fn_read_input mov r8 , rsi mov rsi, inputTemplate call sprint mov rsi, r8 call sprint pop rsi ret quit: call kernel_fn_quit ret next_char: cmp byte [rax], 0 jz count_length inc rax jmp next_char count_length: sub rax, rsi mov rdx, rax jmp kernel_fn_print_string kernel_fn_print_string: mov rax, 1 mov rdi, 1 syscall pop rax pop rdi pop rdx ret kernel_fn_read_input: push rax push rdx push rdi mov rax, 0 mov rdx, maxBytesToRead mov rdi, 0 syscall pop rdi pop rdx pop rax ret kernel_fn_quit: push rax push rdi mov rax, 60 mov rdi, 0 syscall pop rdi pop rax ret
Here, I’m providing the several functions for handling the SYS_WRITE
& SYS_READ
syscalls in 64-bit mode. The 1st remark, which I can make for myself, that I didn’t use the namespaces, which act like private methods in higher languages, like:
subrountineA: mov rcx, firstMsg call sprint jmp .finished .finished: mov rax, secondMsg ...
Peter has asked me: why you designed it that way (with push / pop of all registers you use, instead of letting it clobber rax,rcx,rdx,rsi,rdi, and r8-r11 like the x86-64 SystemV ABI allows for the standard function calling convention?
My answer: I’m pushing it to the stack for safe keeping. When the function has finished it’s logic, these registers should have their previous values, restored via the POP instruction. I’m achieving the result, that any values in the registers will be the same before and after. Also, I didn’t read the SystemV ABI at that moment, but reading right now (https://www.uclibc.org/docs/psABI-x86_64.pdf).
Yes, I’m new to x86/assembler/NASM
world, so I give my apologies, if have developed weak/bad source-code. I want to hear your constructive critique, because later I shall post an answer to Peter Cordes question (https://stackoverflow.com/questions/46087730/what-happens-if-you-use-the-32-bit-int-0x80-linux-abi-in-64-bit-code/) in beginner-friendly manner.
Intended purpose of the future tutorial: the simple application with the CI service, which may help the newbie to learn the correct use of the int 0x80
(for 32-bit) & syscall
(for 64-bit) Linux ABI, as I promise Peter. That’s why CI-service IS IMPORTANT, because it will show the beginner the conflict/exception. I want to prepare the failure tests, which will show the beginner, what happens if to use the 32-bit int 0x80 Linux ABI in 64-bit code with the deep diagnostics. Repo is public, so the beginner may execute the job and view the results in live mode.