I found an implementation of unsigned integer conversion in x86 assembly, and I tried plugging it in but being new to assembly and not having a debugging env there yet, it's difficult to understand why it's not working. I would also like it to work with signed integers so it can capture error messages from syscalls.
Wondering if one could show how to fix this code to get the signed integer to print, without using printf but using strprn provided by this answer.
%define a rdi
%define b rsi
%define c rdx
%define d r10
%define e r8
%define f r9
%define i rax
%define EXIT 0x2000001
%define EXIT_STATUS 0
%define READ 0x2000003 ; read
%define WRITE 0x2000004 ; write
%define OPEN 0x2000005 ; open(path, oflag)
%define CLOSE 0x2000006 ; CLOSE
%define MMAP 0x2000197 ; mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t offset)
; szstr computes the lenght of a string.
; rdi - string address
; rdx - contains string length (returned)
strsz:
xor rcx, rcx ; zero rcx
not rcx ; set rcx = -1 (uses bitwise id: ~x = -x-1)
xor al,al ; zero the al register (initialize to NUL)
cld ; clear the direction flag
repnz scasb ; get the string length (dec rcx through NUL)
not rcx ; rev all bits of negative -> absolute value
dec rcx ; -1 to skip the null-term, rcx contains length
mov rdx, rcx ; size returned in rdx, ready to call write
ret
; strprn writes a string to the file descriptor.
; rdi - string address
; rdx - contains string length
strprn:
push rdi ; push string address onto stack
call strsz ; call strsz to get length
pop rsi ; pop string to rsi (source index)
mov rax, WRITE ; put write/stdout number in rax (both 1)
mov rdi, 1 ; set destination index to rax (stdout)
syscall ; call kernel
ret
; mov ebx, 0xCCCCCCCD
itoa:
xor rdi, rdi
call itoal
ret
; itoa loop
itoal:
mov ecx, eax ; save original number
mul ebx ; divide by 10 using agner fog's 'magic number'
shr edx, 3 ;
mov eax, edx ; store quotient for next loop
lea edx, [edx*4 + edx] ; multiply by 10
shl rdi, 8 ; make room for byte
lea edx, [edx*2 - '0'] ; finish *10 and convert to ascii
sub ecx, edx ; subtract from original number to get remainder
lea rdi, [rdi + rcx] ; store next byte
test eax, eax
jnz itoal
exit:
mov a, EXIT_STATUS ; exit status
mov i, EXIT ; exit
syscall
_main:
mov rdi, msg
call strprn
mov ebx, -0xCCCCCCCD
call itoa
call strprn
jmp exit
section .text
msg: db 0xa, " Hello StackOverflow!!!", 0xa, 0xa, 0
With this working it will be possible to properly print signed integers to STDOUT, so you can log the registers values.
https://codereview.stackexchange.com/questions/142842/integer-to-ascii-algorithm-x86-assembly
How to print a string to the terminal in x86-64 assembly (NASM) without syscall?
How do I print an integer in Assembly Level Programming without printf from the c library?
https://baptiste-wicht.com/posts/2011/11/print-strings-integers-intel-assembly.html
How to get length of long strings in x86 assembly to print on assertion
My answer on How do I print an integer in Assembly Level Programming without printf from the c library? which you already linked shows that serializing an integer into memory as ASCII decimal gives you a length, so you have no use for (a custom version of) strlen here.
(Your msg has an assemble-time constant length, so it's silly not to use that.)
To print a signed integer, implement this logic:
if (x < 0) {
print('-'); // or just was_negative = 1
x = -x;
}
unsigned_intprint(x);
Unsigned covers the abs(most_negative_integer) case, e.g. in 8-bit - (-128) overflows to -128 signed. But if you treat the result of that conditional neg as unsigned, it's correct with no overflow for all inputs.
Instead of actually printing a - by itself, just save the fact that the starting number was negative and stick the - in front of the other digits after generating the last one. For bases that aren't powers of 2, the normal algorithm can only generate digits in reverse order of printing,
My x86-64 print integer with syscall answer treats the input as unsigned, so you should simply use that with some sign-handling code around it. It was written for Linux, but replacing the write system call number will make it work on Mac. They have the same calling convention and ABI.
And BTW, xor al,al is strictly worse than xor eax,eax unless you specifically want to preserve the upper 7 bytes of RAX. Only xor-zeroing of full registers is handled efficiently as a zeroing idiom.
Also, repnz scasb is not fast; about 1 compare per clock for large strings.
For strings up to 16 bytes, you can use a single XMM vector with pcmpeqb / pmovmskb / bsf to find the first zero byte, with no loop. (SSE2 is baseline for x86-64).
Related
I am trying to write a program that accepts 2 digits as user input, and then outputs their sum. I keep getting segmentation error when trying to run program(I am able to input 2 digits, but then the program crashes). I already check answers to similar questions and many of them pointed out to clear the registers, which I did, but I am still getting a segmentation fault.
section .text
global _main ;must be declared for linker (ld)
default rel
_main: ;tells linker entry point
call _readData
call _readData1
call _addData
call _displayData
mov RAX, 0x02000001 ;system call number (sys_exit)
syscall
_addData:
mov byte [sum], 0 ; init sum with 0
lea EAX, [buffer] ; load value from buffer to register
lea EBX, [buffer1] ; load value from buffer1 to register
sub byte [EAX], '0' ; transfrom to digit
sub byte [EBX], '0' ; transform to digit
add [sum], EAX ; increment value of sum by value from register
add [sum], EBX ; increment value of sum by value from 2nd register
add byte [sum], '0' ; convert to ASCI
xor EAX, EAX ; clear registers
xor EBX, EBX ; clear registers
ret
_readData:
mov RAX, 0x02000003
mov RDI, 2
mov RSI, buffer
mov RDX, SIZE
syscall
ret
_readData1:
mov RAX, 0x02000003
mov RDI, 2
mov RSI, buffer1
mov RDX, SIZE
syscall
ret
_displayData:
mov RAX, 0x02000004
mov RDI, 1
mov RSI, sum
mov RDX, SIZE
syscall
ret
section .bss
SIZE equ 4
buffer: resb SIZE
buffer1: resb SIZE
sum: resb SIZE
I see that, unlike other languages I learned, it is quite difficult to find a good source /tutorial about programming assembly using nasm on x86_64 architecture. Is there any kind of walkthrough for beginners(so I do not need to ask on SO everytime I am stuck :D)
I am trying to build an x86 program that reads a file into memory. It uses a few different syscalls, and messes with memory and such. There's a lot in there to figure out.
To simplify debugging and figuring this out, I wanted to add assert statements which, if there's a mismatch, it prints out a nice error message. This is the first step in learning assembly so I can print the numbers and strings that get placed on different registers and such after operations. Then I can print them out and debug them without any fancy tools.
Wondering if one could help me write an ASSERT AND PRINT in NASM for Mac x86-64. I have this so far:
%define a rdi
%define b rsi
%define c rdx
%define d r10
%define e r8
%define f r9
%define i rax
%define EXIT 0x2000001
%define EXIT_STATUS 0
%define READ 0x2000003 ; read
%define WRITE 0x2000004 ; write
%define OPEN 0x2000005 ; open(path, oflag)
%define CLOSE 0x2000006 ; CLOSE
%define MMAP 0x2000197 ; mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t offset)
%define PROT_NONE 0x00 ; no permissions
%define PROT_READ 0x01 ; pages can be read
%define PROT_WRITE 0x02 ; pages can be written
%define PROT_EXEC 0x04 ; pages can be executed
%define MAP_SHARED 0x0001 ; share changes
%define MAP_PRIVATE 0x0002 ; changes are private
%define MAP_FIXED 0x0010 ; map addr must be exactly as requested
%define MAP_RENAME 0x0020 ; Sun: rename private pages to file
%define MAP_NORESERVE 0x0040 ; Sun: don't reserve needed swap area
%define MAP_INHERIT 0x0080 ; region is retained after exec
%define MAP_NOEXTEND 0x0100 ; for MAP_FILE, don't change file size
%define MAP_HASSEMAPHORE 0x0200 ; region may contain semaphores
;
; Assert equals.
;
%macro ASSERT 3
cmp %1, %2
jne prepare_error
prepare_error:
push %3
jmp throw_error
%endmacro
;
; Print to stdout.
;
%macro PRINT 1
mov c, getLengthOf(%1) ; "rdx" stores the string length
mov b, %1 ; "rsi" stores the byte string to be used
mov a, 1 ; "rdi" tells where to write (stdout file descriptor: 1)
mov i, WRITE ; syscall: write
syscall
%endmacro
;
; Read file into memory.
;
start:
ASSERT PROT_READ, 0x01, "Something wrong with PROT_READ"
mov b, PROT_READ
mov a, PROT_WRITE
xor a, b
mov f, 0
mov e, -1
mov d, MAP_PRIVATE
mov c, a
mov b, 500000
mov a, 0
mov i, MMAP
syscall
PRINT "mmap output "
PRINT i ; check what's returned
PRINT "\n"
mov e, i
mov b, O_RDONLY
mov a, "Makefile"
mov i, OPEN
syscall
mov a, i
mov b, e
mov i, READ
syscall
;
; Exit status
;
exit:
mov a, EXIT_STATUS ; exit status
mov i, EXIT ; syscall: exit
syscall
throw_error:
PRINT pop() ; print error or something
jmp exit
mov rsi, "abcdefgh" is a mov-immediate of the string contents, not a pointer to it. It only exists as an immediate if you do that.
Your macro will need to switch to .rodata and back to put the string in memory; possibly you could turn it into a sequence of push-immediate onto the stack with NASM macros, but that sounds hard.
So you can use the usual msglen equ $ - msg to get the length. (Actually using NASM local labels so the macro doesn't create conflicts).
See NASM - Macro local label as parameter to another macro where I wrote basically this answer a couple weeks ago. But not exactly a duplicate because it didn't have the bug of using the string as an immediate.
NASM's mechanism for letting macros switch sections and then return to whatever section they expanded in is to have section foo define a macro __?SECT?__ as [SECTION foo]. See the manual and the above linked Q&A.
; write(1, string, sizeof(stringarray))
; clobbers: RDI, RSI, RDX, RCX,R11 (by syscall itself)
: output: RAX = bytes written, or -errno
%macro PRINT 1
[section .rodata] ; change section without updating __?SECT?__ macro
;; NASM macro-local labels
%%str db %1 ; put the string in read-only memory
%%strln equ $ - %%str ; current position - string start
__?SECT?__ ; change back to original sectoin
mov edx, %%strlen ; len
lea rsi, [rel %%str] ; buf = the string. (RIP-relative for position-independent)
mov edi, 1 ; fd = stdout
mov eax, WRITE
syscall
%endmacro
This doesn't attempt to combine duplicates of the same string. Using it many times with the same message will be inefficient. This doesn't matter for debugging.
I could have left your %defines for RDI, and let NASM optimize mov rdi, 1 (7 bytes) into mov edi, 1 (5 bytes). But YASM won't do that so it's better to make it explicit if you care about anyone building your code with YASM.
I used a RIP-relative LEA because that's the most efficient way to put a static address into a register in position-independent code. In Linux non-PIE executables, use mov esi, %%str (5 bytes and can run on any port, more than LEA). But on OS X, the base virtual address where an executable is mapped/loaded is always above 2^32, and you never want mov r64, imm64 with a 64-bit absolute address.
See How to load address of function or label into register
On Linux, where system-call numbers are small integers, you could use lea eax, [rdi-1 + WRITE] to do eax = SYS_write with a 3 byte instruction vs. 5 for mov.
The standard names for call-number constants are POSIX SYS_foo from sys/syscall.h or Linux __NR_foo from asm/unistd.h. But NASM can't #include C preprocessor #define macros, so you'd need to mechanically convert one of those headers to NASM syntax, e.g. with some script.
Or if manually defining names, just choose %define SYS_write 1
I have to prepare program for 8086 processor which converts quaternary to octal number.
My idea:
Multiplying every digit by exponents of 4 and add to register. Later check the highest exponent of 8 not higher than sum from first step.
Divide by exponents of 8 until remainder equals 0. Every result of dividing is one digit in octal.
But for 16-digits number last exponent of 4 is 4^15. I suppose It isn't optimal algorithm.
Is there any other way? Maybe to binary and group by 3 digits.
Turns out you can indeed process values 3 digits at a time. Done this way, you can process strings of arbitrary length, without being limited by the size of a register. Not sure why you might need to, unless aliens try to communicate with us using ascii strings of quaternary digits with huge lengths. Might happen.
It's possible to do the translation either way (Right to Left or Left to Right). However, there's a bit of a challenge to either:
If you are processing RtL, you need to know the length of the output string before you start (so that you know where to write the digits as you compute them). This is do-able, but a bit tricky. In simplest terms, the length is ((strlen(Q) + 2) / 3) * 2. That almost gets it. However, you can end up with a blank space at the beginning for a number of cases. "1" as well as "10" will give the blank space. "20" won't. The correct value can be computed, but it's annoying.
Likewise, processing LtR has a similar problem. You don't have the problem of figuring out where to write digits, but consider: If the string to convert it "123", then the conversion is simple (33 octal). But what if you start processing, and the complete string is "1231" (155 octal)? In that case what you need to process it like "001231" (01 55). IOW, digits can be processed in groups of 3, but you need to handle the initial case where the number of digits doesn't evenly divide by 3.
Posting solutions to homework is usually something I avoid. However I doubt you are going to turn this in as your 'solution,' and it's (barely) possible that google might send someone here who needs something similar.
A few things to note:
This code is intended to be called from C using Microsoft's fastcall (it made testing easier) and compiled with masm.
While it is written in 32bit (my environment), there's nothing that particularly requires 32bit in it. Since you said you were targeting 8086, I've tried to avoid any 'advanced' instructions. Converting to 16bit or even 64bit should not present much of a challenge.
It processes from left to right.
As with any well-written routine, it validates its parameters. It outputs a zero length string on error, such as invalid digits in the input string.
It will crash if the output buffer is NULL. I suppose I could return a bool on error (currently returns void), but, well, I didn't.
I'm sure the code could be tighter (couldn't it always?), but for "homework project quality," it seems reasonable.
Other than that, that comments should explain the code.
.386
.model flat
.code
; Call from C via:
; extern "C" void __fastcall PrintOct(const char *pQuat, char *pOct);
; On Entry:
; ecx: pQuat
; edx: pOct
; On Exit:
; eax, ecx, edx clobbered
; all others preserved
; If pOct is zero bytes long, an error occurred (probably invalid digits)
#PrintOct#8 PROC
; -----------------------
; If pOct is NULL, there's nothing we can do
test edx, edx
jz Failed
; -----------------------
; Save the registers we modify (except for
; eax, edx and ecx which we treat as scratch).
push esi
push ebx
push edi
mov esi, ecx
mov edi, edx
xor ebx, ebx
; -----------------------
; esi: pQuat
; edi: pOct
; ebx: zero (because we use lea)
; ecx: temp pointer to pQuat
; Reject NULL pQuat
test esi, esi
jz WriteNull
; -----------------------
; Reject 0 length pQuat
mov bl, BYTE PTR [esi]
test bl, bl
jz WriteNull
; -----------------------
; How many chars in pQuat?
mov dl, bl ; bl is first digit as ascii. Preserve it.
CountLoop:
inc ecx ; One more valid char
; While we're counting, check for invalid digits
cmp dl, '0'
jl WriteNull
cmp dl, '3'
jg WriteNull
mov dl, BYTE PTR [ecx] ; Read the next char
test dl, dl ; End of string?
jnz CountLoop
sub ecx, esi
; -----------------------
; At this point, there is at least 1 valid digit, and
; ecx contains # digits
; bl still contains first digit as ascii
; Normally we process 3 digits at a time. But the number of
; digits to process might not be an even multiple of 3.
; This code finds the 'remainder' when dividing ecx by 3.
; It might seem like you could just use 'div' (and you can),
; but 'div' is so insanely expensive, that doing all these
; lines is *still* cheaper than a single div.
mov eax, ecx
mov edx, 0AAAAAAABh
mul edx
shr edx, 1
lea edx, [edx+edx*2]
sub ecx, edx ; This gives us the remainder (0-2).
; If the remainder is zero, use the normal 3 digit load
jz LoadTriplet
; -----------------------
; Build a triplet from however many leading 'odd' digits
; there are (1 or 2). Result is in al.
lea eax, DWORD PTR [ebx-48] ; This get us the first digit
; If there was only 1 digit, don't try to load 2
cmp cl, 1
je OneDigit
; Load the other digit
shl al, 2
mov bl, BYTE PTR [esi+1]
sub bl, 48
or al, bl
OneDigit:
add esi, ecx ; Update our pQuat pointer
jmp ProcessDigits
; -----------------------
; Build a triplet from the next 3 digits.
; Result is in al.
; bl contains the first digit as ascii
LoadTriplet:
lea eax, DWORD PTR [ebx-48]
shl al, 4 ; Make room for the other 2 digits.
; Second digit
mov cl, BYTE PTR [esi+1]
sub cl, '0'
shl cl, 2
or al, cl
; Third digit
mov bl, BYTE PTR [esi+2]
sub bl, '0'
or al, bl
add esi, 3 ; Update our pQuat pointer
; -----------------------
; At this point
; al: Triplet
; ch: DigitWritten (initially zeroed when computing remainder)
ProcessDigits:
mov dl, al
shr al, 3 ; left digit
and dl, 7 ; right digit
; If we haven't written any digits, and we are
; about to write a zero, skip it. This deals
; with both "000123" and "2" (due to OneDigit,
; the 'left digit' might be zero).
; If we haven't written any digits yet (ch == 0), and the
; value we are are about to write is zero (al == 0), skip
; the write.
or ch, al
jz Skip1
add al, '0' ; Convert to ascii
mov BYTE PTR [edi], al ; Write a digit
inc edi ; Update pointer to output buffer
jmp Skip1a ; No need to check again
Skip1:
or ch, dl ; Both check and update DigitWritten
jz Skip2
Skip1a:
add dl, '0' ; Convert to ascii
mov BYTE PTR [edi], dl ; Write a digit
inc edi ; Update pointer to output buffer
Skip2:
; Load the next digit.
mov bl, BYTE PTR [esi]
test bl, bl
jnz LoadTriplet
; -----------------------
; All digits processed. We know there is at least 1 valid digit
; (checked on entry), so if we never wrote anything, the value
; must have been zero. Since we skipped it to avoid
; unnecessary preceding zeros, deal with it now.
test ch, ch
jne WriteNull
mov BYTE PTR [edi], '0'
inc edi
; -----------------------
; Write the trailing NULL. Note that if the returned string is
; 0 bytes long, an error occurred (probably invalid digits).
WriteNull:
mov BYTE PTR [edi], 0
; -----------------------
; Cleanup
pop edi
pop ebx
pop esi
Failed:
ret
#PrintOct#8 ENDP
end
I've run a string with 1,000,000,000 quaternary digit thru it as well as all the values from 0-4,294,967,295. Seems to work.
I for one welcome our new 4-digited alien overlords.
How can I print a negative number in tasm? Please anyone help me.
For e.g If I do a subtraction 2-7=(-5). How can I print -5?
You will need to perform the number conversion yourself (e.g. such as with your own function). I threw this TASM5 compatible Win32 app together as an example although I'm sure you can find countless other number->string conversion samples online.
This might seem like a lot of code for such a simple goal however if you place "common" functions that are designed to be generic enough to be used in any number of programs in an include file or library, the real program is only the appMain() function at the bottom which is only about 10 lines of code. I threw everything in one file for purposes of being a complete working example.
You are interested in the numToString() function shown below which converts a DWORD to a number within a buffer passed to the function. The rest of the code is just a framework to call the function along with some common WriteConsole wrappers. numToString() supports converting numbers to any base (such as hex and binary). In this example, I pass arguments to specify base-10 with the negative flag enabled (indicating to treat the DWORD number as signed).
The algorithm for numToString() is as follows:
-If number is to be interpreted as signed and IS negative, convert it to positive and set bHasSign flag
-Repeatedly divide positive (in loop) number by base (radix) and convert remainder to ASCII char; this gives us each digit of the resulting string starting with the least significant digit
-Append negative "-" to end based on bHasSign flag
-NULL terminate destination buffer
-Because the algorithm actually computes digits from least significant to most significant, we need to reverse the resultant string (including negative sign) before returning. In English, we read digits starting with most significant first.
The reverse algorithm swaps the outer bytes, then the next inner bytes, and so on until it reaches the middle of the string. This swapping allows us to avoid setting up a temporary buffer for the character reversal.
Program Output:
The number is: -352
Program Listing:
.386
.model flat
GetStdHandle PROTO STDCALL :DWORD
WriteConsoleA PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
ExitProcess PROTO STDCALL :DWORD
getStringLength PROTO STDCALL :DWORD
outputString PROTO STDCALL :DWORD, :DWORD
numToString PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
NULL equ 0
STD_OUTPUT_HANDLE equ -11
MAXLEN_BINARYNUM_BUFFER equ 33 ;max string length for number->string buffer assuming smallest base (32 bits) + NULL accounts for largest possible binary number
MAXLEN_BINARYNUM_BUFFER_AS_DWORD equ 9 ;36-byte hack for MAXLEN_BINARYNUM_BUFFER used below so TASM doesn't mess up Win32 stack alignment
CODE_ERROR_BUFLEN equ -10
.data
szMsg db 'The number is: ',0
.code
;
; getStringLength() - returns zero terminated string length in eax
;
getStringLength PROC STDCALL pszString:DWORD
;count the characters in the string and store result in ecx
lea esi,pszString ;make esi point to first character in string
mov esi,[esi]
mov edi,esi ;edi pointer to first character also
cld
##countloop:
lodsb
cmp al,0
jne SHORT ##countloop
dec esi ;we're now pointing past NULL, so back up one
sub esi,edi ;subtract beginning pointer from pointer at NULL so that esi now contains string count
;return string length in returned in eax
mov eax,esi
ret
getStringLength ENDP
;
; outputString(pszString) - outputs a zero terminated string
; returns 0 on failure or final character count upon success (excluding NULL terminator)
;
outputString PROC STDCALL
LOCALS
ARG hConsole:DWORD
ARG pszString:DWORD
LOCAL dwCharsWrote:DWORD
;get string length (in eax)
lea esi,pszString
mov esi,[esi]
call getStringLength ,esi
;load string pointer into esi
lea esi,pszString
mov esi,[esi]
;load dwCharsWrote pointer into edx
lea edx,dwCharsWrote
;write string to console
call WriteConsoleA ,hConsole,edi,eax,edx,NULL
;if the function fails, return 0, otherwise return the characters written
cmp eax,0
jz ##funcdone
mov eax,[dwCharsWrote]
##funcdone:
ret
outputString ENDP ;outputString()
;
; numToString() - converts unsigned DWORD to digit string
;
numToString PROC STDCALL
ARG uNum:DWORD
ARG uBase:DWORD
ARG pDest:DWORD
ARG uDestLen:DWORD
ARG bSigned:DWORD
LOCAL pEnd:DWORD
LOCAL bHasSign:DWORD
;default to number not signed
mov [bHasSign],0
;if we're interpreting number as signed, see if 32nd bit (sign flag) is set
cmp bSigned,0
jz SHORT ##setup_div_loop
test DWORD PTR [uNum],080000000h
jz SHORT ##setup_div_loop
mov [bHasSign],1 ;number is signed: set sign flag, then remove the sign
not [uNum] ; from the bits VIA: bitwise NOT, then adding 1
inc [uNum] ; this will give us an unsigned representation of the same number
; for which we will add a negative sign character at end
;setup divide/remainder loop
##setup_div_loop:
mov edi,[pDest] ;edi points to beginning of dest buffer
mov ebx,edi ;pEnd variable will point 1 past end of buffer
add ebx,[uDestLen]
mov [pEnd],ebx
mov ebx,[uBase] ;ebx will hold the base to convert to
mov eax,uNum ;eax is our number to convert
##divloop:
;range check buffer
cmp edi,[pEnd] ;if we are we outside of buffer?
jae SHORT ##errorbufoverflow ; we've overflowed
;setup 32-bit divide - divide eax by radix (ebx)
xor edx,edx
div ebx
;assume remainder can always fit in byte (should range check radix)
; and convert value to ASCII (values past 9 get hex digits
cmp dl,9
ja SHORT ##convletters
add dl,48 ;convert 0-9 to ASCII digits
jmp SHORT ##chardone
##convletters:
add dl,55 ;convert A-F (letter A starts at 65, and we want to back it up by 10 to 55 such that 65 is for the value 10)
##chardone:
mov BYTE PTR [edi],dl ;remainder goes in buffer
inc edi ;point at next character
cmp eax,0 ;if quotient nonzero, keep dividing
jnz SHORT ##divloop
;
; loop above complete
;
;do we need to add sign?
cmp [bHasSign],0
jz SHORT ##nullTerminate
; yes, range check that we have room
cmp edi,[pEnd]
jae SHORT ##errorbufoverflow
; we have room, add sign character to string
mov BYTE PTR [edi],'-'
inc edi
##nullTerminate:
;range check buffer that we can NULL terminate the string
cmp edi,[pEnd]
jae SHORT ##errorbufoverflow
mov BYTE PTR [edi],0 ;SUCCESS - NULL terminate
;return character count in eax (not counting null)
mov eax,edi ;subtract start of buffer
sub eax,[pDest] ; from ending position of buffer to obtain count
;
; we now have correct numeric string, but with least significant digits first
; we must reverse the string to make it human-readible
;
mov esi,[pDest] ;esi is left pointer
dec edi ;edi is right pointer
##reverseloop:
cmp esi,edi
jae SHORT ##procdone ;if esi ever meets or goes past edi, we're done!
mov dl,BYTE PTR [esi] ;swap esi and edi bytes
xchg dl,BYTE PTR [edi]
mov BYTE PTR [esi],dl
dec edi
inc esi
jmp SHORT ##reverseloop
##errorbufoverflow:
;ERROR: buffer length too small
mov eax,CODE_ERROR_BUFLEN
##procdone:
ret
numToString ENDP
;
; appMain()
;
appMain PROC STDCALL
LOCAL hConsole:DWORD
LOCAL szCode[MAXLEN_BINARYNUM_BUFFER_AS_DWORD]:DWORD
;get handle to console
call GetStdHandle ,STD_OUTPUT_HANDLE
mov hConsole,eax
;output message
call outputString ,hConsole,OFFSET szMsg
;this is your negative value
mov eax,-352
;convert value to string
lea esi,[szCode]
call numToString ,eax,10,esi,MAXLEN_BINARYNUM_BUFFER,1
;output number buffer
lea esi,[szCode]
call outputString ,hConsole,esi
ret
appMain ENDP
;
; Program Entry Point
;
_start:
call appMain
call ExitProcess ,eax
end _start
I have been trying to interface with the standard C library in Windows in assembler and I'm having trouble. For some reason, I can't make printf accept floating point variables, so something is wrong here.
This is the shortest program I can create that demonstrates the problem. I've included comments that explain my understanding of what is supposed to be happening.
Thanks
;
; Hello64.asm
; A simple program to print a floating point number in windows
;
; assemble: nasm float64.asm -f win64
; link: golink /console /entry main float64.obj MSVCRT.dll
;
; tell assembler to generate 64-bit code
;
bits 64
; data segment
section .data use64
pi dq 3.14159
textformat: db "hello, %lf!",0x0a, 0x00 ; friendly greeting
; set up the .text segment for the code
section .text use64
; global main is the entry point
global main
; note that there is no _ before printf here, unlike in OS X
extern printf
main:
mov rcx, textformat
movq xmm0, qword [pi]
mov rax, 1 ; need to tell printf how many floats
call printf
; note next step - this puts a zero in rax
xor rax,rax
ret ; this returns to the OS based on how Windows calls programs.
; this return causes a delay then the program exits.
You managed to mix the microsoft and the sysv convention. The correct way is:
mov rcx, textformat
movq xmm1, qword [pi]
movq rdx, xmm1 ; duplicate into the integer register
sub rsp, 40 ; allocate shadow space and alignment (32+8)
call printf
add rsp, 40 ; restore stack
xor eax, eax
ret
According to MSDN, when using varargs:
For floating-point values only, both the integer and the floating-point register will contain the float value in case the callee expects the value in the integer registers.