I am new to assembly i don't know how to convert 64 bit function to 32 bit to 16 bit to 8 bit
Purpose of below functions is to print number and return number of digits in it.
64 bit:
global print_uint64
section .text
print_uint64:
mov rax,rdi
mov rdi,10
mov rsi,rsp
while:
xor rdx ,rdx
div rdi
add rdx ,48
dec rsi
mov [rsi],dl
cmp rax ,0
jne while
mov rax,1
mov rdi,1
lea rdx,[rsp]
sub rdx,rsi
syscall
lea rax,[rsp]
sub rax,rsi
ret
this works fine
32 bit:
global print_uint32
section .text
print_uint32:
mov eax,edi
mov edi,10
mov rsi,rsp
while:
xor edx ,edx
div edi
add edx ,48
dec rsi
mov [rsi],dl
cmp eax ,0
jne while
mov eax,1
mov edi,1
lea edx,[rsp]
sub edx,esi
syscall
lea eax,[rsp]
sub eax,esi
ret
this works fine
16 bit:
global print_uint16
section .text
print_uint16:
mov ax,di
mov di,10
mov rsi,rsp
while:
xor dx ,dx
div di
add dx ,48
dec rsi
mov [rsi],dl
cmp ax ,0
jne while
mov ax,1
mov di,1
lea dx,[rsp]
sub dx,si
syscall
lea ax,[rsp]
sub ax,si
ret
but this did not work
I studied some questions on stack overflow regarding to this question, what i understand is i can't change rsp to esp because esp set upper 32 bits to zero so when we use [] on that access the memory not allocated to that program so it throws segment fault.
My question is:
1)What is the basic rules to convert 64 bit to 32 bit to 16 bit to 8 bit.
The basic rules is that pointers are still 64-bit no matter what width the data is. Just like in C, sizeof(int*)
and sizeof(char*)
are the same (on normal systems).
This is why all your versions have to use dec rsi
and mov [rsi],dl
: RSP holds a 64-bit pointer. Truncating it to 32-bit will not produce a valid pointer.
Also, syscall numbers and the fd
are still the same size; mov ax,1
and mov di,1
leave garbage in the high bytes of the register. Use strace./my_program
to decode what you actually passed to syscall
.
The narrow versions can just zero-extend their input to 32-bit and jump to the 32-bit version.
But other than that, the basic rules is to use 32-bit operand-size whenever possible; it's the natural size for x86-64 ( The advantages of using 32bit registers/instructions in x86-64 ). eg always zero RDX/EDX/DX with xor edx,edx
.
Writing a 32-bit register zero-extends to 64-bit, unlike 8/16 which just merge into the old value.
Why do x86-64 instructions on 32-bit registers zero the upper part of the full 64-bit register? Using 16-bit mov reg,imm16
and leaving high garbage is presumably why your system call didn't work.
Why doesn't GCC use partial registers?
Notably, lea dx,[rsp]
/ sub dx,si
potentially leaves garbage in the upper bits of RDX, the arg to the write
syscall.
This is a pointer subtract, calculating the number of elements in a char
buffer. It makes no sense to choose an operand-size for this based on the size of the input number. It's actually fine to do a narrow subtract as long as you make sure the result is zero-extended into RDX, because in this case you know the number of digits will be at most 19 (for the 64-bit version) because that's how long 2^64-1 is in base 10.
So mov edx, esp
/ sub edx, esi
is what you should have been doing in all the versions. Since the full RSP and RSI are nearby, their difference is small. Truncating the inputs before subtraction instead of truncating the result after doesn't change the result; carry propagates from low to high bits. See Which 2's complement integer operations can be used without zeroing high bits in the inputs, if only the low part of the result is wanted?
Using LEA to copy a register is not efficient; lea dx, [rsp]
is architecturally identical to mov dx, sp
but slower.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.