I'm writing a program to print binary string of a hardcoded word. Here is how it looks like currently:
main.asm
section .text
global _start
extern _print_binary_content
_start:
push word [word_to_print] ; pushing word. Can we push just one byte?
call _print_binary_content
mov rax, 60
mov rdi, 0
syscall
section .data
word_to_print: dw 0xAB0F
printer.asm
SYS_BRK_NUM equ 0x0C
BITS_IN_WORD equ 0x10
SYS_WRITE_NUM equ 0x01
STD_OUT_FD equ 0x01
FIRST_BIT_BIT_MASK equ 0x01
ASCII_NUMBER_OFFSET equ 0x30
section .text
global _print_binary_content
_print_binary_content:
pop rbp
xor ecx, ecx ;zeroing rcx
xor ebx, ebx ;zeroing rbx
pop bx ;the word to print the binary content of
;sys_brk for current location
mov rax, SYS_BRK_NUM
mov rdi, 0
syscall
;end sys_brk
mov r12, rax ;save the current brake location
;sys_brk for memory allocation 16 bytes
lea rdi, [rax + BITS_IN_WORD]
mov rax, SYS_BRK_NUM
syscall
;end sys_brk
xor ecx, ecx
mov cl, byte BITS_IN_WORD - 1; used as a counter in the loop below
loop:
mov dx, bx
and dx, FIRST_BIT_BIT_MASK
add dx, ASCII_NUMBER_OFFSET
mov [r12 + rcx], dl
shr bx, 0x01
dec cl
cmp cl, 0
jge loop
mov rsi, r12
mov rax, SYS_WRITE_NUM
mov rdi, STD_OUT_FD
mov rdx, BITS_IN_WORD
syscall
push rbp ; pushing return address back
ret
If I compile link and run this program it works. But the question is about performance and maybe conventions of writing assembly programs. In the file printer.asm
I cleaned ecx
twice which looks kind of not optimal. Maybe some registers were used not by their purpose (I used intel-manual).
Can you please help me to improve this very simple program?
brk
)? And then not free it when you're done? I'm wondering if you had a specific reason for doing that instead of using stack memory for your small fixed-size buffer like in this integer->decimal string function. Also, "binary" is ambiguous in this context. I thought from the title you were going to callwrite(1, &word, 2)
, but you're actually converting the word to a base-2 string. \$\endgroup\$push word [word_to_print]
- I would expect this one even to fail to compile, but it works! In 64b common OSes there're often very stringent requirements for thersp
modifications, like keeping it 16B aligned before calling other functions (if you want to respect the ABI calling convention, as in this case you are calling your own custom function, which is not obeying the ABI, you can misalign the stack without running into some crash). But it's not even clear why you put the word argument into the stack, while using custom calling convention, why don't you use registers instead? \$\endgroup\$pop
/push
of return address by usingebp
oresp
for addressing also arguments in memory), but first check 64b linux ABI examples, which is passing arguments in registers. = much easier to learn and understand and faster performance-wise, overall the 64b linux ABI is superior to 32b ABI (but has lot more requirements for thersp
value itself! That's tricky for people moving 32->64). Then check the 32b stack examples, to build your skills. \$\endgroup\$