Reputation: 119
Re: x86 assembly language -
I have three 32-bit signed numbers: n1, n2, and n3.
I want to imul n1 by n2 to get a 64-bit signed result.
I then want to idiv that 64-bit result by n3.
The problem is that if the 64-bit signed result is large enough and/or if n3 is small enough, an overflow will result and idiv will throw a #DE exception.
If idiv simply set #DE on overflow, I could check to confirm that ((n1 * n2) / n3) * n3 + ((n1 * n2) mod n3) = (n1 * n2). If not, overflow would have occurred and I could proceed accordingly.
But #DE does not play nice with others. When it's raised, it reports "Program has stopped working" and then kicks you out.
So i either need to find some way of pre-checking whether an idiv will cause an overflow before I do the division, or I need to do the equivalent of a try ... catch in assembly language.
I've searched the internet (including here) and find very little on this in general; and nothing that is particularly useful.
I've tried inlining the code inside a c++ try ... catch to no avail - it still reports "Program has stopped working" and then kicks you out.
For example, with the two supporting files:
// TC.h
// Version 1.0.0
// MDJ 2016/05/06
extern "C" int q;
extern "C" int m;
and
// TC.s
// Version 1.0.0
// MDJ 2016/05/06
.globl _q
.globl _m
.bss
.align 4
_q:
.space 4
_m:
.space 4
this file runs to completion and produces the correct results:
// TryCatch.cpp
// Version 1.0.0
// MDJ 2016/05/06
#include <iostream>
#include "TC.h"
using namespace std;
int main() {
cout << endl;
try {
# AT&T syntax
asm(
"movl $34, %eax\n\t"
"movl $48, %edx\n\t"
"imull %edx\n\t"
"movl $13, %ecx\n\t"
"idivl %ecx\n\t"
"movl %eax, _q\n\t"
"movl %edx, _m\n\t"
);
}
catch(int e) {
cout << "Caught." << endl;
}
cout << "Reached the End." << endl;
cout << "q = " << q << endl;
cout << "m = " << m << endl;
cout << endl;
return 0;
}
But, if I change n1, n2, and n3 like this:
// TryCatch.cpp
// Version 1.0.0
// MDJ 2016/05/06
#include <iostream>
#include "TC.h"
using namespace std;
int main() {
cout << endl;
try {
# AT&T syntax
asm(
"movl $234567890, %eax\n\t"
"movl $123456789, %edx\n\t"
"imull %edx\n\t"
"movl $3, %ecx\n\t"
"idivl %ecx\n\t"
"movl %eax, _q\n\t"
"movl %edx, _m\n\t"
);
}
catch(int e) {
cout << "Caught." << endl;
}
cout << "Reached the End." << endl;
cout << "q = " << q << endl;
cout << "m = " << m << endl;
cout << endl;
return 0;
}
the "catch" doesn't catch the overflow and the system instead reports "Program has stopped working" and then kicks you out.
Any help would be appreciated.
Upvotes: 0
Views: 1497
Reputation: 119
It suddenly occurred to me that I'm completely on the wrong track (and as a model railroader, that's a truly heinous crime ) Pun intended :-).
But, really, I've been going about this the hard way.
Instead, I should take the easy way: I should go back to my 1950's grammar school and my first adventures with long division.
Instead of puzzling over EDX:EAX being divided by ECX, let's think of a two digit (unsigned) number being divided by a one digit (unsigned) number.
Now, the two-digit number is the dividend, and it has a ones digit and a tens digit. So it can vary between 0 and 99.
And, the one-digit number is the divisor, and it has only a ones digit. Thus, it can vary between 1 and 9 (because division by zero is not allowed).
Consider, for example, 77 divided by 2:
3 8
_____
2 | 7 7
6
_
1 7
1 6
___
1
So, the result is: the quotient is 38 and the remainder is 1.
But, here, like with the dividend, we're allowing the quotient to also have two digits: a tens digit and a ones digit. What would happen if we instead limit the quotient to having only the ones digit.
Then we could call any division, which results in the quotient having any numeral other than zero in the tens digit field, AN OVERFLOW !!!
But, then, what is the condition required to produce such an overflow: ANY DIVISOR WHICH IS SMALLER THAN OR EQUAL TO THE NUMERAL IN THE TENS DIGIT OF THE DIVIDEND !!!
Analogously, in the division of EDX:EAX by ECX, an overflow will occur if ECX <= EDX !!!
And that is thus our simple test for overflow:
ECX <= EDX
That works for unsigned divides.
Pre-checking for signed divide overflow is significantly more complicated. I think this will work, but I'm still testing.
Begin with the 64-bit signed dividend in EDX:EAX and with the 32-bit signed divisor in ECX. Then:
# Temporarily save the dividend
movl %edx, _dividendHigh # Most-significant 32b
movl %eax, _dividendLow # Least-significant 32b
# Check the divisor for zero
testl %ecx, %ecx # Is divisor = 0 ?
jz _DivideByZero # Go if Yes
# Check the divisor for +/- 1
cmpl $1, %ecx
je _dChkA # Go if divisor = 1
cmpl $-1, %ecx
je _dChkA # Go if divisor = -1
jmp _dChkC # Else continue
_dChkA:
# If dividendHigh < -1 or > 0 and divisor = +/- 1
# then overflow will occur.
cmpl $-1, %edx
jl _DivideOverflow # Go if divHigh < -1
cmpl $0, %edx
jg _DivideOverflow # Go if divHigh > 0
# If dividendHigh = -1 and bit 31 of dividendLow = 0
# and divisor = +/- 1 then overflow will occur.
cmpl $-1, %edx
jne _dChkB # Go if divHigh <> -1
bt $31, %eax
jnc _DivideOverflow # Go if divLow b31 = 0
_dChkB:
# If dividendHigh = 0 and bit 31 of dividendLow = 1
# and divisor = +/- 1 then overflow will occur.
cmpl $0, %edx
jne _dChkC # Go if divHigh <> 0
bt $31, %eax
jc _DivideOverflow # Go if divLow b31 = 1
# Check for non-unary overflow
# Overflow will occur if the
# most-significant 33b can be
# divided by the divisor. NOTE:
# that's 33 bits, not 32,
# because all numbers are signed.
# Do dividend shift and manual sign extension
# Check bit 63 to determine if dividend is positive or negative
_dChkC:
bt $31, %edx
jc _dChkD # Go if negative
# Do the 64-bit shift # Positive
# First, shift the Least-significant
# 32b left 1 bit (bit 32 --> CF).
shll $1, %eax
# Then, rotate the Most-significant
# 32b left, through the carry, 1 bit
# (CF --> bit 1 then bit 32 --> CF).
rcll $1, %edx
# Move it to %eax and manually positive-sign extend it
movl %edx, %eax
jmp _dChkE
_dChkD: # Negative
# Do the 64-bit shift
# First, shift the Least-significant
# 32b left 1 bit (bit 32 --> CF).
shll $1, %eax
# Then, rotate the Most-significant
# 32b left, through the carry, 1 bit
# (CF --> bit 1 then bit 32 --> CF).
rcll $1, %edx
# Move it to %eax and manually negative-sign extend it
movl %edx, %eax
movl $-1, %edx
# Do the Test Divide of the
# Most-Significant 33b
_dChkE:
idivl %ecx # EDX:EAX / ECX
# EAX = test quotient
# EDX = test remainder
testl %eax, %eax
jnz _DivideOverflow # Go if Quotient <> 0
# Get the full dividend
movl _dividendHigh, %edx # Most-significant 32b
movl _dividendLow, %eax # Least-significant 32b
# Perform the 64b by 32b division
idivl ecx # EDX:EAX / ECX
# EAX = quotient
# EDX = remainder
Upvotes: 3
Reputation: 364049
Your DivideTester
is ridiculous. You only need to preserve the caller's %ebx
, %esi
, %edi
, %ebp
, and %esp
. You seem to be saving/restoring tons of registers within the same function, and restore the same register multiple times at the end.
Try this:
.globl _DivideTester
_DivideTester:
# extern "C" void DivideTester(void);
# clobbers eax and edx. The C compiler will assume this, because the standard calling convention says functions are allowed to clobber eax, ecx, and edx
# mov $0, %edx
# mov $6742542, %eax
# Instead, set the globals from C, and print before calling
mov _dividendHigh, %edx # load from globals
mov _dividendLow, %eax
# movl _divisor, %ecx
idivl _divisor # EDX:EAX / divisor
mov %eax, _quotient # EAX = Quotient
mov %edx, _remainder # EDX = Remainder
# print the results from C, or use a debugger to inspect them
ret
Or if you insist on hard-coding the constants into the asm, you can still do that. You can still print them from C.
Notice how much easier this function is to read? There's basically nothing to go wrong other than the idiv
. Getting the function calls correct from asm is a lot more work, so don't waste your time on it. Let the compiler do what it's good at. You can still see exactly what the compiler did by disassembling / single-stepping its code, so it's not like you lose out on debug-ability from leaving that part to C. It's more like you avoid whole classes of bugs (like the one you had at first).
You only need operand-size suffixed for something like mov $1234, _memory
, where there's no register to imply the operand-size. I prefer to omit it. If it's ambiguous, as
will give you an error message instead of picking a default, so it's always safe.
Upvotes: 0