Regarding Assembly Language Loops - loops

Can anyone explain what this code does? I kind of understand it, but I don't quite understand what happens when the code label appears below "loop N-Not-1". I'm not sure if I understand loops correctly. I think of them as do-while loops in C++. In this case, wouldn't the loop for N-is-1 continue indefinitely? I thought this was a if-else statement and not a loop?
Write a piece of code that computes the function below:
if (N = 1) then Y = -X
else
Y=X
Assume that the value of X is in the eax register. Also assume that the value of N is in the
ebx register. The computed value of Y need to be placed in the eax register.
Hint 1: Use a loop instruction in your code.
Hint 2: This problem can be solved using less than five instructions.
; eax = X, ebx = N
; Write your code below
mov ecx, ebx
loop N-not-1
N-is-1: neg eax
N-not-1: ; Y = eax

loop instruction operates on the value of ECX. It decreases ECX first and checks whether it is zero. If it is not zero, then it jumps to the specified address. If it is zero then break.
mov ecx, ebx ; this instruction moves the value of N to ecx
loop N-not-1 ; if N is 1 then, on decrementing it becomes 0 and the loop breaks.
N-is-1: neg eax ; if N is 1, eax gets negated as the loop breaks
N-not-1: ; Y = eax // if N is not 1, eax remains unchanged

First of all, this code is very badly presented. I am copying it here without the clutter:
mov ecx, ebx
loop N-not-1
neg eax
N-not-1:
This code is a hack. It does not actually loop. It just makes use of the fact that the loop instruction will do 3 things : decrement ecx, check if it is zero, and jump all in one instruction. It is equivalent to the following:
dec ebx
cmp ebx, 0
jnz N-not-1
neg eax
N-not-1:

loop instruction is like the following C code:
for (; ecx != 0; ecx--) {
// instructions
}
and your code use loop instruction for checking ebx(N) against zero:
ebx = N;
ecx = ebx;
for (; ecx != 0; ecx--) {
// if ecx=1 (N = 1) we enter the loop
eax = -eax
}
Y = eax
I used C like code, but in assembly there is no block and everything is controlled with labels.

Related

x86 function returning char* in C

I want to write a function in x86 which will be called from C program.
The function should look like this:
char *remnth(char *s, int n);
I want it to remove every nth letter from string s and return that string. Here's my remnth.s file:
section.text
global remnth
remnth:
; prolog
push ebp
mov ebp, esp
; procedure
mov eax, [ebp+8]; Text in which I'm removing every nth letter
mov ebx, [ebp+12]; = n
mov ecx, [ebp+8] ; pointer to next letter (replacement)
lopext:
mov edi, [ebp+12] ; edi = n //setting counter
dec edi ; edi-- //we don't go form 'n' to '1' but from 'n-1' to '0'
lop1:
mov cl, [ecx] ; letter which will be a replacement
mov byte [eax], cl ; replace
test cl,cl ; was the replacement equal to 0?
je exit ; if yes that means the function is over
inc eax ; else increment pointer to letter which will be replaced
inc ecx ; increment pointer to letter which is a replacement
dec edi ; is it already nth number?
jne lop1 ; if not then repeat the loop
inc ecx ; else skip that letter by proceeding to the next one
jmp lopext ; we need to set counter (edi) once more
exit:
; epilog
pop ebp
ret
The problem is that when I'm calling this function from main() in C I get Segmentation fault (core dumped)
From what I know this is highly related to pointers, in this case I'm returning *char, and since I've seen some functions that returns int and they worked just fine, I suspect that I forgot about something important with returning a *char properly.
This is what my C file looks like:
#include <stdio.h>
extern char *remnth(char *s,int n);
int main()
{
char txt[] = "some example text\0";
printf("orginal = %s\n",txt);
printf("after = %s\n",remnth(txt,3));
return 0;
}
Any help will be appreciated.
You're using ecx as a pointer, and cl as a work register. Since cl is the low 8 bits of ecx, you're corrupting your pointer with the mov cl, [ecx] instruction. You'll need to change one or the other. Typically, al/ax/eax/rax is used for a temporary work register, as some accesses to the accumulator use shorter instruction sequences. If you use al as a work register, you'll want to avoid using eax as a pointer and use a different register instead (remembering to preserve its contents if necessary).
You need to load the return value into eax before the return. I assume you want to return a pointer to the beginning of the string, so that would be [ebp+8].

