0

When I run my program with the -m32 flag, I receive a segmentation fault error when trying to print out the values of 2 Floating point objects (MyFloat)—I am trying to model a floating point number from scratch using the MyFloat class.

The strange thing is that when I open up the debugger, the 2 MyFloat objects have all the correct properties in them and have implemented the overloading of the << operator, yet printing them out produces a segmentation fault (the segmentation fault occurs at line):

cout << "Usage: " << argv[0] << " float_a +/- float_b" << endl;

Looking at the debugger, I can see the actual values in mf1 and mf2:

$1 = {_vptr.MyFloat = 0x56558e90 <vtable for MyFloat+8>, sign = 0, 
  exponent = 130, mantissa = 2097152}
$1 = {_vptr.MyFloat = 0x56558e90 <vtable for MyFloat+8>, sign = 0, 
  exponent = 130, mantissa = 2097152}

If I do not use the -m32 flag, everything is printed out fine. Below is my Class header, Class implementation, and main.cpp code.

MyFloat.h:

/*
 * MyFloat.h
 *
 *  Created on: Dec 7, 2020
 *      Author: ubuntu
 */

#ifndef MYFLOAT_H_
#define MYFLOAT_H_

#include <iostream>

using namespace std;

class MyFloat {
public:
    //constructors
    MyFloat();
    MyFloat(float f);
    MyFloat(const MyFloat & rhs);
    virtual~MyFloat() {};

    //output
    friend ostream & operator << (std::ostream & strm, const MyFloat & f);

    //addition
    MyFloat operator+(const MyFloat & rhs) const;

    //subtraction
    MyFloat operator-(const MyFloat & rhs) const;

    //comparison
    bool operator==(const float rhs) const;

private:
    unsigned int sign;
    unsigned int exponent;
    unsigned int mantissa;

    void unpackFloat(float f);
    float packFloat() const;;
};

#endif /* MYFLOAT_H_ */

MyFloat.cpp:

/*
 * MyFloat.cpp
 *
 *  Created on: Dec 7, 2020
 *      Author: ubuntu
 */

#include "MyFloat.h"

#include "MyFloat.h"

/*
 * The bitset library is used to debug this program: printing out the binary representation
 * of integers.
 */
#include <bitset>
#include <stdio.h>
#include <string.h>

MyFloat::MyFloat(){
  sign = 0;
  exponent = 0;
  mantissa = 0;
}

MyFloat::MyFloat(float f){
  unpackFloat(f);
}

MyFloat::MyFloat(const MyFloat & rhs){
    sign = rhs.sign;
    exponent = rhs.exponent;
    mantissa = rhs.mantissa;
}


ostream& operator<<(std::ostream &strm, const MyFloat &f){
    //this function is complete. No need to modify it.
    strm << f.packFloat();

    return strm;
}

void MyFloat::unpackFloat(float f) {
    //this function must be written in inline assembly
    //extracts the fields of f into sign, exponent, and mantissa

    __asm__(
            "movl %%eax, %%ebx;"        // Load the floating point number into the sign register
            "shr $31, %%ebx;"       // Shift the sign bit to the end

            // Load in the bitmasks into the EDI and ESI registers
            "movl $0x7f800000, %%edi;"  // Bitmask to retrieve the exponent
            "movl $0x7fffff, %%esi;"    // Bitmask to retrieve the mantissa

            "movl %%eax, %%ecx;"        // Load the floating point number into the exponent register
            "andl %%edi, %%ecx;"        // Cancel all the bits except the mantissa bits
            "shr $23, %%ecx;"           // Shift the mantissa bits to the end

            "movl %%eax, %%edx;"        // Load the floating point number into the mantissa register
            "andl %%esi, %%edx;"        // Cancel all the bits except the mantissa bits
                                        // We do not need to shift the mantissa to the end (since it is
                                        // already at the end)

            // Load the floating pointer
            : "=b" (sign), "=c" (exponent), "=d" (mantissa)
            : "a" (f)
            : "cc"
            );
}//unpackFloat

