Author Topic: Problem with simple program(x64)  (Read 20001 times)

Offline annnDi

  • Jr. Member
  • *
  • Posts: 5
Problem with simple program(x64)
« on: December 01, 2011, 02:49:12 AM »
Hello.
I created this program in x32 version of Ubuntu in asm. It worked okay and recently I installed x64 version of ubuntu and I also installed gcc-multilib so i can run 32 bit code in x64 ubuntu.
I create a modul with gcc -Program.o -o Program -m32.
For some reason, this assignment which lists all numbers between 1 and N that are divisible by 11, doesnt work anymore. Everything worked fine before, but now under x64 it throws floating point exception.
Here is my program. Are there problems with registers that I am using?
Please help me find the mistake, thank you.
PS: Don't mind the comments, they are in our language
Code:
Code: [Select]
bits 32                               ; Delamo v 32 bitnem okolju.
global main                           ; Programsko okolje.
extern printf, scanf                  ; Printf izpisujemo na zaslon, scanf pa beremo z tipkovnice.

section .bss                          ; V section bss deklariramo spremenljivke, katerim dodamo vrednosti kasneje.
  prvoStevilo resd 1                  ; Resd je za stevilo, 1 bajt je za stevilo.

section .data                         ; Section data vsebuje spremenljivke, ki na nek na?in ze vsebujejo vrednosti.
  vrednostSt db "%d",                 ; Z 0 zaklju?ek izpisa ali vnosa. vrednostPrvega ne moremo uporabljat kot izpis,
  vnosStevilo db "Vnesi stevilo: ", 0 ; ker vanj shranimo vneseno stevilo. 
  izpisovanje db "Loop: %d", 10, 0    ; 10 je nova vrstica, 0 je konec izpisa.

main:
  pushad                              ; Shranimo trenutno stanje uporabljenih registrov, splošnonamenske registre.
  push dword vnosStevilo              ; Na sklad porinemo vnosStevila, v tem primeru string.
  call printf                         ; Poklicemo printf, ki izpise tisto, kar je v skladu.
  add esp, 4                          ; Po?istimo sklad. 4 Bajte.   

  push dword prvoStevilo              ; Na sklad porinemo prvoStevilo, v tem primeru integer.
  push dword vrednostSt               ; Na sklad porinemo vrednostSt, v tem primeru string.
  call scanf                          ; To stevilo preberemo in v %d se shrani vrednost prvoStevilo.
  add esp, 8                          ; Po?istimo sklad. 4 Bajte * 2.

  mov ebx, 1                          ; V register shranimo vrednost 1, ker povecujemo iz 1.

.loop1:                               ; Loop po primeru iz predavanj.
  push ebx                            ; Na sklad porinemo ebx, ki vsebuje vrednost 1+loop.
  mov eax, ebx                        ; Divide ecx vedno deli z vrednostjo eax. Moramo dati vrednost noter.
  mov ecx, 11                         ; V ecx vrzemo vrednost 11.
  div ecx                             ; V edx se shrani ostanek.
  cmp edx, 0                          ; Primerja torej ostanek ga z 0.
jnz .loop2                            ; ?e ni enako 0, presko?imo izpis in gremo dalje v "for" loop.

  push dword izpisovanje              ; Na sklad porinemo izpisovanje, ki vsebuje vrednost ebx.
  call printf                         ; Poklicemo printf, ki izpise tisto, kar je v skladu.
  add esp, 4                          ; Po?istimo sklad. 4 bajte * 2 bajta. Spu?emo še tisto iz loop1.

.loop2:
  add ebx, 1                          ; Povecamo stevec za 1.
  cmp ebx, [prvoStevilo]              ; Primerjamo trenutni stevec v loopu z vnesenim stevilom.
  mov edx, 0
jle .loop1                            ; Jump less or equal. ?e je enako, se kon?a, druga?e gre nazaj noter.

  popad                               ; Restoramo registre. Po?isti.
  mov eax, 1                          ; Exit komanda.
  int 0x80                            ; Invoka system call. V kombinaciji mov eax, 1 kli?e "exit()" in kon?a program.
« Last Edit: December 01, 2011, 12:02:37 PM by annnDi »

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Problem with simple program(x64)
« Reply #1 on: December 01, 2011, 04:03:53 AM »
I'm unable to test this on a 64-bit system. On my 32-bit system, it works, as expected.

