flat assembler
Message board for the users of flat assembler.

Index > Windows > NOOB Alert! - Trying to understand the basics

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



Joined: 22 Mar 2021
Posts: 22
Location: Quebec/Canada
avidichard 13 Sep 2021, 00:48
macomics wrote:
For some reason, you removed the alignment of ads. Just go back to using macros. It will be easier this way.
RVA: This fasm operator subtracts the base address from the label address

I actually understood more from pure assembly than with the use of macros. For instance, this code:
Code:
label_start:
    push rax
    invoke GetModuleHandle, NULL
    mov [l_hInstance], rax
    mov [wc.hInstance], rax    

As I understood with the pure assembly code you provided me, I understood that the invoke macro actually executes the function and returns it's value inside rax. This is why we push rax before hand to reserve that place to receive the information. Initially, I never understood why l_hInstance was receiving information it never got. It's from pure assembly that I understood that the l_hInstance comes from GetModuleHandle which was thrown into rax. From there on, rax had the value of the Module Handle. Basically in pseudocode this is what was being done:
Code:
;Pseudocode alert
var MyVar = GetModulHandle
l_hInstance = MyVar
wc.hInstance = MyVar    

What I am trying to achieve in FASM is this:
Code:
var l_hInstance = GetModuleHandle
wc.hInstance = l_hInstance    

Now, to properly get the hang of it. If I would like to have the contents of l_hInstance and push it into my wc.hInstance value. So in FASM, how would I go arround the rax as I can see, the push rax is maybe not necessary. Could I put the contents of GetModuleHandle in l_hInstance from the ".data" section? Something like this?
Code:
section '.data' data readable writeable
  l_hInstance dq [GetModuleHandle]
section '.text' code readable writeable executable
  mov [wc.hInstance], l_hInstance    

Without using rax?

And I got a better understanding of the ",idata" and ".data" section with your basic explanation. Basically, the program does not really execute code in there but simply uses these sections as virtual storage to refer to when needed. Basically, variable storage. And, as I can see, all you need to do is provide the right amount of information and data before the program automatically stops reading lines of code, hence, this part:
Code:
label_dll_import:
    dd rva msvcrt.functions
    dd 0
    dd 0
    dd rva msvcrt.dll
    dd rva msvcrt.functions
    dd 5 dup(0)
  ; DLL import needed 5 data entries, they're now processed so next lines do not not need to be executed
  msvcrt.dll db 'msvcrt.dll', 0
             db rva $ and 1    

Of course, I'm in the basics because I think the program does interpret db, dd and dq and such operands to identify if the next line is required to be executed such as the 2 last lines which contains the rva alignment but the one after is another variable identifier so the program automatically stops executing after the rva alignement.

I also just need to have my answer to this question:
Code:
; Is this better or worst
push mess
call [printf]
push 0
call [exit]

; THAN this?
push ebp
mov ebp, esp
sub esp, 4
mov dword[esp], mess
call [printf]
mov dword[esp], 0
call [exit]    

I see that you mainly use this format:
Code:
push ebp
mov ebp, esp
push 2021
push 9
push 13
push my_message.hello
push format_string
call [printf]
mov esp, ebp    

You seem to opt more with the push and mov links than the simple "push mess" I use. Is there a reason for going the more complexe way? I'm just asking because I find it much easier to use "push mess" rather than using "push ebp", "mov ebp, esp", "sub esp, 4". I also wondered why use "sub esp, 4". Can I do 8, 16 or 32 or 1? What's so important about that number 4?
Post 13 Sep 2021, 00:48
View user's profile Send private message Reply with quote
macomics



Joined: 26 Jan 2021
Posts: 1043
Location: Russia
macomics 13 Sep 2021, 03:24
In fact, it is just necessary to strive to use the processor registers as often as possible. You cannot directly put a value from another global variable into a global variable. Moreover, in the declaration
Code:
l_hInstance dq [GetModuleHandle]    
the value of the variable [GetModuleHandle] is not defined and will finally contain not l_hInstance, but the address of the function that should start executing to return this value to rax. On the other hand, in this topic we have already argued about getting this unfortunate value with DimonSoft. And in the case of an EXE file, you can easily do with the following construction to get (l_hInstance) without any call:
Code:
 l_hInstance dq $ - rva $     