float MyFloat::packFloat() const{

  //this function must be written in inline assembly
  //returns the floating point number represented by this
    unsigned int f;
    float f2;

    __asm__(
            // Load the sign bit into the floating point number
            "movl %%ebx, %%eax;"
            // Move the sign bit from the LSB to the MSB
            "shl $31, %%eax;"

            // Generate the bitmask for loading in the mantissa (EDI register)
            "movl %%ecx, %%edi;"
            // Right now, the mantissa bits are aligned at the end. We want to move them
            // to their proper position (bits 2-9)
            "shl $23, %%edi;"
            // Load the mantissa into the floating point number
            "orl %%edi, %%eax;"

            // Generate the bitmask for loading in the exponent (ESI register)
            "movl %%edx, %%esi;"
            // Load the exponent into the floating point number
            "orl %%esi, %%eax;"

            // Load the floating pointer
            : "= a" (f)
            : "b" (sign), "c" (exponent), "d" (mantissa)
            : "cc", "%edi", "%esi"
            );

    memcpy(&f2, &f, sizeof(f));
    return f2;
}//packFloat
//

main.cpp:

  #include <cstdlib>
  #include <iostream>
  #include "MyFloat.h"

  using namespace std;

  int main(int argc, char ** argv) {
      float f1, f2;
      //float fres;
      MyFloat mfres;

      cout.precision(1000);
      if (argc != 4) {
          cout << "Usage: " << argv[0] << " float_a +/- float_b" << endl;
      } else {
          f1 = strtof(argv[1], NULL);
          f2 = strtof(argv[3], NULL);

          MyFloat mf1(f1);
          MyFloat mf2(f2);
          cout << mf1 << ' ' << argv[2][0] << ' ' << mf2 << endl;

          //cout << "I did it = " << boolalpha << (mfres == fres) << endl;
      }

      return 0;
  }

Note that I am also compiling with the -std=c++11 flag.

Adam Lee
  • 436
  • 1
  • 14
  • 49
  • 3
    Are you sure your clobbers are properly handling all the registers you are playing with here? I see a ton of registers used, but no telling the compiler about them. Another reason to hate inline assembler and use .S files if you truly need assembly. – Michael Dorgan Dec 07 '20 at 18:40
  • 1
    `"movl %%ecx, %%edi;"` modifies EDI, but there's no clobber on that register. You'd certainly expect this to crash if the compiler was keeping a pointer in EDI or some other register you stepped on. This kind of bug can only be found with asm-level debugging, not source-level. – Peter Cordes Dec 07 '20 at 18:41
  • 1
    Also, `reinterpret_cast(f);` type punning is undefined behaviour, unless you compile with `-fno-strict-aliasing`. Use C++20 `std::bit_cast` to reinterpret values https://en.cppreference.com/w/cpp/numeric/bit_cast, or use memcpy (or a union, which GNU C supports as an extension to ISO C++) – Peter Cordes Dec 07 '20 at 18:44
  • I didn't verify that those bugs are exactly what's causing your problem, but I closed it as a duplicate because it's not worth anyone's time to dig deeper until you fix those likely-fatal register-clobber bugs which are probably the cause. See https://stackoverflow.com/tags/inline-assembly/info for guides. – Peter Cordes Dec 07 '20 at 18:55
  • @PeterCordes Gotcha, so I need to add "%edi" to the list of clobbers in packFloat(). With regards to C++20 std::bit_cast, since I am required to compile the program in c++11, I would have to use memcpy right? – Adam Lee Dec 07 '20 at 18:59
  • memcpy or a union. Since you're using GNU inline asm, other GNU features are guaranteed to be available. Also, `"edi"` is not the only register you write that isn't an output or clobber, check your other asm statement. And use early-clobber declarations on your outputs. (Although since you hard-code your input registers instead of letting the compiler pick with `"r"(var)`, that can't be a problem.) – Peter Cordes Dec 07 '20 at 19:04
  • @PeterCordes I believe in my updated question, I have fixed the missing clobbers (in the unpackFloat() function, I believe all the registers I have modified are already specified as outputs so there is no need to put the constraints). And I have used memcpy to reinterpret the integer as a float. However, I still receive a segmentation fault and am not too sure that the registers are the issue considering that I can see the vtables in the debugger following my unpackFloat() call. Thoughts? – Adam Lee Dec 07 '20 at 20:06
  • You only fixed one of your asm statements. The other one could just use immediates for AND instead of doing a `mov` into ESI and EDI, but in its current state it's broken exactly the same way, like I mentioned earlier. – Peter Cordes Dec 07 '20 at 20:08
  • Seeing the vtables in a debugger has nothing to do with anything. That not relevant at all to showing that you aren't messing up the compiler's registers. If you want to check anything, find out which instruction segfaults, and why. – Peter Cordes Dec 07 '20 at 20:11
  • @PeterCordes Thanks, everything seems to be working now :D. – Adam Lee Dec 07 '20 at 20:16

0 Answers0