I have to count the number of words in a string that you get as an argument from the command line.
First I made this program:
#include <stdio.h>
#include <string.h>
int main( int argc, char *argv[] ){
char* s;
if(argc==1)
{
s="";
} else {
s = argv[1];
}
//char* s = " aqr b qabxx xryc pqr"; example
int x;
asm volatile(
".intel_syntax noprefix;"
"mov eax,%1;"
"xor edx,edx;"
"jmp petla;"
"petla0:"
"inc eax;"
"petla:"
"cmp [eax],byte ptr 0;"
"jz wyjscie;"
"cmp [eax],byte ptr 32;"
"jz petla0;"
"inc edx;"
"petla1:"
"inc eax;"
"cmp [eax],byte ptr 0;"
"jz wyjscie;"
"cmp [eax],byte ptr 32;"
"jz petla;"
"jmp petla1;"
"wyjscie:"
"mov %0,edx;"
".att_syntax prefix;"
: "=r" (x)
: "r" (s)
: "eax","edx"
);
printf("%hd\n",x);
return 0;
}
and it works fine; I get 5 as answer for "aqr b qabxx xryc pqr". But I need my program written only using assembly code. Something like this:
.intel_syntax noprefix
.globl main
.text
main:
mov ecx,?
?<- here is the issue: I don't know how to get an argument from the command line and access it as a char *.
xor edx,edx
jmp petla
petla0:
inc ecx
petla:
cmp byte ptr [ecx],0
jz wyjscie
cmp byte ptr [ecx],32
jz petla0
inc edx
petla1:
inc ecx
cmp byte ptr [ecx],0
jz wyjscie
cmp byte ptr [ecx], 32
jz petla
jmp petla1
wyjscie:
push edx
push offset msg
call printf
add esp, 8
mov edx,0
ret
.data
msg: .ascii "number of words=%d\n"
So first, let's look at your "working" code. While it works, there are a few "teachable" items here.
First of all, please get in the habit of using comments in your code. I realize English is not your first language so I probably couldn't read your comments, but still, you should have them.
Second, stop using ; to terminate your asm instructions. Yes, it looks a little clunkier to use \n\t, but when you use gcc's -S to output the assembler (a great way to see what's really going on), your code will be a mess without \n\t.
So far, that gets us:
asm volatile(
".intel_syntax noprefix\n\t"
// %1 is read-only, so use eax as temp
"mov eax,%1\n\t"
// # of words found
"xor edx,edx\n\t"
"jmp petla\n"
// Skip over spaces
"petla0:\n\t"
"inc eax\n"
"petla:\n\t"
"cmp [eax],byte ptr 0\n\t"
"jz wyjscie\n\t" // End of string
"cmp [eax],byte ptr 32\n\t"
"jz petla0\n\t" // Another space
// Starting new word
"inc edx\n"
// Walk the rest of the current word
"petla1:\n\t"
"inc eax\n\t"
"cmp [eax],byte ptr 0\n\t"
"jz wyjscie\n\t" // End of string
"cmp [eax],byte ptr 32\n\t"
"jz petla\n\t" // End of word
"jmp petla1\n" // Not end of word
"wyjscie:\n\t"
"mov %0,edx\n\t"
".att_syntax prefix"
: "=r" (x)
: "r" (s)
: "eax","edx"
);
Third, you need to understand that when using extended asm, %0 is just a way to refer to whatever is being passed in as the first argument. In this case, you specify that it must be a register ("=r"). So the value is already a register. Instead of using both edx and %0, you can store the count directly in %0.
Fourth, the purpose of byte ptr is so the assembler knows whether [eax] means: The byte at [eax], the word as [eax], the dword at [eax], etc. Such being the case, it is more commonly placed on the other side of a cmp instruction:
asm volatile(
".intel_syntax noprefix\n\t"
// %1 is read-only, so use eax as temp
"mov eax,%1\n\t"
// # of words found
"xor %0,%0\n\t"
"jmp petla\n"
// Skip over spaces
"petla0:\n\t"
"inc eax\n"
"petla:\n\t"
"cmp byte ptr [eax], 0\n\t"
"jz wyjscie\n\t" // End of string
"cmp byte ptr [eax], ' '\n\t"
"jz petla0\n\t" // Another space
// Starting new word
"inc %0\n"
// Walk the rest of the current word
"petla1:\n\t"
"inc eax\n\t"
"cmp byte ptr [eax], 0\n\t"
"jz wyjscie\n\t" // End of string
"cmp byte ptr [eax], ' '\n\t"
"jz petla\n\t" // End of word
"jmp petla1\n" // Not end of word
"wyjscie:\n\t"
".att_syntax prefix"
: "=r" (x)
: "r" (s)
: "eax","edx"
);
What's next? Oh yeah. When you use jz or jnz, if it doesn't jump, the code falls thru to the next instruction. This means that this:
"cmp byte ptr [eax], 0\n\t"
"jz wyjscie\n\t" // End of string
"cmp byte ptr [eax], ' '\n\t"
"jz petla\n\t" // End of word
"jmp petla1\n" // Not end of word
"wyjscie:\n\t"
Can be done like this:
"cmp byte ptr [eax], 0\n\t"
"jz petla\n\t" // End of word
"cmp byte ptr [eax], ' '\n\t"
"jnz petla1\n\t" // Not end of string
"wyjscie:\n\t"
As a general rule, I avoid doing memory reads multiple times. So where you do:
"cmp byte ptr [eax], 0\n\t"
"cmp byte ptr [eax], ' '\n\t"
I would do:
"mov dl, [eax]\n\t"
"cmp dl, 0\n\t"
"cmp dl, ' '\n\t"
This also lets us get rid of the byte ptr. dl can only hold a byte, so that must be what we are reading.
Another subtle point: In your original code, when you are walking the letters, if you encounter a space, you jump back to petla, where you check again to see if it is a space instead of to petla0 to read the next byte.
And 2 other nits: When comparing something with zero, I use test instead of cmp (generates slightly better code). And while it does exactly the same thing, when I compare 2 values (cmp edx, ' '), I find it easier to think in terms of "Are these things 'equal'" rather than "Is the difference between them zero?" As a result, I would use je instead of jz.
Putting all this together gives me:
asm (
".intel_syntax noprefix\n\t"
// %1 is read-only, so use eax as temp
"mov eax, %1\n\t"
// # of words found
"xor %0,%0\n"
// Skip over spaces
"petla0:\n\t"
"mov dl, [eax]\n\t"
"inc eax\n\t"
"test dl, dl\n\t"
"jz wyjscie\n\t" // End of string
"cmp dl, ' '\n\t"
"je petla0\n\t" // Another space
// Starting new word
"inc %0\n"
// Walk the rest of the current word
"petla1:\n\t"
"mov dl, [eax]\n\t"
"inc eax\n\t"
"cmp dl, ' '\n\t"
"je petla0\n\t" // end of word
"test dl, dl\n\t"
"jnz petla1\n" // not end of string
"wyjscie:\n"
".att_syntax prefix;"
: "=r" (x)
: "r" (s)
: "eax", "edx", "cc", "memory"
);
I also removed the volatile. Since you are using the output (by printing x), this is not required.
I will let you roll any of this that you want to keep into your pure asm by yourself.
As for why your pure asm doesn't work, I'm not on linux, so I can't run this. However, I don't see anything actually wrong with your counting code. You might look at this for accessing the command line arguments, but what you are doing should not give you 1.
How are you specifying your command line? I suspect you are not using the " marks around your string: a.out " aqr b qabxx xryc pqr". This would cause each word to be treated as a separate (null terminated) argument.
Edit 1: After some more reading, it looks like the pointer to argv[1] really should be at [esp + 8]. At least on linux. You aren't on Windows, right? Pretty sure it uses a different scheme.
You can try this to make sure your asm is working correctly, but I'm pretty sure that isn't your problem.
lea ecx, str
// Down by .data add:
str: .ascii " asdf adfs asd f adsf "
You can try using the msg format string you have to print argc. If you are passing the arguments correctly, this should be 2.
Change your msg to use %s, and print out the value from argv[0] (aka [esp+4]). This should be the program name.
Using that %s, you can print out argv[1].
Related
I make program in order to find maximum depth of parenthesis in String.
At first, I make C program with Assembly code, it works good:
#include <stdio.h>
int main(){
char *s ="((abc) ∗ (ab+(ab)∗(cdx7)))−xxab((()))c";
int x;
asm volatile (
".intel_syntax noprefix;"
"mov eax,%1;"
"lea ebx,[eax];"
"xor eax,eax;" ecx - currentDepth, edx - MaxDepth
"xor ecx,ecx;"
"xor edx,edx;"
"loop:"
"mov al,[ebx];"
"or al, al;" // check if end of string
"jz print;" // if end jump to print
"cmp al, '(';"
"je increase;"
"cmp al,')';"
"je decrease;"
"inc ebx;" // next char
"jmp loop;"
"increase:"
"inc ecx; cmp edx,ecx;js changeMax;inc ebx;jmp loop;"
"changeMax:"
"mov edx,ecx; inc ebx; jmp loop;"
"decrease:"
"dec ecx;inc ebx;jmp loop;"
"print:"
"mov %0, edx;"
".att_syntax prefix;"
: "=r" (x)
: "r" (s)
: "eax", "ebx", "ecx", "edx"
);
printf("%d", x);
return 0;
}
But I have to make it in pure Assembler with .s extension, so i try this:
.intel_syntax noprefix
.globl main
.text
main:
mov eax,offset expr
lea ebx,[eax]
xor eax,eax
xor ecx,ecx
xor edx,edx
loop:
mov al,[ebx]
or al,al
jz print
cmp al,'('
je increase
cmp al,')'
je decrease
inc ebx
jmp loop
increase:
inc ecx
cmp edx,ecx
js changeMax
inc ebx
jmp loop
changeMax:
mov edx,ecx
inc ebx
jmp loop
decrease:
dec ecx
inc ebx
jmp loop
print:
push edx
call printf
data:
mesg: .ascii "%d\n"
expr: .ascii "((x))"
But I got segmentation fault(core dumped)
Maybe labels are working differently than in first program in c.
But I don't know, please help me.
UPDATE
Thank you Jester
I edited label print, now it print out the result, but after that I still get segmentation fault.
print:
push edx
mov edx, offset mesg
push edx
call printf
ret
Ok, I remove it from the stack. Now i don't get segmentation fault, but after the result i have some rubbish text
print:
push edx
mov edx, offset mesg
push edx
call printf
add esp,8
ret
xor edx,edx
ret
Output:
2
f�f��UWVS������×
Update 2
Ok, everything works right now, I use string from command line ./a.out '((x))'
Someone can check that everything is ok.
.intel_syntax noprefix
.globl main
.text
main:
pop eax #return address
pop eax #return argc
pop eax #return argv
mov eax,[eax+4] #argv[1]
sub esp,12 #return stack to the right position
lea ebx,[eax]
xor eax,eax
xor ecx,ecx
xor edx,edx
loop:
mov al,[ebx]
or al,al
jz print
cmp al,'('
je increase
cmp al,')'
je decrease
inc ebx
jmp loop
increase:
inc ecx
cmp edx,ecx
js changeMax
inc ebx
jmp loop
changeMax:
mov edx,ecx
inc ebx
jmp loop
decrease:
dec ecx
inc ebx
jmp loop
print:
push edx
mov edx, offset mesg
push edx
call printf
add esp,8
ret
mov edx,0
ret
data:
mesg: .asciz "%d\n"
In one of the exercises in my assembler course we have to create a program that will change all uppercase letters in char* to lowercase.
char *a = "AAA BB CCC";
char *b;
asm(
".intel_syntax noprefix;"
"xor ecx, ecx;"
"xor eax, eax;"
"lea eax, [%[a]];" // getting address of the first char into eax
"loop:"
"inc ecx;" // loop variable
"cmp ecx, 10;" // 10 cycles for very character in *a
"jne lowercase;"
"jmp end;"
"lowercase:"
"mov bl, [eax];" // copy value stored under eax address to bl
"add bl, 32;" // make it lowercase in ASCII
"mov [eax], bl;" // copy it back to address stored in eax
"inc eax;" // move pointer to the next char
"jmp loop;"
"end:"
"lea %[b], [eax];"
".att_syntax prefix;"
: [b] "=r" (b)
: [a] "r" (a)
: "eax", "ebx", "ecx"
);
My idea was to get address of the first char, increase it by 32 (making it lowercase in ASCII), save it on the same address, than move pointer to the next char and so on. Getting first char and adding 32 to it works fine, its this line that throws "Segmentation fault":
"mov [eax], bl;"
From what I understand it should change value under address held in eax to value of bl. I feel like I'm missing something obvious, but I'm pretty new to this and everything I found so far on the net leads me to believe that this is how I should do it.
Hi I'm still new to the Assembly language using gcc compiler, right now I'm working on functions.
My program asks the user for 4 int values, store those values in register eax, ebx, ecx and edx, then calls a function to divide (ebx/eax). I store the value of "d" after the division because as I understand idiv uses edx to store the residue. It then subtracts (eax-ecx) and multiply (eax*edx), then returns the value inside of register eax. For some reason I get a segmentation fault: 11.
Here is my code:
#include <stdio.h>
int a, b, c, d;
int main (void)
{
printf("Dame a: ");
scanf("%d", &a);
printf("Dame b: ");
scanf("%d", &b);
printf("Dame c: ");
scanf("%d", &c);
printf("Dame d: ");
scanf("%d", &d);
__asm( ".intel_syntax noprefix;"
"xor eax, eax;"
"mov eax, dword ptr [_a];"
"xor ebx, ebx;"
"mov ebx, dword ptr [_b];"
"xor ecx, ecx;"
"mov ecx, dword ptr [_c];"
"Call fun1;"
"mov dword ptr [_a], eax;"
"fun1: xor edx, edx;"
"idiv ebx;"
"sub eax, ecx;"
"mov edx, dword ptr [_d];"
"imul eax, edx;"
"ret;"
".att_syntax");
printf("%d\n", a);
}
Is it something to do with some pointer error?
Aside from the other errors pointed out in comments, you have a significant issue here:
"mov ecx, dword ptr [_c];"
"Call fun1;"
"mov dword ptr [_a], eax;"
"fun1: xor edx, edx;"
"idiv ebx;"
"sub eax, ecx;"
"mov edx, dword ptr [_d];"
"imul eax, edx;"
"ret;"
Consider the program flow. Your C code falls into this assembly code. The assembly code calls an internal function of its own (not a problem), which then returns to the instruction before the call... Still no problem. A value is moved into EAX... and you then fall through your function to a return. This is horribly bad.
By falling through to that ret you are bypassing the entire C function epilog. This means that the stack is not properly cleaned up, nor is the stack from restored. This will almost certainly lead to a crash.
I have a huge problem with printing the specific word backwards. I'm trying to find words with '*' at the beggining and print them backwards, the rest should be printed normally.
For example:
Input: aaa1 ab0 1kk *ddd *lel 2cccc2 c1
Output aaa1 ab0 1kk ddd* lel* 2cccc2 c1
All I have is finding the words, finding the ones with '' and printing normally the words without ''.
Please help me and thank you in advance for your attention to this matter...
Have to write it in C language and here's my code
int main() {
char *x = "aaa1 ab0 1kk *ddd *lel
2cccc2 c1";
char bufor[100];
asm volatile (
".intel_syntax noprefix;"
"mov eax, %0;"
"push eax;"
"mov eax, %1;"
"push eax;"
"call zadanie1;"
"jmp wyjscie;"
"zadanie1:"
//
// FUNCTION START
//
"pushad;"
"mov esi, [esp+40];"
"mov edx, [esp+36];"
"push edx;"
"xor ecx, ecx;"
// MAIN LOOP - WORDS SEARCHING
"zad_loop:"
"mov edx, [esp];"
"lodsb;"
"test al, al;"
"jz zad_loop_end;"
"cmp al, 0x20;"
"jz zad_loop_end;"
"mov [edx+ecx], al;"
"inc ecx;"
"jmp zad_loop;"
// MAIN LOOP END
"zad_loop_end:"
"mov [edx+ecx], ch;"
"push eax;"
"push ecx;"
"test ecx, ecx;"
"jz not_print;"
// IS THE FIRST CHAR '*'
"lea eax, [edx];"
"mov al, [eax];"
"cmp al, '*';"
"jz backwards;"
"test al, al;"
"jz not_print;"
// PRINTING THE WORD WITHOUT '*'
"mov edx, [esp];"
"mov ecx, [esp+8];"
"mov ebx, 1;"
"push eax;"
"mov eax, 4;"
"int 0x80;"
"push 0x20;"
"call print_char;"
"pop eax;"
// PRINTING THE WORD WITH '*' - BACKWARDS
"backwards:"
// SKIP PRINTING
"not_print:"
"pop ecx;"
"pop eax;"
"xor ecx, ecx;"
"test al, al;"
"jnz zad_loop;"
// FUNCTION END
"pop edx;"
"push 0x0A;"
"call print_char;"
"popad;"
"ret 8;"
// CHAR OUTPUT
"print_char:"
"pushad;"
"mov edx, 1;"
"lea ecx, [esp+36];"
"mov ebx, 1;"
"mov eax, 4;"
"int 0x80;"
"popad;"
"ret 4;"
"wyjscie:"
".att_syntax prefix;"
:
: "r" (x), "r" (bufor)
: "eax"
);
return 0;
}
Designate a register to be a counter (pick any one you aren't using for other stuff) and set its value to 0.
When you hit a * character, push characters onto the stack until it finds a blank, incrementing the counter with each push.
When a blank is found, pop characters off the stack, decrementing the counter and printing the character each time, until the counter is 0.
I don't know your compiler, but I think your compiler would insert the code that you wrote as text in variables instead of compiling the instructions, because a text that begins with a " will be interpreted as text. Drop these chars.
I would like to ask some help form You! I have a project with a lot of C source. Most of them compiled with gcc, but some compiled with Intel compiler. This later codes have a lot of inline asm codes in Microsoft's MASM format. I would like to compile the whole project with gcc and modify as few code as I can. So I wrote a perl script which converts the intel format inline asm to GAS format. (BTW: I compile to 32 bit on a 64 bit Linux machine).
My problem that I have to specify for gcc that in the inline asm("...") which C variables are passed to the code adding the :: [var1] "m" var1, [var2] "m" var2, ... line at the end.
Is it a way to avoid this explicit specification?
My tryings:
The dummy test C code is simply replaces 4 characters the destination char array with the elements of the source char array (I know this is not the best way to do it. It is just a stupid example).
In the original function there is no explicit specification, but it can be compiled with Intel compiler (shame on me, but I did not test this, but it should work with Intel compiler as I made it based on the real code). LOOP label is used a lot of times, even in the same C source file.
#include <stdio.h>
void cp(char *pSrc, char *pDst) {
__asm
{
mov esi, pSrc
mov edi, pDst
mov edx, 4
LOOP:
mov al, [esi]
mov [edi], al
inc esi
inc edi
dec edx
jnz LOOP
};
}
int main() {
char src[] = "abcd";
char dst[] = "ABCD";
cp(src, dst);
printf("SRC: '%s', DST: '%s'\n", src, dst);
return 0;
}
Result is: SRC: 'abcd', DST: 'abcd'
The working converted cp code is (compiled with gcc).
GAS (AT&T) format (compiled: gcc -ggdb3 -std=gnu99 -m32 -o asm asm.c)
void cp(char *pSrc, char *pDst) {
asm(
"mov %[pSrc], %%esi\n\t"
"mov %[pDst], %%edi\n\t"
"mov $4, %%edx\n\t"
"LOOP%=:\n\t"
"mov (%%esi), %%al\n\t"
"mov %%al, (%%edi)\n\t"
"inc %%esi\n\t"
"inc %%edi\n\t"
"dec %%edx\n\t"
"jnz LOOP%=\n\t"
: [pDst] "=m" (pDst)
: [pSrc] "m" (pSrc)
: "esi", "edi", "edx", "al"
);
}
Intel format (compiled: gcc -ggdb3 -std=gnu99 -m32 -masm=intel -o asm asm.c)
void cp(char *pSrc, char *pDst) {
asm(".intel_syntax noprefix\n\t");
asm(
"mov esi, %[pSrc]\n\t"
"mov edi, %[pDst]\n\t"
"mov edx, 4\n\t"
"LOOP%=:\n\t"
"mov al, [esi]\n\t"
"mov [edi], al\n\t"
"inc esi\n\t"
"inc edi\n\t"
"dec edx\n\t"
"jnz LOOP%=\n\t"
: [pDst] "=m" (pDst)
: [pSrc] "m" (pSrc)
: "esi", "edi", "edx", "al"
);
asm(".intel_syntax prefix");
}
Both codes are working, but it needs some code modification (inserting the '%' characters, collecting the variables, modifying the jump labels and jump functions).
I also tried this version:
void cp(char *pSrc, char *pDst) {
asm(".intel_syntax noprefix\n\t");
asm(
"mov esi, pSrc\n\t"
"mov edi, pDst\n\t"
"mov edx, 4\n\t"
"LOOP:\n\t"
"mov al, [esi]\n\t"
"mov [edi], al\n\t"
"inc esi\n\t"
"inc edi\n\t"
"dec edx\n\t"
"jnz LOOP\n\t"
);
asm(".intel_syntax prefix");
}
But it drops
gcc -ggdb3 -std=gnu99 -masm=intel -m32 -o ./asm.exe ./asm.c
/tmp/cc2F9i0u.o: In function `cp':
/home/TAG_VR_20130311/vr/vr/slicecodec/src/./asm.c:41: undefined reference to `pSrc'
/home/TAG_VR_20130311/vr/vr/slicecodec/src/./asm.c:41: undefined reference to `pDst'
collect2: ld returned 1 exit status
Is there a way to avoid the definition of the input arguments and avoid the modifications of local labels?
ADDITION
I tried to use global variable. For this g constrain has to be used instead of m.
char pGlob[] = "qwer";
void cp(char *pDst) {
asm(".intel_syntax noprefix\n\t"
"mov esi, %[pGlob]\n\t"
"mov edi, %[pDst]\n\t"
"mov edx, 4\n\t"
"LOOP%=:\n\t"
"mov al, [esi]\n\t"
"mov [edi], al\n\t"
"inc esi\n\t"
"inc edi\n\t"
"dec edx\n\t"
"jnz LOOP%=\n\t"
".intel_syntax prefix" : [pDst] "=m" (pDst) : [pGlob] "g" (pGlob)
: "esi", "edi", "edx", "al");
}
ADDITION#2
I tried
"lea esi, pGlob\n\t" // OK
"lea esi, %[_pGlob]\n\t" // BAD
//"lea esi, pGlob_not_defined\n\t" // BAD
//gcc failed with: undefined reference to `pGlob_not_defined'
compiled to
lea esi, pGlob
lea esi, OFFSET FLAT:pGlob // BAD
//compilation fails with: Error: suffix or operands invalid for `lea'
It seems that only function local variables has to be defined. Global variable may be added to the trailer, but not really necessary. Both are working:
"mov esi, pGlob\n\t" // OK
"mov esi, %[_pGlob]\n\t" // OK
compiled to
mov esi, OFFSET FLAT:pGlob
mov esi, OFFSET FLAT:pGlob
I defined a function local variable. It has to be defined in the constraint part:
void cp(char *pDst) {
char pLoc[] = "yxcv";
asm(".intel_syntax noprefix\n\t"
...
//"mov esi, pLoc\n\t" // BAD
"mov esi, %[_pLoc]\n\t" // OK, 'm' BAD
...
".intel_syntax prefix" : [_pDst] "=m" (pDst) : [_pLoc] "g" (pLoc)
: "esi", "edi", "edx", "al");
Unfortunately it should be determined what are the global and what are the local variables. It is not easy as the asm code can be defined in C macros and even the surrounding function is not definite. I think only the precompiler has enough information to this. Maybe the code has to be precompiled with gcc -E ....
I realized that not defining the output in the constraint part, some code can be eliminated by the optimizer.
TIA!
Yes, you do need to specify the registers explicitly. GCC will not do that for you. And you can't (generally) put C variable names inside the ASM string.
Your final code block looks perfectly good, to me, but in GCC you don't need to choose which registers to use yourself. You should also use the volatile keyword to prevent the compiler thinking the code doesn't do anything given that it has no outputs.
Try this:
char pGlob[] = "qwer";
void cp(char *pDst) {
asm volatile (".intel_syntax noprefix\n\t"
"mov edx, 4\n\t"
"LOOP%=:\n\t"
"mov al, [%[pGlob]]\n\t"
"mov [%[pDst]], al\n\t"
"inc %[pGlob]\n\t"
"inc %[pDst]\n\t"
"dec edx\n\t"
"jnz LOOP%=\n\t"
".intel_syntax prefix" :: [pGlob] "g" (pGlob), [pDst] "g" (pDst) : "edx");
}
This way the compiler deals with loading the variables and chooses the registers for you (thereby eliminating a pointless copy from one register to another). Ideally you'd also eliminate the explicit use of edx, but it's not really necessary here.
Of course, in this silly example I would just recode the whole thing in C and let the compiler do its job.