When learning assembly programming, I found it extremely beneficial to be able to print registers. This is actually a very good program to write yourself while learning asm, but I will post it here anyways. Printing a string is much different than printing binary registers. If I have a register with values 1AB3529F, that will be printed as “..R.” Where “.” is non displayable characters.
We are going to write a very short routine that I will call printr (for print register). We are also going to write an even shorter driver program that will call printr and have it print our register that we pass to it. Let us start with the driver.
; Executable name - NONE ; Version - 1.0 ; Created Date - 20111108 ; Author - Jason Torola ; Description - A driver program to test our print routine ; Regs used - the value to be printed is passed to printr ; eax - number to be printed ; extern printr section .data section .bss section .text global _start _start: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; begin logic ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov eax,01f039453h ; Get number to print call printr ; Call our print routine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; end logic ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Exit: mov eax,1 ; sys_exit mov ebx,0 ; Set RC = 0 int 80h ; and return
As you can see, this is a very basic driver program. All it does is call our routine and exits. Call is actually a special instruction as it pushes the address of the next instruction after itself onto the stack (In our case it is `mov eax,1`). Call then transfers execution to the address represented by the label printr. Ret is the instruction that is associated with Call. Ret pops the address of the instruction pushed onto the stack by Call, and transfers execution to that address. It is very simple.
Somebody very wise told me (and still tells me actually), “It isn’t magic, it’s all just bits and bytes.” He is very correct. It may look like it is doing some sort of voodoo magic to get it’s work done, but when you look under the covers, it’s all very simple.
Let us look at our very simple print routine.
; Executable name - NONE ; Version - 1.0 ; Created Date - 20111108 ; Author - Jason Torola ; Description - A print routine for Linux ; Regs used - the value to be printed is passed in ; with eax ; section .data section .bss output resb 10 ; Output buffer that will hold our number carriage resb 1 ; Newline character section .text global printr printr: pushad ; Save all regs ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; begin logic ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov edi,eax ; save number to be printed mov ecx,10 ; base 10 output lea ebx,[output+9] ; set output field pointer to last byte mov [carriage],byte 10 ; move newline character to output field ; Divide the number you want to print by 10, the remainder will be ; the number and so on. E.G. if your number is 936 ; 936/10 = 93 rem 6 ; 93/10 = 9 rem 3 ; 9/10 = 0 rem 9 ; pt_loop01: xor edx,edx ; clear for divide div ecx ; Divide edx:eax by 10 ; In order to convert the decimal remainder to character, ; you simply add character 0 to the number. For example ; if your decimal number is 6 you take ; 6 + '0' = 6 + 48 = 54 = '6' add edx,'0' ; convert digit to char mov [ebx],byte dl ; save character to output field dec ebx ; decriment output pointer ; Done printing? We are done when our number is zero. test eax,eax ; Is our number zero? jnz pt_loop01 ; no, divide again pt_done: inc ebx ; get true start of number ; Print value mov eax,4 ; sys_write mov ecx,ebx ; get output buffer location lea edx,[output+11] ; Get pointer to byte past output buffer sub edx,ebx ; subtract both pointers to get length mov ebx,1 ; STDOUT int 80h ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; end logic ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Exit: ; Restore regs that were used popad ret ; return to caller
Again, notice how I do not do sys_exit. A sys_exit would return control back to the operating system, which is what I do not want to do. The stack hasn’t been cleaned up yet (there is still the return address on it). Not to mention our program could still have more work to do.
If you look closely also, you will notice that this program uses a “do while” loop. This is important because if we use a “while” loop, we will not be able to print a register that is zero. It will simply just print a newline character.
Let’s see how it turns out
jason@fitz:~/programming/programs/printr$ make nasm -w+all -f elf -l printr.lst printr.asm nasm -w+all -f elf -l driver.lst driver.asm ld -m elf_i386 -o run printr.o driver.o jason@fitz:~/programming/programs/printr$ ./run 520328275 jason@fitz:~/programming/programs/printr$
As we can see 1F039453 hex is indeed 520328275 decimal.
There is another way to print numbers which involves a very useful algorithm that will allow somebody to create their own bignum class. Using this method I was able to print numbers with as many digits as my computer had memory to hold. This was all in assembly. Remember, it’s all just bits and bytes.
Jason Torola