Author Topic: Need help with bottom counting loop (newb)  (Read 8037 times)

Offline dudio

  • New Member
  • Posts: 1
Need help with bottom counting loop (newb)
« on: June 03, 2010, 07:18:12 PM »
In my mind, this should print out

1
2
3
4
5
6
7
8
9
10
Done.

But it doesn't. It looks like it prints an address twice and then says Done. I just started getting serious w/ asm a few days ago. Any help?  ;o

[section .data]
counter: db '%d',10,0
finish: db 'DONE.',10,0

[section .text]

global _main
extern _printf

_main:

mov ecx, 1
push counter
call _printf
mov eax, 10
add esp, 4

loop:
   
   push counter
   call _printf
   inc ecx
   add esp, 4
   
   cmp ecx, eax
   jl finished
   
finished:

push finish
call _printf
add esp, 4

mov eax, 0
ret

Offline Keith Kanios

  • Full Member
  • **
  • Posts: 383
  • Country: us
    • Personal Homepage
Re: Need help with bottom counting loop (newb)
« Reply #1 on: June 03, 2010, 07:41:33 PM »
You push the pointer to counter, but you are not pushing the value of %d.

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Need help with bottom counting loop (newb)
« Reply #2 on: June 03, 2010, 11:16:53 PM »
CPUs have gotten wider, and much faster, but they still haven't implemented the "do what I meant" instruction. :(

Your primary problem is more of a "C problem" than an "asm problem", really. You've essentially said printf("%d");... but haven't given it an integer to print! Printf looks at your "format string", and sees it expects one more parameter - an integer to be displayed as decimal - so it prints whatever is next up the stack... probably the return address for "main".

So you want to push the number to print first, and then the format string saying to print it. You'll want to remove both parameters after the call - "add esp, 8" (I like to write it as "add esp, 4 * 2", to indicate 2 parameters at 4 bytes each - not important, but it needs to be 8).  You've got the number to print in ecx (so "push ecx"), and the "ending value" in eax. Good plan, but unfortunately it turns out that these are both bad choices... :(

The "C calling convention", besides specifying that parameters are pushed on the stack right to left, specifies that certain registers must retain their values across calls, other registers are "scratch" and can be trashed, the return value (if any - "printf" returns the number of items printed, I think) is in eax. Oops. The registers which must be preserved are ebx, esi, edi, and ebp (esp needs to be right, but for a different reason - "ret" depends on it). This leaves ecx (and edx, unless the function returns a 64-bit value) as "scratch". Oops again. No guarantee that "printf" will alter ecx "behind our back", but it's allowed to... and I think you'll find that it does.

We can still use all the registers, we just need to be aware of the "rules". You could continue to use eax as an "ending value" and ecx as the number to print...

Code: [Select]
push eax ; save regs printf will trash
push ecx

push ecx ; number to print
push counter ; format string
call printf  ; oops, "_printf" for 'doze
add esp, 4 * 2 ; "clean up" stack - remove parameters

pop ecx ; restore registers we were using
pop eax
...

You will observe that we've pushed ecx twice, and if we were "clever" we could have utilized the same push to pass the parameter and preserve the value that printf is (likely) going to trash. Such "cleverness" would make the code difficult to "maintain" - I'd avoid it. You could use some of the registers that printf *will* preserve - if you do that, remember that main's caller (C's "startup" code - crt0.o or so) expects 'em to be preserved, and we have to do it if we alter 'em! You could also use "local variables" on the stack - overkill in this case (but might make a nice example).

I don't know why you're doing that first call to printf, but at the end of your "loop" (since "loop" is the name of an instruction, it's a terrible name for a label, IMHO - name it for what it does - "number_printing_loop:" or something - but it'll work - or would, if you actually jumped to it) you've got:

Code: [Select]
 
   cmp ecx, eax
   jl finished
   
finished:
; good from here

It's confusing, since Intel syntax has the operands in "opposite" order, but this jumps if ecx is less than eax, not if eax is less than ecx (still confuses me, once in a while!). So you probably want to jump to "number_printing_loop" (or "loop") - actually you'd want to jump if less or equal. Since eax and ecx were being trashed behind your back, I'm not surprised you had trouble getting this right. Oh, and if the jump isn't taken, what happens?

Strictly speaking, "jl" (and "jg") apply to signed numbers. Anything with the high bit set is considered negative. Since a "count" is logically unsigned, you really should be using "jb" (jump if first operand is "below" second operand) and/or "ja" (jump if "above"). It won't cause a problem here - you'll get bored long before you've printed two billion and change numbers (7FFFFFFFh - don't expect me to remember all those digits in decimal! - is the "biggest" number jl/jg would consider "greater" - after that it's negative)

A compiler wants to know if a number is signed or unsigned, and will whine if you attempt to compare signed with unsigned. It needs to know whether to emit "jg" or "ja"! Since we're selecting the instructions here, we need to know (Nasm doesn't care - neither is an error). The condition codes seem so confusing - there are so many of them! Once you realize that a good many of them are aliases for the same opcode (je=jz, jb=jc" jnb=jge=jnc, etc...) it becomes a little more manageable. But there *is* a difference with signed vs unsigned condition codes - the former check the overflow flag (xor's it with the carry flag, typically). You don't need to grasp this all at once - learn a couple simple ones, and work outwards from there.

Anyway...

Give printf the correct number of parameters, be aware of which registers printf can alter, and straighten out your "loop control" a little... I think you can fix it fairly easily.

Best,
Frank


Offline sapero

  • Jr. Member
  • *
  • Posts: 9
Re: Need help with bottom counting loop (newb)
« Reply #3 on: June 04, 2010, 07:35:49 AM »
Another trick taken from GCC compiler: if you have an cdecl function call inside a loop, instead pushing the arguments and then adjusting the stack, you could first make a room on the stack, MOV' the arguments and just call the function:
Code: [Select]
segment .text
global _main
extern _printf

_main:
; printf(format, counter)
sub  esp,8 ; make room for printf args
; [esp+0] - format
; [esp+4] - counter
%define format esp
%define counter esp+4

mov  dword[format], szFormat
mov  dword[counter], 1 ; from 1
.loop:
call _printf
add  dword[counter], 1
cmp  dword[counter], 10 ; to 10
jbe .loop

mov  dword[format], finish
call _printf

add  esp,8
mov  eax,0
ret

segment .data
szFormat: db "%d\n",0
finish:   db 'DONE.',10,0

This will be impossible if the called function (here printf) modifies the stack:
Code: [Select]
_printf:
  xchg esi, [esp+4] ; load format to esi
  ...
  inc esi
  loop
  xchg esi, [esp+4] ; restore esi
In this case you'll need to refresh all your [esp+] arguments before calling such function.
« Last Edit: June 04, 2010, 07:47:23 AM by sapero »