stack_to_string procedure

There are many things to say here to build up my mind and make a sketch of the skeleton for this procedure. One thing to consider is that I have to design a pre-formatted string that makes it easy to cast the values of the registers into it and to display the same on the screen. There are 14 registers considering the flags, so I can put them in two lines. If I separate the registers with two space characters then it happens that the first "x" of register IP is 9 bytes away from the first "x" of the register FL (flags) which is exactly the same byte-distance from the first "x" of register BP and the first "x" of register SI (Fig. A).

this is the pre-formatted string as it will appear on screen
Fig. A - pre-formatted string as it will appear on screen.

In order to display two lines, I need to insert two bytes corresponding to /Lf (Line Feed 0x0A) and /Cr (Carriage Return 0x0D) so the trick is to keep two-bytes equal-spacing between all registers which is a very convenient way to store the string in memory. To achieve this, I introduce two /SP (empty Space 0x20) characters between the end and beginning of registers. Since I have equal spacing, it will be easy to implement an algorithm that fills the pre-formatted string and that makes always the same jump when it ends filling the information of one register and moves forward to start filling the next one. The string must end with the byte 0x00 to signalize that the show_string procedure has to stop sending characters to the screen. The sequence of the registers in the pre-formatted string must follow the reverse sequence of PUSHes done by AP-PROBE (last in – first out) as I want to keep the code as simple as possible. Now that I have an idea of the pre-formatted string and a very primitive idea of the mechanism the code may use, it is time to sketch the bare-bones skeleton of the stack_to_string procedure.

stack_to_string - bare bones: 1st attempt
stack_to_string: ################################################################################ # # This procedure uses a for loop in 8086 assembly to take the status saved # on the stack, convert the hex in ASCII and saves it in the pre-formatted # string. # # INPUT: 14 parameters on the stack # OUTPUT: --- # REGISTER USAGE: ??? # THIS ONE CALLS: ??? # ################################################################################ ############################ # adjust source pointer # DS:[SI] starts at SS:[BP] ############################ ############################################################################## # adjust destination pointer # ES:[DI] starts at the first "x" of "IP=xxxx" in the pre-formatted string ############################################################################## ################################### # for each element on the stack # (for 14 elements) ################################### ############################################ # take one element # (each element is a word from the stack) ############################################ ################################################################## # convert hex word to ASCII and save inside pre-formatted string ################################################################## ######################################################################## # jump to the beginning of next register in the pre-formatted string ######################################################################## ######################### # next element ######################### ##################### # end of procedure ##################### RET ########################## # pre-formatted string ########################## formatted_str: "IP=xxxx FL=xxxx AX=xxxx BX=xxxx CX=xxxx DX=xxxx BP=xxxx" /Lf /Cr "SI=xxxx DI=xxxx CS=xxxx DS=xxxx ES=xxxx SS=xxxx SP=xxxx" /Lf /Cr /00

In the first attempt, I wrote the code down in a way that it seemed to be reasonable. I started thinking about how I could implement the different blocks and I realized that the block "convert hex word to ASCII and save inside pre-formatted string" actually is a bit more complex than it may seem at first glance. Every word is 16-bits long, and the hexadecimal representation is done with 4 ASCII characters. This means that I have to proceed with a conversion nibble by nibble so I reworked a bit the skeleton and I made it more complex to account for this problem. In the second attempt, you can see such a change.

stack_to_string - bare bones: 2nd attempt
stack_to_string: ################################################################################ # # This procedure uses a for loop in 8086 assembly to take the status saved # on the stack, convert the hex in ASCII and saves it in the pre-formatted # string. # # INPUT: 14 parameters on the stack # OUTPUT: --- # REGISTER USAGE: ??? # THIS ONE CALLS: ??? # ################################################################################ ############################ # adjust source pointer # DS:[SI] starts at SS:[BP] ############################ ############################################################################## # adjust destination pointer # ES:[DI] starts at the first "x" of "IP=xxxx" in the pre-formatted string ############################################################################## ################################### # for each element on the stack # (for 14 elements) ################################### ############################################ # take one element # (each element is a word from the stack) ############################################ ################################################################## # convert hex word to ASCII and save inside pre-formatted string ################################################################## #################### # for each nibble #################### #################### # take a nibble #################### ################################ # convert the nibble to ASCII ################################ ################################## # save the ASCII in the string ################################## ################# # next nibble ################# ######################################################################## # jump to the beginning of next register in the pre-formatted string ######################################################################## ######################### # next element ######################### ##################### # end of procedure ##################### RET ########################## # pre-formatted string ########################## formatted_str: "IP=xxxx FL=xxxx AX=xxxx BX=xxxx CX=xxxx DX=xxxx BP=xxxx" /Lf /Cr "SI=xxxx DI=xxxx CS=xxxx DS=xxxx ES=xxxx SS=xxxx SP=xxxx" /Lf /Cr /00

Finally, I felt confident about the working plan and I started coding it.

