flat assembler
Message board for the users of flat assembler.
Index
> Linux > Hexplore Assembly Demo 1 |
| Author |
|
|
chastitywhiterose 05 Feb 2026, 12:19
I don't know why I did this to myself, but I rewrote my Hexplore program in pure Intel Assembly language for Linux.
I made a short video to show exactly what it looks like using it https://www.youtube.com/watch?v=JtXdWqpmK4A The source is twice as big as chastehex and is split between 4 source files. This program is a full interactive hex editor that runs in a terminal. Using only Linux system calls and ANSI escape sequences, I can display all the text where I want in the terminal and even do fancy things like highlight the currently selected byte with green. Every time you switch to the previous/next page with the "Page Up" or "Page_Down" keys, it changes the file offset of a file named RAM and reads the new data. But it also saves the current page back to the file before switching pages or ending the program. The entire executable produced from my source and FASM is less than 3 kilobytes and runs extremely fast. Obviously, this assembly source is not portable to other platforms like the C version is, but it was a fun challenge! Code: format ELF executable entry main include 'chastelib32.asm' include "chasteio32.asm" include 'hexplore-ansi.asm' main: mov [radix],16 ;can choose radix for integer output! mov [int_width],1 mov [int_newline],0 mov eax, 2 ; invoke SYS_FORK (kernel opcode 2) int 80h cmp eax, 0 ; if eax is zero we are in the child process jz child ; jump if eax is zero to child label parent: ;the program that continues running after the child process ends ;before we begin the game loop, we will attempt to open a file named "RAM" mov eax,RAM_filename ; this filename is defined later in this source call putstring call putline call open cmp eax,0 js main_end ;end program if RAM could not be opened mov [RAM_filedesc],eax ; save the file descriptor number for later use ;before the main loop, we will load up to 256 bytes from the file ;I already wrote a function to do this in hexplore-ansi.asm ;and it handles it even when less than 256 bytes are read call file_load_page ;this is the game loop where were get input and process it accordingly loop_read_keyboard: ;this loop keeps reading from the keyboard mov eax,ansi_clear ;move to top left of screen call putstring mov eax,ansi_home ;move to top left of screen call putstring mov [red],0xFF mov [green],0xFF mov [blue],0xFF call set_text_rgb mov [x],16 mov [y],0 call move_cursor mov eax,title call putstring mov [x],57 mov [y],0 call move_cursor mov al,'X' call putchar mov al,'=' call putchar mov eax,[RAM_x_select] call putint call putspace mov al,'Y' call putchar mov al,'=' call putchar mov eax,[RAM_y_select] call putint mov [x],0 mov [y],2 call move_cursor call RAM_hexdump ;this section provides a visual way of knowing which byte is selected mov eax,[RAM_y_select] ;which row is it on? Y vertical coordinate mov [y],eax add [y],2 ;mov eax,[RAM_y_begin] ;add eax,18 mov eax,[RAM_x_select] ;which row is it on? Y vertical coordinate mov ebx,3 ;we will multiply by 3 on the next line mul ebx add eax,8 mov [x],eax ;change color for brackets mov [red],0xFF mov [green],0x00 mov [blue],0xFF call set_text_rgb call move_cursor mov al,'[' call putchar add [x],3 call move_cursor mov al,']' call putchar ;end of brackets section sub [x],2 ;go back a few characters call move_cursor ;change color again for byte highlight mov [red],0x00 mov [green],0xFF mov [blue],0x00 call set_text_rgb ;obtain selected byte for proper indexing changes mov ebx,[RAM_y_select] shl ebx,4 add ebx,[RAM_x_select] add ebx,RAM mov eax,0 mov al,[ebx] ;get the byte at this address for printing mov [int_width],2 call putint ;change color back to white mov [red],0xFF mov [green],0xFF mov [blue],0xFF call set_text_rgb ;where to move cursor in next function call mov [x],0 mov [y],19 call move_cursor ;move the cursor before displaying help information mov eax,help call putstring ;where to move cursor in next function call mov [x],0 mov [y],0 call move_cursor ;move the cursor before displaying keypress information mov eax,0 ;zero eax to receive the key value in al mov al,[key]; ;move the key pressed last time into al call putint ;print the number of this key call putspace ;print a space to keep it readable call putchar ;print the character in al register call putline ;print a line to make it easier to read ;will pause until a key is pressed call getchar ;call my function that reads a single byte from the keyboard cmp al,'q' ;test for q key. q stands for quit in this context jz main_shutdown ;jump to end of program if q was pressed call hexplore_input ;call the function to process the input and operate the editor jmp loop_read_keyboard ;continue the game loop main_shutdown: ;not just end, but save data and close the file properly ;when the program ends, we must first save bytes we changed to the file! call file_save_page mov eax,[RAM_filedesc] ;file number to close call close main_end: mov eax, 1 ; invoke SYS_EXIT (kernel opcode 1) mov ebx, 0 ; return 0 status on exit - 'No Errors' int 80h title db 'Hexplore : Chastity White Rose',0 help db 'Arrows=Select_Byte q=quit page_up/down=navigate_file',0xA db '0-f=Enter_Hexadecimal',0 ; This is the end of the parent process or main program ; The child process below only uses the stty command before returning to the parent proces child: ;This child process disables line buffer with stty ;execute a command from the child process mov edx, environment ; address of environment variables mov ecx, arguments ; address of the arguments to pass to the commandline mov ebx, command ; address of the file to execute mov eax, 11 ; invoke SYS_EXECVE (kernel opcode 11) int 80h ;this is the end of the child process which became the stty command and then terminated naturally byte_brackets db '[ ]',0 ;for displaying brackets around selected byte ;The execve call requires the path to a program, arguments passed to that program, and environment variables if relevant ;these strings are the execve call data command db '/bin/stty', 0h ; command to execute arg1 db 'cbreak', 0h arguments dd command dd arg1 ; arguments to pass to commandline (in this case just one) dd 0h ; end the struct environment dd 0h ; arguments to pass as environment variables (inthis case none) end the struct ;key is defined as dword even though only a byte is used ;this way, it loads into eax without trouble key db 0,0 prefix_k db "k=",0 getchar: push ebx push ecx push edx mov edx,1 ;number of bytes to read mov ecx,key ;address to store the bytes mov ebx,0 ;read from stdin mov eax,3 ;invoke SYS_READ (kernel opcode 3) int 80h ;call the kernel xor eax,eax ;set eax to 0 mov al,[key] ;set lowest part of eax to key read pop edx pop ecx pop ebx ret RAM_filename db "RAM",0 RAM_filedesc dd 0 ; file descriptor bytes_read dd 0 RAM db 0x100 dup '?',0 RAM_address dd 0 ;this is actually the address of the file which is used as a RAM substitute RAM_x_select dd 0 RAM_y_select dd 0 RAM_y_begin dd 2 RAM_hexdump: mov ebx,RAM mov ebp,[RAM_address] mov edx,0 ;the Y value for this loop RAM_dump_loop: mov ecx,0 ;the X value for this loop mov eax,ebp mov [int_width],8 call putint call putspace mov [int_width],2 mov eax,0 dump_byte_row: mov al,[ebx] normal_print: call putint call putspace normal_print_skip: inc ebx inc ecx cmp ecx,0x10; jnz dump_byte_row ;optionally, print chars after hex bytes call RAM_hexdump_text_row call putline add ebp,0x10 inc edx cmp edx,0x10 jnz RAM_dump_loop ret RAM_hexdump_text_row: push eax push ebx push ecx push edx sub ebx,0x10 mov ecx,0 mov eax,0 next_char: mov al,[ebx] ;if char is below '0' or above '9', it is outside the range of these and is not a digit cmp al,0x20 jb not_printable cmp al,0x7E ja not_printable printable: ;if char is in printable range,leave as is and proceed to next index jmp next_index not_printable: mov al,'.' ;otherwise replace with placeholder value next_index: call putchar inc ebx inc ecx cmp ecx,0x10 jnz next_char pop edx pop ecx pop ebx pop eax ret
|
|||||||||||||||||||||||||||||||
|
|
revolution 05 Feb 2026, 12:33
I think it would be safer to use SYS_IOCTL with a termios structure to change the tty mode, rather than using /bin/stty.
|
|||
|
|
revolution 05 Feb 2026, 15:54
SYS_IOCTL is a syscall. The only dependency is the kernel.
For specific numbers on x86: Code: SYS32_ioctl = 54 SYSx32_ioctl = 0x40000000 + 514 SYS64_ioctl = 16 |
|||
|
|
chastitywhiterose 05 Feb 2026, 16:06
revolution wrote: SYS_IOCTL is a syscall. The only dependency is the kernel. I am interested but don't have a clue how to know which registers to load those numbers with and what they mean. All of my assembly knowledge is scattered through different tutorials and outdated references. But if there is a way to do this syscall and understand it, I could probable shave 100 bytes off my executable. What system call reference are you using? |
|||
|
|
revolution 05 Feb 2026, 16:20
If the goal is to make the code as small as possible then using ioctl probably isn't the way. The termios structure needs some massaging to make sure all the options are set as required.
|
|||
|
|
revolution 05 Feb 2026, 16:36
The basic layout would be like this:
Code: struc termios2 { .input_flags rd 1 .output_flags rd 1 .control_flags rd 1 .local_flags rd 1 .line_discipline rb 1 .control_characters rb 19 ; NCCS .input_speed rd 1 .output_speed rd 1 }virtual at 0 termios2 termios2 sizeof.termios2 = $ end virtual HANDLE_STD_OUTPUT = 1 _IOC_WRITE = 1 _IOC_READ = 2 TCGETS2 = _IOC_READ shl 30 + sizeof.termios2 shl 16 + 0x542a TCSETSW2 = _IOC_WRITE shl 30 + sizeof.termios2 shl 16 + 0x542c termios termios2 ; instantiate in RAM ; eax ebx ecx edx int 0x80 ; SYS_IOCTL, HANDLE_STD_OUTPUT, TCGETS2, termios ; make modifications to termios structure here int 0x80 ; SYS_IOCTL, HANDLE_STD_OUTPUT, TCSETSW2, termios Last edited by revolution on 05 Feb 2026, 16:53; edited 1 time in total |
|||
|
|
revolution 05 Feb 2026, 16:40
BTW: It it good manners to save the existing state in termios buffer, make modifications in a new buffer, and then restore the previous state at exit. Then the user doesn't get a shock with new console settings making them angry.
|
|||
|
|
chastitywhiterose 05 Feb 2026, 17:25
revolution wrote: The basic layout would be like this: Yeah it probably won’t help me save size but I might make a separate example just to see if I can get this working at all. I have never actually used structures in fasm but this code looks legit. |
|||
|
|
revolution 05 Feb 2026, 17:55
There are a bunch of flags that can be set. The list looks something like this:
Code: ; /usr/include/asm-generic/ioctls.h _IOC_WRITE = 1 _IOC_READ = 2 TCGETS = 0x5401 TCSETSW = 0x5403 ; Allow the output buffer to drain, and set the current serial port settings TIOCEXCL = 0x540c TIOCOUTQ = 0x5411 FIONREAD = 0x541b TCGETS2 = _IOC_READ shl 30 + sizeof.termios2 shl 16 + 0x542a TCSETSW2 = _IOC_WRITE shl 30 + sizeof.termios2 shl 16 + 0x542c ; /usr/include/asm-generic/termbits.h CBAUD = 0010017o B0 = 0000000o ; hang up B50 = 0000001o B75 = 0000002o B110 = 0000003o B134 = 0000004o B150 = 0000005o B200 = 0000006o B300 = 0000007o B600 = 0000010o B1200 = 0000011o B1800 = 0000012o B2400 = 0000013o B4800 = 0000014o B9600 = 0000015o B19200 = 0000016o B38400 = 0000017o CSIZE = 0000060o CS5 = 0000000o CS6 = 0000020o CS7 = 0000040o CS8 = 0000060o CSTOPB = 0000100o CREAD = 0000200o PARENB = 0000400o PARODD = 0001000o HUPCL = 0002000o CLOCAL = 0004000o CBAUDEX = 0010000o BOTHER = 0010000o B57600 = 0010001o B115200 = 0010002o B230400 = 0010003o B460800 = 0010004o B500000 = 0010005o B576000 = 0010006o B921600 = 0010007o B1000000 = 0010010o B1152000 = 0010011o B1500000 = 0010012o B2000000 = 0010013o B2500000 = 0010014o B3000000 = 0010015o B3500000 = 0010016o B4000000 = 0010017o CIBAUD = 002003600000o ; input baud rate CMSPAR = 010000000000o ; mark or space (stick) parity CRTSCTS = 020000000000o ; flow control ; /usr/include/asm-generic/termbits.h IGNBRK = 0000001o BRKINT = 0000002o IGNPAR = 0000004o PARMRK = 0000010o INPCK = 0000020o ISTRIP = 0000040o INLCR = 0000100o IGNCR = 0000200o ICRNL = 0000400o IUCLC = 0001000o IXON = 0002000o IXANY = 0004000o IXOFF = 0010000o IMAXBEL = 0020000o IUTF8 = 0040000o ISIG = 0000001o ICANON = 0000002o XCASE = 0000004o ECHO = 0000010o ECHOE = 0000020o ECHOK = 0000040o ECHONL = 0000100o NOFLSH = 0000200o TOSTOP = 0000400o ECHOCTL = 0001000o ECHOPRT = 0002000o ECHOKE = 0004000o FLUSHO = 0010000o PENDIN = 0040000o IEXTEN = 0100000o EXTPROC = 0200000o OPOST = 0000001o OLCUC = 0000002o ONLCR = 0000004o OCRNL = 0000010o ONOCR = 0000020o ONLRET = 0000040o OFILL = 0000100o OFDEL = 0000200o NLDLY = 0000400o NL0 = 0000000o NL1 = 0000400o CRDLY = 0003000o CR0_ = 0000000o CR1_ = 0001000o CR2_ = 0002000o CR3_ = 0003000o TABDLY = 0014000o TAB0 = 0000000o TAB1 = 0004000o TAB2 = 0010000o TAB3 = 0014000o XTABS = 0014000o BSDLY = 0020000o BS0 = 0000000o BS1 = 0020000o VTDLY = 0040000o VT0 = 0000000o VT1 = 0040000o FFDLY = 0100000o FF0 = 0000000o FF1 = 0100000o |
|||
|
< Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2026, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.