It is better to use the commands so that even if the module (EXE) is moved when loading into memory, a mechanism designed just to correct these parameters will work.
Code:
 lea rax, [$]
xor ax, ax    

avidichard wrote:
As I understood with the pure assembly code you provided me, I understood that the invoke macro actually executes the function and returns it's value inside rax.
You're wrong about the macro. It is not invoke that puts the value in rax.
Code:
invoke func, arg0, arg1, arg2, arg3, arg4, arg5
    
Generates the following lines.
Code:
sub rsp, 48
mov [rsp+40], arg5
mov [rsp+32], arg4
mov r9, arg3
mov r8, arg2
mov rdx, arg1
mov rcx, arg0
call func
add rsp, 48    
And nothing more. But any function after it has been called (the call command) returns the result in the rax register.
Code:
wndProc:
 ...
 xor rax, rax  ; <- result
 lea rsp, [rbp-24]
 pop rdi
 pop rsi
 pop rbx
 retn    
avidichard wrote:
Of course, I'm in the basics because I think the program does interpret db, dd and dq and such operands to identify if the next line is required to be executed such as the 2 last lines which contains the rva alignment but the one after is another variable identifier so the program automatically stops executing after the rva alignement.
format of the data definition string
Code:
<label_name> directive value0<,value1,...,valueM,>\
<valueM+1,...,valueN,>\
<lastValue>    
where: label_name (optional)an arbitrary label name that will be associated with the current address in the program;
directive - one of the data definition directives: (1 byte) file, db,rb
(unicode characters, 2 bytes)du
(2 bytes) dw, rw
(4 bytes) dd, rd
(6 bytes) df,dp,rf,rp
(8 bytes) dq,rq
(10 bytes) dt,rt
and a set of values added to the output file of the program (written to the EXE file). The values in triangular brackets are optional, but at least one must be present. Also, in the db and du directives, the string can be present instead of the value
avidichard wrote:
I also just need to have my answer to this question:
Code:

; Is this better or worst
push mess
call [printf]
push 0
call [exit]

; THAN this?
push ebp
mov ebp, esp
sub esp, 4
mov dword[esp], mess
call [printf]
mov dword[esp], 0
call [exit]
In 32-bit mode, option 1 is better. But now you are writing code without taking into account the types of function calls and stack balancing. If you want to use msvcrt, then all the functions in it have the cdecl type, in which the parameters are passed through the stack in the reverse order of enumeration and must be pushed out of it by the calling subroutine. But in the Windows API, a different type of calls is used - stdcall. They clear the stack themselves after returning to the calling subroutine and do not require stack balancing. When using msvcrt, it is best to do this:
Code:
push ebp
mov ebp, esp
push mess
call [printf]
mov esp, ebp
push 0
call [exit]
; mov esp, ebp ; But the program will not return from the exit function
; pop ebp
    
avidichard wrote:
You seem to opt more with the push and mov links than the simple "push mess" I use. Is there a reason for going the more complexe way? I'm just asking because I find it much easier to use "push mess" rather than using "push ebp", "mov ebp, esp", "sub esp, 4". I also wondered why use "sub esp, 4". Can I do 8, 16 or 32 or 1? What's so important about that number 4?
you can perform any arithmetic operations with the stack pointer. at least sub/add esp, 1048576. BUT! Since you are working not just with a number, but with a pointer to a certain memory area, you need to understand the mechanism of the stack. Simply adding or subtracting a large number from the pointer will not cause an error. But when you have to use them , then an error will occur in the program. Usually, the stack pointer does not change its values by more than +/-4096 bytes. Otherwise, you get to the addresses that do not correspond to the memory page is not defined or there is no mark about the need to assign it. Then the program will receive an error and if it cannot handle it, it will be forcibly terminated by the operating system.
https://software.intel.com/content/www/us/en/develop/articles/intel-sdm.html#combined
5.10.5 Checking Alignment wrote:

