flat assembler
Message board for the users of flat assembler.

Index > Windows > nasm printf from msvcrt.dll

Author
Thread Post new topic Reply to topic
gavin



Joined: 20 Jul 2008
Posts: 23
gavin 24 Jul 2008, 22:21
I'm learning console input ouput but cannot for the life of me get this working.
Everything looks correct....


Code:
;  compile with:
;  NASMW.EXE -fobj printf.asm
;  link with:
;  ALINK.EXE printf.obj -c -oPE


%include "D:\programming\nasm\include\windows.inc"


EXTERN exit
IMPORT exit msvcrt.dll
EXTERN printf
IMPORT printf msvcrt.dll

segment .data USE32
            format db "%s",13,0
               string db "Hello",13,0

segment .code USE32

..start
  push dword format
        push dword string
        call [printf]
     mov esp,ebp
 pop ebp
        ret 8             

        push 0
        call [exit]
    
Post 24 Jul 2008, 22:21
View user's profile Send private message Reply with quote
zhak



Joined: 12 Apr 2005
Posts: 501
Location: Belarus
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
Post 24 Jul 2008, 22:39
View user's profile Send private message Reply with quote
zhak



Joined: 12 Apr 2005
Posts: 501
Location: Belarus
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
Post 24 Jul 2008, 22:46
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
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    
Post 24 Jul 2008, 23:59
View user's profile Send private message Reply with quote
gavin



Joined: 20 Jul 2008
Posts: 23
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?
Post 25 Jul 2008, 02:12
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
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]
Post 25 Jul 2008, 02:30
View user's profile Send private message Reply with quote
gavin



Joined: 20 Jul 2008
Posts: 23
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]

    
Post 25 Jul 2008, 03:09
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
LocoDelAssembly 25 Jul 2008, 03:34
Don't use mov esp,ebp/pop ebp after calling anymore Smile* 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
Post 25 Jul 2008, 03:34
View user's profile Send private message Reply with quote
gavin



Joined: 20 Jul 2008
Posts: 23
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
    
Post 25 Jul 2008, 15:59
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
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...
Post 25 Jul 2008, 17:18
View user's profile Send private message Reply with quote
baldr



Joined: 19 Mar 2008
Posts: 1651
baldr 26 Jul 2008, 10:53
gavin wrote:
...
So basically with cdecl we add esp, arguments * 4 and with stdcall we do nothing as it is cleared up for us.
...
There are fastcall and thiscall calling conventions also, to fill the gap...
Post 26 Jul 2008, 10:53
View user's profile Send private message Reply with quote
gavin



Joined: 20 Jul 2008
Posts: 23
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.
Post 27 Jul 2008, 14:03
View user's profile Send private message Reply with quote
okasvi



Joined: 18 Aug 2005
Posts: 382
Location: Finland
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    
Post 27 Jul 2008, 15:07
View user's profile Send private message MSN Messenger Reply with quote
gavin



Joined: 20 Jul 2008
Posts: 23
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?
Post 29 Jul 2008, 15:07
View user's profile Send private message Reply with quote
asmcoder



Joined: 02 Jun 2008
Posts: 784
asmcoder 29 Jul 2008, 15:09
[content deleted]


Last edited by asmcoder on 14 Aug 2009, 14:56; edited 1 time in total
Post 29 Jul 2008, 15:09
View user's profile Send private message Reply with quote
gavin



Joined: 20 Jul 2008
Posts: 23
gavin 29 Jul 2008, 15:20
push eip+5 doesn't make sense to me at all.
Post 29 Jul 2008, 15:20
View user's profile Send private message Reply with quote
LocoDelAssembly
Your code has a bug


Joined: 06 May 2005
Posts: 4624
Location: Argentina
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    
This one is compilable and shows that the mechanism works. Let me point out some facts, EIP always points to the next instruction and the "push eip / add dword [esp], value" can be read as "push (eip+value)" that since it does not exist I have emulated it with the aforementioned instructions pair.

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).
Post 06 Aug 2008, 00:19
View user's profile Send private message Reply with quote
gavin



Joined: 20 Jul 2008
Posts: 23
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.

Wink
Post 11 Aug 2008, 23:42
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2024, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.