; Listing 9-9 ; ; Formatted string output: option casemap:none tab = 9 nl = 10 .const ttlStr byte "Listing 9-9", 0 fmtStr1 byte "fmtOut: value=%19I64d, string='%s'" byte nl, 0 .data buffer byte 30 dup (?) .code externdef printf:proc ; Return program title to C++ program: public getTitle getTitle proc lea rax, ttlStr ret getTitle endp ; utoStr- ; ; Unsigned integer to string. ; ; Inputs: ; ; RAX: Unsigned integer to convert ; RDI: Location to hold string. ; ; Note: for 64-bit integers, resulting ; string could be as long as 20 bytes ; (including the zero-terminating byte). bigNum qword 1000000000000000000 utoStr proc push rcx push rdx push rdi push rax sub rsp, 10 ; Quick test for zero to handle that special case: test rax, rax jnz not0 mov byte ptr [rdi], '0' jmp allDone ; The FBSTP instruction only supports 18 digits. ; 64-bit integers can have up to 19 digits. ; Handle that 19th possible digit here: not0: cmp rax, bigNum jb lt19Digits ; The number has 19 digits (which can be 0-9). ; pull off the 19th digit: xor edx, edx div bigNum ;19th digit in AL mov [rsp+10], rdx ;Remainder or al, '0' mov [rdi], al inc rdi ; The number to convert is non-zero. ; Use BCD load and store to convert ; the integer to BCD: lt19Digits: fild qword ptr [rsp+10] fbstp tbyte ptr [rsp] ; Begin by skipping over leading zeros in ; the BCD value (max 19 digits, so the most ; significant digit will be in the LO nibble ; of DH). mov dx, [rsp+8] mov rax, [rsp] mov ecx, 20 jmp testFor0 Skip0s: shld rdx, rax, 4 shl rax, 4 testFor0: dec ecx ;Count digits we've processed test dh, 0fh ;Because the number is not 0 jz Skip0s ;this always terminates ; At this point the code has encountered ; the first non-0 digit. Convert the remaining ; digits to a string: cnvrtStr: and dh, 0fh or dh, '0' mov [rdi], dh inc rdi mov dh, 0 shld rdx, rax, 4 shl rax, 4 dec ecx jnz cnvrtStr ; Zero-terminte the string and return: allDone: mov byte ptr [rdi], 0 add rsp, 10 pop rax pop rdi pop rdx pop rcx ret utoStr endp ; itoStr - Signed integer to string conversion ; ; Inputs: ; RAX - Signed integer to convert ; RDI - Destination buffer address itoStr proc push rdi push rax test rax, rax jns notNeg ; Number was negative, emit '-' and negate ; value. mov byte ptr [rdi], '-' inc rdi neg rax ; Call utoStr to convert non-negative number: notNeg: call utoStr pop rax pop rdi ret itoStr endp ; uSize- ; Determines how many character positions it will take ; to hold a 64-bit numeric-to-string conversion. ; VERY brute-force algorithm. Just compares the value ; in RAX against 18 powers of 10 to determine if there ; are 1-19 digits in the number. ; ; Input ; RAX- Number to check ; ; Returns- ; RAX- Number of character positions required. dig2 qword 10 dig3 qword 100 dig4 qword 1000 dig5 qword 10000 dig6 qword 100000 dig7 qword 1000000 dig8 qword 10000000 dig9 qword 100000000 dig10 qword 1000000000 dig11 qword 10000000000 dig12 qword 100000000000 dig13 qword 1000000000000 dig14 qword 10000000000000 dig15 qword 100000000000000 dig16 qword 1000000000000000 dig17 qword 10000000000000000 dig18 qword 100000000000000000 dig19 qword 1000000000000000000 uSize proc push rdx cmp rax, dig10 jae ge10 cmp rax, dig5 jae ge5 mov edx, 4 cmp rax, dig4 jae allDone dec edx cmp rax, dig3 jae allDone dec edx cmp rax, dig2 jae allDone dec edx jmp allDone ge5: mov edx, 9 cmp rax, dig9 jae allDone dec edx cmp rax, dig8 jae allDone dec edx cmp rax, dig7 jae allDone dec edx cmp rax, dig6 jae allDone dec edx ;Must be 5 jmp allDone ge10: cmp rax, dig14 jae ge14 mov edx, 13 cmp rax, dig13 jae allDone dec edx cmp rax, dig12 jae allDone dec edx cmp rax, dig11 jae allDone dec edx ;Must be 10 jmp allDone ge14: mov edx, 19 cmp rax, dig19 jae allDone dec edx cmp rax, dig18 jae allDone dec edx cmp rax, dig17 jae allDone dec edx cmp rax, dig16 jae allDone dec edx cmp rax, dig15 jae allDone dec edx ;Must be 14 allDone: mov rax, rdx ;Return digit count pop rdx ret uSize endp ; iSize- ; Determines the number of print positions required by ; a 64-bit signed integer. iSize proc test rax, rax js isNeg jmp uSize ; Effectively a call and ret ; If the number is negative, negate it, call uSize, ; and then bump the size up by 1 (for the '-' character) isNeg: neg rax call uSize inc rax ret iSize endp ; utoStrSize- ; Converts an unsigned integer to a formatted string ; having at least "minDigits" character positions. ; If the actual number of digits is smaller than ; "minDigits" then this procedure inserts encough ; "pad" characters to extend the size of the string. ; ; Inputs: ; RAX - Number to convert to string ; CL- minDigits (minimum print positions) ; CH- Padding character ; RDI - Buffer pointer for output string utoStrSize proc push rcx push rdi push rax call uSize ;Get actual number of digits sub cl, al ;>= the minimum size? jbe justConvert ; If the minimum size is greater than the number of actual ; digits, we need to emit padding characters here. ; ; Note that this code used "sub" rather than "cmp" above. ; As a result, CL now contains the number of padding ; characters to emit to the string (CL is always positive ; at this point, as negative and zero results would have ; branched to justConvert). padLoop: mov [rdi], ch inc rdi dec cl jne padLoop ; Okay, any necessary padding characters have already been ; added to the string. Call utostr to convert the number ; to a string and append to the buffer: justConvert: mov rax, [rsp] ;Retrieve original value call utoStr pop rax pop rdi pop rcx ret utoStrSize endp ; itoStrSize- ; Converts a signed integer to a formatted string ; having at least "minDigits" character positions. ; If the actual number of digits is smaller than ; "minDigits" then this procedure inserts encough ; "pad" characters to extend the size of the string. ; ; Inputs: ; RAX - Number to convert to string ; CL- minDigits (minimum print positions) ; CH- Padding character ; RDI - Buffer pointer for output string itoStrSize proc push rcx push rdi push rax call iSize ;Get actual number of digits sub cl, al ;>= the minimum size? jbe justConvert ; If the minimum size is greater than the number of actual ; digits, we need to emit padding characters here. ; ; Note that this code used "sub" rather than "cmp" above. ; As a result, CL now contains the number of padding ; characters to emit to the string (CL is always positive ; at this point, as negative and zero results would have ; branched to justConvert). padLoop: mov [rdi], ch inc rdi dec cl jne padLoop ; Okay, any necessary padding characters have already been ; added to the string. Call utostr to convert the number ; to a string and append to the buffer: justConvert: mov rax, [rsp] ;Retrieve original value call itoStr pop rax pop rdi pop rcx ret itoStrSize endp ; Here is the "asmMain" function. public asmMain asmMain proc push rbp mov rbp, rsp sub rsp, 64 ;Shadow storage ; Because the functions all preserve RDI, we only need ; to do this once: lea rdi, buffer mov rax, 1 mov cl, 19 mov ch, '0' call utoStrSize lea rcx, fmtStr1 mov rdx, 1 lea r8, buffer call printf mov rax, 12 mov cl, 19 mov ch, '.' call utoStrSize lea rcx, fmtStr1 mov rdx, 12 lea r8, buffer call printf mov rax, 123 mov cl, 19 mov ch, '+' call utoStrSize lea rcx, fmtStr1 mov rdx, 123 lea r8, buffer call printf mov rax, 1234 mov cl, 19 mov ch, '*' call utoStrSize lea rcx, fmtStr1 mov rdx, 1234 lea r8, buffer call printf mov rax, 12345 mov cl, 19 mov ch, '$' call utoStrSize lea rcx, fmtStr1 mov rdx, 12345 lea r8, buffer call printf mov rax, 123456 mov cl, 19 mov ch, '_' call utoStrSize lea rcx, fmtStr1 mov rdx, 123456 lea r8, buffer call printf mov rax, 1234567 mov cl, 19 mov ch, '-' call utoStrSize lea rcx, fmtStr1 mov rdx, 1234567 lea r8, buffer call printf mov rax, 12345678 mov cl, 19 mov ch, '@' call utoStrSize lea rcx, fmtStr1 mov rdx, 12345678 lea r8, buffer call printf mov rax, 123456789 mov cl, 19 mov ch, ' ' call utoStrSize lea rcx, fmtStr1 mov rdx, 123456789 lea r8, buffer call printf mov rax, 1234567890 mov cl, 19 mov ch, ' ' call utoStrSize lea rcx, fmtStr1 mov rdx, 1234567890 lea r8, buffer call printf mov rax, 12345678901 mov cl, 19 mov ch, ' ' call utoStrSize lea rcx, fmtStr1 mov rdx, 12345678901 lea r8, buffer call printf mov rax, 123456789012 mov cl, 19 mov ch, ' ' call utoStrSize lea rcx, fmtStr1 mov rdx, 123456789012 lea r8, buffer call printf mov rax, 1234567890123 mov cl, 19 mov ch, ' ' call utoStrSize lea rcx, fmtStr1 mov rdx, 1234567890123 lea r8, buffer call printf mov rax, 12345678901234 mov cl, 19 mov ch, ' ' call utoStrSize lea rcx, fmtStr1 mov rdx, 12345678901234 lea r8, buffer call printf mov rax, 123456789012345 mov cl, 19 mov ch, ' ' call utoStrSize lea rcx, fmtStr1 mov rdx, 123456789012345 lea r8, buffer call printf mov rax, 1234567890123456 mov cl, 19 mov ch, ' ' call utoStrSize lea rcx, fmtStr1 mov rdx, 1234567890123456 lea r8, buffer call printf mov rax, 12345678901234567 mov cl, 19 mov ch, ' ' call utoStrSize lea rcx, fmtStr1 mov rdx, 12345678901234567 lea r8, buffer call printf mov rax, 123456789012345678 mov cl, 19 mov ch, ' ' call utoStrSize lea rcx, fmtStr1 mov rdx, 123456789012345678 lea r8, buffer call printf mov rax, 1234567890123456789 mov cl, 19 mov ch, ' ' call utoStrSize lea rcx, fmtStr1 mov rdx, 1234567890123456789 lea r8, buffer call printf allDone: leave ret ;Returns to caller asmMain endp end