At first, I didn't see the "mov edx, 0" at all - edx wants to be zero before the "div", or you get that exception, as you probably know. I arbitrarily made edx non-zero before falling into the loop, and sure enough, "floating point exception"! I "fixed" this by moving your "mov edx, 0" from the bottom of the loop to just before the "div", so it gets executed first-time-through. I suspect that "scanf" is leaving edx in a different condition (as it's allowed to do), on your new system than it was on your old system. In other words, your program has a "hidden bug" in it, and only works because edx happens to be zero on the first time through the loop! See if making that change solves the problem.

Best,
Frank


Offline annnDi

  • Jr. Member
  • *
  • Posts: 5
Re: Problem with simple program(x64)
« Reply #2 on: December 01, 2011, 12:01:16 PM »
Hello Frank.
Thank you for your input. Your suggestion was correct, I had to mov edx, 0 before dividing.
But doesn't div ecx means that we are dividing ecx with eax and assembly automatically puts remainder into edx?
Why do I need to set edx to 0 before i mov remainder into edx? What could scanf have with all of this?
Thank you very much, annnDi.

Offline Rob Neff

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 429
  • Country: us
Re: Problem with simple program(x64)
« Reply #3 on: December 01, 2011, 12:25:09 PM »
In x64 the calling convention is that function parameters are mostly passed via registers - not the stack.
Read http://www.x86-64.org/documentation.html for correct x64 programming.
See http://www.asmcommunity.net/projects/nasmx/ for a framework that easily enables you to program for both 32 and 64 bit environments.

Edit: Also, in loop1 you push ebx but do not pop, thus your stack becomes corrupted prior to the popad instruction.
« Last Edit: December 01, 2011, 12:35:40 PM by Rob Neff »

Offline annnDi

  • Jr. Member
  • *
  • Posts: 5
Re: Problem with simple program(x64)
« Reply #4 on: December 01, 2011, 01:50:10 PM »
Hello Rob.
I now got used to x32 asm in ubuntu. Therefore I installed gcc-multilib so I can run 32 bit code on x64 Ubuntu.
Are you saying, that even though I am running 32 bit code on x64 ubuntu it still isn't 100% equal as to when I was running 32 bit code on x32 ubuntu?
Thank you, Andi.

Offline Rob Neff

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 429
  • Country: us
Re: Problem with simple program(x64)
« Reply #5 on: December 01, 2011, 04:40:44 PM »
No, I'm not saying that.  I simply read the subject title, quickly glanced at your code, and assumed you were trying to write 64 bit code.  My apologies for not delving deeper into the problem.

You most certainly can write 32-bit apps to run on 64-bit operating systems.  I'm at work right now with no access to a 64-bit machine so I can't look at it further until tonight.  Nothing is jumping out at me right now regarding an FP exception other than you may be exceeding your allocated stack due to unbalanced pushes of ebx...

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Problem with simple program(x64)
« Reply #6 on: December 01, 2011, 05:33:22 PM »
Well, to begin with... no, "div ecx" doesn't exactly divide eax by ecx. It divides edx:eax (that is, edx * 4G + eax) by ecx. Divides a 64-bit number by a 32-bit number. If the quotient won't fit in eax (which would be true if ecx >= edx) it generates a "divide overflow exception" which is reported (in Linux) as "floating point exception" or (in dos) as "divide by zero exception". Neither error message is very helpful, IMO, especially considering how common this error is! edx doesn't always have to be zero, it has to be the intended high 32 bits - even though we don't care about edx in this case, the CPU uses it. For other sizes of "div"...

http://home.myfairpoint.net/fbkotler/nasmdocc.html#section-A.4.59

This isn't the world's best instruction set reference - Intel/AMD manuals would be better - but it uses Nasm syntax... and I'm used to it... so I "rescued" it when it was removed from the Nasm manual...

The only reason "scanf" comes into it is that it's the last library call before the "div". When interacting with C, we need to be aware of the C calling convention. The relevant part is that certain registers (ebp, ebx, esi, and edi) are "preserved" across a call, and other registers (ecx, edx) are allowed to be "trashed" (eax is the return value). There's no guarantee that edx will be altered, but it's allowed to be. I don't think it's the fact that your "new" library is part of a 64-bit system, any "different" library could treat ecx and edx differently. If we want to play with C, we need to play by C's rules (as Rob points out, 64-bit rules are completely different!). I think Agner Fog has a document on calling conventions...

