Saturday 9 February 2019

c - Why does not GCC generate correct assembly code for int division?




I have written the following C code. But in runtime it gave me incorrect values for the variable sb, so i tried to debug it with GDB and i found out that the assembly code for the int division E(vdes->addr, bs) (#define E(X,Y) X/Y) was completely incomprehensible and seems to be not doing the right thing.



File: main.c



typedef struct virtual_file_descriptor
{
int dfb;
int addr;


} vfd;

vfd vdes;

if(!strcmp(argv[1], "write")){
vdes.dfb = atoi(argv[2]);
vdes.addr = atoi(argv[3]);
vwrite(&vdes, inbuffer, atoi(argv[4]));
}



File: vwrite.c



#define E(X,Y)  X/Y
#define bs sizeof(D_Record)*MAX_BLOCK_ENTRIES

int vwrite(vfd *vdes, char *buffer, int size){
if(!vdes)
return -1;


int sb, nb, offset;

sb = E(vdes->addr, bs) + 1; // i did 140/280 => wrong result
offset = vdes->addr - (sb - 1) * bs;

printf("size=%d bs=%d addr=%d sb=%d offset=%d\n\n", size, bs, vdes->addr, sb, offset);

}



The assembly language generated for the int devision was (Which is wrong and doesn't contain any sings of doing an arithmetic division) :



(gdb) n
58 sb = E(vdes->addr, bs) + 1;
(gdb) x/10i $pc
=> 0x80001c3d : mov 0x8(%ebp),%eax
0x80001c40 : mov 0x4(%eax),%eax
0x80001c43 : shr $0x2,%eax
0x80001c46 : mov $0x24924925,%edx

0x80001c4b : mul %edx
0x80001c4d : mov %edx,%eax
0x80001c4f : shl $0x2,%eax
0x80001c52 : add %edx,%eax
0x80001c54 : add %eax,%eax
0x80001c56 : add $0x1,%eax
0x80001c59 : mov %eax,-0x2c(%ebp)
0x80001c5c : mov 0x8(%ebp),%eax
0x80001c5f : mov 0x4(%eax),%eax
0x80001c62 : mov %eax,%edx



I copied the same code sequence to a new standalone file and everything works fine (Correct results and correct assembly code). So i came to wonder why doesn't the first code work ?



File: test.c



#define E(X,Y) X/Y

int main(int argc, char **argv){


int sb = E(atoi(argv[1]), atoi(argv[2]));

return 0;
}


Assembly code generated for previous code (which is a nicely understandable and correct code for doing int devision):



   .
.

call atoi
.
call atoi
.
.
0x800005db : mov %eax,%ecx
0x800005dd : mov %edi,%eax
0x800005df : cltd
0x800005e0 : idiv %ecx
0x800005e2 : mov %eax,-0x1c(%ebp)


Answer



The original question had



#define bs      280


It was later changed to:



#define bs      sizeof(D_Record)*MAX_BLOCK_ENTRIES



To avoid issues using bs in other expressions, this should be



#define bs      (sizeof(D_Record)*MAX_BLOCK_ENTRIES)


The define for E should be:



#define E(X,Y) ((X)/(Y))



The generated assembly code appears to be based on



#define bs      sizeof(D_Record)*MAX_BLOCK_ENTRIES
#define E(X,Y) X/Y
... E(vdes->addr, bs) ...


So divide by 28 using shift and multiply, then multiply by 10.




        mov    0x4(%eax),%eax       ;eax = dividend
shr $0x2,%eax ;eax = dividend/4 (pre shift)
mov $0x24924925,%edx ;edx = multiply constant
mul %edx ;edx = dividend/28 (no post shift)
mov %edx,%eax ;eax = (dividend/28)*10
shl $0x2,%eax
add %edx,%eax
add %eax,%eax



For the eax = edx*10 sequence, I'm not sure why lea wasn't used:



        lea    (%edx,%edx,2),eax    ;eax = edx*5
add %eax,%eax ;eax = edx*10


Link to prior thread with an explanation how divide by constant is converted into multiply and shifts.



Why does GCC use multiplication by a strange number in implementing integer division?



No comments:

Post a Comment

php - file_get_contents shows unexpected output while reading a file

I want to output an inline jpg image as a base64 encoded string, however when I do this : $contents = file_get_contents($filename); print &q...