How can I find the last 0 on an array in nasm?

I'm writing code to find the last 0 on an array.
Basically I need to move a new value on the "top" of each array, if it has only zeros it puts it at the end and if it finds other value it puts it on the last 0 (I'm treating my arrays as piles).
So far my subroutine works fine for the most part but sometimes it rewrites a value that I don't want (instead of getting the first value different from 0 it takes the next one). Here's the code I've been using to get the "top" of the array.
TOP:
xor ecx,ecx
xor ebx,ebx
TOP_FOR:
mov bx,word[eax+ecx*2] ;eax has the pointer of the array
cmp ecx,n ;n is the array's length
je END_TOP
inc ecx
cmp bx,0
je TOP_FOR
;here i get the direction of the first value different
END_TOP: ;from 0 but in my code i need the last 0, so
dec ecx ;i decrease ecx (result of this subrutine)
ret
For example,
If I put an array with 0,2 I expect ecx = 0, but with that input actually get 1.
With the array 1,2 I get 0 (which is what I want)
with the array 0,0 I get 1 (what I want, again)
Edit: tried starting the loop on n-1 and it's giving me even weirder results.
TOP:
xor ecx,ecx
;xor ebx,ebx
mov ecx,n-1
TOP_FOR:
;mov bx,word[eax+ecx*2]
cmp word[eax+ecx*2],0
je FIN_TOPE
dec ecx
cmp ecx,0
jne TOP_FOR
END_TOP:
ret
Your logic is totally backwards. Your cmp/je loop condition leaves the loop when you find the first non-zero. (And you've already incremented ECX after loading, but before checking it).
So after your loop, ECX = index of the element after the first non-zero element.
You at least 2 options:
remember the last-seen 0 in another register, and use it at the end of the loop
loop backwards, starting with ECX = n-1, and exit the loop on the first zero. (Or on dec ecx producing 0.)
One of these is obviously more efficient and easier than the other. :P
I'll leave it up to you to solve the off-by-1 problems, but probably you want to have the ecx < n or ecx >= 0 check at the bottom of the loop, e.g. dec ecx / jge TOP_FOR. i.e. a do{}while(--i) loop.
Also, normally EBX is a call-preserved register. You don't need to use it at all, though. cmp word [eax + ecx*2], 0 works fine.
Also in your current code, you read 2 bytes past the end of the array. potentially faulting if it was at the end of a page. (You don't use it, though, so it's not a correctness problem other than that.) You use ECX as an index before checking if it's too large! That problem goes away if you just use a memory, immediate cmp.
Also, normally a pointer-increment is more efficient. After the loop you can subtract and right-shift to get an index.

ASSEMBLY - output an array with 32 bit register vs 16 bit

I'm was working on some homework to print out an array as it's sorting some integers from an array. I have the code working fine, but decided to try using EAX instead of AL in my code and ran into errors. I can't figure out why that is. Is it possible to use EAX here at all?
; This program sorts an array of signed integers, using
; the Bubble sort algorithm. It invokes a procedure to
; print the elements of the array before, the bubble sort,
; once during each iteration of the loop, and once at the end.
INCLUDE Irvine32.inc
.data
myArray BYTE 5, 1, 4, 2, 8
;myArray DWORD 5, 1, 4, 2, 8
currentArray BYTE 'This is the value of array: ' ,0
startArray BYTE 'Starting array. ' ,0
finalArray BYTE 'Final array. ' ,0
space BYTE ' ',0 ; BYTE
.code
main PROC
MOV EAX,0 ; clearing registers, moving 0 into each, and initialize
MOV EBX,0 ; clearing registers, moving 0 into each, and initialize
MOV ECX,0 ; clearing registers, moving 0 into each, and initialize
MOV EDX,0 ; clearing registers, moving 0 into each, and initialize
PUSH EDX ; preserves the original edx register value for future writeString call
MOV EDX, OFFSET startArray ; load EDX with address of variable
CALL writeString ; print string
POP EDX ; return edx to previous stack
MOV ECX, lengthOf myArray ; load ECX with # of elements of array
DEC ECX ; decrement count by 1
L1:
PUSH ECX ; save outer loop count
MOV ESI, OFFSET myArray ; point to first value
L2:
MOV AL,[ESI] ; get array value
CMP [ESI+1], AL ; compare a pair of values
JGE L3 ; if [esi] <= [edi], don't exch
XCHG AL, [ESI+1] ; exchange the pair
MOV [ESI], AL
CALL printArray ; call printArray function
CALL crlf
L3:
INC ESI ; increment esi to the next value
LOOP L2 ; inner loop
POP ECX ; retrieve outer loop count
LOOP L1 ; else repeat outer loop
PUSH EDX ; preserves the original edx register value for future writeString call
MOV EDX, OFFSET finalArray ; load EDX with address of variable
CALL writeString ; print string
POP EDX ; return edx to previous stack
CALL printArray
L4 : ret
exit
main ENDP
printArray PROC uses ESI ECX
;myArray loop
MOV ESI, OFFSET myArray ; address of myArray
MOV ECX, LENGTHOF myArray ; loop counter (5 values within array)
PUSH EDX ; preserves the original edx register value for future writeString call
MOV EDX, OFFSET currentArray ; load EDX with address of variable
CALL writeString ; print string
POP EDX ; return edx to previous stack
L5 :
MOV AL, [ESI] ; add an integer into eax from array
CALL writeInt
PUSH EDX ; preserves the original edx register value for future writeString call
MOV EDX, OFFSET space
CALL writeString
POP EDX ; restores the original edx register value
ADD ESI, TYPE myArray ; point to next integer
LOOP L5 ; repeat until ECX = 0
CALL crlf
RET
printArray ENDP
END main
END printArray
; output:
;Starting array. This is the value of array: +1 +5 +4 +2 +8
;This is the value of array: +1 +4 +5 +2 +8
;This is the value of array: +1 +4 +2 +5 +8
;This is the value of array: +1 +2 +4 +5 +8
;Final array. This is the value of array: +1 +2 +4 +5 +8
As you can see the output sorts the array just fine from least to greatest. I was trying to see if I could move AL into EAX, but that gave me a bunch of errors. Is there a work around for this so I can use a 32 bit register and get the same output?
Using EAX is definitely possible, in fact you already are. You asked "I was trying to see if I could move AL into EAX, but that gave me a bunch of errors." Think about what that means. EAX is the extended AX register, and AL is the lower partition of AX. Take a look at this diagram:image of EAX register
. As you can see, moving AL into EAX using perhaps the MOVZX instruction would simply put the value in AL into EAX and fill zeroes in from right to left. You'd be moving AL into AL, and setting the rest of EAX to 0. You could actually move everything into EAX and run the program just the same and there'd be no difference because it's using the same part of memory.
Also, why are you pushing and popping EAX so much? The only reason to push/pop things from the runtime stack is to recover them later, but you never do that, so you can just let whatever is in EAX at the time just die.
If you still want to do an 8-bit store, you need to use an 8-bit register. (AL is an 8-bit register. IDK why you mention 16 in the title).
x86 has widening loads (movzx and movsx), but integer stores from a register operand always take a register the same width as the memory operand. i.e. the way to store the low byte of EAX is with mov [esi], al.
In printArray, you should use movzx eax, byte ptr [esi] to zero-extend into EAX. (Or movsx to sign-extend, if you want to treat your numbers as int8_t instead of uint8_t.) This avoids needing the upper 24 bits of EAX to be zeroed.
BTW, your code has a lot of unnecessary instructions. e.g.
MOV EAX,0 ; clearing registers, moving 0 into each, and initialize
totally pointless. You don't need to "init" or "declare" a register before using it for the first time, if your first usage is write-only. What you do with EDX is amusing:
MOV EDX,0 ; clearing registers, moving 0 into each, and initialize
PUSH EDX ; preserves the original edx register value for future writeString call
MOV EDX, OFFSET startArray ; load EDX with address of variable
CALL writeString ; print string
POP EDX ; return edx to previous stack
"Caller-saved" registers only have to be saved if you actually want the old value. I prefer the terms "call-preserved" and "call-clobbered". If writeString destroys its input register, then EDX holds an unknown value after the function returns, but that's fine. You didn't need the value anyway. (Actually I think Irvine32 functions at most destroy EAX.)
In this case, the previous instruction only zeroed the register (inefficiently). That whole block could be:
MOV EDX, OFFSET startArray ; load EDX with address of variable
CALL writeString ; print string
xor edx,edx ; edx = 0
Actually you should omit the xor-zeroing too, because you don't need it to be zeroed. You're not using it as counter in a loop or anything, all the other uses are write-only.
Also note that XCHG with memory has an implicit lock prefix, so it does the read-modify-write atomically (making it much slower than separate mov instructions to load and store).
You could load a pair of bytes using movzx eax, word ptr [esi] and use a branch to decide whether to rol ax, 8 to swap them or not. But store-forwarding stalls from byte stores forwarding to word loads isn't great either.
Anyway, this is getting way off topic from the title question, and this isn't codereview.SE.

Start outer incrementation value with 0 in the inner cicle - assembly

I want to copy a 5x5 matrix of bits to a peripherical. The problem I´m having is that I can´t start the column cicle with the line incrementation variable with 0. In a high-level it would be like this (very simple):
for (line=0;line<4;line++)
for (column=0;column<4;column+++)
R2- line
R3- column
line_cicle:
CMP R2, 4
JZ end
ADD R2,1
column_cicle:
; do stuff that is not depend of the end of a line
CMP R3, 4
JZ line_cicle
; do stuff that is depend of the end of a line
ADD R3, 1
JMP column_cicle
That ADD R2,1 is what is messing up, but where do I put it so that it doesn´t start with 1?
I don't really understand what you're saying/doing with your proposed assembly implementation. Why are you initializing the register to 1 when your loop is supposed to start at 0?
But a for loop nested in another for loop is a relatively simple thing to write, so let's start over and just take things one step at a time, starting from the high-level C code:
for (line=0;line<4;line++)
for (column=0;column<4;column++)
Here is the first (outer) for loop:
xor eax, eax ; line = 0
.LineLoop:
; Do something with line (EAX).
; ...
inc eax ; ++line
cmp eax, 4
jb .LineLoop ; keep looping if line < 4
; We are now finished with the loop.
Now, of course, a compiler wouldn't generate this code. This is a very small loop—it only goes around 4 times—so the overhead of the loop is probably going to be substantial compared to the code that gets executed inside, on each iteration. So a compiler would actually unroll the loop 4 times, producing code that is not only faster but more readable. However, I digress…we were writing loops. :-)
We have the outer loop, and we need the inner loop. Of course, the inner loop is basically the same thing as the outer loop, just with a different variable. Here is the inner loop:
xor edx, edx ; column = 0
.ColumnLoop:
; Do something with column (EDX).
; ...
inc edx ; ++column
cmp edx, 4
jb .ColumnLoop ; keep looping if column < 4
; We are now finished with the loop.
Simple enough, right? I just changed the variable/register and the label name. The last task is to nest them. It turns out that is simple, too. The inner loop's code just gets stuck right in the outer loop's code, right there where I said Do something with line (EAX), since the inner loop is going to do something with line—it's going to loop through all of the columns associated with that line. It is another copy-paste job:
xor eax, eax ; line = 0
.LineLoop:
xor edx, edx ; column = 0
.ColumnLoop:
; Do something with line (EAX) and column (EDX).
; ...
inc edx ; ++column
cmp edx, 4
jb .ColumnLoop ; keep looping if column < 4
inc eax ; ++line
cmp eax, 4
jb .LineLoop ; keep looping if line < 4
; We are now finished with both loops.
Remember that you can choose different registers for your loop counters. I just arbitrarily chose EAX and EDX. If you are going to call a function inside the body of the loop that does something with the line and column, and that function expects its parameters to be passed in different registers, then you might as well use those registers as your loop counters.
Note that there is a slightly more optimal way to write this code that would eliminate the cmp instructions. Instead of starting from 0 and counting up (which requires us to do a comparison to see if we've reached the end yet), we can start from the end and count down. Then, we just take advantage of the fact that the dec instruction sets the zero flag (ZF) when the result is 0, branching directly on that flag, instead of having to do a comparison. The code is easier to understand than the explanation:
mov eax, 4 ; line = 4
.LineLoop:
mov edx, 4 ; column = 4
.ColumnLoop:
; Do something with line (EAX) and column (EDX).
; ...
dec edx ; --column
jnz .ColumnLoop ; keep looping if column > 0
dec eax ; --line
jnz .LineLoop ; keep looping if line > 0
; We are now finished with both loops.
The only issue with this is that you are looping backwards over the lines and columns. This is usually not a problem, though.

ASSEMBLY: Binary Search on a Sorted String Array

After having spent several days on this binary search problem, which must be done completely in Assembly, I'm not quite sure where my logic breaks down when it comes to searching for name matches [case-insensitive] from a sorted array.
Any help would be greatly appreciated:
THE C PROGRAM
/*
int b_search (char list[100][20], int count, char* token);
list – the starting address of the list of names to be searched
count – total number of names in the list
token – name to be searched in the list
*/
This is the list of names:
Arturo Bryan chris David Jon Mark shane SIMON Thomas TONY
The following are all tokens, names to be searched in the list:
// tests elements with exact match, for example: "Jon", "shane", "TONY"
// tests case insensitivity, for example: "Chris", "BryAN"
// tests if code detects partial and nonsensical queries, for example: "Art" [short for Arturo], "garbage"
// tests a list with an odd number of names, for example: "DAVe", "Jona"
I've constantly been getting either infinite loops somewhere in the logic, finding
that the index returned is always 0 or finding that it incorrectly returns "name not found."
Again, any help would be greatly appreciated; thanks for reading.
// =================================================================================
MY CODE:
int b_search (char list[100][20], int count, char* token)
{
__asm
{
// Function returns the positionmove of the token in the list, starting with 1.
// If name is NOT found, return 0.
// Registers used:
// EAX: logical OR value
// EBX: toLowercase() loop counter
// ECX: total number of names
// EDI: name to be searched
// ESI: list pointer
mov eax, 0 ; // zero out the result
mov ebx, 0 ; // zero out EBX for use
mov esi, list ; // move the list pointer to ESI
mov edi, token ; // the name to be searched to EDI
// YOUR CODE HERE
// list - the starting address of the list of names to be searched
// count - total number of names in the list
// token - name to be searched in the list
// ==================================================================================================================
// CHANGE TOKEN LETTERS TO LOWERCASE
// ==================================================================================================================
TOKEN_LOWERCASE: // Cycles through every char in token and converts them to lowercase
mov eax, [edi + ebx] ; // move char of token into EAX
or eax, 0x20 ; // convert to lowercase by logical OR with 0010 0000
mov [edi + ebx], eax ; // move new char back into EAX
inc ebx ; // increments loop counter
cmp [edi + ebx], 0x00 ; // checks if the next char is a null terminator
jnz TOKEN_LOWERCASE ; // exit loop in the presence of a null terminator
// ==================================================================================================================
// BINARY SEARCH RECURSION - FIRST ITERATION LOCATION
// All registers are now open except for EDI and ESI
// ==================================================================================================================
mov eax, 0 ; // set the minimum value to be index first [0]
mov ecx, count ; // set the maximum value to be index last [index.length]
mov edx, 0 ; // zero out EDX for use
push eax ; // push minimum value EAX back onto stack
push ecx ; // push maximum value ECX back onto stack
BEGIN_BINARY_SEARCH: // return here for recursion
mov eax, 0 ; // zero out EAX for use
//mov ebx, 0 ; // zero out EBX for use
mov ecx, 0 ; // zero out ECX for use
mov edx, 0 ; // zero out EDX for use
// FIRST IN, LAST OUT
pop ecx ; // maximum value; first in, last out
pop eax ; // minimum value; first in, last out
cmp ecx, eax ; // compares the maximum and minimum values
jl DONE_EXIT ; // all operations completed, goto DONE_EXIT [KNOWN ISSUE]
mov edx, eax ; // move EAX into EDX
add edx, ecx ; // add EAX and ECX, store it into EDX
sar edx, 0x01 ; // shifts arithmetic right, dividing EDX by 2
// FIRST IN, LAST OUT
push eax ; // push minimum value EAX back onto stack
push ecx ; // push maximum value ECX back onto stack
mov eax, 0 ; // move EAX to 0 for use *****
mov ebx, 0 ; // move EBX to 0 for use [external counter, see "RECURSION CONCLUDES"]
mov ecx, 0 ; // move ECX to 0 for use
// ==============================================================================================================
// INNER RECURSIVE LOOP
// Registers to keep track of:
// ECX = token[i]
// EAX = element[i]
// ==============================================================================================================
GO_LOWER: // loop to check if cursor needs to go lower
mov ecx, edx ; // move EDX and copy it into ECX; SEE BELOW:
imul ecx, 0x14 ; // OFFSET_TOTAL = COUNT * 20[Decimal]
add ecx, ebx ; // adds offset to EBX
mov eax, [esi + ecx] ; // moves element[i] into EAX, where list + 20 * externalCount + internalCount
// ECX held the offset; it has been moved to EAX, so ECX can be reset
mov ecx, 0 ; // reset ECX with every iteration to prepare for another address's contents
mov ecx, [edi + ebx] ; // move token element into ECX
cmp eax, 0x00 ; // compares EAX to zero; checks for null terminator; SEE BELOW:
jz NULL_TERM_CHECK ; // if IS zero, then jump to IS_NULL
jnz NOT_NULL ; // if NOT zero, then jump to NOT_NULL
// ==========================================================================================================
NULL_TERM_CHECK: // procedure to check contents of ECX are a null terminator at this point
//cmp ecx, 0x00 ; // checks for null terminator
cmp ecx, eax ; // compares token and element
jz IS_MATCH ; // if IS null terminator, then reached end of String
jl DONE_GO_LOWER ; // if token.length() is shorter then element.length()
jg DONE_GO_HIGHER ; // if token.length() is longer than element.length()
//jnz DONE_EXIT ; // if NOT null terminator, function is not yet finished; proceed:
// ==========================================================================================================
NOT_NULL: // proceed with the rest of the function
or eax, 0x20 ; // logical OR with EAX will return the letter in lowercase
sub ecx, eax ; // -32 -> 0 -> +32; result indicates need to jump DONE_GO_LOWER or DONE_GO_HIGHER
jl DONE_GO_LOWER ; // jump to GO_LOWER if less than zero;
jg DONE_GO_HIGHER ; // jump to GO_HIGHER if greater than zero
inc ebx ; // increments loop counter if slips through
jmp GO_LOWER ; // return to GO_LOWER for recursion
// ==============================================================================================================
// ==================================================================================================================
// RECURSION CONCLUDES - END ITERATION LOCATION
// Registers EAX, EBX and ECX are now open
// Register EDX is reserved for being the external loop counter
// ==================================================================================================================
// ==================================================================================================================
DONE_GO_LOWER:
// FIRST IN, LAST OUT
pop ecx ; // pop maximum value back into ECX from stack
pop eax ; // pop minimum value back into EAX from stack
mov ecx, edx ; // move EDX into ECX, copying the value
sub ecx, 0x01 ; // subtracts 1 from current makes the maximum
push eax ; // push minimum value EAX back onto stack
push ecx ; // push maximum value ECX back onto stack
jmp BEGIN_BINARY_SEARCH ; // jump back to beginning of recursion
// ==================================================================================================================
// ==================================================================================================================
DONE_GO_HIGHER:
// FIRST IN, LAST OUT
pop ecx ; // pop maximum value back into ECX from stack
pop eax ; // pop minimum value back into EAX from stack
mov eax, edx ; // move EDX into EAX, updating the minimum
add eax, 0x01 ; // adds 1 to current makes the minimum
push eax ; // push minimum value EAX back onto stack
push ecx ; // push maximum value ECX back onto stack
jmp BEGIN_BINARY_SEARCH ; // jump back to beginning of recursion
// ==================================================================================================================
DONE_EXIT:
mov eax, 0 ; // move eax back to 0 to finish up
jmp DONE ; // jump to default done location
// ==================================================================================================================
IS_MATCH:
mov eax, edx ; // move ESP contents into EAX
jmp DONE ; // done with everything
// END PROCEDURE: DEFAULT TO HERE WHEN FINISHED
DONE: // ALL OPERATIONS FINISHED
}
}
#Edward is exactly right. Here is a C routine that's not hard to translate. My quick assembly version turned out to have 39 instructions.
#include <stdio.h>
int bsearch(char a[][20], int count, char *key)
{
// Answer lies in a[lo .. hi-1].
int lo = 0, hi = count;
while (lo < hi) {
// Midpoint of range where answer must lie.
int mid = (lo + hi) / 2;
// This simulates condition codes for key comparison.
int cmp;
// Pointers and character values from key and midpoint strings.
char *p_key = key, *p_mid = a[mid], ch_key, ch_mid;
// Pointers advance together through key and midpoint strings, stopping at '\0'.
for (;;) {
// Fetch characters from key and array.
ch_key = *p_key, ch_mid = *p_mid;
// Break if null is found;
if (ch_key == 0 || ch_mid == 0) break;
// Convert to lower case if necessary.
if ('A' <= ch_key && ch_key <= 'Z') ch_key += 'a' - 'A';
if ('A' <= ch_mid && ch_mid <= 'Z') ch_mid += 'a' - 'A';
// Break if inequality is found.
if (ch_key != ch_mid) break;
// Move to next character pair.
p_key++;
p_mid++;
}
// Set the condition codes based on character difference.
cmp = ch_key - ch_mid;
// If equal, we're done.
if (cmp == 0) return mid;
// Shrink the range based on comparison result.
if (cmp < 0) hi = mid;
else lo = mid + 1;
}
return -1;
}
int main(void) {
static char a[][20] = {
"Arturo", "Bryan", "chris", "David", "Jon", "Mark", "shane", "SIMON", "Thomas", "TONY"
};
static char keys[][20] = {
"ARTURo", "brYAn", "cHRiS", "dAvID", "jON", "MaRk", "sHAne", "sImON", "THOmas", "TonY" , "AAAA", "foo", "ZZZZZ"
};
#define COUNT(A) (sizeof A / sizeof A[0])
int i;
for (i = 0; i < COUNT(keys); i++) {
printf("%s: %d\n", keys[i], bsearch(a, COUNT(a), keys[i]));
}
return 0;
}
There are a number of problems with the code. In no particular order, here's what I found:
Altering the passed token
The code alters the token that was passed in, which may be acceptable, but you must check for a NUL terminator before you or the value with 0x20 and store it back in place.
Better would be to set the string to lowercase only when doing the comparison, leaving the passed string unaltered.
Comments are not helpful
Lines like this:
mov edx, eax ; // move EAX into EDX
are not helpful. A programmer can see that the contents of EAX is being moved into EDX. The comment should tell me why this is happening.
Problems keeping track of register contents
When you have mov ecx, 0 and two lines later pop ecx it tells me (and it should tell you!) that you're not keeping track of the contents of the register. Those are comments that will help you; I often write comment blocks above labeled lines (jump targets) that tell what I'm expecting to be in each register and on the stack. You have that in a few places, but not all of the relevant registers are listed. It really helps debugging code like this. Ideally, you'd assign some purpose to each register and then use them only for that purpose in the entire rest of the code.
Failure to lowercase the list entries
In the code after the GO_LOWER label, you're loading a letter of the next entry in the list into eax and the next letter of the token into ecx but only the latter has been converted to lowercase for comparison.
Convoluted branching
These lines are overly complex:
cmp eax, 0x00
jz NULL_TERM_CHECK
jnz NOT_NULL
NULL_TERM_CHECK:
This could be very much simplified:
cmp eax, 0x00
jnz NOT_NULL
Because the code will go to the next instruction anyway, you no longer need the other branch, and because it is used nowhere else, you can also eliminate the label.
Binary search confusion
The intent appears to be to do a binary search, which typically starts in the middle of an ordered list and then does a comparison to figure out whether the item is in the top half or the bottom half of the range. Your code appears to be starting from the first element rather than the middle and things can't work very well from there.
General advice:
Try writing a correct and working routine in C and then replace a small portion at a time with the corresponding assembly language routine. You'll spend less time debugging the basic algorithm and more time successfully implementing working assembly language code.

Resources