stack_to_string - final
stack_to_string: ################################################################################ # # This procedure uses a for loop in 8086 assembly to take the status saved # on the stack, convert the hex in ASCII and saves it in the pre-formatted # string. # # INPUT: 14 parameters on the stack # OUTPUT: --- # REGISTER USAGE: BP, SI, DI, CX, BX, DS, ES, AX # THIS ONE CALLS: out_a_nibble, nibble_to_ASCII # ################################################################################ ############################ # adjust source pointer # DS:[SI] starts at SS:[BP] ############################ mov bp, sp # save the stack pointer mov si, bp # point at top of the stack push ss pop ds # DS = SS ############################################################################## # adjust destination pointer # ES:[DI] starts at the first "x" of "IP=xxxx" in the pre-formatted string ############################################################################## mov ax, formatted_str: # MOV ES, formatted_str: doesn't exist and we need # a work around mov es, ax # ES = AX = formatted_str: mov di, 3 # DI = 3, because the first "x" is 3 bytes ahead from # start of string. ################################### # for each element on the stack # (for 14 elements) ################################### push cx mov cx, 14 element_on_stack: ############################################ # take one element # (each element is a word from the stack) ############################################ lodsw # AX = DS:[SI]; SI = SI + 2 mov bx, ax # AX is going to be manipulated later on (nibble by nibble) # so we keep a copy of its original value in BX ################################################################## # convert hex word to ASCII and save inside pre-formatted string ################################################################## #################### # for each nibble #################### push cx mov cx, 4 nibble_by_nibble: #################### # take a nibble #################### # BX contains already the source word to select the nibble from # CX is the nibble index changing from 4 to 1 in the for loop # the function "out_a_nibble" can be called immediatelly. call out_a_nibble: ################################ # convert the nibble to ASCII ################################ # AL contains already the nibble to be converted # the function "nibble_to_ASCII" can be called immediatelly. call nibble_to_ASCII: ################################## # save the ASCII in the string ################################## # AL contains already the ASCII code to store. stosb # ES:[DI] = AL; DI = DI + 1 ################# # next nibble ################# loop nibble_by_nibble: pop cx ######################################################################## # jump to the beginning of next register in the pre-formatted string ######################################################################## add di, 5 # pos.: 0 1 2 3 4 5 6 7 8 9 a b c d e f ... # string format: A X = x x x x B X = x x x x ... ######################### # next element ######################### loop element_on_stack: pop cx ##################### # end of procedure ##################### RET ########################## # pre-formatted string ########################## formatted_str: "IP=xxxx FL=xxxx AX=xxxx BX=xxxx CX=xxxx DX=xxxx BP=xxxx" /Lf /Cr "SI=xxxx DI=xxxx CS=xxxx DS=xxxx ES=xxxx SS=xxxx SP=xxxx" /Lf /Cr /00

As you can see, I planned to use two other sub-procedures: the first to separate the word a nibble at a time (out_a_nibble sub-procedure) and the second to convert the selected nibble into ASCII code (nibble_to_ASCII sub-procedure). I will plan the design of these procedures in such a way that the first procedure out_a_nibble accepts inputs in the same format as they are created inside the FOR-loop that calls it and, at the same time, it will set outputs exactly in the format that the second procedure nibble_to_ASCII requires them to be. In this way, I don't have to perform any register manipulation and/or input/output adaptation between the caller and the called procedure in assembly.

Before moving on in the definition of the two other sub-procedures, I want to spend some words on stack_to_string not really about what it does but rather in the way it is written. As you see I massively use comments. You may think I do it for you, but indeed I do it for me 😉. Moreover, although the assembly doesn't support a structured syntax such as C with the use of parenthesis, I do try to highlight the logical nests in the code by my writing style as much as I can. Without it, I would be completely lost. I know by experience that having with me the code written in this way is much like having a very good drawing1 of the thing I want to build when I go the sample shop (DEBUG.EXE) and start building it with my hands. Now the point is that this part of the job can be made automatic using a piece of software called assembler which in turn expects the code to follow a certain syntax that it can make interpretation of and convert into machine code. But remember that I don't have any assembler so I will learn and (maybe) "feel" what the job of an assembler software looks like by doing the assembling by hands. Isn't that cool? I like it!

Another aspect I want to highlight is how to build up logical loops in x86 assembly language. The CPU has just "jumps" available; some of them are conditionals and some are a very special one. Consider the LOOP instruction: this is again an example of a piece of software hard-wired inside the circuits of the CPU. To understand it, you can consider the following instruction: LOOP a_memory_location:. What it does is equivalent to:

DEC CX JE a_memory_location:

Typically we use the LOOP instruction combined with the usage of CX register to build the equivalent logic of a FOR loop in 8086 CPU. So the translation of the FOR loop logic in assembly looks something like this:

################ # high level ################ for (i=0 to 3) { doSomething } ############## # Assembly ############## push cx mov cx, 4 loop_begins_here: doSomething loop loop_begins_here: pop cx

The point is that the register CX is locked by the private use that the LOOP instruction makes of it. Moreover, we cannot exclude that CX had a value important to another piece of code somehow or somewhere else, so we have to take care not to destroy this value and instead preserve it on the stack. There is no guarantee that our FOR loop logic is at the root level, chances are that this logic is already nested. For this reason, it is a good practice to consider for granted that it is always nested somewhere and use PUSH and POP instruction at the begin and at the end of the logic block.

################ # high level ################ for (i=0 to 3) { for (j=0 to 5){ doSomething } } ############## # Assembly ############## push cx mov cx, 4 1st_level: push cx mov cx, 6 2nd_level: doSomething loop 2nd_level: pop cx loop 1st_level: pop cx

As you can see, if you write a block of code sticking to the rule of PUSHing and POPing CX at the beginning and end of a logical block, you can nest it without any problem. These kinds of simple rules are coded into the compilers which use these rules in the operation of compiling: translate from high-level language to assembly.



  1. If you consider that I am a mechanical engineer, it is natural for me to think about drawing and manufacturing and then make parallel and find commonalities with what I am currently learning. This page of the diary (today a post) was written by the end of 2014. Looking at it back now, this parallel between software engineering and mechanical engineering was just planting a seed in my mind that had to sprout in something more defined late in February 2019 and find a place in my diary in the post "A different perspective" (some many posts are still to come before I get there in the blog). [click back]

<PREV.  -  ALL  -  NEXT>

Comments