Making rainbows happen in your terminal

Hey there! In this post, I will be coding a clone of the lolcat program. Lolcat is basically cat with colors. It copies its standard input to its standard output, but modifies the content so that it’s colorful. Here’s a screenshot of lolcat.

Please note that this article is a bit long. You can go here for screenshots and the final code.

The original lolcat is written in Ruby, but our rewrite will be in x64 assembly. So let’s begin by creating a makefile in our project directory.

all : nasm -f elf64 -o lolcat.o lolcat.asm ld -o lolcat lolcat.o clean : rm -f lolcat.o lolcat test : make all echo "Testing the lolcat ^_^" | ./lolcat

We will be using nasm as our assembler. The -f elf64 part means that we want a 64-bit ELF binary. After we write our code, we can run make to compile it or make test to test it. That said, let’s get to writing the code.

In this article I will write the assembly code and give an approximate translation to C under it

section .text global _start _start: nop

void main () { }

In assembly, all of our executable code lives in the .text section. We make _start global, because that’s where the assembler will look for the entry point of our program.

The exit function

This program basically does nothing. The nop instruction does not do anything, so this program should exit cleanly right? In reality, if we were to run this program we would be greeted with a Segmentation fault. That is because we need to use the exit syscall. Normally the C standard library does this for us but since we aren’t using it, we need to do it ourselves. So let’s create an exit function that we will call at the end of _start.

exit: mov rax, 60 mov rdi, 0 syscall

void exit () { exit_syscall ( 0 ); // Exits with return code 0 }

This code calls the 60th system call with the argument 0, which is basically exit(0); in C. Now our start function should look like this.

_start: call exit

void main () { exit (); }

If we ran this now, we would get the expected result of nothing happening.

Putting characters on the screen

But doing nothing isn’t very exciting now, is it? Let’s do something more interesting and print something to the console. First of all, we are going to need a character buffer. This is the equivalent of a char in C. We will use this to read and write characters. This will go to a special section called .data. This is where our strings and static variables will live.

section .data char_buffer db 0

This is similar to unsigned char char_buffer = 0; in C. To print a character, we will use the 1st system call, which is write.

put_char: mov [char_buffer], rdi mov rax, 1 mov rdi, 1 mov rsi, char_buffer mov rdx, 1 syscall ret

void put_char ( char character ) { // The write syscall takes a character buffer and a length // Since our function prints only 1 character, that length is always 1 syscall_write ( STDOUT , & character , 1 ); }

This function gets the first argument, puts it into char_buffer and calls write(STDOUT, char_buffer, 1); .

By modifying our _start function to this, we can print Test.

_start: mov rdi, 'T' call put_char mov rdi, 'e' call put_char mov rdi, 's' call put_char mov rdi, 't' call put_char call exit

void main () { put_char ( 'T' ); put_char ( 'e' ); put_char ( 's' ); put_char ( 't' ); exit (); }

Reading characters

In order to have a working cat, we need to read characters and write them back. Right now we can only write characters. Let’s create a function to read characters as well. We will use system call 0 for this, which is read.

read_char: mov rax, 0 mov rdi, 0 mov rsi, char_buffer mov rdx, 1 syscall mov rax, [char_buffer] ret

char read_char () { char * char_buffer ; //* // Read one character from the standard input and put it into char_buffer. syscall_read ( STDIN , & char_buffer , 1 ); return char_buffer ; }

I don’t know if you noticed this, but this function doesn’t do any error handling. We can make it return NULL when there are no more characters to read.

read_char: mov rax, 0 mov rdi, 0 mov rsi, char_buffer mov rdx, 1 syscall cmp eax, 0 jg .success mov rax, 0 ret .success: mov rax, [char_buffer] ret

char read_char () { char * char_buffer ; //* int result = syscall_read ( STDIN , & char_buffer , 1 ); if ( result > 0 ) { return char_buffer ; } else { return 0 } }

We have a cat

With those additions, we should be able to make a regular, non-colored cat now. Let’s change our start function to this.

_start: call read_char mov rdi, rax cmp rdi, 0 je .end call put_char jmp _start .end: call exit

void main () { while ( true ) { char input_character = read_char (); if ( input_character != 0 ) { put_char ( input_character ); } else { break ; } } exit (); }

Bring in the rainbows

So how can we change the color of the text we’re printing? With ANSI Codes of course. This means we can print ESC[31m to turn our text red or ESC[32;1m to make it bright green. ESC is 27 in decimal.

We will make a function that takes an integer and changes colors to 1,2,3,4,5,6,7 repeating. The reason we don’t want to print black is because we don’t want to have invisible characters on a black terminal.

set_color: push rdi mov rdi, 27 ;escape call put_char mov rdi, '[' call put_char mov rdi, '3' call put_char pop rdi mov rax, rdi mov rdi, 6 div rdi mov rdi, '0' inc rdx add rdi, rdx call put_char mov rdi, ';' call put_char mov rdi, '1' call put_char mov rdi, 'm' call put_char ret

void set_color ( int i ) { put_char ( 27 ); // ANSI Escape put_char ( '[' ); put_char ( '3' ); char color = ( i % 6 ) + 1 ; // Between 1-7 put_char ( '0' + color ); // Turn integer to ASCII char put_char ( ';' ); put_char ( '1' ); // Make the colors bright put_char ( 'm' ); }

Now we can modify our _start function once again in order to use this new color function. We will keep an integer value and increment it every time we print a character. This will change the color of the printed text in a repeated fashion.

_start: mov r12, 0 .loop: call read_char mov rdi, rax cmp rdi, 0 je .end call put_char mov rdi, r12 call set_color inc r12 jmp .loop .end: mov rdi, 0 call exit

void main () { int color = 0 ; while ( true ) { char input_character = read_char (); if ( input_character != 0 ) { put_char ( input_character ); set_color ( i ); i ++ ; } else { break ; } } exit (); }

Conclusion

And like that, our lolcat is complete. Time to test it. Here are some screenshots.

[cowsay lolcat](/img/articles/lolcat-clone-in-x64-assembly/screenshot.png)

[hexdump lolcat](/img/articles/lolcat-clone-in-x64-assembly/screenshot_2.png)

You can find the whole asm file here.

Thanks for sticking till the end. That was quite a long article but I hope you liked it.