http://www.agner.org

Lots of interesting stuff there! I really like his "objconv".

Rob also points out the error with "push ebx" in the loop. I forgot to mention that. Since you exit with "sys_exit", it doesn't cause a problem in this case, but it definitely isn't right. I don't think you need to do it at all (ebx is one of the registers that's "preserved", so calling printf won't alter it), but if you do, make sure to "pop" it every time!

You probably already know this, but it isn't too "obvious". When you "call" anything, the return address is stored on the stack. When you get to "ret", that return address has to be the next thing on the stack, or you're "off in the weeds" instead of returning to the intended address. "main" is called by some "startup code" that gcc provides, but since you don't "ret" to it, the corrupted stack doesn't cause an error. Still an important thing to know!

Best,
Frank


Offline annnDi

  • Jr. Member
  • *
  • Posts: 5
Re: Problem with simple program(x64)
« Reply #7 on: December 01, 2011, 07:53:05 PM »
Hello Frank. I really appreciate you pointing in me in the right directions. What exacly does that mean - edx:eax (that is, edx * 4G + eax)? I red on wikibooks, that div ecx = ecx / eax + remainder, which gets stored into edx. This is how my program works, i guess.
Also, In your post, I dont' quite understand what "preserved" and "trashed" means.
And lastly, yes, I will start to close my main with ret.
Thank you very much.

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Problem with simple program(x64)
« Reply #8 on: December 01, 2011, 10:56:28 PM »
"edx:eax" is just a way to express a 64-bit number using two 32-bit registers. Think of it as a single bitfield, with edx on the left. Or, edx times 4294967296 plus eax.

"div ecx" does edx:eax / ecx, not ecx / eax. If Wikibooks says different, Wikibooks is wrong. The quotient goes in eax, the remainer in edx. If the remainder is zero, the number is evenly divisible by 11, and you print it out. Hmmm, I told you wrong! You do use the "push ebx" as a parameter to printf! So you need to "push ebx" inside your "if clause", and "add esp, 8" (not 4) to keep your stack "sane".
Code: [Select]
...
  mov ebx, 1                          ; V register shranimo vrednost 1, ker povecujemo iz 1.

.loop1:                               ; Loop po primeru iz predavanj.
  mov eax, ebx                        ; Divide ecx vedno deli z vrednostjo eax. Moramo dati vrednost noter.
  mov ecx, 11                         ; V ecx vrzemo vrednost 11.
  mov edx, 0
  div ecx                             ; V edx se shrani ostanek.
  cmp edx, 0                          ; Primerja torej ostanek ga z 0.
jnz .loop2                            ; ?e ni enako 0, presko?imo izpis in gremo dalje v "for" loop.

  push ebx                            ; Na sklad porinemo ebx, ki vsebuje vrednost 1+loop.
  push dword izpisovanje              ; Na sklad porinemo izpisovanje, ki vsebuje vrednost ebx.
  call printf                         ; Poklicemo printf, ki izpise tisto, kar je v skladu.
  add esp, 8                          ; Po?istimo sklad. 4 bajte * 2 bajta. Spu?emo ?e tisto iz loop1.

.loop2:
  add ebx, 1                          ; Povecamo stevec za 1.
  cmp ebx, [prvoStevilo]              ; Primerjamo trenutni stevec v loopu z vnesenim stevilom.
jle .loop1                            ; Jump less or equal. ?e je enako, se kon?a, druga?e gre nazaj noter.
...

Or something like that...

"preserved" (or "non-volatile") means that if we alter the value of ebp, ebx, esi, or edi, we have to put 'em back the way they were before exiting the function. (and C functions will do the same for us).

"Trashed" just means that the value can be altered within the function (and C is allowed to do the same to us).

Exiting "main" with "ret" would be more portable than the Linux-specific sys_exit, and it would "prove" that you haven't corrupted the stack, so I guess that's a good idea.

Best,
Frank


Offline annnDi

  • Jr. Member
  • *
  • Posts: 5
Re: Problem with simple program(x64)
« Reply #9 on: December 02, 2011, 01:47:19 PM »
Hello, Frank and Rob. Thank you very much for your input.
If I'll have questions in future, i'll post them on this forum.
Andi.