4
\$\begingroup\$

This fasm x64 (Linux) code seems very crude and repetitive, but it gets the job done. How could I perform this task in a more idiomatic manner?

format ELF64 executable 3
segment readable executable
entry $
prompt_user:
mov edx,prompt_len 
lea rsi,[prompt] ;<------ String
mov edi,1 ; STDOUT
mov eax,1 ; sys_write
syscall
get_user_input:
mov rax, 0 ; sys_read
mov rdi, 0 ; STDIN
lea rsi, [bit_string] ; <----- defined memory location
mov rdx, 2 ; <---- # of chars to read
syscall
;DEBUG CODE:
;lea rax, [bit_string]
;mov [rax], byte 'A'
;mov [rax+1], byte 'B'
lea rax, [bit_string]
cmp [rax], byte '0'
je load_0
cmp [rax], byte '1'
je load_1
cmp [rax], byte '2'
je load_2
cmp [rax], byte '3'
je load_3
cmp [rax], byte '4'
je load_4
cmp [rax], byte '5'
je load_5
cmp [rax], byte '6'
je load_6
cmp [rax], byte '7'
je load_7
cmp [rax], byte '8'
je load_8
cmp [rax], byte '9'
je load_9
cmp [rax], byte 'A'
je load_A
cmp [rax], byte 'B'
je load_B
cmp [rax], byte 'C'
je load_C
cmp [rax], byte 'D'
je load_D
cmp [rax], byte 'E'
je load_E
cmp [rax], byte 'F'
je load_F
load_0:
lea rsi, [n0]
jmp print_nibble
load_1:
lea rsi, [n1]
jmp print_nibble
load_2:
lea rsi, [n2]
jmp print_nibble
load_3:
lea rsi, [n3]
jmp print_nibble
load_4:
lea rsi, [n4]
jmp print_nibble
load_5:
lea rsi, [n5]
jmp print_nibble
load_6:
lea rsi, [n6]
jmp print_nibble
load_7:
lea rsi, [n7]
jmp print_nibble
load_8:
lea rsi, [n8]
jmp print_nibble
load_9:
lea rsi, [n9]
jmp print_nibble
load_A:
lea rsi, [nA]
jmp print_nibble
load_B:
lea rsi, [nB]
jmp print_nibble
load_C:
lea rsi, [nC]
jmp print_nibble
load_D:
lea rsi, [nD]
jmp print_nibble
load_E:
lea rsi, [nE]
jmp print_nibble
load_F:
lea rsi, [nF]
jmp print_nibble
nibble_two:
lea rax, [bit_string]
cmp [rax+1], byte '0'
je load_0
cmp [rax+1], byte '1'
je load_1
cmp [rax+1], byte '2'
je load_2
cmp [rax+1], byte '3'
je load_3
cmp [rax+1], byte '4'
je load_4
cmp [rax+1], byte '5'
je load_5
cmp [rax+1], byte '6'
je load_6
cmp [rax+1], byte '7'
je load_7
cmp [rax+1], byte '8'
je load_8
cmp [rax+1], byte '9'
je load_9
cmp [rax+1], byte 'A'
je load_A
cmp [rax+1], byte 'B'
je load_B
cmp [rax+1], byte 'C'
je load_C
cmp [rax+1], byte 'D'
je load_D
cmp [rax+1], byte 'E'
je load_E
cmp [rax+1], byte 'F'
je load_F
print_intro:
mov edx,intro_len 
lea rsi,[intro] 
mov edi,1 ; STDOUT
mov eax,1 ; sys_write
syscall
ret
print_nibble:
cmp [position], 0 ; If were on first iter, then print intro
jne skip_intro
push rsi
call print_intro
pop rsi
skip_intro:
mov rax, 1
cmp al, [position]
jl go_out
mov edx, 4 
mov edi,1 ; STDOUT
mov eax,1 ; sys_write
syscall
add [position], 1
jmp nibble_two
go_out:
mov edx,1 
lea rsi,[nl] 
mov edi,1 ; STDOUT
mov eax,1 ; sys_write
syscall
xor edi,edi ; exit code 0
mov eax,60 ; sys_exit
syscall
segment readable writeable
prompt db 'Enter a byte in the format: F6', 0xA
prompt_len = $ - prompt
nl db 0xA
intro db 'In binary: ', 0
intro_len = $ - intro
bit_string rb 2
position db 0
n0 db '0000', 0
n1 db '0001', 0
n2 db '0010', 0
n3 db '0011', 0
n4 db '0100', 0
n5 db '0101', 0
n6 db '0110', 0
n7 db '0111', 0
n8 db '1000', 0
n9 db '1001', 0
nA db '1010', 0
nB db '1011', 0
nC db '1100', 0
nD db '1101', 0
nE db '1110', 0
nF db '1111', 0
asked Nov 30, 2019 at 4:56
\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

In a high-level language, I would write it as:

def put_nibble(buf: array[byte], n: int):
 buf[0] = '0' + ((n shr 3) bitand 1)
 buf[1] = '0' + ((n shr 2) bitand 1)
 buf[2] = '0' + ((n shr 1) bitand 1)
 buf[3] = '0' + ((n shr 0) bitand 1)
def char_to_nibble(ch: byte) -> int:
 if '0' <= ch <= '9': return ch
 ch = ch bitor 0x20 # convert to lowercase
 if 'a' <= ch <= 'z': return ch - 'a' + 10
 error
def byte_to_binary(b: str):
 buf = byte[8] # reserve memory on the stack
 hi = char_to_nibble(b[0])
 put_nibble(buf, hi)
 lo = char_to_nibble(b[1])
 put_nibble(but + 4, lo)
 sys_write(1, buf, 8)

This way you avoid comparing each possible digit on its own.

The above code also reduces memory access to the memory on the stack, since it doesn't need any external strings for the bit patterns.

Sure, the put_nibble code looks a bit repetitive, but you can merge all the '0' + ... together into a single addition:

add dword [buf], 0x30303030 ; 0x30 == '0'

You could also compute the buffer for a whole nibble in a register and then write it to memory in a single instruction:

; input: al = the nibble to print
; output: ebx = 4 bytes ASCII buffer containing the 4 binary digits
xor ebx, ebx
rcr al, 1
adc ebx, '0'
rol ebx, 8
rcr al, 1
adc ebx, '0'
rol ebx, 8
rcr al, 1
adc ebx, '0'
rol ebx, 8
rcr al, 1
adc ebx, '0'
rol ebx, 8
mov [buf], ebx

Once you have translated the above high-level code into assembler, you probably want to optimize the code a bit. Or just use a compiler to do the heavy work for you. Write your code in C or in Go, assemble it into machine code and look at the result. Some keywords for searching:

  • go tool objdump
  • gcc -S binary.c -O2

By the way, you should not place the string literals for the prompt and the bit patterns in a writeable segment since they are not intended to be overwritten by any part of your program.

answered Nov 30, 2019 at 11:00
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.