flat assembler
Message board for the users of flat assembler.

Index > Windows > Learning Win32 programming

Goto page Previous  1, 2, 3  Next
Author
Thread Post new topic Reply to topic
Inagawa



Joined: 24 Mar 2012
Posts: 153
Inagawa 25 Apr 2012, 17:02
Thanks Guru. I must ask a stupid question once again, I'm sorry, but I can't get it through my thick head.
Code:
proc !GetChar uses edx

  cinvoke      getchar
  mov          [Look], al

  ret
endp
    


How do I extract the BYTE charcode without the program crashing? Look is a db.
Post 25 Apr 2012, 17:02
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1619
Location: Toronto, Canada
AsmGuru62 25 Apr 2012, 19:26
So, AL is also a DB -- why is it crashing?
On which line is it crashing exactly?
Post 25 Apr 2012, 19:26
View user's profile Send private message Send e-mail Reply with quote
bzdashek



Joined: 15 Feb 2012
Posts: 147
Location: Tolstokvashino, Russia
bzdashek 25 Apr 2012, 19:52
This doesn't crash, Inagawa:

Code:
format PE console
entry start
include 'win32a.inc'

start:
        call    !GetChar
    movzx   eax,[Look]
  cinvoke putchar,eax
 cinvoke exit,0


Look  db ?



proc !GetChar uses edx 

  cinvoke      getchar 
  mov          [Look], al 

  ret 
endp

data import
  library msvc,'MSVCRT.DLL'

  import msvc,\
   getchar,'getchar',\
      putchar,'putchar',\
      exit,'exit'
end data
    
Post 25 Apr 2012, 19:52
View user's profile Send private message Reply with quote
Inagawa



Joined: 24 Mar 2012
Posts: 153
Inagawa 26 Apr 2012, 12:06
Ah, thanks. The problem was on my side, a typo in '.data' section. It's all good for now.
Post 26 Apr 2012, 12:06
View user's profile Send private message Reply with quote
typedef



Joined: 25 Jul 2010
Posts: 2909
Location: 0x77760000
typedef 26 Apr 2012, 21:11
Inagawa wrote:
Ah, thanks. The problem was on my side, a typo in '.data' section. It's all good for now.