When the CPL is 3, alignment of memory references can be checked by setting the AM flag in the CR0 register and
the AC flag in the EFLAGS register. Unaligned memory references generate alignment exceptions (#AC). The
processor does not generate alignment exceptions when operating at privilege level 0, 1, or 2. See Table 6-7 for a
description of the alignment requirements when alignment checking is enabled.
6.2.2 Stack Alignment wrote:
The stack pointer for a stack segment should be aligned on 16-bit (word) or 32-bit (double-word) boundaries, depending on the width of the stack segment. The D flag in the segment descriptor for the current code segment sets the stack-segment width (see “Segment Descriptors” ... ). The PUSH and POP instructions
use the D flag to determine how much to decrement or increment the stack pointer on a push or pop operation,
respectively. When the stack width is 16 bits, the stack pointer is incremented or decremented in 16-bit increments;
when the width is 32 bits, the stack pointer is incremented or decremented in 32-bit increments. Pushing a 16-bit
value onto a 32-bit wide stack can result in stack misaligned (that is, the stack pointer is not aligned on a double-word boundary). One exception to this rule is when the contents of a segment register (a 16-bit segment selector)
are pushed onto a 32-bit wide stack. Here, the processor automatically aligns the stack pointer to the next 32-bit
boundary.
The processor does not check stack pointer alignment. It is the responsibility of the programs, tasks, and system
procedures running on the processor to maintain proper alignment of stack pointers. Misaligning a stack pointer
can cause serious performance degradation and in some instances program failures.
see also Interrupt 17—Alignment Check Exception (#AC)


Last edited by macomics on 13 Sep 2021, 11:38; edited 1 time in total
Post 13 Sep 2021, 03:24
View user's profile Send private message Reply with quote
macomics



Joined: 26 Jan 2021
Posts: 1043
Location: Russia
macomics 13 Sep 2021, 03:42
In order not to deal with the code using the tuck method, but to clearly see what is happening, take the debugger program (32-bit-OllyDbg or 64-bit-x64dbg)

https://www.ollydbg.de/download.htm

https://x64dbg.com/#start
Post 13 Sep 2021, 03:42
View user's profile Send private message Reply with quote
avidichard



Joined: 22 Mar 2021
Posts: 22
Location: Quebec/Canada
avidichard 14 Sep 2021, 03:04
macomics wrote:
In fact, it is just necessary to strive to use the processor registers as often as possible. You cannot directly put a value from another global variable into a global variable. Moreover, in the declaration
Code:
Code:
l_hInstance dq [GetModuleHandle]    

the value of the variable [GetModuleHandle] is not defined and will finally contain not l_hInstance, but the address of the function that should start executing to return this value to rax. On the other hand, in this topic we have already argued about getting this unfortunate value with DimonSoft. And in the case of an EXE file, you can easily do with the following construction to get (l_hInstance) without any call:
Code:
l_hInstance dq $ - rva $    

It is better to use the commands so that even if the module (EXE) is moved when loading into memory, a mechanism designed just to correct these parameters will work.
Code:
lea rax, [$]
xor ax, ax    

I don't understand what you mean about those lines of code. Where would you put them? Which lines would they replace in the snipets I initially provided?

Also, as I understand, push eax and mov eax would be recommended instead of the push mess just to get used to always initialise a space in memory for the information we're about to handle. Meaning, it works, but it's better to start off with the right practices for futur proofing our programming. That's what I seem to understand from what you say.

Again, I have no idea where where these would go in the example you provided.
Code:
lea rax, [$]
xor ax, ax    

I also was questioning as to why use rbp and rsp in the "Hello, world!" command line example instead of eax such as:
Code:
push eax
mov eax, mess
call [printf]    

Is it because console PE uses different than PE GUI?

As for the RVAs, yes, I had forgoten to put them back. I corrected my code.
Post 14 Sep 2021, 03:04
View user's profile Send private message Reply with quote
macomics



Joined: 26 Jan 2021
Posts: 1043
Location: Russia
macomics 14 Sep 2021, 06:09
avidichard wrote:
I don't understand what you mean about those lines of code. Where would you put them? Which lines would they replace in the snipets I initially provided?

Code:
  format PE ... ; or PE64 ...
  entry entry_point

section '.code' ...
entry_point:

anywhere:  ; first option       ; for PE 64
  lea eax, [entry_point]        ; lea rax, [entry_point]  I use the label entry_point because it is closest to the beginning of the file.
  xor ax, ax                    ; xor ax, ax          If the label is further than 64 kb from the beginning of the file, you will have to use a longer command:
     ; sub eax, rva entry_point ; sub rax, rva entry_point

section '.data' ...  ; second option       ; for PE 64
  l_hInstance  dd $ - rva $                ; or l_hInstance dq $ - rva $
; the disadvantage is that you will get in l_hInstance not the real address, but the one that was used
; as the default value (or specified in the directive: format ... at hInstance_address )

section '.idata' ...
include 'INCLUDE/MACRO/IMPORT32.INC' ; include 'INCLUDE/MACRO/IMPORT64.INC'
library ...
import ...
import ...
 ...
import ...

; the following lines are only for the DLL (32-bit), but then you can only use the first method (with commands)
section '.reloc' fixups data readable discardable
if $$=$
  dd 8,0
end if
; for PE64, the first method calculates the label address relative to the current address in (rip) and it no longer needs fixes
    

avidichard wrote:
Also, as I understand, push eax and mov eax would be recommended instead of the push mess just to get used to always initialise a space in memory for the information we're about to handle. Meaning, it works, but it's better to start off with the right practices for futur proofing our programming. That's what I seem to understand from what you say.
If I understand correctly what you mean, then I will clarify. Now we have a mess in the discussion, because you are rushing between different formats (32/64).

As for 32-bit code, you'd better start working with stack only with the commands push <any>/pop <any> and mov ebp, esp/mov esp, ebp. Then, with a high degree of probability, you will not violate the stack alignment, but you will not think about this problem either. And when you gain the necessary experience, you will choose the options for working with stack yourself.

It is more difficult with 64-bit code, because it is impossible to do without taking into account the alignment of stack addresses. It is necessary to make sure that the value in the rsp is always a multiple of 16, but the push/pop commands work with half the required amount of data. Therefore, you have to monitor the rsp value and you can't do without it. But when using macro instructions, this problem disappears because they will monitor the alignment themselves, and you will not have to think about it. Otherwise, it is also better to start working with the stack in 64-bit mode by using the commands push <any>/pop <any> and mov rbp, rsp/mov rsp, rbp. Then you will have a minimal chance of breaking something.

avidichard wrote:
Again, I have no idea where where these would go in the example you provided.
Code:
lea rax, [$]
xor ax, ax        
see the answer for the first quote

avidichard wrote:
I also was questioning as to why use rbp and rsp in the "Hello, world!" command line example instead of eax such as:
Code:
push eax
mov eax, mess
call [printf]    
    
Is it because console PE uses different than PE GUI?

Because you can only use them in 64-bit programs, and this piece of code apparently refers to a 32-bit program.

But when calling this function, the stack will not be cleared of the value that will be put in it by the command push eax. Therefore, for this particular example, there are two ways to solve this problem. Due to the fact that the value is one, it is possible to use just one additional command (pop edx, the register does not play a role here, but in eax-the result and it is better not to rewrite it).
Code:
  push mess
  call [printf]
  pop edx
    

However, this function may have more than one parameter, depending on the format string (mess). Then the second solution will be more universal.
Code:
  ; !!!only 32-bit mode!!!
  push ebp          ; now [esp] = old ebp            <- saving the old ebp value
  mov ebp, esp      ; saving the current value of the stack top
  push 0            ; now [esp] = 0, [esp+4] = old ebp
  push 1            ; now [esp] = 1, [esp+4] = 0, [esp+8] = old ebp
  push 2            ; now [esp] = 2, [esp+4] = 1, [esp+8] = 0, [esp+12] = old ebp
  push 3            ; now [esp] = 3, [esp+4] = 2, [esp+8] = 1, [esp+12] = 0, [esp+16] = old ebp
  push mess         ; now [esp] = mess, [esp+4] = 3, [esp+8] = 2, [esp+12] = 1, [esp+16] = 0, [esp+20] = old ebp
  call [printf]     ; now [esp] = returned_here, [esp+4] = mess, [esp+8] = 3, [esp+12] = 2, [esp+16] = 1, [esp+20] = 0, [esp+24] = old ebp
returned_here:      ; now [esp] = mess, [esp+4] = 3, [esp+8] = 2, [esp+12] = 1, [esp+16] = 0, [esp+20] = old ebp
  mov esp, ebp      ; now [esp] = old ebp
  pop ebp           ; now esp clear, ebp will be restored

; these are the features of the type of function being called
; most functions of the msvcrt library have similar features
; However:

  push 0               ; now [esp] = 0
  push 0               ; now [esp] = 0, [esp+4] = 0
  push 0               ; now [esp] = 0, [esp+4] = 0, [esp + 8] = 0
  push msg             ; now [esp] = msg, [esp+4] = 0, [esp+8] = 0, [esp+12] = 0
  call [GetMessage]    ; now [esp] = second_return, [esp+4] = msg, [esp+8] = 0, [esp+12] = 0, [esp+16] = 0
second_return:         ; now esp clear

; For Windows API functions, a different type of call is usually used. And you don't need to clear the stack behind them.
    

avidichard wrote:
As for the RVAs, yes, I had forgoten to put them back. I corrected my code.
I ask you again-use the macros "library" and "import". They do not represent anything terrible, but simply save you from unnecessary mistakes made by inattention. If you want to include only these two macros to your project, then they are in the file: INCLUDE/MACRO/IMPORT(32/64).INC
While you are dealing with the basics of assembly language, it is better to leave the automatic tools that relate to the PE file format.
Moreover, when using macros, listing the entire list of functions in all the libraries you know will not lead to the creation of a huge structure. It will leave only those libraries and functions that you use in your code. Whereas once compiled such a list will save you from constantly digging in various sources to list the names of functions you need.
Post 14 Sep 2021, 06:09
View user's profile Send private message Reply with quote
avidichard



Joined: 22 Mar 2021
Posts: 22
Location: Quebec/Canada
avidichard 14 Sep 2021, 07:17
My days are from the VB6 era. I had to manually put in modules the constants, structs and functions. They were not supplied for me. The problem with the included files using the "include" command is that it includes the entire file with a bunch of stuff I won't even be using. For instance, one example I got simply included the kernel32 functions but still only used the msvcrt dll. That was stupid for me. I don't need kernel32 when all I want to do is write a single string in command line.

So I am used to import and write things manually and YES, from time to time, forget about a thing or 2. But that's how I learn and program. I don't like to use prebuilt stuff just because it's easy. That's the exact reason I moved from other programming. AT the limit, I'll even want to know how the program compiles and converts ASM to binary. After all, that's what FASM does. What if I wanted to get rid of FASM and simply have it my way? That's the entire freedom of learning assembly without depending on macros.

Yes, I know it's a very hard, long path. But in the end, it provides freedom. No more dependecies on .Net frameworks or other dependencies other than the necessary to run apps on Windows such as using msvcrt for console because nowdays, 16 bit is not supported without an emulator of some sort.

The other thing I read was that eax was for 32 bits and rax for 64 bits or "e$$" for 32 and "r$$" for 64.
Code:
push eax
mov eax, mess
call [printf]    

macomics wrote:
Because you can only use them in 64-bit programs, and this piece of code apparently refers to a 32-bit program.

I know I kind of messed a bit the console 32 bit and the Windows 64 bits. But the idea I am trying to make is to see something that carries on to the next.

Of course eax, rbp, rcx, etc will have to be used properly depening on the environement we are programming in. When you look at the low end of things, you initially had to defin your starting address at the beginning of your code then work your way down by playing with your stacks. I just want to find a universal (kind-of) way to always properly code without depending on macros. For example:

1. Reserve memory
2. Set data in memory
3. Use data
4. Clear memory of unused data

I guess this is pretty much universal. Then depending on your 32/64, you may need to align and add additional steps in between them for example, step:

2-a: Align stack for 64 bits

But I expect to do that on my own and not need someone to walk me through it. All I am asking is what is the basics of the 4 steps I initially wrote because clearly THIS, to me, is not normal:
Code:
; SIMPLE form
; Print's the message on screen
push mess
call [printf]
push 0
; Exit the program
call [exit]

; OTHER POSSIBILITY with more lines
; Print's the message on screen
push ebp
mov ebp, esp
sub esp, 4
mov dword[esp], mess
call [printf]
mov dword[esp], 0
; Exit the program
call [exit]    

Why would I chose the simplest form if the later form is a better practice?

AND, as I did not receive an answer yet for this question: Where does ebp come from? WHY use ebp/esp and not ax or eax or any other things that can be pushed with the same supported data?

Also, this, in your example:
Code:
call [GetMessage] ; now [esp] = second_return, [esp+4] = msg, [esp+8] = 0, [esp+12] = 0, [esp+16] = 0
second_return: ; now esp clear    

To me, a label is nothing more than a pointer to help us put logics in our programming or jump to when needed. BUT as you are stating, the label seems to play a role in memory/stack clearing for some weird reason. How can the label "second_return" clear esp if there is no code after it to clear the said esp?

Yes, ASM is low level programming, but there needs to be some logics somewhere. In other example, you cleared rax with "xor rax, rax", other examples will clear with "push 0" and now, we can clear using a simple label. That's what's messing my mind up. I understand the stack thing, although I see it as a string and a position inside a string as a table with rows, but that's just for my own comprehension. It's easier for me to understand:

; String style
push 0 ; Creates "_ _ _ _"

rather than:

; Stack style
push 0
; creates
; _
; _
; _
; _

I'm sorry if my noob questions look so basic, but there's weird inconsistencies between what I've read elsewhere and what is being said here so I just want to clear my head up and understand how to make things work in the good way to write it the right way and not just go for the easy way out because everything is done for you.

I'm proud that because of this thread, I have been able to understand how to program a simple console program and able to create a Windows program in x64. All I want to know is now how to integrate other Window API calls to add buttons and lables and other stuff in my window (I'll figure that out by myself). I'm just trying to find the basics here and see how I can interact with a user in console and transform it to make it more complex to make it interact in Windows.

I was also wondering if my message length is important and if I can overflow the allocated space for the stirng to be stored in a register and thus, crash the program. I added a "Press any key to continue." message and nothing bad happened and the porgram executes without any problem and all I did was add text so I'm wondering if we also need to specify how much space in memory wee need to allocate for the string to fit because in some placed, we need to do this "str_len equ $ - mess" and if it's a good practice to allocate this space, how would I go upon using this in the console example we have in this thread?
Post 14 Sep 2021, 07:17
View user's profile Send private message Reply with quote
macomics



Joined: 26 Jan 2021
Posts: 1043
Location: Russia
macomics 14 Sep 2021, 09:46
avidichard wrote:
For instance, one example I got simply included the kernel32 functions but still only used the msvcrt dll. That was stupid for me. I don't need kernel32 when all I want to do is write a single string in command line.
Then what prevents you from creating a separate file with approximately the following content and including it in all your projects:
Code:
 ; All the functions that I have ever used
section '.idata' import data readable writeable

include "INCLUDE/MACRO/IMPORT", CODE_MODE, ".INC" ; This file contains only 2 macros

library kernel32.dll, "kernel32.dll",\
user32.dll, "user32.dll",\
gdi32.dll, "gdi32.dll",\
msvcrt.dll, "msvcrt.dll"

import kernel32.dll,\
   ExitProcess, "ExitProcess",\
   GetModuleHandle, "GetModuleHandleA"

import user32.dll,\
   CreateWindowEx, "CreateWindowExA",\
   DefWindowProc, "DefWindowProcA",\
   DispatchMessage, "DispatchMessageA",\
   GetMessage, "GetMessageA",\
   LoadCursor, "LoadCursorA",\
   LoadIcon, "LoadIconA",\
   PostQuitMessage, "PostQuitMessage",\
   RegisterClassEx, "RegisterClassExA",\
   TranslateMessage, "TranslateMessage"

import gdi32.dll,\
   CreateSolidBrush, "CreateSolidBrush"

import msvcrt.dll,\
   exit, "exit",\
   printf, "printf"
    

and in your project
Code:
  format ...
  CODE_MODE  equ "32"  ; CODE_MODE equ "64"
  include "my_include.inc"
  ...
    


avidichard wrote:
So I am used to import and write things manually and YES, from time to time, forget about a thing or 2. But that's how I learn and program. I don't like to use prebuilt stuff just because it's easy. That's the exact reason I moved from other programming. AT the limit, I'll even want to know how the program compiles and converts ASM to binary. After all, that's what FASM does. What if I wanted to get rid of FASM and simply have it my way? That's the entire freedom of learning assembly without depending on macros.
Do as you know, it's just that you don't study assembly language. You just overload yourself at one stage of the study with deeper and unnecessary things. First, it is better to get used to writing code and only then delve into the features of the structure of PE files, algorithms for loading programs by the operating system and the logic of converting commands into binary data.

see
https://flatassembler.net/docs.php?article=win32
https://flatassembler.net/docs.php?article=manual#2.1

avidichard wrote:
Why would I chose the simplest form if the later form is a better practice?

This is the beauty of assembler. There is no universal way to do everything right for all cases without exception. At the moment, there is no great harm if you do something wrong. But in the window program there is a message processing cycle that must perform an infinite number of iterations for hours (just by the way). And if each iteration of the loop will lose a piece of the stack, then you may not quickly detect it. 64 kb of stack can leak very slowly because the message queue of the program will not be constantly filled. And the passes of this ~16000 cycle passes will be completed in 1-2 hours. At the same time, the memory will leak and someday the program will give an error.

Therefore, it is important to approach the call of each specific function very carefully. You will have to monitor the state of the stack and the safety of all processor registers without exception in each specific operation. Perhaps you had to calculate something and the result of the calculation is in the edx register, then you will have to save this value somewhere before calling the function. Otherwise, if you need it after calling the function, you will lose it.
avidichard wrote:
AND, as I did not receive an answer yet for this question: Where does ebp come from? WHY use ebp/esp and not ax or eax or any other things that can be pushed with the same supported data?
In all standard types of function calls, it is common to keep only some processor registers in their original form These registers are ebx, ebp, esp, esi and edi.(rbx, rbp, esp, rsi, rdi for 64-bit). Therefore, once you save their values in your function, you can use them as variables to store data between function calls. Even if a subfunction uses these registers, it must return their original values before returning to the function that called it. The value returned by the function usually falls into eax. And ecx and edx have an undefined state after the call. Other special registers (st0 - st7, xmm0 - xmm7, etc) must be cleared before calling the external function. Otherwise, an overflow may occur or the information will be lost. Before returning to the calling function, their state should also remain unoccupied.

Although when organizing your internal call types, no one restricts you to these rules. They are only needed for organizing inter-module interaction (calls to external functions).
avidichard wrote:
How can the label "second_return" clear esp if there is no code after it to clear the said esp?
In my example, the labels only indicate the addresses where the program will get to after returning from the corresponding call command. This is necessary to demonstrate that call also uses the stack and puts a certain value there. But after returning to your function, this value is removed from the stack.
avidichard wrote:
I'm sorry if my noob questions look so basic, but there's weird inconsistencies between what I've read elsewhere and what is being said here so I just want to clear my head up and understand how to make things work in the good way to write it the right way and not just go for the easy way out because everything is done for you.
The stack is just a large array of bytes divided into fixed-length elements (due to the fact that I wrote about alignment). Personally, I perceive the stack as a sufficient block of memory. In my programs, everything fits to the maximum in the memory in the stack (local variables). So in a large number of cases, you do not have to call the memory allocation function. BUT! I'm not saying that it should be used this way and only this way. Although I have already given an example of this approach:
Code:
; in eax = Class style
; in edx = lpfnWndProc
; in ebx = lpszClassName
; out ax = hAtom or NULL
  RegisterMyClasses:
    push ebp
    mov ebp, esp
    sub esp, 48               ; sizeof.WNDCLASSEX
    mov dword [esp], 48       ; wcx.cbSize
    mov dword [esp+4], eax    ; wcx.style
    mov dword [esp+8], edx    ; wcx.lpfnWndProc
    mov dword [esp+12], 0     ; wcx.cbClsExtra
    mov dword [esp+16], 0     ; ecx.cbWndExtra
    mov dword [esp+20], entry_point
    push 32512                ; IDI_APPLICATION
    push 0
    call [LoadIconW]
    mov word [esp+20], 0      ; hInsance fixup
    mov dword [esp+24], eax   ; wcx.hIcon
    mov dword [esp+44], eax   ; wcx.hIconSm
    push 32512                ; IDC_ARROW
    push 0
    call [LoadCursorW]
    mov dword [esp+28], eax   ; wcx.hCursor
    mov dword [esp+32], 17    ; wcx.hbrBackground = COLOR_WINDOWFRAME
    mov dword [esp+36], 0     ; wcx.lpszMenuName
    mov dword [esp+40], ebx   ; wcx. lpszClassName
    push esp
    call [RegisterClassExW]
    mov esp, ebp
    pop ebp
    retn
; For clarity, I replaced everything with mov commands and commented, but with the use of macros, it looks like this:
;  func RegClassEx, cdecl, rsi
;  begin
;     shl rax, 32
;     xor rcx, rcx
;     lea rax, [rax+80]
;     add rsp, 32 ; My macros prepare the stack for external calls automatically, so I have to take this place in the stack into account. 64-bit eh.
;     push rcx rbx rcx COLOR_WINDOWFRAME rcx rcx rcx rcx rdx rax
;     mov rsi, rsp
;     sub rsp, 32
;     call API LoadIcon, rcx, IDI_APPLICATION
;     or rax, rax
;     jz @f
;     lea rdx, [entry_point]
;     mov [rsi+40], rax
;     xor dx, dx
;     mov [rsi+72], rax
;     mov [rsi+32], rdx
;     call API LoadCursor, NULL, IDC_CURSOR
;     or rax, rax
;     jz @f
;     mov [rsi+48], rax
;     call API RegisterClassEx, rsi
;  @@:
;     return DONE
    

avidichard wrote:
"str_len equ $ - mess" and if it's a good practice to allocate this space, how would I go upon using this in the console example we have in this thread?
Yes. This is often done. In general, the calculation of hInstance from the last post is based on exactly the same thing, only the initial address of loading the program into memory is taken as the beginning of the line. But you haven't got to dynamic strings yet. So for now, this solution will be the best for you. Just do not use equ for this, but =!!! ("str_len = $ - mess"). Otherwise it will generate the following wrong lines:
Code:
mess db "My message", 0
correct_address:
str_len equ $ - mess ; saved: str_len is "$ - mess"
str_len2 = $ - mess ; calculated: str_len2 is 11
...
that_address:
  push str_len  ; push $ - mess, but $ = that_address instead of correct_address
  push str_len2 ; push 11 Because str_len2 will be calculated at the moment it appears in the text
    

ADD: Try that
Code:
push mess
call [printf]
. . .
mess file "name_of_that_file.asm"
 db 0    
Post 14 Sep 2021, 09:46
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

< 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-2025, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.