Why doesn't this simple assembly program return a number?
I'm trying to get it to print 12
hex, but its printing gibberish.
global _start
section .text
_start:
mov eax, 0x4
mov ebx, 0x1
mov ecx, var1
mov edx, 2
int 0x80
;exit the program gracefully
mov eax, 0x1
mov ebx, 0x5
int 0x80
section .data
var1: db 0x12
The primary stumbling block you have run into is attempting to write the value of 0x12
(ASCII 18) to stdout
. A quick check of the ASCII chart will reveal that this value is non-printable. In assembly, you can only write characters to stdout
. What that means is when faced with writing a numeric value , you have to separate the value into its digits, convert the digits to their ASCII representation (by adding '0'
or 0x30
or 48
decimal) to the value.
It's no different than manually converting a number to a string in C or any other language. You basically divide the original number by 10
repeatedly, saving the remainder each time as the digit to a buffer, then writing the buffer out in reverse order to write the ASCII representation of the number to stdout
(there are many example on SO).
Before looking at an example that will separate all digits into a buffer and write them to stdout
, let's just cover some basics in your example and get it to print something (anything). The first concept in assembly you need to cement in your brain is that all labels (variables) in assembly point to a memory address not a numeric value. When you assign a label in assembly, eg:
var1 db 0x12
you are storing a single byte of data whose value is 12-hex
at the memory location pointed to by var1
. var1
is a pointer to that memory location. In nasm, if you want to operate on/or reference the value at that location, you must dereference the pointer to the address by enclosing the pointer in [ ]
(just like dereferencing a pointer in C with *var1
).
The next concept to cement, is that sys_write (syscall 4)
expects a beginning memory address, not a value, stored in ecx
. It will then write edx
bytes of data to the file descriptor stored in ebx
(1 - stdout
). mov
ing the address of var1
into edx
will allow us to dereference the value at that address by operating on [edx]
. To convert individual digits to their ASCII value you add '0'
(or 0x30) to them, but what will the result be if we just add '0'
to [edx]
? (present value 0x12
(18) + 0x30
(48) = 0x42
(66) -- which just happens to be ASCII 'B'
)
(we will aslo cheat and append a newline
( 0xa
) to the end of var1
and make it 2-bytes so we don't have to mess with a separate call)
Putting this together with your example would result in:
section .text
global _start
_start:
mov eax, 4 ; linux sys_write
mov ebx, 1 ; stdout
mov ecx, var1 ; mov address of var1 to ecx
add byte [ecx], 0x30 ; add '0' to first byte of var1
mov edx, 2 ; number of chars to print
int 0x80 ; syscall
mov eax, 0x1 ; __NR_exit 1
xor ebx, ebx ; exit code of 0
int 0x80
section .data
var1 db 0x12, 0xa ; ASCII 18 (non-printable) with newline
Compiling and running the program will result in:
$ ./convert
B
Now we can move on to a full example that will separate each digit for the value stored at var1
and then print each digit of 0x12
(decimal 18) to stdout
followed by a newline
. (outputting a conversion to the hex representation is left to you -- there are several examples you can search the web and find, int 80h.org comes to mind.)
A brief example in nasm would be:
section .text
global _start
_start:
mov edi, result ; address of buffer in destination index
xor eax, eax ; zero out eax
mov al, [var1] ; put the value of var1 in al to divide
mov bl, 10 ; base 10 divisor to find remainder
; separate remainder digits into result buffer
remloop:
div bl ; divide current value by 10
mov [edi], ah ; move the remainder to result
cmp al, 0 ; is the quotient zero?
je printchar ; if it is we are done
xor ah, ah
inc edi ; move offset in result string (note digits
jmp remloop ; of answer are stored in reverse order)
printchar:
add byte [edi], 0x30 ; add ascii '0' to digit to get printable char
mov eax, 4 ; linux sys_write
mov ebx, 1 ; stdout
mov ecx, edi ; point to current digit
mov edx, 1 ; number of chars to print
int 0x80 ; syscall
dec edi ; point to next digit
cmp edi, result ; are we past the final digit?
jge printchar ; if not, keep printing to stdout
; print newline
mov eax, 4 ; linux sys_write
mov ebx, 1 ; stdout
mov ecx, newline ; address of newline
mov edx, 1 ; number of chars to print
int 0x80 ; syscall
; exit the program gracefully
mov eax, 0x1 ; __NR_exit 1
mov ebx, 0x5 ; exit code of 5
int 0x80
section .data
var1 db 0x12 ; ASCII 18 (non-printable)
result times 8 db 0 ; 8 byte buffer for result
newline db 0xa ; newline character
If you build the code:
nasm -f elf -o convert.o convert.asm
ld -m elf_i386 -o convert convert.o
You can then display the ASCII output of 0x12
or 18
ASCII:
$ ./convert
18
For an excellent web reference for Assembly see The Art of Assembly Language Programming . Read it. All of it. It's that good. While it is primarily written for 8086, all principles are 100% applicable to current assembly programming. The only differences are register sizes, calling conventions and syscall numbers for x86_64.
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.