flat assembler
Message board for the users of flat assembler.
Index
> Windows > nasm printf from msvcrt.dll |
Author |
|
zhak 24 Jul 2008, 22:39
kernel32.dll contains API functions to work with console. For example, WriteConsole or ReadConsole. But to use them you should get standard input or output handle first. See Win32 API reference for details.
I use smth like this: section '.code' code readable executable main: invoke GetStdHandle, STD_OUTPUT_HANDLE mov [hStdOutput], eax invoke GetStdHandle, STD_INPUT_HANDLE mov [hStdInput], eax invoke WriteConsole, [hStdOutput],szMsg,dMsgSize,dBytesWritten,0 invoke ReadConsole, [hStdInput],lpBuffer,dSize,dBytesRead,0 |
|||
24 Jul 2008, 22:39 |
|
zhak 24 Jul 2008, 22:46
what about printf.... as I remember, format should be
format db "%s",0 but not format db "%s",13,0 it's incorrect, imho. also, you need 13,10 sequence for carriage return/line feed. not only 13 |
|||
24 Jul 2008, 22:46 |
|
LocoDelAssembly 24 Jul 2008, 23:59
Since it is the C library you in fact need 10 and it is recommended to have one somewhere just in case line buffering is enabled (the buffer is flushed when either it is full or a LF character appears in the stream flushing all the contents up to that LF character).
Code: include 'win32a.inc' NUMBER = $DEADBEEF format PE console 4.0 push NUMBER push fmt call [printf] add esp, 8 push nextLine call [printf] add esp, 4 push nextPrint call [printf] add esp, 4 push bye call [printf] add esp, 4 call [getchar] call [ExitProcess] fmt db "Hello, I'm printing number %X, I am the most useful program ever!", 10, 0 nextLine db "Now I'm in another line;", 0 nextPrint db "I'm still in the same line", 10, 0 bye db "Good bye :)", 0 data import library kernel32,'KERNEL32.DLL',\ msvcrt,'MSVCRT.DLL ' import kernel32,\ ExitProcess,'ExitProcess' import msvcrt,\ getchar, 'getchar',\ printf ,'printf' end data [edit]I want to add that when you set a breakpoint for WriteConsole for the program above in OllyDbg the following buffer is passed: Code: 0006FAF4 48 65 6C 6C 6F 2C 20 49 27 6D 20 70 72 69 6E 74 Hello, I'm print 0006FB04 69 6E 67 20 6E 75 6D 62 65 72 20 44 45 41 44 42 ing number DEADB 0006FB14 45 45 46 2C 20 49 20 61 6D 20 74 68 65 20 6D 6F EEF, I am the mo 0006FB24 73 74 20 75 73 65 66 75 6C 20 70 72 6F 67 72 61 st useful progra 0006FB34 6D 20 65 76 65 72 21 0D 0A m ever!.. As you can see printf converted $0A to the $0D, $0A sequence automatically since that is the meaning the '\n' char has on Windows platforms. The call stack: Code: Call stack of main thread Address Stack Procedure / arguments Called from Frame 0006FA64 7C81CC71 kernel32.WriteConsoleA kernel32.7C81CC6C 0006FAB0 0006FA68 00000007 hConsole = 00000007 0006FA6C 0006FAF4 Buffer = 0006FAF4 0006FA70 00000049 CharsToWrite = 49 (73.) 0006FA74 0006FAD8 pWritten = 0006FAD8 0006FA78 00000000 pReserved = NULL 0006FAB4 77C00218 ? kernel32.WriteFile MSVCRT.77C00212 0006FAB8 00000007 hFile = 00000007 0006FABC 0006FAF4 Buffer = 0006FAF4 0006FAC0 00000049 nBytesToWrite = 49 (73.) 0006FAC4 0006FAD8 pBytesWritten = 0006FAD8 0006FAC8 00000000 pOverlapped = NULL 0006FF00 77C0035A MSVCRT.77C00109 MSVCRT.77C00355 0006FEFC 0006FF3C 77C0EDB2 ? MSVCRT._write MSVCRT.77C0EDAD 0006FF38 0006FF40 00000001 handle = 1 0006FF44 001D42B0 buf = 001D42B0 0006FF48 00000048 len = 48 (72.) 0006FF5C 77C1311B MSVCRT.77C0ED7D MSVCRT.77C13116 0006FF58 0006FF6C 77C118AB MSVCRT.77C13100 MSVCRT.77C118A6 0006FF68 0006FFB8 00401010 ? MSVCRT.printf console.<ModuleEntryPoint>+0 0006FFB4 0006FFBC 00401049 format = "Hello, I'm printing number %X, I am the most useful program ever!\n" 0006FFC0 DEADBEEF <%X> = DEADBEEF |
|||
24 Jul 2008, 23:59 |
|
gavin 25 Jul 2008, 02:12
I changed the string to from 13 to 10.Thanks for that.
Can anyone explain why my code won't work? |
|||
25 Jul 2008, 02:12 |
|
LocoDelAssembly 25 Jul 2008, 02:30
Does your build procedure construct a CONSOLE executable? Windows won't create one for you when you use printf.
This: Code: call [printf] mov esp,ebp pop ebp ret 8 I suppose that is your way to remove printf's arguments from the stack? note that the last three instructions should be replaced with "add esp, 8" then, otherwise you won't never call exit(0). Try using "ALINK.EXE printf.obj -c -oPE console", maybe that's the way to create a console application (check with alink.exe -? to see how to properly set the subsystem because I'm guessing). [edit]I have downloaded alink, here the -? output Code: (output omitted) Options for PE files: -base addr Set base address of image -filealign addr Set section alignment in file -objectalign addr Set section alignment in memory -subsys xxx Set subsystem used Available options are: console Select character mode con " char " windows Select windowing mode win " gui " native Select native mode posix Select POSIX mode (output omitted) So it should be something like "ALINK.EXE printf.obj -c -oPE -subsys console" I don't have a NASM package and I'm not willing to get one so I can't test this for you. [/edit] |
|||
25 Jul 2008, 02:30 |
|
gavin 25 Jul 2008, 03:09
Code: call [printf] mov esp,ebp pop ebp ret 8 Well spotted I only started learning about calling conventions ,functions and the call stack and got mixed up. That switch did it thank you very much. I'll post my code just incase someone else has the same problems. Thanks again for your spending your time on this. One more question. Should I be using mov esp,ebp pop ebp to clean up the stack since its a _cdecl function? Code: NASM printf ; ;compile with: ; NASMW.EXE -fobj printf.asm ;link with: ; ALINK.EXE printf.obj -c -oPE ;ALINK.EXE printf.obj -c -oPE -subsys console %include "D:\programming\nasm\include\windows.inc" EXTERN ExitProcess IMPORT ExitProcess Kernel32.dll EXTERN printf IMPORT printf Msvcrt.dll segment .data USE32 string db "Hello",10,0 segment .code USE32 ..start push string call [printf] mov esp,ebp pop ebp add esp, 4 push dword 0 call [ExitProcess] |
|||
25 Jul 2008, 03:09 |
|
LocoDelAssembly 25 Jul 2008, 03:34
Don't use mov esp,ebp/pop ebp after calling anymore * That instruction sequence (which is equivalent to the leave instruction), is for functions epilogues.
A little example: Code: include 'win32a.inc' cdecl = 1 format PE console 4.0 push 7 push 31 call addTwoNumbers if defined cdecl ; then we need to release the space (or use it, it is important to be aware of it if you don't release it) add esp, 8 end if push eax push fmt call [printf] add esp, 8 call [getchar] push 0 call [ExitProcess] addTwoNumbers: ; A and B operands are passed on the stack ;;;; Prologue push ebp mov ebp, esp mov eax, [ebp+8] add eax, [ebp+12] ;;;; Epilogue leave ; Or mov esp, ebp | pop ebp if defined cdecl ret ; The stack has the space for the parameters allocated yet else ; stdcall ret 8 ; The space for the parameters has been released end if fmt db "addTwoNumbers(31, 7) = %u", 10, 0 data import library kernel32,'KERNEL32.DLL',\ msvcrt,'MSVCRT.DLL ' import kernel32,\ ExitProcess,'ExitProcess' import msvcrt,\ getchar, 'getchar',\ printf ,'printf' end data *Well, you could actually in the case that after returning from a cdecl function called from your own function you want to return, in such case the "add esp, 4*x" won't be needed because it will be immediately overwritten by "mov esp, ebp" (or leave). [edit] Added "push 0" before "call [ExitProcess]"[/edit] Last edited by LocoDelAssembly on 25 Jul 2008, 22:48; edited 1 time in total |
|||
25 Jul 2008, 03:34 |
|
gavin 25 Jul 2008, 15:59
Hi Locodelassembly
After looking at your examples I think I understand now what is happening perfectly. So basically with cdecl we add esp, arguments * 4 and with stdcall we do nothing as it is cleared up for us. The way you explained it was brilliant, much appreciated. If you don't mind,would you have a look at this and tell me if my understanding of this is crystal clear. Again thanks for spending your time helping me. Code: push sometext call [printf] add esp,4 From the above code here's whats happening below. I did this without a debugger . Code: ;sub esp,4 ebp 00000000 frame pointer ebp ;mov esp,sometext ebp+4 00000000 return address ;push eip + 2 ebp+8 00000000 sometext ;jmp _printf ;push ebp ;mov ebp,esp ;printf does it's thing...... ; mov esp, ebp --> stack now becomes ; esp 00000000 frame pointer now becomes esp ; ebp+4 00000000 return address ; ebp+8 00000000 sometext ; pop ebp --> stack now becomes ; ebp+4 00000000 return address ; ebp+8 00000000 sometext ;ret pop return address off stack and jmp to it ;add esp 4 ebp+8 00000000 sometext ; add esp was the return address and it now cleans the stack to what it was before we ran the above fucntion |
|||
25 Jul 2008, 15:59 |
|
LocoDelAssembly 25 Jul 2008, 17:18
There are some mistakes but the overall understanding is good.
Lets suppose that this is the code the CPU will execute: Code: push fmt call [printf] add esp, 4*1 The CPU will execute these "micro instructions": Code: sub esp, 4 mov [esp], fmt ; [ESP] = OFFSET fmt push eip + 5 ; [ESP] = return address ; [ESP+4] = OFFSET fmt jmp printf ; printf's prologue sub esp, 4 mov [esp], ebp ; [ESP] = old EBP; [ESP+4] = return address; [ESP+8] = OFFSET fmt mov ebp, esp ; [EBP] = old EBP; [EBP+4] = return address; [EBP+8] = OFFSET fmt sub esp, size_of_local_vars ; Now from EBP+8 and up we have the parameters and from EBP-4 down ; to ESP we have the local variables. From the ESP perspective we have ; from ESP to ESP+size_of_local_var the local variables and from ; ESP+size_of_local_var+8 and up the parameters but since every ; PUSH/POP affects the stack pointer (ESP) those bounds are not fixed ; along the entire function body. . . . ; printf's epilogue mov esp, ebp ; [ESP] = old EBP; [ESP+4] = return address; [ESP+8] = OFFSET fmt (or garbage since the function could used the parameter as local variable) mov ebp, [esp] add esp, 4 ; [ESP] = return address; [ESP+4] = OFFSET fmt (or garbage since the function could used the parameter as local variable) jmp dword [esp] ; [ESP] = return address; [ESP+4] = OFFSET fmt (or garbage since the function could used the parameter as local variable) add esp, 4 ; [ESP] = OFFSET fmt (or garbage since the function could used the parameter as local variable) add esp, 4*1 I hope I did no mistake, I'm not of good mood today actually... |
|||
25 Jul 2008, 17:18 |
|
baldr 26 Jul 2008, 10:53
gavin wrote: ... |
|||
26 Jul 2008, 10:53 |
|
gavin 27 Jul 2008, 14:03
Hi,
Push argument is basically sub esp-4*1 where 1 is the number of arguments assuming 32bit proccessor? Code: push fmt call [printf] add esp, 4*1 I'm not sure where you got eip+5 from. Isn't the return address basically the bytes add esp 4? Is the ret instruction equal to add esp, 4 jmp dword [esp] Same as you have shown in the prinf epilogue. Thanks again for your great replies much appriceated. |
|||
27 Jul 2008, 14:03 |
|
okasvi 27 Jul 2008, 15:07
Code: push param push eip+5 jmp printf ; sizeof "jmp printf" = 5bytes ; so eip+5 does point here, where printf returns |
|||
27 Jul 2008, 15:07 |
|
gavin 29 Jul 2008, 15:07
Is this documented anywhere?
Code: push param push eip+5 jmp printf ; sizeof "jmp printf" = 5bytes ; so eip+5 does point here, where printf returns Where do I learn more about this? |
|||
29 Jul 2008, 15:07 |
|
asmcoder 29 Jul 2008, 15:09
[content deleted]
Last edited by asmcoder on 14 Aug 2009, 14:56; edited 1 time in total |
|||
29 Jul 2008, 15:09 |
|
gavin 29 Jul 2008, 15:20
push eip+5 doesn't make sense to me at all.
|
|||
29 Jul 2008, 15:20 |
|
LocoDelAssembly 06 Aug 2008, 00:19
Let me re-post a sightly different version that uses real CPU instructions and is executable (i.e. not a sequential list of executed instructions):
Code: include 'win32a.inc' macro push operand { common if operand eq eip call $ + 5 ; near call instruction takes 5 bytes on 32-bits mode ( E8 00000000 ) else push operand end if } macro mov dest, src { common if dest eq eip jmp src else mov dest, src end if } format PE console 4.0 sub esp, 4 mov dword [esp], fmt ; [ESP] = OFFSET fmt push eip ; [ESP] = address of "add dword [esp], .fake_return_address - $" ; [ESP+4] = OFFSET fmt add dword [esp], .fake_return_address - $ ; [ESP] = return address ; [ESP+4] = OFFSET fmt mov eip, _printf .fake_return_address: ; "fake" because the next instruction does something that it is done before returning control to the caller add esp, 4 ; [ESP] = OFFSET fmt (or garbage since the function could used the parameter as local variable) .real_return_address: add esp, 4*1 ; Space for the N params released (N=1 in this case) cinvoke getchar invoke ExitProcess, 0 fmt db "Hello World :D", 10, 0 size_of_local_vars = 0 ; We don't have any local variables ; printf's prologue _printf: sub esp, 4 mov [esp], ebp ; [ESP] = old EBP; [ESP+4] = return address; [ESP+8] = OFFSET fmt mov ebp, esp ; [EBP] = old EBP; [EBP+4] = return address; [EBP+8] = OFFSET fmt sub esp, size_of_local_vars ; Now from EBP+8 and up we have the parameters and from EBP-4 down ; to ESP we have the local variables. From the ESP perspective we have ; from ESP to ESP+size_of_local_var the local variables and from ; ESP+size_of_local_var+8 and up the parameters but since every ; PUSH/POP affects the stack pointer (ESP) those bounds are not fixed ; along the entire function body. ; Now we gonna forward the call to the real printf instead of writting our own code push dword [ebp+12] push dword [ebp+8] push eip add dword [esp], .return_address - $ ; [ESP] = return address ; [ESP+4] = OFFSET fmt mov eip, dword [printf] ; "dword [printf]" because we are accessing a function pointer variable stored in the import table .return_address: ; printf's epilogue mov esp, ebp ; [ESP] = old EBP; [ESP+4] = return address; [ESP+8] = OFFSET fmt (or garbage since the function could used the parameter as local variable) mov ebp, [esp] add esp, 4 ; [ESP] = return address; [ESP+4] = OFFSET fmt (or garbage since the function could used the parameter as local variable) mov eip, dword [esp] ; [ESP] = return address; [ESP+4] = OFFSET fmt (or garbage since the function could used the parameter as local variable) data import library kernel32,'KERNEL32.DLL',\ msvcrt,'MSVCRT.DLL ' import kernel32,\ ExitProcess,'ExitProcess' import msvcrt,\ getchar, 'getchar',\ printf ,'printf' end data I hope it is clear now but if it is not you can always compile it an execute it step-by-step in a debugger (OllyDbg for example). |
|||
06 Aug 2008, 00:19 |
|
gavin 11 Aug 2008, 23:42
I understand it all very clearly now thanks to you.
I wasn't expecting you to code all that but i'm glad you did. It will help alot of other people not just me. You explained it very well. |
|||
11 Aug 2008, 23:42 |
|
< Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2024, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.