doesn't matter what name you give to a section as long as it's 8 bytes and has appropriate page attributes (i.e: Read / Write / Execute Wink
Post 26 Apr 2012, 21:11
View user's profile Send private message Reply with quote
Inagawa



Joined: 24 Mar 2012
Posts: 153
Inagawa 27 Apr 2012, 11:12
Yes, I have no idea how it happened, but my .data section was marked as "readable executable", that's why it crashed, there was no way to write there.

Anyway, guys. Could you give me an idea how to approach variadic functions? I was thinking something about the first argument marking "how many" DWORD arguments there are. There has to be a better way. Or are compilers doing it like this and then implicitly passing the number of arguments to the function?

Something like this.

Code:
proc !Emit uses ecx ebx, Count:DWORD, Output

  mov          ebx, [Output]
  mov          ecx, [Count]

.OutputLoop:
  push         ecx
  invoke       lstrlenA, ebx
  invoke       WriteFile, [_fileHandle], ebx, eax, _bytesWritten, 0
  pop          ecx
  add          ebx, 4
  loop         .OutputLoop

  ret
endp

 _bytesWritten          dd ?
    
Post 27 Apr 2012, 11:12
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20304
Location: In your JS exploiting you and your system
revolution 27 Apr 2012, 11:40
You might want to make the varadic function use the C calling standard and allow the caller to release the stack.
Code:
ccall printf, arg1, arg2, arg3
proc printf c uses regs, args
  lea ebx,[args]
  ;code
endp    
Post 27 Apr 2012, 11:40
View user's profile Send private message Visit poster's website Reply with quote
Inagawa



Joined: 24 Mar 2012
Posts: 153
Inagawa 27 Apr 2012, 11:44
I don't get it. The three dots are part of the code? Like the variable argument list from C? Perhaps an example might get the idea across, please?

Edit: I see, the dots got me confused. I'll look into this

Edit2: I'm going back to doing it by hand, the procedures are nothing but a source of confusing problems.
Post 27 Apr 2012, 11:44
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1619
Location: Toronto, Canada
AsmGuru62 27 Apr 2012, 15:51
@Inagawa:
Why do it by hand? As there exists a nice macro ccall, which does exactly that -- pass ANY # of parameters onto stack AND automatically release the stack after the call. This macro works right out of the box from FASM package -- no need to do any additional effort.

Notice how in the following code I am not using 'proc' to declare my functions -- just using a label with no dot in front:
Code:
Foo:
      ;
   ; At this point your parameters begins at address [ESP+4],
  ; because [ESP] points to a return address on stack
 ; (from where you called).
  ;
   ret

...

  ccall   Foo, 1, 2, 3, 4   ; Pass ANY # of parameters here
...
    mov     eax, <something>
      ccall   BlackBox, a, b, c
   ;
   ; EAX is preserved by BlackBox!
     ;
...
;
; This function will not damage ANY registers
;
BlackBox:
        pusha
       lea     ebp, [esp + 36]  ; 36 = 8 registers pushed by PUSHA + ret. address
  ;
   ; Now 1st parameter is at [EBP]
     ;     2nd is at [EBP+4]
     ;     3rd is at [EBP+8] and so on
   ;


   popa
        ret
;
; What if you need to return some register back to caller, like EAX or other?
; No problem! All you need, is to store the return value into its place into area
; taken by PUSHA and it will be loaded back by POPA before RET.
;
Get_EAX_Back:
 pusha
       lea     ebp, [esp + 36]

 ; ... do all kind of calculations here ...



      ;
   ; Store ESI into EAX place on stack
 ; Why 7*4? Because EAX pushed 1st by PUSHA
  ; (read CPU manual -- the order is defined there)
   ;
   mov     [esp + 7*4], esi

        popa    ; This will load EAX with the value of ESI before returning
 ret
;
; To avoid the offsets used with EBP to access parameters - just use
; a 'virtual' statement for your parameters:
;
virtual at 0
PARAMS:
  .P1 dd ?
    .P2 dd ?
    .P3 dd ?
    ;
   ; ... and so on ...
 ;
end virtual
;
; then inside a function - you can just use this:
;
 mov ecx, [ebp + PARAMS.P5]
;
; instead of fuguring out the offset to parameter #5
;
    
Post 27 Apr 2012, 15:51
View user's profile Send private message Send e-mail Reply with quote
Inagawa



Joined: 24 Mar 2012
Posts: 153
Inagawa 28 Apr 2012, 11:00
This is immensely helpful! Let me just ask a couple of follow up questions to make it clear for me.

At the end of your Get_EAX_Back function, could I do it like that?
Code:
        popa    
        mov    eax, esi  ; This will load EAX with the value of ESI before returning
        ret 
    


Also, what makes you push all the registers? I thought EAX, ECX and EDX are scratch registers and saving them all is just a waste of clock cycles - am I wrong?

I still don't understand how do functions know how many parameters are on the stack specifically for them. How do functions know how far to reach out to the stack without reading data that wasn't pushed for them?

I need to study this, I'll be here later. And thanks a lot
Post 28 Apr 2012, 11:00
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1619
Location: Toronto, Canada
AsmGuru62 28 Apr 2012, 11:29
Code:
pusha
...
popa ; This line will restore old ESI from stack and
     ; damage the ESI you calculated to return

mov eax, esi   ; old ESI returned instead of the new one.
ret
    

Saving all registers can be very useful to write code, because when you write code,
you know that most of your functions do not damage any registers, so if you hold some value
across a few calls -- this value is still there, in the register, so it may be reused again,
for some other purpose. However, if some function is used which actually returns some value, then of course, that
register is destroyed.

I see, that you trying to optimize your code early -- a good thing, but
not really making a difference. The 'real' optimization is picking the proper algorithm for
your task. Also, optimizing makes sense if you process a lot of information, like
reading and processing 100Mb file or some very large nested loop(s).
Or calculating some formula millions of times per second. In extreme
cases -- so to speak.

Also, without measuring the speed of your code, you can't really be sure that your
optimization works. So, in case you really want to get results from the optimization of the
piec of code -- you need to measure the amount of relative time this piece uses.
For that purpose, there is an API: QueryPerformanceCounter().
It must be called before the piece of code to be measured and after it and then subtract the results.
You need to run that measurement a few times, then take an average of your measurements.
Then you make some improvements to the code and measure it again.
In most cases, the result of improvement is not really visible. However, if the
improvement is an algorithm change -- THEN you will see some results!

Now, you were asking how the function 'knows'? But you are the one writing that function!
You can pass a count of values you have as parameters as a first parameter and then
use it inside the function to access other parameters.

The question is what is your function doing with variable number of parameters?
What exactly do you need it for?
I suspect that you have an improper code design somewhere.
I write code for a lot of years and I never needed functions like that.
Well, there are printf kind of functions, but they are all written already!
Smile

Or, maybe you need to pass an array of values to the function and the count is
not known. For these cases you pass TWO parameters:

- an address of the first value in the array (the rest will follow the 1st)
- a count of values in the array.

You see: two parameters is enough to pass an array to any function.
Post 28 Apr 2012, 11:29
View user's profile Send private message Send e-mail Reply with quote
Inagawa



Joined: 24 Mar 2012
Posts: 153
Inagawa 28 Apr 2012, 14:25
I will need a lot of time to digest all this, thanks so much for the extensive info.

Can I make virtual local to the function? Just like the .P1 is local to PARAMS, can PARAMS be local to a function?


Edit: I took what you told me earlier and used it. It seems to work perfectly. Am I using everything okay?
Code:
;================================================================
;=== EMIT.INC ===================================================
;================================================================
; Description: Emits a line of assembly into a txt file.
;
; Arguments: Count, ...
;================================================================
!Emit:

  ;=== PROLOGUE ==================================
  push         ebx ebp
  mov          ebp, esp

  ;=== WRITE TO FILE =============================
  mov          ecx, [!Emit.Params.Count]
  lea          ebx, [!Emit.Params.Strings]
.Output:
  push         ecx

  ;
  ; Get the string length and output
  ;
  invoke       lstrlenA, DWORD[ebx]
  invoke       WriteFile, [_fileHandle], DWORD[ebx], eax, _bytesWritten, 0

  add          ebx, 4
  pop          ecx

loop           .Output

  ;=== EPILOGUE ==================================
  ;
  ; Restore the stackframe and return success
  ;
  pop          ebp ebx
  mov          ecx, 1
  ret


;=== DATA =======================================================
  ;
  ; 2x4 (EBX EBP) + 4 (Return Address)
  ;
  virtual at ebp+12
  !Emit.Params:

        .Count                     dd ?
        .Strings                   dd ?

  end virtual

  _bytesWritten              dd ?
    
Post 28 Apr 2012, 14:25
View user's profile Send private message Reply with quote
Inagawa



Joined: 24 Mar 2012
Posts: 153
Inagawa 29 Apr 2012, 15:12
I also discovered that my !Emit cannot output numbers. I need to create a function that will translate a number into ascii. I created this:

Code:
;=============================================================================
;=== NUMBER TO LETTER.INC ====================================================
;=============================================================================
; Description: Less-than-elegant function to convert raw numbers into ASCII.
;
; Arguments: Number
;=============================================================================
!NumberToLetter:

  ;=== PROLOGUE ==================================
  push        ebp ebx esi edi
  mov          ebp, esp

  ;=== GET THE NUMBER SIZE =======================
  mov          eax, [!NumberToLetter.Params.Number]

  cmp          eax, 10
  jle          .NumberBelow_10
  cmp          eax, 100
  jle          .NumberBelow_100
  cmp          eax, 1000
  jle          .NumberBelow_1_000
  cmp          eax, 10000
  jle          .NumberBelow_10_000
  cmp          eax, 100000
  jle          .NumberBelow_100_000
  cmp          eax, 1000000
  jle          .NumberBelow_1_000_000
  cmp          eax, 10000000
  jle          .NumberBelow_10_000_000
  cmp          eax, 100000000
  jle          .NumberBelow_100_000_000
  cmp          eax, 1000000000
  jle          .NumberBelow_1_000_000_000

  ;=== INITIALIZE THE DIVISOR ====================
.NumberBelow_10:

  mov          ecx, 10
  mov          esi, 1
  jmp          .Divide

.NumberBelow_100:

  mov          ecx, 100
  mov          esi, 2
  jmp          .Divide

.NumberBelow_1_000:

  mov          ecx, 1000
  mov          esi, 3
  jmp          .Divide

.NumberBelow_10_000:

  mov          ecx, 10000
  mov          esi, 4
  jmp          .Divide

.NumberBelow_100_000:

  mov          ecx, 100000
  mov          esi, 5
  jmp          .Divide

.NumberBelow_1_000_000:

  mov          ecx, 1000000
  mov          esi, 6
  jmp          .Divide

.NumberBelow_10_000_000:

  mov          ecx, 10000000
  mov          esi, 7
  jmp          .Divide

.NumberBelow_100_000_000:

  mov          ecx, 100000000
  mov          esi, 8
  jmp          .Divide

.NumberBelow_1_000_000_000:

  mov          ecx, 1000000000
  mov          esi, 9
  jmp          .Divide

  ;=== DIVIDE ====================================
.Divide:

  mov          edx, [!NumberToLetter.Params.Number]

.DivisionLoop:

  ;
  ; Move the Number inside EAX
  ; Sign-extend EAX
  ; Divide EDX:EAX by ECX
  ;
  mov          eax, edx
  cdq
  idiv         ecx

  ;
  ; Quickstore EDX and EAX
  ;
  push         edx
  push         eax

  ;
  ; Move the main divisor into EAX
  ; Move a new, temporary divisor into ECX
  ; Sign-extend EAX
  ; Divide the main divisor by the temporary one
  ; Load the new, smaller divisor back into ECX
  ;
  mov          eax, ecx
  mov          ecx, 10
  cdq
  idiv         ecx
  mov          ecx, eax

  ;
  ; Restore EAX and EDX
  ;
  pop          eax
  pop          edx

  ;
  ; Temporary output
  ;
  push         eax ecx edx
  cinvoke      printf, <'Single numbers: %i', 10>, eax
  pop          edx ecx eax

  ;
  ; Exit prematurely if ECX is 0. (Cannot divide by 0)
  ;
  cmp          ecx, 0
  je           .End

  dec          esi
  cmp          esi, 0
  jmp           .DivisionLoop

.End:


  ;=== EPILOGUE ==================================
  pop          edi esi ebx ebp

ret

;=== DATA ====================================================================
  ;
  ; 4x4 (EBP EBX ESI EDI) + 4 (return address) = 20
  ;
  virtual at ebp+20
  !NumberToLetter.Params:
    .Number                  dd ?
  end virtual
    

It's really ugly, though.

If someone wants to look at it and point out faults in it, I'd be most happy.
Post 29 Apr 2012, 15:12
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1619
Location: Toronto, Canada
AsmGuru62 29 Apr 2012, 22:48
Here is a couple of functions:
They will convert the EAX into a string.
1st function will assume EAX is unsigned value.
2nd function will assume EAX is signed value.
Code:
Lib_UInt32_to_ASCII:
; ---------------------------------------------------------------------------
; INPUT:
;   EAX = unsigned value to convert
;   EDI = ASCII buffer
; ---------------------------------------------------------------------------
    pusha
    push      10
    pop       ecx          ; ECX = 10 (EAX will be divided by 10)
    xor       ebx, ebx     ; EBX = 0 (counter of digits to store)

.divide_by_10:
    xor       edx, edx
    div       ecx          ; 64bit value in EDX:EAX divided by 10

    add       dl, '0'      ; DL is now a digit in range ('0'..'9')
    push      edx          ; Put it into stack
    inc       ebx          ; Count how many digits are put on stack

    test      eax, eax     ; Is there more to divide?
    jnz       .divide_by_10
    ;
    ; At this point all the digits stored on stack
    ;
.store_to_buffer:
    pop       eax          ; AL = next digit
    stosb                  ; Store it into buffer at EDI and increment EDI

    sub       ebx, 1       ; Is there more digits on stack
    jnz       .store_to_buffer

    mov       [edi], bl    ; Terminate buffer with null character
    popa
    ret

Lib_Int32_to_ASCII:
; ---------------------------------------------------------------------------
; INPUT:
;   EAX = signed value to convert
;   EDI = ASCII buffer
; ---------------------------------------------------------------------------
; Now, before the output we'll need to test if the value in EAX has a sign.
; If it has -- value is made into unsigned and then Lib_UInt32_to_ASCII is
; used to print it.
; ---------------------------------------------------------------------------
    pusha
    add       eax, 0
    jns       .print_me
    ;
    ; Value in EAX is negative
    ;
    neg       eax
    mov       byte [edi], '-'
    inc       edi

.print_me:
    call      Lib_UInt32_to_ASCII
    popa
    ret
    

I tested them in debugger with some simple code:
Code:
sBuffer db '*****************'
...
    mov       edi, sBuffer
    mov       eax, 0FFFFFFFFh
    call      Lib_UInt32_to_ASCII
    call      Lib_Int32_to_ASCII
    xor       eax, eax
    call      Lib_UInt32_to_ASCII
    mov       eax, 6400
    imul      eax, 100
    call      Lib_UInt32_to_ASCII
    neg       eax
    call      Lib_Int32_to_ASCII
    
Post 29 Apr 2012, 22:48
View user's profile Send private message Send e-mail Reply with quote
Inagawa



Joined: 24 Mar 2012
Posts: 153
Inagawa 30 Apr 2012, 18:53
You have been programming for quite a while, haven't you? Very Happy

What do these lines do?
Code:
add       dl, '0'      ; DL is now a digit in range ('0'..'9') 
;====
test      eax, eax     ; Is there more to divide? 
    


Also, why do you initialize ECX by pushing the 10 on stack and then popping it into ECX? Is that a better practice?


Edit: Oh, I understand the "add dl, '0'"! But I'd never think of that, to be honest.

Edit2: I found out that the test checks if eax is zero. Why do it like this? Is this better than doing it like cmp eax, 0 (I have heard somewhere that code with lots of explicitly declared 0s is bad)


Last edited by Inagawa on 30 Apr 2012, 19:28; edited 1 time in total
Post 30 Apr 2012, 18:53
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1619
Location: Toronto, Canada
AsmGuru62 30 Apr 2012, 19:28
Well, this:
Code:
push 10
pop ecx
    

will result in smaller code (3 bytes).
Against this:
Code:
mov ecx, 10       ; 5 bytes of code
    

However, the MOV is of course, faster than TWO instructions.
You need to choose the one for the case you working on.

TEST is recommended instruction to check the register for zero.
Code:
test  eax, eax
jz  .Reg_EAX_is_Zero
...
test ecx, ecx
jnz .Reg_ECX_is_not_zero
    


TEST with zero may be slower than TEST with same register.
The opcodes where both operands are registers are preferred against
the ones where one of the operands is in memory. In this case zero is
called immediate operand, however, it is still in memory.
Post 30 Apr 2012, 19:28
View user's profile Send private message Send e-mail Reply with quote
typedef



Joined: 25 Jul 2010
Posts: 2909
Location: 0x77760000
typedef 30 Apr 2012, 20:17
TEST is also good if you are going to use the result otherwise one could also use AND which modifies the operand.
Post 30 Apr 2012, 20:17
View user's profile Send private message Reply with quote
Inagawa



Joined: 24 Mar 2012
Posts: 153
Inagawa 07 May 2012, 09:47
I have returned back to Win32 programming and there's something basic I haven't really understood. It's about subtracting from ESP when you call a procedure. All the books say it's for the "automatic variables", so does that mean that I have to subtract 4 for every push I do on the stack?

Let's say I have a procedure,
Code:
push ebp
mov ebp, esp
sub esp, 16

push eax
push eax
push eax
push eax
pop eax
pop eax
pop eax
pop eax

add esp, 16
pop ebp
    


Do I have it right? I simply have to count all the PUSHes and subtract their (count*4) from ESP?

If I really had to guess, I'd say it's in case that processor starts another timeslice, it doesn't trample over my auto variables, since the stack top is shifted. Is it something like that?
Post 07 May 2012, 09:47
View user's profile Send private message Reply with quote
AsmGuru62



Joined: 28 Jan 2004
Posts: 1619
Location: Toronto, Canada
AsmGuru62 07 May 2012, 14:29
The ESP is used to reserve space on stack for local (some call it automatic) variables.
If you do not have the locals in a procedure - you do not need to do anything with ESP.
All you need to ensure is that when your code 'meets' the RET instruction - the return address is on the top of the stack.
That means you need to not count PUSH/POP, but you can balance the stack some other way, like this:
Code:
        push    eax
 push    eax
 push    eax
 push    eax

     ...


 add     esp, 4*4   ; <-- 4 DWORDs were PUSH-ed
   ret
    

There is no connection to a CPU time slices. The task switch will not disturb any of your data.
However, in a multithreaded code - you DO need to protect the data, which is a READ/WRITE data for
multiple threads. If you have read only data - and it is read (but not written to) by
multiple threads - you do not need to protect such data.
CPU will properly read it from multiple threads.

EBP is needed for access to the parameters passed to a function and
local variables. FASM has a macro 'local' - this macro fully automates the work with local variables in a procedure, but then you need to also use the 'proc' macro to define your functions. These macros work together, I believe.


Last edited by AsmGuru62 on 09 May 2012, 16:46; edited 1 time in total
Post 07 May 2012, 14:29
View user's profile Send private message Send e-mail Reply with quote
Inagawa



Joined: 24 Mar 2012
Posts: 153
Inagawa 07 May 2012, 14:41
Thanks for the concise answer, the term "auto variables" confused me, it's not the best choice of words in an assembly environment.
Post 07 May 2012, 14:41
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  
Goto page Previous  1, 2, 3  Next

< 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.